func main() { // Define the options for the command line and config file options parser. opts := optparse.Parser( "Usage: live-server <config.yaml> [options]\n", "live-server 0.0.1") debug := opts.Bool([]string{"-d", "--debug"}, false, "enable debug mode") genConfig := opts.Bool([]string{"-g", "--gen-config"}, false, "show the default yaml config") frontendHost := opts.StringConfig("frontend-host", "", "the host to bind the HTTPS Frontends to") frontendPort := opts.IntConfig("frontend-port", 9040, "the base port for the HTTPS Frontends [9040]") officialHost := opts.StringConfig("offficial-host", "", "the official public host for the HTTPS Frontends") primaryHosts := opts.StringConfig("primary-hosts", "", "limit the primary HTTPS Frontend to the specified host pattern") primaryCert := opts.StringConfig("primary-cert", "cert/primary.cert", "the path to the primary host's TLS certificate [cert/primary.cert]") primaryKey := opts.StringConfig("primary-key", "cert/primary.key", "the path to the primary host's TLS key [cert/primary.key]") noSecondary := opts.BoolConfig("no-secondary", false, "disable the secondary HTTPS Frontend [false]") secondaryHosts := opts.StringConfig("secondary-hosts", "", "limit the secondary HTTPS Frontend to the specified host pattern") secondaryCert := opts.StringConfig("secondary-cert", "cert/secondary.cert", "the path to the secondary host's TLS certificate [cert/secondary.cert]") secondaryKey := opts.StringConfig("secondary-key", "cert/secondary.key", "the path to the secondary host's TLS key [cert/secondary.key]") errorDirectory := opts.StringConfig("error-dir", "error", "the path to the HTTP error files directory [error]") logDirectory := opts.StringConfig("log-dir", "log", "the path to the log directory [log]") runDirectory := opts.StringConfig("run-dir", "run", "the path to the run directory to store locks, pid files, etc. [run]") staticDirectory := opts.StringConfig("static-dir", "www", "the path to the static files directory [www]") staticMaxAge := opts.IntConfig("static-max-age", 86400, "max-age cache header value when serving the static files [86400]") noLivequery := opts.BoolConfig("no-livequery", false, "disable the LiveQuery node and WebSocket/Comet support [false]") websocketPrefix := opts.StringConfig("websocket-prefix", "/.live/ws", "URL path prefix for WebSocket requests [/.live/ws]") cometPrefix := opts.StringConfig("comet-prefix", "/.live/poll", "URL path prefix for Comet requests [/.live/poll]") livequeryHost := opts.StringConfig("livequery-host", "", "the host to bind the LiveQuery node to") livequeryPort := opts.IntConfig("livequery-port", 9050, "the port (both UDP and TCP) to bind the LiveQuery node to [9050]") livequeryExpiry := opts.IntConfig("livequery-expiry", 40, "maximum number of seconds a LiveQuery subscription is valid [40]") cookieKeyPath := opts.StringConfig("cookie-key", "cert/cookie.key", "the path to the file containing the key used to sign cookies [cert/cookie.key]") cookieName := opts.StringConfig("cookie-name", "user", "the property name of the cookie containing the user id [user]") acceptors := opts.StringConfig("acceptor-nodes", "localhost:9060", "comma-separated addresses of Acceptor nodes [localhost:9060]") acceptorKeyPath := opts.StringConfig("acceptor-key", "cert/acceptor.key", "the path to the file containing the Acceptor secret key [cert/acceptor.key]") runAcceptor := opts.BoolConfig("run-as-acceptor", false, "run as an Acceptor node [false]") acceptorIndex := opts.IntConfig("acceptor-index", 0, "this node's index in the Acceptor nodes address list [0]") leaseExpiry := opts.IntConfig("lease-expiry", 7, "maximum number of seconds a lease from an Acceptor node is valid [7]") noRedirect := opts.BoolConfig("no-redirect", false, "disable the HTTP Redirector [false]") httpHost := opts.StringConfig("http-host", "", "the host to bind the HTTP Redirector to") httpPort := opts.IntConfig("http-port", 9080, "the port to bind the HTTP Redirector to [9080]") redirectURL := opts.StringConfig("redirect-url", "", "the URL that the HTTP Redirector redirects to") pingPath := opts.StringConfig("ping-path", "/.ping", `URL path for a "ping" request [/.ping]`) enableHSTS := opts.BoolConfig("enable-hsts", false, "enable HTTP Strict Transport Security (HSTS) on redirects [false]") hstsMaxAge := opts.IntConfig("hsts-max-age", 50000000, "max-age value of HSTS in number of seconds [50000000]") 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]") logRotate := opts.StringConfig("log-rotate", "never", "specify one of 'hourly', 'daily' or 'never' [never]") noConsoleLog := opts.BoolConfig("no-console-log", false, "disable server requests being logged to the console [false]") maintenanceMode := opts.BoolConfig("maintenance", false, "start up in maintenance mode [false]") extraConfig := opts.StringConfig("extra-config", "", "path to a YAML config file with additional options") // Parse the command line options. os.Args[0] = "live-server" args := opts.Parse(os.Args) // Print the default YAML config file if the ``-g`` flag was specified. if *genConfig { opts.PrintDefaultConfigFile("live-server") runtime.Exit(0) } // Setup the console logger early. if !*noConsoleLog { log.AddConsoleLogger() log.ConsoleFilters["ls"] = func(items []interface{}) (bool, []interface{}) { return true, items[2 : len(items)-2] } } // Set the debug mode flag if the ``-d`` flag was specified. debugMode = *debug var instanceDirectory string var configPath string var err error // Assume the parent directory of the config as the instance directory. if len(args) >= 1 { if args[0] == "help" { opts.PrintUsage() runtime.Exit(0) } configPath, err = filepath.Abs(filepath.Clean(args[0])) if err != nil { runtime.StandardError(err) } err = opts.ParseConfig(configPath, os.Args) if err != nil { runtime.StandardError(err) } instanceDirectory, _ = filepath.Split(configPath) } else { opts.PrintUsage() runtime.Exit(0) } // Load the extra config file with additional options if one has been // specified. if *extraConfig != "" { extraConfigPath, err := filepath.Abs(filepath.Clean(*extraConfig)) if err != nil { runtime.StandardError(err) } extraConfigPath = runtime.JoinPath(instanceDirectory, extraConfigPath) err = opts.ParseConfig(extraConfigPath, os.Args) if err != nil { runtime.StandardError(err) } } // Create the log directory if it doesn't exist. logPath := runtime.JoinPath(instanceDirectory, *logDirectory) err = os.MkdirAll(logPath, 0755) if err != nil { runtime.StandardError(err) } // Create the run directory if it doesn't exist. runPath := runtime.JoinPath(instanceDirectory, *runDirectory) err = os.MkdirAll(runPath, 0755) if err != nil { runtime.StandardError(err) } // Initialise the runtime -- which will run ``live-server`` on multiple // processors if possible. runtime.Init() // Handle running as an Acceptor node if ``--run-as-acceptor`` was // specified. if *runAcceptor { // Exit if the `--acceptor-index`` is negative. if *acceptorIndex < 0 { runtime.Error("The --acceptor-index cannot be negative.") } var index int var selfAddress string var acceptorNodes []string // Generate a list of all the acceptor node addresses and exit if we // couldn't find the address four ourselves at the given index. for _, acceptor := range strings.Split(*acceptors, ",") { acceptor = strings.TrimSpace(acceptor) if acceptor != "" { if index == *acceptorIndex { selfAddress = acceptor } else { acceptorNodes = append(acceptorNodes, acceptor) } } index += 1 } if selfAddress == "" { runtime.Error("Couldn't determine the address for the acceptor.") } // Initialise the process-related resources. runtime.InitProcess(fmt.Sprintf("acceptor-%d", *acceptorIndex), runPath) return } // Initialise the process-related resources. runtime.InitProcess("live-server", runPath) // Ensure that the directory containing static files exists. staticPath := runtime.JoinPath(instanceDirectory, *staticDirectory) dirInfo, err := os.Stat(staticPath) if err == nil { if !dirInfo.IsDir() { runtime.Error("Static path %q is not a directory", staticPath) } } else { runtime.StandardError(err) } // Load up all static files into a mapping. staticFiles := make(map[string]*StaticFile) getFiles(staticPath, staticFiles, "") // Pre-format the Cache-Control header for static files. staticCache := fmt.Sprintf("public, max-age=%d", *staticMaxAge) staticMaxAge64 := time.Duration(*staticMaxAge) // Exit if the directory containing the 50x.html files isn't present. errorPath := runtime.JoinPath(instanceDirectory, *errorDirectory) dirInfo, err = os.Stat(errorPath) if err == nil { if !dirInfo.IsDir() { runtime.Error("Error path %q is not a directory", errorPath) } } else { runtime.StandardError(err) } // Load the content for the HTTP ``400``, ``500``, ``502`` and ``503`` // errors. error400, error400Length = getErrorInfo(errorPath, "400.html") error500, error500Length = getErrorInfo(errorPath, "500.html") error502, error502Length = getErrorInfo(errorPath, "502.html") error503, error503Length = getErrorInfo(errorPath, "503.html") // Initialise the TLS config. tlsconf.Init() // Setup the file loggers. var rotate int switch *logRotate { case "daily": rotate = log.RotateDaily case "hourly": rotate = log.RotateHourly case "never": rotate = log.RotateNever default: runtime.Error("Unknown log rotation format %q", *logRotate) } _, err = log.AddFileLogger("live-server", logPath, rotate, log.InfoLog) if err != nil { runtime.Error("Couldn't initialise logfile: %s", err) } _, err = log.AddFileLogger("error", logPath, rotate, log.ErrorLog) if err != nil { runtime.Error("Couldn't initialise logfile: %s", err) } var liveMode bool // Setup the live support as long as it hasn't been disabled. if !*noLivequery { go handleLiveMessages() acceptorKey, err = ioutil.ReadFile(runtime.JoinPath(instanceDirectory, *acceptorKeyPath)) if err != nil { runtime.StandardError(err) } cookieKey, err = ioutil.ReadFile(runtime.JoinPath(instanceDirectory, *cookieKeyPath)) if err != nil { runtime.StandardError(err) } liveMode = true _ = *livequeryHost _ = *livequeryPort _ = *cookieName _ = *leaseExpiry livequeryTimeout = (time.Duration(*livequeryExpiry) / 2) * 1000000000 } // Create a container for the Frontend instances. frontends := make([]*Frontend, 0) // Create a channel which is used to toggle the state of the live-server's // maintenance mode based on process signals. maintenanceChannel := make(chan bool, 1) // Fork a goroutine which toggles the maintenance mode in a single place and // thus ensures "thread safety". go func() { for { enabledState := <-maintenanceChannel for _, frontend := range frontends { if enabledState { frontend.maintenanceMode = true } else { frontend.maintenanceMode = false } } } }() // Register the signal handlers for SIGUSR1 and SIGUSR2. runtime.SignalHandlers[os.SIGUSR1] = func() { maintenanceChannel <- true } runtime.SignalHandlers[os.SIGUSR2] = func() { maintenanceChannel <- false } // Let the user know how many CPUs we're currently running on. fmt.Printf("Running live-server with %d CPUs:\n", runtime.CPUCount) // If ``--public-address`` hasn't been specified, generate it from the given // frontend host and base port values -- assuming ``localhost`` for a blank // host. publicHost := *officialHost if publicHost == "" { if *frontendHost == "" { publicHost = fmt.Sprintf("localhost:%d", *frontendPort) } else { publicHost = fmt.Sprintf("%s:%d", *frontendHost, *frontendPort) } } // Setup and run the primary HTTPS Frontend. frontends = append(frontends, initFrontend("primary", *frontendHost, *frontendPort, publicHost, *primaryHosts, *primaryCert, *primaryKey, *cometPrefix, *websocketPrefix, instanceDirectory, *upstreamHost, *upstreamPort, *upstreamTLS, *maintenanceMode, liveMode, staticCache, staticFiles, staticMaxAge64)) // Setup and run the secondary HTTPS Frontend. if !*noSecondary { frontends = append(frontends, initFrontend("secondary", *frontendHost, *frontendPort+1, publicHost, *secondaryHosts, *secondaryCert, *secondaryKey, *cometPrefix, *websocketPrefix, instanceDirectory, *upstreamHost, *upstreamPort, *upstreamTLS, *maintenanceMode, liveMode, staticCache, staticFiles, staticMaxAge64)) } // Enter a wait loop if the HTTP Redirector has been disabled. if *noRedirect { loopForever := make(chan bool, 1) <-loopForever } // Otherwise, setup the HTTP Redirector. if *httpHost == "" { *httpHost = "localhost" } if *redirectURL == "" { *redirectURL = "https://" + publicHost } httpAddr := fmt.Sprintf("%s:%d", *httpHost, *httpPort) httpListener, err := net.Listen("tcp", httpAddr) if err != nil { runtime.Error("Cannot listen on %s: %v", httpAddr, err) } hsts := "" if *enableHSTS { hsts = fmt.Sprintf("max-age=%d", *hstsMaxAge) } redirector := &Redirector{ hsts: hsts, pingPath: *pingPath, url: *redirectURL, } // Start a goroutine which runs the HTTP redirector. go func() { err = http.Serve(httpListener, redirector) if err != nil { runtime.Error("Couldn't serve HTTP Redirector: %s", err) } }() fmt.Printf("* HTTP Redirector running on http://%s:%d -> %s\n", *httpHost, *httpPort, *redirectURL) // Enter the wait loop for the process to be killed. loopForever := make(chan bool, 1) <-loopForever }
func DefaultOpts(name string, opts *optparse.OptionParser, argv []string) (bool, string, string) { var ( configPath string instanceDirectory string err error ) debug := opts.Bool([]string{"-d", "--debug"}, false, "enable debug mode") genConfig := opts.Bool([]string{"-g", "--gen-config"}, false, "show the default yaml config") runDirectory := opts.StringConfig("run-dir", "run", "the path to the run directory to store locks, pid files, etc. [run]") logDirectory := opts.StringConfig("log-dir", "log", "the path to the log directory [log]") logRotate := opts.StringConfig("log-rotate", "never", "specify one of 'hourly', 'daily' or 'never' [never]") noConsoleLog := opts.BoolConfig("no-console-log", false, "disable server requests being logged to the console [false]") extraConfig := opts.StringConfig("extra-config", "", "path to a YAML config file with additional options") // Parse the command line options. args := opts.Parse(argv) // Print the default YAML config file if the ``-g`` flag was specified. if *genConfig { opts.PrintDefaultConfigFile(name) Exit(0) } // Enable the console logger early. if !*noConsoleLog { log.AddConsoleLogger() } // Assume the parent directory of the config as the instance directory. if len(args) >= 1 { configPath, err = filepath.Abs(filepath.Clean(args[0])) if err != nil { StandardError(err) } err = opts.ParseConfig(configPath, os.Args) if err != nil { StandardError(err) } instanceDirectory, _ = filepath.Split(configPath) Profile = strings.Split(filepath.Base(configPath), ".")[0] } else { opts.PrintUsage() Exit(0) } // Load the extra config file with additional options if one has been // specified. if *extraConfig != "" { extraConfigPath, err := filepath.Abs(filepath.Clean(*extraConfig)) if err != nil { StandardError(err) } extraConfigPath = JoinPath(instanceDirectory, extraConfigPath) err = opts.ParseConfig(extraConfigPath, os.Args) if err != nil { StandardError(err) } } // Create the log directory if it doesn't exist. logPath := JoinPath(instanceDirectory, *logDirectory) err = os.MkdirAll(logPath, 0755) if err != nil { StandardError(err) } // Create the run directory if it doesn't exist. runPath := JoinPath(instanceDirectory, *runDirectory) err = os.MkdirAll(runPath, 0755) if err != nil { StandardError(err) } // Setup the file and console logging. var rotate int switch *logRotate { case "daily": rotate = log.RotateDaily case "hourly": rotate = log.RotateHourly case "never": rotate = log.RotateNever default: Error("Unknown log rotation format %q", *logRotate) } _, err = log.AddFileLogger(name, logPath, rotate, log.InfoLog) if err != nil { Error("Couldn't initialise logfile: %s", err) } _, err = log.AddFileLogger("error", logPath, rotate, log.ErrorLog) if err != nil { Error("Couldn't initialise logfile: %s", err) } // Initialise the runtime -- which will run the process on multiple // processors if possible. Init() // Initialise the process-related resources. InitProcess(name, runPath) return *debug, instanceDirectory, runPath }