2011-01-28 07:07:18 +00:00
/ *
Copyright 2011 Google Inc .
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 .
* /
2016-03-14 01:59:26 +00:00
package main // import "camlistore.org/website"
2011-01-03 23:49:19 +00:00
import (
"bytes"
2015-11-11 12:35:31 +00:00
"crypto/rand"
2015-11-10 09:11:31 +00:00
"crypto/tls"
2015-11-11 16:54:18 +00:00
"encoding/json"
2015-12-27 04:49:29 +00:00
"errors"
2011-01-03 23:49:19 +00:00
"flag"
"fmt"
2012-06-16 17:20:32 +00:00
"html/template"
2011-01-03 23:49:19 +00:00
"io"
"io/ioutil"
"log"
2015-11-10 09:11:31 +00:00
"net"
2012-02-21 10:11:28 +00:00
"net/http"
"net/http/httputil"
2015-11-11 12:35:31 +00:00
"net/smtp"
2012-02-21 10:11:28 +00:00
"net/url"
2011-01-03 23:49:19 +00:00
"os"
2012-02-21 10:11:28 +00:00
"os/exec"
2011-06-22 00:07:59 +00:00
"path/filepath"
2011-01-28 19:17:08 +00:00
"regexp"
2015-11-11 12:35:31 +00:00
"runtime"
2011-01-03 23:49:19 +00:00
"strings"
2013-03-15 23:26:21 +00:00
txttemplate "text/template"
2011-06-22 00:07:59 +00:00
"time"
2014-08-18 23:37:51 +00:00
2015-01-19 15:23:46 +00:00
"camlistore.org/pkg/deploy/gce"
2015-01-27 15:21:41 +00:00
"camlistore.org/pkg/netutil"
2015-11-11 12:35:31 +00:00
"camlistore.org/pkg/osutil"
2014-08-18 23:37:51 +00:00
"camlistore.org/pkg/types/camtypes"
2016-04-20 23:43:47 +00:00
"github.com/russross/blackfriday"
2015-11-24 23:10:27 +00:00
"go4.org/cloud/cloudlaunch"
2015-08-18 08:19:49 +00:00
"golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
2015-12-24 22:51:15 +00:00
compute "google.golang.org/api/compute/v1"
2015-11-10 12:10:16 +00:00
storageapi "google.golang.org/api/storage/v1"
2015-08-16 16:39:10 +00:00
"google.golang.org/cloud"
"google.golang.org/cloud/compute/metadata"
2015-12-29 05:08:17 +00:00
"google.golang.org/cloud/datastore"
2015-08-15 20:49:27 +00:00
"google.golang.org/cloud/logging"
2015-12-04 21:36:02 +00:00
"google.golang.org/cloud/storage"
2011-01-03 23:49:19 +00:00
)
const defaultAddr = ":31798" // default webserver address
2011-07-07 00:01:35 +00:00
var h1TitlePattern = regexp . MustCompile ( ` <h1>([^<]+)</h1> ` )
2011-01-28 19:17:08 +00:00
2011-01-03 23:49:19 +00:00
var (
2015-12-24 22:51:15 +00:00
httpAddr = flag . String ( "http" , defaultAddr , "HTTP address" )
httpsAddr = flag . String ( "https" , "" , "HTTPS address" )
2013-06-24 22:50:49 +00:00
root = flag . String ( "root" , "" , "Website root (parent of 'static', 'content', and 'tmpl" )
logDir = flag . String ( "logdir" , "" , "Directory to write log files to (one per hour), or empty to not log." )
2015-12-24 22:51:15 +00:00
logStdout = flag . Bool ( "logstdout" , true , "Whether to log to stdout" )
2013-06-24 22:50:49 +00:00
tlsCertFile = flag . String ( "tlscert" , "" , "TLS cert file" )
tlsKeyFile = flag . String ( "tlskey" , "" , "TLS private key file" )
2015-12-24 22:51:15 +00:00
buildbotBackend = flag . String ( "buildbot_backend" , "" , "[optional] Build bot status backend URL" )
buildbotHost = flag . String ( "buildbot_host" , "" , "[optional] Hostname to map to the buildbot_backend. If an HTTP request with this hostname is received, it proxies to buildbot_backend." )
alsoRun = flag . String ( "also_run" , "" , "[optiona] Path to run as a child process. (Used to run camlistore.org's ./scripts/run-blob-server)" )
2015-12-27 04:49:29 +00:00
devMode = flag . Bool ( "dev" , false , "in dev mode" )
2013-06-24 22:50:49 +00:00
2015-08-16 16:39:10 +00:00
gceProjectID = flag . String ( "gce_project_id" , "" , "GCE project ID; required if not running on GCE and gce_log_name is specified." )
gceLogName = flag . String ( "gce_log_name" , "" , "GCE Cloud Logging log name; if non-empty, logs go to Cloud Logging instead of Apache-style local disk log files" )
gceJWTFile = flag . String ( "gce_jwt_file" , "" , "If non-empty, a filename to the GCE Service Account's JWT (JSON) config file." )
2015-12-24 22:51:15 +00:00
gitContainer = flag . Bool ( "git_container" , false , "Use git from the `camlistore/git` Docker container; if false, the system `git` is used." )
2016-01-18 16:12:46 +00:00
flagChromeBugRepro = flag . Bool ( "chrome_bug" , false , "Run the chrome bug repro demo for issue #660. True in production." )
2015-11-11 12:35:31 +00:00
)
var (
inProd bool
2015-08-16 16:39:10 +00:00
2014-08-18 23:37:51 +00:00
pageHTML , errorHTML , camliErrorHTML * template . Template
packageHTML * txttemplate . Template
2011-01-03 23:49:19 +00:00
)
2012-06-16 17:20:32 +00:00
var fmap = template . FuncMap {
2015-11-08 21:45:09 +00:00
// "": textFmt, // Used to work in Go 1.5
2013-01-14 22:33:49 +00:00
"html" : htmlFmt ,
2012-06-16 17:20:32 +00:00
"htmlesc" : htmlEscFmt ,
2011-01-03 23:49:19 +00:00
}
// Template formatter for "" (default) format.
2012-06-16 17:20:32 +00:00
func textFmt ( w io . Writer , format string , x ... interface { } ) string {
2011-01-03 23:49:19 +00:00
writeAny ( w , false , x [ 0 ] )
2012-06-16 17:20:32 +00:00
return ""
2011-01-03 23:49:19 +00:00
}
// Template formatter for "html" format.
2012-06-16 17:20:32 +00:00
func htmlFmt ( w io . Writer , format string , x ... interface { } ) string {
2011-01-03 23:49:19 +00:00
writeAny ( w , true , x [ 0 ] )
2012-06-16 17:20:32 +00:00
return ""
2011-01-03 23:49:19 +00:00
}
2012-06-16 17:20:32 +00:00
// Template formatter for "htmlesc" format.
func htmlEscFmt ( w io . Writer , format string , x ... interface { } ) string {
2011-01-03 23:49:19 +00:00
var buf bytes . Buffer
writeAny ( & buf , false , x [ 0 ] )
template . HTMLEscape ( w , buf . Bytes ( ) )
2012-06-16 17:20:32 +00:00
return ""
2011-01-03 23:49:19 +00:00
}
// Write anything to w; optionally html-escaped.
func writeAny ( w io . Writer , html bool , x interface { } ) {
switch v := x . ( type ) {
case [ ] byte :
writeText ( w , v , html )
case string :
writeText ( w , [ ] byte ( v ) , html )
default :
if html {
var buf bytes . Buffer
fmt . Fprint ( & buf , x )
writeText ( w , buf . Bytes ( ) , true )
} else {
fmt . Fprint ( w , x )
}
}
}
// Write text to w; optionally html-escaped.
func writeText ( w io . Writer , text [ ] byte , html bool ) {
if html {
template . HTMLEscape ( w , text )
return
}
w . Write ( text )
}
func applyTemplate ( t * template . Template , name string , data interface { } ) [ ] byte {
var buf bytes . Buffer
2011-02-21 20:12:28 +00:00
if err := t . Execute ( & buf , data ) ; err != nil {
2011-01-03 23:49:19 +00:00
log . Printf ( "%s.Execute: %s" , name , err )
}
return buf . Bytes ( )
}
func servePage ( w http . ResponseWriter , title , subtitle string , content [ ] byte ) {
2013-03-15 18:26:24 +00:00
// insert an "install command" if it applies
if strings . Contains ( title , cmdPattern ) && subtitle != cmdPattern {
toInsert := `
< h3 > Installation < / h3 >
< pre > go get camlistore . org / cmd / ` + subtitle + ` < / pre >
< h3 > Overview < / h3 > < p > `
content = bytes . Replace ( content , [ ] byte ( "<p>" ) , [ ] byte ( toInsert ) , 1 )
}
2011-01-03 23:49:19 +00:00
d := struct {
Title string
Subtitle string
2012-06-16 17:20:32 +00:00
Content template . HTML
2011-01-03 23:49:19 +00:00
} {
title ,
subtitle ,
2012-06-16 17:20:32 +00:00
template . HTML ( content ) ,
2011-01-03 23:49:19 +00:00
}
2015-01-19 15:23:46 +00:00
if err := pageHTML . ExecuteTemplate ( w , "page" , & d ) ; err != nil {
2011-01-03 23:49:19 +00:00
log . Printf ( "godocHTML.Execute: %s" , err )
}
}
func readTemplate ( name string ) * template . Template {
2011-06-22 00:07:59 +00:00
fileName := filepath . Join ( * root , "tmpl" , name )
2011-01-03 23:49:19 +00:00
data , err := ioutil . ReadFile ( fileName )
if err != nil {
2011-02-02 20:27:30 +00:00
log . Fatalf ( "ReadFile %s: %v" , fileName , err )
2011-01-03 23:49:19 +00:00
}
2012-06-16 17:20:32 +00:00
t , err := template . New ( name ) . Funcs ( fmap ) . Parse ( string ( data ) )
2011-01-03 23:49:19 +00:00
if err != nil {
2011-02-02 20:27:30 +00:00
log . Fatalf ( "%s: %v" , fileName , err )
2011-01-03 23:49:19 +00:00
}
return t
}
func readTemplates ( ) {
2014-08-18 23:37:51 +00:00
pageHTML = readTemplate ( "page.html" )
errorHTML = readTemplate ( "error.html" )
camliErrorHTML = readTemplate ( "camlierror.html" )
2013-03-15 23:26:21 +00:00
// TODO(mpl): see about not using text template anymore?
packageHTML = readTextTemplate ( "package.html" )
2011-01-03 23:49:19 +00:00
}
2012-02-21 10:11:28 +00:00
func serveError ( w http . ResponseWriter , r * http . Request , relpath string , err error ) {
2014-08-18 23:37:51 +00:00
contents := applyTemplate ( errorHTML , "errorHTML" , err ) // err may contain an absolute path!
2011-01-03 23:49:19 +00:00
w . WriteHeader ( http . StatusNotFound )
2011-01-25 20:43:43 +00:00
servePage ( w , "File " + relpath , "" , contents )
2011-01-03 23:49:19 +00:00
}
2013-10-11 16:33:43 +00:00
const gerritURLPrefix = "https://camlistore.googlesource.com/camlistore/+/"
2013-08-18 15:08:18 +00:00
var commitHash = regexp . MustCompile ( ` ^p=camlistore.git;a=commit;h=([0-9a-f]+)$ ` )
2013-08-28 20:09:01 +00:00
// empty return value means don't redirect.
func redirectPath ( u * url . URL ) string {
2013-08-18 15:08:18 +00:00
// Example:
// /code/?p=camlistore.git;a=commit;h=b0d2a8f0e5f27bbfc025a96ec3c7896b42d198ed
2013-08-28 20:09:01 +00:00
if strings . HasPrefix ( u . Path , "/code/" ) {
m := commitHash . FindStringSubmatch ( u . RawQuery )
2013-08-18 15:08:18 +00:00
if len ( m ) == 2 {
2013-10-11 16:33:43 +00:00
return gerritURLPrefix + m [ 1 ]
2013-08-18 15:08:18 +00:00
}
}
2013-08-28 20:09:01 +00:00
if strings . HasPrefix ( u . Path , "/gw/" ) {
path := strings . TrimPrefix ( u . Path , "/gw/" )
if strings . HasPrefix ( path , "doc" ) || strings . HasPrefix ( path , "clients" ) {
2013-10-11 16:33:43 +00:00
return gerritURLPrefix + "master/" + path
2013-08-28 20:09:01 +00:00
}
// Assume it's a commit
2013-10-11 16:33:43 +00:00
return gerritURLPrefix + path
2013-08-28 20:09:01 +00:00
}
return ""
}
func mainHandler ( rw http . ResponseWriter , req * http . Request ) {
if target := redirectPath ( req . URL ) ; target != "" {
http . Redirect ( rw , req , target , http . StatusFound )
return
}
2013-11-21 23:04:54 +00:00
if dest , ok := issueRedirect ( req . URL . Path ) ; ok {
http . Redirect ( rw , req , dest , http . StatusFound )
return
}
2016-04-25 21:56:30 +00:00
findAndServeFile ( rw , req , filepath . Join ( * root , "content" ) )
2011-01-03 23:49:19 +00:00
}
2013-08-23 04:53:49 +00:00
// modtime is the modification time of the resource to be served, or IsZero().
// return value is whether this request is now complete.
func checkLastModified ( w http . ResponseWriter , r * http . Request , modtime time . Time ) bool {
if modtime . IsZero ( ) {
return false
}
// The Date-Modified header truncates sub-second precision, so
/ / use mtime < t + 1 s instead of mtime <= t to check for unmodified .
if t , err := time . Parse ( http . TimeFormat , r . Header . Get ( "If-Modified-Since" ) ) ; err == nil && modtime . Before ( t . Add ( 1 * time . Second ) ) {
h := w . Header ( )
delete ( h , "Content-Type" )
delete ( h , "Content-Length" )
w . WriteHeader ( http . StatusNotModified )
return true
}
w . Header ( ) . Set ( "Last-Modified" , modtime . UTC ( ) . Format ( http . TimeFormat ) )
return false
}
2016-04-25 21:56:30 +00:00
// findAndServeFile finds the file in root to satisfy req. This method will
// map URLs to exact filename matches, falling back to files ending in ".md" or
// ".html". For example, a request for "/foo" may be served by a file named
// foo, foo.md, or foo.html. Requests that map to directories may be served by
// an index.html or README.md file in that directory.
func findAndServeFile ( rw http . ResponseWriter , req * http . Request , root string ) {
relPath := req . URL . Path [ 1 : ] // serveFile URL paths start with '/'
if strings . Contains ( relPath , ".." ) {
return
}
var (
absPath string
fi os . FileInfo
err error
)
for _ , ext := range [ ] string { "" , ".md" , ".html" } {
absPath = filepath . Join ( root , relPath + ext )
fi , err = os . Lstat ( absPath )
if err == nil || ! os . IsNotExist ( err ) {
break
}
}
if err != nil {
log . Print ( err )
serveError ( rw , req , relPath , err )
return
}
// if directory request, try to find an index file
if fi . IsDir ( ) {
for _ , index := range [ ] string { "index.html" , "README.md" } {
absPath = filepath . Join ( root , relPath , index )
fi , err = os . Lstat ( absPath )
if err == nil || ! os . IsNotExist ( err ) {
break
}
if fi . IsDir ( ) {
log . Printf ( "Error serving website content: %q is a directory" , absPath )
return
}
}
}
if err != nil {
log . Print ( err )
serveError ( rw , req , relPath , err )
return
}
if checkLastModified ( rw , req , fi . ModTime ( ) ) {
return
}
serveFile ( rw , req , relPath , absPath )
}
// serveFile serves a file from disk, converting any markdown to HTML.
2011-01-03 23:49:19 +00:00
func serveFile ( rw http . ResponseWriter , req * http . Request , relPath , absPath string ) {
data , err := ioutil . ReadFile ( absPath )
if err != nil {
serveError ( rw , req , absPath , err )
2011-01-25 20:43:43 +00:00
return
2011-01-03 23:49:19 +00:00
}
2011-01-28 19:17:08 +00:00
2015-07-23 21:25:43 +00:00
data = blackfriday . MarkdownCommon ( data )
2011-01-28 19:17:08 +00:00
title := ""
if m := h1TitlePattern . FindSubmatch ( data ) ; len ( m ) > 1 {
title = string ( m [ 1 ] )
}
servePage ( rw , title , "" , data )
2011-01-03 23:49:19 +00:00
}
2011-11-30 21:08:01 +00:00
func isBot ( r * http . Request ) bool {
agent := r . Header . Get ( "User-Agent" )
return strings . Contains ( agent , "Baidu" ) || strings . Contains ( agent , "bingbot" ) ||
strings . Contains ( agent , "Ezooms" ) || strings . Contains ( agent , "Googlebot" )
}
2011-01-26 06:48:15 +00:00
type noWwwHandler struct {
Handler http . Handler
}
func ( h * noWwwHandler ) ServeHTTP ( rw http . ResponseWriter , r * http . Request ) {
2011-11-30 21:08:01 +00:00
// Some bots (especially Baidu) don't seem to respect robots.txt and swamp gitweb.cgi,
// so explicitly protect it from bots.
2012-02-21 10:11:28 +00:00
if ru := r . URL . RequestURI ( ) ; strings . Contains ( ru , "/code/" ) && strings . Contains ( ru , "?" ) && isBot ( r ) {
2011-11-30 21:08:01 +00:00
http . Error ( rw , "bye" , http . StatusUnauthorized )
log . Printf ( "bot denied" )
return
}
2011-01-28 05:12:14 +00:00
host := strings . ToLower ( r . Host )
if host == "www.camlistore.org" {
2014-07-31 15:37:19 +00:00
scheme := "https"
if r . TLS == nil {
scheme = "http"
}
http . Redirect ( rw , r , scheme + "://camlistore.org" + r . URL . RequestURI ( ) , http . StatusFound )
2011-01-28 05:12:14 +00:00
return
}
2011-01-26 06:48:15 +00:00
h . Handler . ServeHTTP ( rw , r )
}
2013-06-24 22:50:49 +00:00
// runAsChild runs res as a child process and
// does not wait for it to finish.
func runAsChild ( res string ) {
cmdName , err := exec . LookPath ( res )
if err != nil {
log . Fatalf ( "Could not find %v in $PATH: %v" , res , err )
}
cmd := exec . Command ( cmdName )
2013-07-02 05:26:44 +00:00
cmd . Stderr = os . Stderr
cmd . Stdout = os . Stdout
2013-06-24 22:50:49 +00:00
log . Printf ( "Running %v" , res )
if err := cmd . Start ( ) ; err != nil {
2013-10-08 18:46:20 +00:00
log . Fatalf ( "Program %v failed to start: %v" , res , err )
2013-06-24 22:50:49 +00:00
}
go func ( ) {
if err := cmd . Wait ( ) ; err != nil {
log . Fatalf ( "Program %s did not end successfully: %v" , res , err )
}
} ( )
}
2015-12-04 21:36:02 +00:00
func gceDeployHandlerConfig ( ctx context . Context ) ( * gce . Config , error ) {
2015-11-11 16:54:18 +00:00
if inProd {
2015-12-04 21:36:02 +00:00
return deployerCredsFromGCS ( ctx )
2015-11-11 16:54:18 +00:00
}
clientId := os . Getenv ( "CAMLI_GCE_CLIENTID" )
if clientId != "" {
return & gce . Config {
ClientID : clientId ,
ClientSecret : os . Getenv ( "CAMLI_GCE_CLIENTSECRET" ) ,
Project : os . Getenv ( "CAMLI_GCE_PROJECT" ) ,
ServiceAccount : os . Getenv ( "CAMLI_GCE_SERVICE_ACCOUNT" ) ,
DataDir : os . Getenv ( "CAMLI_GCE_DATA" ) ,
} , nil
}
configFile := filepath . Join ( osutil . CamliConfigDir ( ) , "launcher-config.json" )
data , err := ioutil . ReadFile ( configFile )
if err != nil {
2015-12-27 04:49:29 +00:00
return nil , fmt . Errorf ( "error reading launcher-config.json (expected of type https://godoc.org/camlistore.org/pkg/deploy/gce#Config): %v" , err )
2015-11-11 16:54:18 +00:00
}
var config gce . Config
if err := json . Unmarshal ( data , & config ) ; err != nil {
return nil , err
}
return & config , nil
}
2015-12-27 04:49:29 +00:00
// gceDeployHandler returns an http.Handler for a GCE launcher,
2015-04-14 22:41:52 +00:00
// configured to run at /prefix/ (the trailing slash can be omitted).
2015-11-11 16:54:18 +00:00
// The launcher is not initialized if:
// - in production, the launcher-config.json file is not found in the relevant bucket
// - neither CAMLI_GCE_CLIENTID is set, nor launcher-config.json is found in the
// camlistore server config dir.
2015-12-27 21:52:35 +00:00
func gceDeployHandler ( ctx context . Context , prefix string ) ( * gce . DeployHandler , error ) {
2015-11-13 18:39:29 +00:00
var hostPort string
var err error
2015-12-27 21:52:35 +00:00
scheme := "https"
2015-11-13 18:39:29 +00:00
if inProd {
2015-04-14 22:41:52 +00:00
hostPort = "camlistore.org:443"
2015-11-13 18:39:29 +00:00
} else {
2015-12-27 04:49:29 +00:00
addr := * httpsAddr
if * devMode && * httpsAddr == "" {
addr = * httpAddr
2015-12-27 21:52:35 +00:00
scheme = "http"
2015-12-27 04:49:29 +00:00
}
hostPort , err = netutil . ListenHostPort ( addr )
2015-11-13 18:39:29 +00:00
if err != nil {
2015-12-27 04:49:29 +00:00
// the deploy handler needs to know its own
// hostname or IP for the oauth2 callback.
return nil , fmt . Errorf ( "invalid -https flag: %v" , err )
2015-11-13 18:39:29 +00:00
}
2015-04-14 22:41:52 +00:00
}
2015-12-04 21:36:02 +00:00
config , err := gceDeployHandlerConfig ( ctx )
2015-11-11 16:54:18 +00:00
if config == nil {
2015-12-27 04:49:29 +00:00
return nil , err
2015-04-14 22:41:52 +00:00
}
2015-11-11 16:54:18 +00:00
gceh , err := gce . NewDeployHandlerFromConfig ( hostPort , prefix , config )
2015-01-19 15:23:46 +00:00
if err != nil {
2015-12-27 04:49:29 +00:00
return nil , fmt . Errorf ( "NewDeployHandlerFromConfig: %v" , err )
2015-01-19 15:23:46 +00:00
}
2015-12-27 21:52:35 +00:00
2015-01-19 15:23:46 +00:00
pageBytes , err := ioutil . ReadFile ( filepath . Join ( * root , "tmpl" , "page.html" ) )
if err != nil {
2015-12-27 04:49:29 +00:00
return nil , err
2015-01-19 15:23:46 +00:00
}
2015-12-27 21:52:35 +00:00
if err := gceh . AddTemplateTheme ( string ( pageBytes ) ) ; err != nil {
2015-12-27 04:49:29 +00:00
return nil , fmt . Errorf ( "AddTemplateTheme: %v" , err )
2015-01-19 15:23:46 +00:00
}
2015-12-27 21:52:35 +00:00
gceh . SetScheme ( scheme )
log . Printf ( "Starting Camlistore launcher on %s://%s%s" , scheme , hostPort , prefix )
2015-12-27 04:49:29 +00:00
return gceh , nil
2015-01-19 15:23:46 +00:00
}
2015-11-08 15:48:51 +00:00
var launchConfig = & cloudlaunch . Config {
Name : "camweb" ,
2015-11-08 20:29:04 +00:00
BinaryBucket : "camlistore-website-resource" ,
2015-11-08 15:48:51 +00:00
GCEProjectID : "camlistore-website" ,
2015-11-10 12:10:16 +00:00
Scopes : [ ] string {
storageapi . DevstorageFullControlScope ,
compute . ComputeScope ,
logging . Scope ,
2015-12-29 05:08:17 +00:00
datastore . ScopeDatastore ,
datastore . ScopeUserEmail , // whose email? https://github.com/GoogleCloudPlatform/gcloud-golang/issues/201
2015-11-10 12:10:16 +00:00
} ,
2015-11-08 15:48:51 +00:00
}
2015-11-11 12:35:31 +00:00
func checkInProduction ( ) bool {
2015-11-08 21:20:35 +00:00
if ! metadata . OnGCE ( ) {
return false
}
proj , _ := metadata . ProjectID ( )
2015-11-08 21:45:09 +00:00
inst , _ := metadata . InstanceName ( )
2015-11-08 21:20:35 +00:00
log . Printf ( "Running on GCE: %v / %v" , proj , inst )
return proj == "camlistore-website" && inst == "camweb"
}
2015-11-11 14:32:00 +00:00
const prodSrcDir = "/var/camweb/src/camlistore.org"
2015-11-10 12:10:16 +00:00
2015-11-08 21:20:35 +00:00
func setProdFlags ( ) {
2015-11-11 12:35:31 +00:00
inProd = checkInProduction ( )
if ! inProd {
2015-11-08 21:20:35 +00:00
return
}
2015-12-27 04:49:29 +00:00
if * devMode {
log . Fatal ( "can't use dev mode in production" )
}
2015-11-10 12:10:16 +00:00
log . Printf ( "Running in production; configuring prod flags & containers" )
2016-01-18 16:12:46 +00:00
* flagChromeBugRepro = true
2015-11-08 21:20:35 +00:00
* httpAddr = ":80"
2015-11-10 09:11:31 +00:00
* httpsAddr = ":443"
2015-11-20 15:47:43 +00:00
* buildbotBackend = "https://travis-ci.org/camlistore/camlistore"
* buildbotHost = "build.camlistore.org"
2015-11-08 21:20:35 +00:00
* gceLogName = "camweb-access-log"
2015-11-10 12:10:16 +00:00
* root = filepath . Join ( prodSrcDir , "website" )
* gitContainer = true
2015-11-11 12:35:31 +00:00
* emailsTo = "camlistore-commits@googlegroups.com"
* smtpServer = "50.19.239.94:2500" // double firewall: rinetd allow + AWS
2015-11-10 12:10:16 +00:00
os . RemoveAll ( prodSrcDir )
if err := os . MkdirAll ( prodSrcDir , 0755 ) ; err != nil {
2015-11-08 21:20:35 +00:00
log . Fatal ( err )
}
2015-11-11 12:35:31 +00:00
log . Printf ( "fetching git docker image..." )
2015-11-10 12:10:16 +00:00
getDockerImage ( "camlistore/git" , "docker-git.tar.gz" )
2015-11-11 12:35:31 +00:00
getDockerImage ( "camlistore/demoblobserver" , "docker-demoblobserver.tar.gz" )
log . Printf ( "cloning camlistore git tree..." )
2015-11-10 12:10:16 +00:00
out , err := exec . Command ( "docker" , "run" ,
2015-11-11 12:35:31 +00:00
"--rm" ,
2015-11-10 12:10:16 +00:00
"-v" , "/var/camweb:/var/camweb" ,
"camlistore/git" ,
"git" ,
"clone" ,
2015-11-11 12:35:31 +00:00
"--depth=1" ,
2015-11-10 12:10:16 +00:00
"https://camlistore.googlesource.com/camlistore" ,
prodSrcDir ) . CombinedOutput ( )
2015-11-08 21:20:35 +00:00
if err != nil {
2015-11-10 12:10:16 +00:00
log . Fatalf ( "git clone: %v, %s" , err , out )
2015-11-08 21:20:35 +00:00
}
2015-11-10 12:10:16 +00:00
os . Chdir ( * root )
2015-11-11 12:35:31 +00:00
log . Printf ( "Starting." )
sendStartingEmail ( )
}
func randHex ( n int ) string {
buf := make ( [ ] byte , n / 2 + 1 )
rand . Read ( buf )
return fmt . Sprintf ( "%x" , buf ) [ : n ]
}
func runDemoBlobserverLoop ( ) {
if runtime . GOOS != "linux" {
return
}
if _ , err := exec . LookPath ( "docker" ) ; err != nil {
return
}
2015-11-11 14:45:50 +00:00
const name = "demoblob3179"
if err := exec . Command ( "docker" , "kill" , name ) . Run ( ) ; err == nil {
// It was actually running.
exec . Command ( "docker" , "rm" , name ) . Run ( )
log . Printf ( "Killed, removed old %q container." , name )
}
2015-11-11 12:35:31 +00:00
for {
2016-02-07 22:06:08 +00:00
var stderr bytes . Buffer
2015-11-11 12:35:31 +00:00
cmd := exec . Command ( "docker" , "run" ,
"--rm" ,
2015-11-11 14:45:50 +00:00
"--name=" + name ,
2015-11-11 14:32:00 +00:00
"-e" , "CAMLI_ROOT=" + prodSrcDir + "/website/blobserver-example/root" ,
2015-11-11 12:35:31 +00:00
"-e" , "CAMLI_PASSWORD=" + randHex ( 20 ) ,
2015-11-11 14:32:00 +00:00
"-v" , camSrcDir ( ) + ":" + prodSrcDir ,
2015-11-11 12:35:31 +00:00
"--net=host" ,
2015-11-11 14:32:00 +00:00
"--workdir=" + prodSrcDir ,
2015-11-11 12:35:31 +00:00
"camlistore/demoblobserver" ,
"camlistored" ,
"--openbrowser=false" ,
"--listen=:3179" ,
2015-11-11 14:32:00 +00:00
"--configfile=" + prodSrcDir + "/website/blobserver-example/example-blobserver-config.json" )
2016-02-07 22:06:08 +00:00
cmd . Stderr = & stderr
2015-11-11 12:35:31 +00:00
err := cmd . Run ( )
if err != nil {
2016-02-07 22:06:08 +00:00
log . Printf ( "Failed to run demo blob server: %v, stderr: %v" , err , stderr . String ( ) )
2015-11-11 12:35:31 +00:00
}
if ! inProd {
return
}
time . Sleep ( 10 * time . Second )
}
}
func sendStartingEmail ( ) {
contentRev , err := exec . Command ( "docker" , "run" ,
"--rm" ,
"-v" , "/var/camweb:/var/camweb" ,
2015-11-11 14:32:00 +00:00
"-w" , prodSrcDir ,
2015-11-11 12:35:31 +00:00
"camlistore/git" ,
"/bin/bash" , "-c" ,
"git show --pretty=format:'%ad-%h' --abbrev-commit --date=short | head -1" ) . Output ( )
cl , err := smtp . Dial ( * smtpServer )
if err != nil {
log . Printf ( "Failed to connect to SMTP server: %v" , err )
}
defer cl . Quit ( )
if err = cl . Mail ( "noreply@camlistore.org" ) ; err != nil {
return
}
if err = cl . Rcpt ( "brad@danga.com" ) ; err != nil {
return
}
if err = cl . Rcpt ( "mathieu.lonjaret@gmail.com" ) ; err != nil {
return
}
wc , err := cl . Data ( )
if err != nil {
return
}
_ , err = fmt . Fprintf ( wc , ` From : noreply @ camlistore . org ( Camlistore Website )
To : brad @ danga . com , mathieu . lonjaret @ gmail . com
Subject : Camlistore camweb restarting
Camlistore website starting with binary XXXXTODO and content at git rev % s
` , contentRev )
if err != nil {
return
}
wc . Close ( )
2015-11-10 12:10:16 +00:00
}
func getDockerImage ( tag , file string ) {
have , err := exec . Command ( "docker" , "inspect" , tag ) . Output ( )
if err == nil && len ( have ) > 0 {
return // we have it.
}
url := "https://storage.googleapis.com/camlistore-website-resource/" + file
err = exec . Command ( "/bin/bash" , "-c" , "curl --silent " + url + " | docker load" ) . Run ( )
if err != nil {
2015-11-08 21:45:09 +00:00
log . Fatal ( err )
}
2015-11-08 21:20:35 +00:00
}
2015-12-04 21:36:02 +00:00
// ctxt returns a Context suitable for Google Cloud Storage or Google Cloud
// Logging calls with the projID project ID.
func ctxt ( projID string ) context . Context {
var hc * http . Client
if * gceJWTFile != "" {
jsonSlurp , err := ioutil . ReadFile ( * gceJWTFile )
if err != nil {
log . Fatalf ( "Error reading --gce_jwt_file value: %v" , err )
}
jwtConf , err := google . JWTConfigFromJSON ( jsonSlurp , logging . Scope )
if err != nil {
log . Fatalf ( "Error reading --gce_jwt_file value: %v" , err )
}
hc = jwtConf . Client ( context . Background ( ) )
} else {
if ! metadata . OnGCE ( ) {
log . Fatal ( "No --gce_jwt_file and not running on GCE." )
}
var err error
hc , err = google . DefaultClient ( oauth2 . NoContext )
if err != nil {
log . Fatal ( err )
}
}
return cloud . NewContext ( projID , hc )
}
// projectID returns the GCE project ID used for running this camweb on GCE
// and/or for logging on Google Cloud Logging, if any.
func projectID ( ) string {
if * gceProjectID != "" {
return * gceProjectID
}
projID , err := metadata . ProjectID ( )
if projID == "" || err != nil {
log . Fatalf ( "GCE project ID needed but --gce_project_id not specified (and not running on GCE); metadata error: %v" , err )
}
return projID
}
2011-01-03 23:49:19 +00:00
func main ( ) {
2015-11-08 15:48:51 +00:00
launchConfig . MaybeDeploy ( )
2011-01-03 23:49:19 +00:00
flag . Parse ( )
2015-11-08 21:20:35 +00:00
setProdFlags ( )
2011-01-03 23:49:19 +00:00
2011-01-25 20:43:43 +00:00
if * root == "" {
2012-02-21 10:11:28 +00:00
var err error
2011-01-25 20:43:43 +00:00
* root , err = os . Getwd ( )
if err != nil {
2011-02-02 20:27:30 +00:00
log . Fatalf ( "Failed to getwd: %v" , err )
2011-01-25 20:43:43 +00:00
}
}
2013-03-15 23:26:21 +00:00
readTemplates ( )
2016-04-13 20:55:18 +00:00
if err := initGithubSyncing ( ) ; err != nil {
log . Fatalf ( "error setting up syncing to github: %v" )
}
2015-11-11 12:35:31 +00:00
go runDemoBlobserverLoop ( )
2011-06-22 00:07:59 +00:00
2011-01-03 23:49:19 +00:00
mux := http . DefaultServeMux
2011-06-30 04:13:03 +00:00
mux . Handle ( "/favicon.ico" , http . FileServer ( http . Dir ( filepath . Join ( * root , "static" ) ) ) )
mux . Handle ( "/robots.txt" , http . FileServer ( http . Dir ( filepath . Join ( * root , "static" ) ) ) )
mux . Handle ( "/static/" , http . StripPrefix ( "/static/" , http . FileServer ( http . Dir ( filepath . Join ( * root , "static" ) ) ) ) )
mux . Handle ( "/talks/" , http . StripPrefix ( "/talks/" , http . FileServer ( http . Dir ( filepath . Join ( * root , "talks" ) ) ) ) )
2013-03-14 00:42:56 +00:00
mux . Handle ( pkgPattern , godocHandler { } )
mux . Handle ( cmdPattern , godocHandler { } )
2014-08-18 23:37:51 +00:00
mux . HandleFunc ( errPattern , errHandler )
2011-01-25 20:43:43 +00:00
2013-08-14 16:59:31 +00:00
mux . HandleFunc ( "/r/" , gerritRedirect )
2015-09-02 14:25:07 +00:00
mux . HandleFunc ( "/dl/" , releaseRedirect )
2015-11-11 12:35:31 +00:00
mux . HandleFunc ( "/debug/ip" , ipHandler )
mux . HandleFunc ( "/debug/uptime" , uptimeHandler )
2014-03-19 02:28:07 +00:00
mux . Handle ( "/docs/contributing" , redirTo ( "/code#contributing" ) )
mux . Handle ( "/lists" , redirTo ( "/community" ) )
2011-06-13 21:24:51 +00:00
2014-10-23 20:21:53 +00:00
mux . HandleFunc ( "/contributors" , contribHandler ( ) )
2011-01-03 23:49:19 +00:00
mux . HandleFunc ( "/" , mainHandler )
2013-01-14 22:33:49 +00:00
if * buildbotHost != "" && * buildbotBackend != "" {
buildbotUrl , err := url . Parse ( * buildbotBackend )
if err != nil {
log . Fatalf ( "Failed to parse %v as a URL: %v" , * buildbotBackend , err )
}
buildbotHandler := httputil . NewSingleHostReverseProxy ( buildbotUrl )
bbhpattern := strings . TrimRight ( * buildbotHost , "/" ) + "/"
mux . Handle ( bbhpattern , buildbotHandler )
}
2015-12-04 21:36:02 +00:00
// ctx initialized now, because gceLauncher needs it first (when in prod).
// Other users are the GCE logger, and serveHTTPS (in prod).
var ctx context . Context
var projID string
if inProd || * gceLogName != "" {
projID = projectID ( )
ctx = ctxt ( projID )
}
2015-12-27 21:52:35 +00:00
gceLauncher , err := gceDeployHandler ( ctx , "/launch/" )
if err != nil {
2015-12-27 04:49:29 +00:00
log . Printf ( "Not installing GCE /launch/ handler: %v" , err )
mux . HandleFunc ( "/launch/" , func ( w http . ResponseWriter , r * http . Request ) {
http . Error ( w , fmt . Sprintf ( "GCE launcher disabled: %v" , err ) , 500 )
} )
} else {
2015-12-27 21:52:35 +00:00
mux . Handle ( "/launch/" , gceLauncher )
2015-01-19 15:23:46 +00:00
}
2011-01-29 19:53:22 +00:00
var handler http . Handler = & noWwwHandler { Handler : mux }
2011-01-29 20:31:35 +00:00
if * logDir != "" || * logStdout {
2015-08-16 12:30:09 +00:00
handler = NewLoggingHandler ( handler , NewApacheLogger ( * logDir , * logStdout ) )
2011-01-29 19:53:22 +00:00
}
2015-08-16 16:39:10 +00:00
if * gceLogName != "" {
2015-10-12 17:36:29 +00:00
logc , err := logging . NewClient ( ctx , projID , * gceLogName )
2015-08-16 16:39:10 +00:00
if err != nil {
log . Fatal ( err )
}
if err := logc . Ping ( ) ; err != nil {
log . Fatalf ( "Failed to ping Google Cloud Logging: %v" , err )
}
handler = NewLoggingHandler ( handler , gceLogger { logc } )
2015-11-13 18:39:29 +00:00
if gceLauncher != nil {
logc , err := logging . NewClient ( ctx , projID , * gceLogName )
if err != nil {
log . Fatal ( err )
}
logc . CommonLabels = map [ string ] string {
"from" : "camli-gce-launcher" ,
}
logger := logc . Logger ( logging . Default )
logger . SetPrefix ( "launcher: " )
gceLauncher . SetLogger ( logger )
}
2015-08-16 16:39:10 +00:00
}
2011-01-31 23:04:16 +00:00
2015-12-27 04:49:29 +00:00
emailErr := make ( chan error )
startEmailCommitLoop ( emailErr )
2013-08-23 04:33:23 +00:00
2013-06-24 22:50:49 +00:00
if * alsoRun != "" {
runAsChild ( * alsoRun )
}
2011-06-21 17:27:11 +00:00
httpServer := & http . Server {
Addr : * httpAddr ,
Handler : handler ,
2013-02-02 17:42:20 +00:00
ReadTimeout : 5 * time . Minute ,
WriteTimeout : 30 * time . Minute ,
2011-06-21 17:27:11 +00:00
}
2015-12-27 04:49:29 +00:00
httpErr := make ( chan error )
2011-06-21 17:27:11 +00:00
go func ( ) {
2015-12-24 22:51:15 +00:00
log . Printf ( "Listening for HTTP on %v" , * httpAddr )
2015-12-27 04:49:29 +00:00
httpErr <- httpServer . ListenAndServe ( )
2011-06-21 17:27:11 +00:00
} ( )
2015-12-27 04:49:29 +00:00
httpsErr := make ( chan error )
2011-06-21 17:27:11 +00:00
if * httpsAddr != "" {
go func ( ) {
2015-12-27 04:49:29 +00:00
httpsErr <- serveHTTPS ( ctx , httpServer )
2011-06-21 17:27:11 +00:00
} ( )
2011-01-03 23:49:19 +00:00
}
2011-01-31 23:04:16 +00:00
2016-01-18 16:12:46 +00:00
if * flagChromeBugRepro {
go func ( ) {
log . Printf ( "Repro handler failed: %v" , repro ( ":8001" , "foo:bar" ) )
} ( )
}
2015-12-27 04:49:29 +00:00
select {
case err := <- emailErr :
log . Fatalf ( "Error sending emails: %v" , err )
case err := <- httpErr :
log . Fatalf ( "Error serving HTTP: %v" , err )
case err := <- httpsErr :
log . Fatalf ( "Error serving HTTPS: %v" , err )
}
2015-11-10 09:11:31 +00:00
}
2015-12-04 21:36:02 +00:00
func serveHTTPS ( ctx context . Context , httpServer * http . Server ) error {
2015-11-10 09:11:31 +00:00
log . Printf ( "Starting TLS server on %s" , * httpsAddr )
httpsServer := new ( http . Server )
* httpsServer = * httpServer
httpsServer . Addr = * httpsAddr
2015-11-11 12:35:31 +00:00
if ! inProd {
2015-12-27 04:49:29 +00:00
if * tlsCertFile == "" {
return errors . New ( "unspecified --tlscert flag" )
}
if * tlsKeyFile == "" {
return errors . New ( "unspecified --tlskey flag" )
}
2015-11-10 09:11:31 +00:00
return httpsServer . ListenAndServeTLS ( * tlsCertFile , * tlsKeyFile )
}
2015-12-04 21:36:02 +00:00
cert , err := tlsCertFromGCS ( ctx )
2015-11-10 09:11:31 +00:00
if err != nil {
2015-12-27 04:49:29 +00:00
return fmt . Errorf ( "error loading TLS certs from GCS: %v" , err )
2015-11-10 09:11:31 +00:00
}
httpsServer . TLSConfig = & tls . Config {
Certificates : [ ] tls . Certificate { * cert } ,
}
2015-12-24 22:51:15 +00:00
log . Printf ( "Listening for HTTPS on %v" , * httpsAddr )
2015-11-10 09:11:31 +00:00
ln , err := net . Listen ( "tcp" , * httpsAddr )
if err != nil {
return err
}
return httpsServer . Serve ( tls . NewListener ( tcpKeepAliveListener { ln . ( * net . TCPListener ) } , httpsServer . TLSConfig ) )
}
type tcpKeepAliveListener struct {
* net . TCPListener
}
func ( ln tcpKeepAliveListener ) Accept ( ) ( c net . Conn , err error ) {
tc , err := ln . AcceptTCP ( )
if err != nil {
return
}
tc . SetKeepAlive ( true )
tc . SetKeepAlivePeriod ( 3 * time . Minute )
return tc , nil
}
2015-12-04 21:36:02 +00:00
func tlsCertFromGCS ( ctx context . Context ) ( * tls . Certificate , error ) {
sc , err := storage . NewClient ( ctx )
2015-11-10 09:11:31 +00:00
if err != nil {
return nil , err
}
slurp := func ( key string ) ( [ ] byte , error ) {
const bucket = "camlistore-website-resource"
2015-12-04 21:36:02 +00:00
rc , err := sc . Bucket ( bucket ) . Object ( key ) . NewReader ( ctx )
2015-11-10 09:11:31 +00:00
if err != nil {
return nil , fmt . Errorf ( "Error fetching GCS object %q in bucket %q: %v" , key , bucket , err )
}
defer rc . Close ( )
return ioutil . ReadAll ( rc )
}
certPem , err := slurp ( "ssl.crt" )
if err != nil {
return nil , err
}
keyPem , err := slurp ( "ssl.key" )
if err != nil {
return nil , err
}
cert , err := tls . X509KeyPair ( certPem , keyPem )
if err != nil {
return nil , err
}
return & cert , nil
2011-01-31 23:04:16 +00:00
}
2015-12-04 21:36:02 +00:00
func deployerCredsFromGCS ( ctx context . Context ) ( * gce . Config , error ) {
sc , err := storage . NewClient ( ctx )
2015-11-11 16:54:18 +00:00
if err != nil {
return nil , err
}
slurp := func ( key string ) ( [ ] byte , error ) {
const bucket = "camlistore-website-resource"
2015-12-04 21:36:02 +00:00
rc , err := sc . Bucket ( bucket ) . Object ( key ) . NewReader ( ctx )
2015-11-11 16:54:18 +00:00
if err != nil {
return nil , fmt . Errorf ( "Error fetching GCS object %q in bucket %q: %v" , key , bucket , err )
}
defer rc . Close ( )
return ioutil . ReadAll ( rc )
}
var cfg gce . Config
data , err := slurp ( "launcher-config.json" )
if err != nil {
return nil , err
}
if err := json . Unmarshal ( data , & cfg ) ; err != nil {
return nil , fmt . Errorf ( "Could not JSON decode camli GCE launcher config: %v" , err )
}
return & cfg , nil
}
2014-12-11 06:08:18 +00:00
var issueNum = regexp . MustCompile ( ` ^/(?:issue|bug)s?(/\d*)?$ ` )
2013-02-18 05:40:57 +00:00
2013-11-21 23:04:54 +00:00
// issueRedirect returns whether the request should be redirected to the
// issues tracker, and the url for that redirection if yes, the empty
// string otherwise.
func issueRedirect ( urlPath string ) ( string , bool ) {
m := issueNum . FindStringSubmatch ( urlPath )
2013-02-18 05:40:57 +00:00
if m == nil {
2013-11-21 23:04:54 +00:00
return "" , false
}
issueNumber := strings . TrimPrefix ( m [ 1 ] , "/" )
2014-12-11 06:08:18 +00:00
suffix := ""
2013-11-21 23:04:54 +00:00
if issueNumber != "" {
2014-12-11 06:08:18 +00:00
suffix = "/" + issueNumber
2013-02-18 05:40:57 +00:00
}
2014-12-11 06:08:18 +00:00
return "https://github.com/camlistore/camlistore/issues" + suffix , true
2013-02-18 05:40:57 +00:00
}
2013-08-14 16:59:31 +00:00
func gerritRedirect ( w http . ResponseWriter , r * http . Request ) {
dest := "https://camlistore-review.googlesource.com/"
if len ( r . URL . Path ) > len ( "/r/" ) {
dest += r . URL . Path [ 1 : ]
}
http . Redirect ( w , r , dest , http . StatusFound )
2011-02-02 21:00:32 +00:00
}
2015-09-02 14:25:07 +00:00
func releaseRedirect ( w http . ResponseWriter , r * http . Request ) {
2015-12-30 18:50:54 +00:00
if r . URL . Path == "/dl" || r . URL . Path == "/dl/" {
http . Redirect ( w , r , "https://camlistore.org/download/" , http . StatusFound )
return
}
2015-12-30 19:10:41 +00:00
dest := "https://storage.googleapis.com/camlistore-release/" + strings . TrimPrefix ( r . URL . Path , "/dl/" )
2015-09-02 14:25:07 +00:00
http . Redirect ( w , r , dest , http . StatusFound )
}
2014-03-19 02:28:07 +00:00
func redirTo ( dest string ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
http . Redirect ( w , r , dest , http . StatusFound )
} )
2014-02-06 22:14:19 +00:00
}
2011-02-02 21:00:32 +00:00
// Not sure what's making these broken URLs like:
//
// http://localhost:8080/code/?p=camlistore.git%3Bf=doc/json-signing/json-signing.txt%3Bhb=master
//
// ... but something is. Maybe Buzz? For now just re-write them
// . Doesn't seem to be a bug in the CGI implementation, though, which
// is what I'd originally suspected.
2013-08-14 16:59:31 +00:00
/ *
2011-02-02 21:00:32 +00:00
func ( fu * fixUpGitwebUrls ) ServeHTTP ( rw http . ResponseWriter , req * http . Request ) {
2012-02-21 10:11:28 +00:00
oldUrl := req . URL . String ( )
2011-02-02 21:00:32 +00:00
newUrl := strings . Replace ( oldUrl , "%3B" , ";" , - 1 )
if newUrl == oldUrl {
fu . handler . ServeHTTP ( rw , req )
return
}
http . Redirect ( rw , req , newUrl , http . StatusFound )
}
2013-08-14 16:59:31 +00:00
* /
2011-11-28 22:52:58 +00:00
func ipHandler ( w http . ResponseWriter , r * http . Request ) {
out , _ := exec . Command ( "ip" , "-f" , "inet" , "addr" , "show" , "dev" , "eth0" ) . Output ( )
str := string ( out )
pos := strings . Index ( str , "inet " )
if pos == - 1 {
return
}
2011-11-30 21:08:01 +00:00
str = str [ pos + 5 : ]
2011-11-28 22:52:58 +00:00
pos = strings . Index ( str , "/" )
if pos == - 1 {
return
}
str = str [ : pos ]
w . Write ( [ ] byte ( str ) )
}
2014-08-18 23:37:51 +00:00
2015-11-11 12:35:31 +00:00
var startTime = time . Now ( )
func uptimeHandler ( w http . ResponseWriter , r * http . Request ) {
fmt . Fprintf ( w , "%v" , time . Now ( ) . Sub ( startTime ) )
}
2014-08-19 15:30:51 +00:00
const (
errPattern = "/err/"
toHyperlink = ` <a href="$1$2">$1$2</a> `
)
var camliURLPattern = regexp . MustCompile ( ` (https?://camlistore.org)([a-zA-Z0-9\-\_/]+)? ` )
2014-08-18 23:37:51 +00:00
func errHandler ( w http . ResponseWriter , r * http . Request ) {
errString := strings . TrimPrefix ( r . URL . Path , errPattern )
defer func ( ) {
if x := recover ( ) ; x != nil {
http . Error ( w , fmt . Sprintf ( "unknown error: %v" , errString ) , http . StatusNotFound )
}
} ( )
err := camtypes . Err ( errString )
data := struct {
Code string
2014-08-19 15:30:51 +00:00
Description template . HTML
2014-08-18 23:37:51 +00:00
} {
Code : errString ,
2014-08-19 15:30:51 +00:00
Description : template . HTML ( camliURLPattern . ReplaceAllString ( err . Error ( ) , toHyperlink ) ) ,
2014-08-18 23:37:51 +00:00
}
contents := applyTemplate ( camliErrorHTML , "camliErrorHTML" , data )
w . WriteHeader ( http . StatusFound )
servePage ( w , errString , "" , contents )
}
2015-11-11 12:35:31 +00:00
func camSrcDir ( ) string {
if inProd {
return prodSrcDir
}
dir , err := osutil . GoPackagePath ( "camlistore.org" )
if err != nil {
log . Fatalf ( "Failed to find the root of the Camlistore source code via osutil.GoPackagePath: %v" , err )
}
return dir
}