Beispiel #1
0
func main() {

	opts := optparse.Parser(
		"Usage: matchdb <config.yaml> [options]\n",
		"matchdb 0.0.1")

	host := opts.StringConfig("host", "",
		"the host to bind matchdb to")

	port := opts.IntConfig("port", 8090,
		"the port to bind matchdb to [8090]")

	allocLimit := opts.IntConfig("alloc-limit", 5000000,
		"maximum allocation limit in kilobytes [5000000]")

	hashKey := opts.StringConfig("hash-key", "",
		"16-byte hash key encoded as a 32-byte hex string")

	awsAccessKey := opts.StringConfig("aws-access-key", "",
		"the AWS Access Key for DynamoDB")

	awsSecretKey := opts.StringConfig("aws-secret-key", "",
		"the AWS Secret Key for DynamoDB")

	awsRegion := opts.StringConfig("aws-region", "us-east-1",
		"the AWS Region for DynamoDB [us-east-1]")

	masterTable := opts.StringConfig("master-table", "",
		"the DynamoDB table for the master lock")

	masterTimeout := opts.IntConfig("master-timeout", 6000,
		"timeout in milliseconds for the master lock [6000]")

	os.Args[0] = "matchdb"
	runtime.DefaultOpts("matchdb", opts, os.Args)

	initHashKey(*hashKey)
	master := NewMaster("", *awsAccessKey, *awsSecretKey, *awsRegion, *masterTable, time.Duration(*masterTimeout)*time.Millisecond)

	db := &DB{}
	db.Run(*host, *port, master, int64(*allocLimit))

}
Beispiel #2
0
func main() {

	// Define the options for the command line and config file options parser.
	opts := optparse.Parser(
		"Usage: matchweb <config.yaml> [options]\n",
		"matchweb 0.0.1")

	host := opts.StringConfig("host", "",
		"the host to bind the matchweb to")

	port := opts.IntConfig("port", 9040,
		"the port to bind the matchweb to [9040]")

	redirectURL := opts.StringConfig("redirect-url", "",
		"the URL that the HTTP Redirector redirects to")

	redirectorHost := opts.StringConfig("redirector-host", "",
		"the host to bind the HTTP Redirector to")

	redirectorPort := opts.IntConfig("redirector-port", 9080,
		"the port to bind the HTTP Redirector to [9080]")

	hstsMaxAge := opts.IntConfig("hsts-max-age", 31536000,
		"max-age value of HSTS in number of seconds [0 (disabled)]")

	clusterID := opts.StringConfig("cluster-id", "",
		"the cluster id to use when responding to ping requests")

	matchdbServer := opts.StringConfig("matchdb-server", "",
		"the address for a single-node MatchDB server setup")

	hashKey := opts.StringConfig("hash-key", "",
		"16-byte hash key encoded as a 32-byte hex string")

	awsAccessKey := opts.StringConfig("aws-access-key", "",
		"the AWS Access Key for DynamoDB")

	awsSecretKey := opts.StringConfig("aws-secret-key", "",
		"the AWS Secret Key for DynamoDB")

	awsRegion := opts.StringConfig("aws-region", "us-east-1",
		"the AWS Region for DynamoDB [us-east-1]")

	masterTable := opts.StringConfig("master-table", "",
		"the DynamoDB table for the master lock")

	masterTimeout := opts.IntConfig("master-timeout", 6000,
		"timeout in milliseconds for the master lock [6000]")

	routingTimeout := opts.IntConfig("routing-timeout", 3000,
		"timeout in milliseconds for routing entries [3000]")

	publishKey := opts.StringConfig("publish-key", "",
		"the shared secret for publishing new items")

	publishClusterID := opts.IntConfig("publish-cluster-id", 0,
		"the cluster id to use when acknowledging publish requests")

	upstreamHost := opts.StringConfig("upstream-host", "localhost",
		"the upstream host to connect to [localhost]")

	upstreamPort := opts.IntConfig("upstream-port", 8080,
		"the upstream port to connect to [8080]")

	upstreamTLS := opts.BoolConfig("upstream-tls", false,
		"use TLS when connecting to upstream [false]")

	websocketOrigin := opts.StringConfig("websocket-origin", "",
		"limit websocket calls to the given origin if specified")

	maintenanceMode := opts.BoolConfig("maintenance", false,
		"start up in maintenance mode [false]")

	// Setup the console log filter.
	log.ConsoleFilters[logPrefix] = func(items []interface{}) (bool, []interface{}) {
		return true, items[2 : len(items)-2]
	}

	// Parse the command line options.
	os.Args[0] = "matchweb"
	runtime.DefaultOpts("matchweb", opts, os.Args)

	// Initialise the TLS config.
	tlsconf.Init()

	// Initialise ping/pong variables.
	setupPong("matchweb", *clusterID)

	// Initialise the key for hashing slots.
	initHashKey(*hashKey)

	// Ensure required config values.
	if *publishKey == "" {
		runtime.Error("The publish-key cannot be empty")
	}

	server := &LiveServer{
		httpClient:       &http.Client{Transport: &http.Transport{TLSClientConfig: tlsconf.Config}},
		publishKey:       []byte(*publishKey),
		publishKeyLength: len(*publishKey),
		websocketOrigin:  *websocketOrigin,
	}

	setUpstreamInfo(server, *upstreamHost, *upstreamPort, *upstreamTLS, *publishClusterID)

	if *hstsMaxAge != 0 {
		server.hstsEnabled = true
		server.hsts = fmt.Sprintf("max-age=%d", *hstsMaxAge)
	}

	// Enable maintenance handling.
	frontends := []Maintainable{server}
	handleMaintenance(frontends, *maintenanceMode)

	// Setup the HTTP Redirector.
	runRedirector(*redirectorHost, *redirectorPort, *redirectURL, *hstsMaxAge)

	// Run the Live Server.
	runHTTP("Live Server", *host, *port, server, "")

	// Enter the wait loop for the process to be killed.
	loopForever := make(chan bool, 1)
	<-loopForever

}
Beispiel #3
0
func main() {

	// Define the options for the command line and config file options parser.
	opts := optparse.New(
		"Usage: planfile <config.yaml> [options]\n",
		"planfile 0.0.1")

	cookieKeyFile := opts.StringConfig("cookie-key-file", "cookie.key",
		"the file containing the key to sign cookie values [cookie.key]")

	gaHost := opts.StringConfig("ga-host", "",
		"the google analytics hostname to use")

	gaID := opts.StringConfig("ga-id", "",
		"the google analytics id to use")

	httpAddr := opts.StringConfig("http-addr", ":8888",
		"the address to bind the http server [:8888]")

	oauthID := opts.Required().StringConfig("oauth-id", "",
		"the oauth client id for github")

	oauthSecret := opts.Required().StringConfig("oauth-secret", "",
		"the oauth client secret for github")

	redirectURL := opts.StringConfig("redirect-url", "/.oauth",
		"the redirect url for handling oauth [/.oauth]")

	repository := opts.Required().StringConfig("repository", "",
		"the username/repository on github")

	secureMode := opts.BoolConfig("secure-mode",
		"enable hsts and secure cookies [false]")

	title := opts.StringConfig("title", "Planfile",
		"the title for the web app [Planfile]")

	refreshKey := opts.StringConfig("refresh-key", "",
		"key for anonymously calling refresh at /.refresh?key=<refresh-key>")

	refreshOpt := opts.IntConfig("refresh-interval", 1,
		"the number of through-the-web edits before a full refresh [1]")

	debug, instanceDirectory, _, logPath, _ = runtime.DefaultOpts("planfile", opts, os.Args, true)

	service := &oauth.OAuthService{
		ClientID:     *oauthID,
		ClientSecret: *oauthSecret,
		Scope:        "public_repo",
		AuthURL:      "https://github.com/login/oauth/authorize",
		TokenURL:     "https://github.com/login/oauth/access_token",
		RedirectURL:  *redirectURL,
		AcceptHeader: "application/json",
	}

	assets := map[string]string{}
	json.Unmarshal(readFile("assets.json"), &assets)
	setupPygments()

	mutex := sync.RWMutex{}
	repo := &Repo{Path: *repository}

	err := repo.Load(callGithubAnon)
	if err != nil {
		runtime.Exit(1)
	}

	repo.Title = *title
	repo.Updated = time.Now().UTC()
	repoJSON, err := json.Marshal(repo)
	if err != nil {
		runtime.StandardError(err)
	}

	refreshCount := 0
	refreshInterval := *refreshOpt
	refreshKeySet := *refreshKey != ""
	refreshKeyBytes := []byte(*refreshKey)

	secret := readFile(*cookieKeyFile)
	newContext := func(w http.ResponseWriter, r *http.Request) *Context {
		return &Context{
			r:      r,
			w:      w,
			secret: secret,
			secure: *secureMode,
		}
	}

	register := func(path string, handler func(*Context), usegzip ...bool) {
		gzippable := len(usegzip) > 0 && usegzip[0]
		http.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
			log.Info("serving %s", r.URL)
			w.Header().Set("Content-Type", "text/html; charset=utf-8")
			if gzippable && httputil.Parse(r, "Accept-Encoding").Accepts("gzip") {
				buf := &bytes.Buffer{}
				enc := gzip.NewWriter(buf)
				handler(newContext(GzipWriter{enc, w}, r))
				enc.Close()
				w.Header().Set("Content-Encoding", "gzip")
				w.Header().Set("Content-Length", strconv.Itoa(buf.Len()))
				buf.WriteTo(w)
			} else {
				handler(newContext(w, r))
			}
		})
	}

	anon := []byte(", null, null, '', false")
	authFalse := []byte("', false")
	authTrue := []byte("', true")

	header := []byte(`<!doctype html>
<meta charset=utf-8>
<title>` + html.EscapeString(*title) + `</title>
<link href="//fonts.googleapis.com/css?family=Abel|Coustard:400" rel=stylesheet>
<link href=/.static/` + assets["planfile.css"] + ` rel=stylesheet>
<body><script>DATA = ['` + *gaHost + `', '` + *gaID + `', `)

	footer := []byte(`];</script>
<script src=/.static/` + assets["planfile.js"] + `></script>
<noscript>Sorry, your browser needs <a href=http://enable-javascript.com>JavaScript enabled</a>.</noscript>
`)

	register("/", func(ctx *Context) {
		mutex.RLock()
		defer mutex.RUnlock()
		ctx.Write(header)
		ctx.Write(repoJSON)
		avatar := ctx.GetCookie("avatar")
		user := ctx.GetCookie("user")
		if avatar != "" && user != "" {
			ctx.Write([]byte(", '" + user + "', '" + avatar + "', '" + ctx.GetCookie("xsrf")))
			if ctx.IsAuthorised(repo) {
				ctx.Write(authTrue)
			} else {
				ctx.Write(authFalse)
			}
		} else {
			ctx.Write(anon)
		}
		ctx.Write(footer)
	}, true)

	register("/.api", func(ctx *Context) {
		mutex.RLock()
		defer mutex.RUnlock()
		if cb := ctx.FormValue("callback"); cb != "" {
			ctx.Write([]byte(cb))
			ctx.Write([]byte{'('})
			ctx.Write(repoJSON)
			ctx.Write([]byte{')', ';'})
		} else {
			ctx.Write(repoJSON)
		}
	}, true)

	register("/.login", func(ctx *Context) {
		b := make([]byte, 20)
		if n, err := rand.Read(b); err != nil || n != 20 {
			ctx.Error("Couldn't access cryptographic device", err)
			return
		}
		s := hex.EncodeToString(b)
		ctx.SetCookie("xsrf", s)
		ctx.Redirect(service.AuthCodeURL(s))
	})

	register("/.logout", func(ctx *Context) {
		ctx.ExpireCookie("auth")
		ctx.ExpireCookie("avatar")
		ctx.ExpireCookie("token")
		ctx.ExpireCookie("user")
		ctx.ExpireCookie("xsrf")
		ctx.Redirect("/")
	})

	notAuthorised := []byte("ERROR: Not Authorised!")

	savedHeader := []byte(`<!doctype html>
<meta charset=utf-8>
<title>` + html.EscapeString(*title) + `</title>
<body><script>SAVED="`)

	savedFooter := []byte(`"</script><script src=/.static/` + assets["planfile.js"] + `></script>`)

	exportRepo := func(ctx *Context) bool {
		repo.Updated = time.Now().UTC()
		repoJSON, err = json.Marshal(repo)
		if err != nil {
			ctx.Error("Couldn't encode repo data during refresh", err)
			return false
		}
		return true
	}

	refresh := func(ctx *Context) {
		err := repo.Load(ctx.CreateCallGithub())
		if err != nil {
			log.Error("couldn't rebuild planfile info: %s", err)
			ctx.Write([]byte("ERROR: " + err.Error()))
			return
		}
		exportRepo(ctx)
	}

	saveItem := func(ctx *Context, update bool) {
		mutex.Lock()
		defer mutex.Unlock()
		if !ctx.IsAuthorised(repo) {
			ctx.Write(notAuthorised)
			return
		}
		if !isEqual([]byte(ctx.FormValue("xsrf")), []byte(ctx.GetCookie("xsrf"))) {
			ctx.Write(notAuthorised)
			return
		}
		callGithub := ctx.CreateCallGithub()
		err := repo.UpdateInfo(callGithub)
		if err != nil {
			ctx.Error("Couldn't update repo info", err)
			return
		}
		var id, path, message string
		if update {
			id = ctx.FormValue("id")
			path = ctx.FormValue("path")
		} else {
			baseID := ctx.FormValue("id")
			id = baseID
			count := 0
			for repo.Exists(id + ".md") {
				count += 1
				id = fmt.Sprintf("%s%d", baseID, count)
			}
			path = id + ".md"
		}
		content := strings.Replace(ctx.FormValue("content"), "\r\n", "\n", -1)
		tags := ctx.FormValue("tags")
		title := ctx.FormValue("title")
		redir := "/"
		if ctx.FormValue("summary") == "yes" {
			if id != "/" {
				content = fmt.Sprintf(`---
title: %s
---

%s`, title, content)
				if strings.HasPrefix(id, "summary.") {
					redir = "/" + id[8:]
				} else {
					// Shouldn't ever happen. But just in case...
					redir = "/" + id
				}
			}
		} else {
			redir = "/.item." + id
			content = fmt.Sprintf(`---
id: %s
tags: %s
title: %s
---

%s`, id, tags, title, content)
		}
		if title == "" {
			title = id
		}
		if update {
			message = "update: " + title + "."
		} else {
			message = "add: " + title + "."
		}
		log.Info("SAVE PATH: %q for %q", path, title)
		err = repo.Modify(ctx, path, content, message)
		if err != nil {
			if update {
				ctx.Error("<a href='/.refresh'>Try refreshing.</a> Couldn't update item", err)
			} else {
				ctx.Error("<a href='/.refresh'>Try refreshing.</a> Couldn't save new item", err)
			}
			return
		}
		refreshCount++
		if refreshCount%refreshInterval == 0 {
			refresh(ctx)
		} else {
			repo.AddPlanfile(path, []byte(content), callGithub)
			if !exportRepo(ctx) {
				return
			}
		}
		ctx.Write(savedHeader)
		ctx.Write([]byte(html.EscapeString(redir)))
		ctx.Write(savedFooter)
	}

	register("/.modify", func(ctx *Context) {
		saveItem(ctx, true)
	})

	register("/.new", func(ctx *Context) {
		saveItem(ctx, false)
	})

	register("/.oauth", func(ctx *Context) {
		s := ctx.FormValue("state")
		if s == "" {
			ctx.Redirect("/.login")
			return
		}
		if !isEqual([]byte(s), []byte(ctx.GetCookie("xsrf"))) {
			ctx.ExpireCookie("xsrf")
			ctx.Redirect("/.login")
			return
		}
		t := &oauth.Transport{OAuthService: service}
		tok, err := t.ExchangeAuthorizationCode(ctx.FormValue("code"))
		if err != nil {
			ctx.Error("Auth Exchange Error", err)
			return
		}
		jtok, err := json.Marshal(tok)
		if err != nil {
			ctx.Error("Couldn't encode token", err)
			return
		}
		ctx.SetCookie("token", hex.EncodeToString(jtok))
		ctx.token = tok
		user := &User{}
		err = ctx.Call("/user", user, nil, false)
		if err != nil {
			ctx.Error("Couldn't load user info", err)
			return
		}
		ctx.SetCookie("avatar", user.AvatarURL)
		ctx.SetCookie("user", user.Login)
		ctx.Redirect("/")
	})

	register("/.preview", func(ctx *Context) {
		rendered, err := renderMarkdown([]byte(ctx.FormValue("content")))
		if err != nil {
			ctx.Error("Couldn't render Markdown", err)
			return
		}
		ctx.Write(rendered)
	}, true)

	register("/.refresh", func(ctx *Context) {
		if !ctx.IsAuthorised(repo) {
			if !(refreshKeySet && isEqual(refreshKeyBytes, []byte(ctx.FormValue("key")))) {
				ctx.Write(notAuthorised)
				return
			}
		}
		mutex.Lock()
		defer mutex.Unlock()
		refresh(ctx)
		ctx.Redirect("/")
	})

	mimetypes := map[string]string{
		"css":  "text/css",
		"gif":  "image/gif",
		"ico":  "image/x-icon",
		"jpeg": "image/jpeg",
		"jpg":  "image/jpeg",
		"js":   "text/javascript",
		"png":  "image/png",
		"swf":  "application/x-shockwave-flash",
		"txt":  "text/plain",
	}

	registerStatic := func(filepath, urlpath string) {
		_, ext := rsplit(filepath, ".")
		ctype, ok := mimetypes[ext]
		if !ok {
			ctype = "application/octet-stream"
		}
		if debug {
			register(urlpath, func(ctx *Context) {
				ctx.SetHeader("Content-Type", ctype)
				ctx.Write(readFile(filepath))
			}, strings.HasPrefix(ctype, "text/"))
		} else {
			content := readFile(filepath)
			register(urlpath, func(ctx *Context) {
				ctx.SetHeader("Cache-Control", "public, max-age=86400")
				ctx.SetHeader("Content-Type", ctype)
				ctx.Write(content)
			}, strings.HasPrefix(ctype, "text/"))
		}
	}

	for _, path := range assets {
		registerStatic(filepath.Join(instanceDirectory, "static", path), "/.static/"+path)
	}

	wwwPath := filepath.Join(instanceDirectory, "www")
	if files, err := ioutil.ReadDir(wwwPath); err == nil {
		for _, file := range files {
			if !file.IsDir() {
				registerStatic(filepath.Join(wwwPath, file.Name()), "/"+file.Name())
			}
		}
	}

	log.Info("Listening on %s", *httpAddr)
	server := &http.Server{
		Addr:         *httpAddr,
		ReadTimeout:  30 * time.Second,
		WriteTimeout: 30 * time.Second,
	}
	err = server.ListenAndServe()
	if err != nil {
		runtime.Error("couldn't bind to tcp socket: %s", err)
	}

}