2015-01-13 14:43:45 -05:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2015-05-04 17:23:16 -05:00
|
|
|
"bytes"
|
2015-01-13 18:14:00 -05:00
|
|
|
"flag"
|
2015-04-15 15:11:32 -05:00
|
|
|
"fmt"
|
2015-05-04 17:23:16 -05:00
|
|
|
"io/ioutil"
|
2015-01-13 14:43:45 -05:00
|
|
|
"log"
|
2015-05-04 07:53:54 -05:00
|
|
|
"os"
|
2015-05-20 21:46:27 -05:00
|
|
|
"os/exec"
|
2015-05-04 07:53:54 -05:00
|
|
|
"path"
|
2015-04-24 21:08:14 -05:00
|
|
|
"runtime"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2015-01-13 14:43:45 -05:00
|
|
|
|
2015-05-21 01:06:53 -05:00
|
|
|
"github.com/mholt/caddy/app"
|
2015-01-13 14:43:45 -05:00
|
|
|
"github.com/mholt/caddy/config"
|
|
|
|
"github.com/mholt/caddy/server"
|
|
|
|
)
|
|
|
|
|
2015-04-09 11:08:22 -05:00
|
|
|
var (
|
2015-05-07 14:09:40 -05:00
|
|
|
conf string
|
|
|
|
cpu string
|
|
|
|
version bool
|
2015-04-09 11:08:22 -05:00
|
|
|
)
|
2015-01-19 01:11:21 -05:00
|
|
|
|
|
|
|
func init() {
|
2015-05-06 00:19:14 -05:00
|
|
|
flag.StringVar(&conf, "conf", "", "Configuration file to use (default="+config.DefaultConfigFile+")")
|
2015-05-21 01:06:53 -05:00
|
|
|
flag.BoolVar(&app.Http2, "http2", true, "Enable HTTP/2 support") // TODO: temporary flag until http2 merged into std lib
|
|
|
|
flag.BoolVar(&app.Quiet, "quiet", false, "Quiet mode (no initialization output)")
|
2015-04-24 21:08:14 -05:00
|
|
|
flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
|
2015-05-04 07:53:54 -05:00
|
|
|
flag.StringVar(&config.Root, "root", config.DefaultRoot, "Root path to default site")
|
2015-04-29 23:30:12 -05:00
|
|
|
flag.StringVar(&config.Host, "host", config.DefaultHost, "Default host")
|
2015-04-28 23:13:00 -05:00
|
|
|
flag.StringVar(&config.Port, "port", config.DefaultPort, "Default port")
|
2015-05-07 14:09:40 -05:00
|
|
|
flag.BoolVar(&version, "version", false, "Show version")
|
2015-01-19 01:11:21 -05:00
|
|
|
}
|
|
|
|
|
2015-01-13 14:43:45 -05:00
|
|
|
func main() {
|
2015-05-06 15:57:32 -05:00
|
|
|
flag.Parse()
|
|
|
|
|
2015-05-07 14:09:40 -05:00
|
|
|
if version {
|
2015-05-21 01:06:53 -05:00
|
|
|
fmt.Printf("%s %s\n", app.Name, app.Version)
|
2015-05-07 14:09:40 -05:00
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
2015-04-24 21:08:14 -05:00
|
|
|
// Set CPU cap
|
2015-05-21 01:06:53 -05:00
|
|
|
err := app.SetCPU(cpu)
|
2015-04-24 21:08:14 -05:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2015-04-15 15:11:32 -05:00
|
|
|
// Load config from file
|
2015-05-04 17:23:16 -05:00
|
|
|
allConfigs, err := loadConfigs()
|
2015-01-13 14:43:45 -05:00
|
|
|
if err != nil {
|
2015-05-04 07:53:54 -05:00
|
|
|
log.Fatal(err)
|
2015-04-18 10:53:43 -05:00
|
|
|
}
|
2015-01-13 14:43:45 -05:00
|
|
|
|
2015-04-18 10:53:43 -05:00
|
|
|
// Group by address (virtual hosts)
|
2015-05-21 01:06:53 -05:00
|
|
|
addresses, err := config.ArrangeBindings(allConfigs)
|
2015-04-15 15:11:32 -05:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start each server with its one or more configurations
|
|
|
|
for addr, configs := range addresses {
|
2015-07-11 13:00:11 -05:00
|
|
|
s, err := server.New(addr.String(), configs)
|
2015-01-13 14:43:45 -05:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
2015-05-21 01:06:53 -05:00
|
|
|
s.HTTP2 = app.Http2 // TODO: This setting is temporary
|
|
|
|
app.Wg.Add(1)
|
2015-01-13 14:43:45 -05:00
|
|
|
go func(s *server.Server) {
|
2015-05-21 01:06:53 -05:00
|
|
|
defer app.Wg.Done()
|
2015-01-13 14:43:45 -05:00
|
|
|
err := s.Serve()
|
|
|
|
if err != nil {
|
2015-05-01 14:35:17 -05:00
|
|
|
log.Fatal(err) // kill whole process to avoid a half-alive zombie server
|
2015-01-13 14:43:45 -05:00
|
|
|
}
|
|
|
|
}(s)
|
2015-04-23 14:28:05 -05:00
|
|
|
|
2015-05-21 01:06:53 -05:00
|
|
|
app.Servers = append(app.Servers, s)
|
2015-05-21 01:40:05 -05:00
|
|
|
}
|
2015-05-21 01:06:53 -05:00
|
|
|
|
2015-05-21 01:40:05 -05:00
|
|
|
// Show initialization output
|
|
|
|
if !app.Quiet {
|
|
|
|
var checkedFdLimit bool
|
|
|
|
for addr, configs := range addresses {
|
|
|
|
for _, conf := range configs {
|
|
|
|
// Print address of site
|
|
|
|
fmt.Println(conf.Address())
|
|
|
|
|
|
|
|
// Note if non-localhost site resolves to loopback interface
|
|
|
|
if addr.IP.IsLoopback() && !isLocalhost(conf.Host) {
|
|
|
|
fmt.Printf("Notice: %s is only accessible on this machine (%s)\n",
|
|
|
|
conf.Host, addr.IP.String())
|
2015-05-20 21:06:30 -05:00
|
|
|
}
|
2015-05-21 01:40:05 -05:00
|
|
|
}
|
2015-05-20 21:46:27 -05:00
|
|
|
|
2015-05-22 19:34:00 -05:00
|
|
|
if !checkedFdLimit && !addr.IP.IsLoopback() {
|
|
|
|
checkFdlimit()
|
|
|
|
checkedFdLimit = true
|
2015-05-20 21:06:30 -05:00
|
|
|
}
|
2015-04-23 14:28:05 -05:00
|
|
|
}
|
2015-01-13 14:43:45 -05:00
|
|
|
}
|
|
|
|
|
2015-05-21 01:40:05 -05:00
|
|
|
// Wait for all listeners to stop
|
2015-05-21 01:06:53 -05:00
|
|
|
app.Wg.Wait()
|
2015-01-13 14:43:45 -05:00
|
|
|
}
|
2015-04-15 15:11:32 -05:00
|
|
|
|
2015-05-22 19:34:00 -05:00
|
|
|
// checkFdlimit issues a warning if the OS max file descriptors is below a recommended minimum.
|
|
|
|
func checkFdlimit() {
|
|
|
|
const min = 4096
|
|
|
|
|
|
|
|
// Warn if ulimit is too low for production sites
|
|
|
|
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
|
|
|
|
out, err := exec.Command("sh", "-c", "ulimit -n").Output() // use sh because ulimit isn't in Linux $PATH
|
|
|
|
if err == nil {
|
|
|
|
// Note that an error here need not be reported
|
|
|
|
lim, err := strconv.Atoi(string(bytes.TrimSpace(out)))
|
|
|
|
if err == nil && lim < min {
|
2015-05-22 20:24:48 -05:00
|
|
|
fmt.Printf("Warning: File descriptor limit %d is too low for production sites.\nAt least %d is recommended. Set with \"ulimit -n %d\".\n", lim, min, min)
|
2015-05-22 19:34:00 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-21 01:40:58 -05:00
|
|
|
// isLocalhost returns true if the string looks explicitly like a localhost address.
|
2015-05-20 21:06:30 -05:00
|
|
|
func isLocalhost(s string) bool {
|
|
|
|
return s == "localhost" || s == "::1" || strings.HasPrefix(s, "127.")
|
|
|
|
}
|
|
|
|
|
2015-05-04 17:23:16 -05:00
|
|
|
// loadConfigs loads configuration from a file or stdin (piped).
|
|
|
|
// Configuration is obtained from one of three sources, tried
|
|
|
|
// in this order: 1. -conf flag, 2. stdin, 3. Caddyfile.
|
|
|
|
// If none of those are available, a default configuration is
|
|
|
|
// loaded.
|
|
|
|
func loadConfigs() ([]server.Config, error) {
|
|
|
|
// -conf flag
|
|
|
|
if conf != "" {
|
|
|
|
file, err := os.Open(conf)
|
2015-05-04 07:53:54 -05:00
|
|
|
if err != nil {
|
2015-05-04 17:23:16 -05:00
|
|
|
return []server.Config{}, err
|
2015-05-04 07:53:54 -05:00
|
|
|
}
|
2015-05-04 17:23:16 -05:00
|
|
|
defer file.Close()
|
|
|
|
return config.Load(path.Base(conf), file)
|
2015-05-04 07:53:54 -05:00
|
|
|
}
|
|
|
|
|
2015-05-04 17:23:16 -05:00
|
|
|
// stdin
|
2015-05-05 21:31:25 -05:00
|
|
|
fi, err := os.Stdin.Stat()
|
|
|
|
if err == nil && fi.Mode()&os.ModeCharDevice == 0 {
|
2015-05-06 10:16:10 -05:00
|
|
|
// Note that a non-nil error is not a problem. Windows
|
|
|
|
// will not create a stdin if there is no pipe, which
|
|
|
|
// produces an error when calling Stat(). But Unix will
|
|
|
|
// make one either way, which is why we also check that
|
|
|
|
// bitmask.
|
2015-05-05 21:31:25 -05:00
|
|
|
confBody, err := ioutil.ReadAll(os.Stdin)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
if len(confBody) > 0 {
|
|
|
|
return config.Load("stdin", bytes.NewReader(confBody))
|
|
|
|
}
|
2015-05-04 07:53:54 -05:00
|
|
|
}
|
|
|
|
|
2015-05-04 17:23:16 -05:00
|
|
|
// Caddyfile
|
|
|
|
file, err := os.Open(config.DefaultConfigFile)
|
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return []server.Config{config.Default()}, nil
|
|
|
|
}
|
|
|
|
return []server.Config{}, err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
2015-05-06 10:16:10 -05:00
|
|
|
|
2015-05-04 17:23:16 -05:00
|
|
|
return config.Load(config.DefaultConfigFile, file)
|
2015-05-04 07:53:54 -05:00
|
|
|
}
|