caddy/main.go

168 lines
4.3 KiB
Go
Raw Normal View History

2015-01-14 03:43:45 +08:00
package main
import (
"bytes"
"flag"
2015-04-16 04:11:32 +08:00
"fmt"
"io/ioutil"
2015-01-14 03:43:45 +08:00
"log"
"os"
"os/exec"
"path"
"runtime"
"strconv"
"strings"
2015-01-14 03:43:45 +08:00
"github.com/mholt/caddy/app"
2015-01-14 03:43:45 +08:00
"github.com/mholt/caddy/config"
"github.com/mholt/caddy/server"
)
var (
2015-05-08 03:09:40 +08:00
conf string
cpu string
version bool
)
func init() {
2015-05-06 13:19:14 +08:00
flag.StringVar(&conf, "conf", "", "Configuration file to use (default="+config.DefaultConfigFile+")")
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)")
flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
flag.StringVar(&config.Root, "root", config.DefaultRoot, "Root path to default site")
2015-04-30 12:30:12 +08:00
flag.StringVar(&config.Host, "host", config.DefaultHost, "Default host")
flag.StringVar(&config.Port, "port", config.DefaultPort, "Default port")
2015-05-08 03:09:40 +08:00
flag.BoolVar(&version, "version", false, "Show version")
}
2015-01-14 03:43:45 +08:00
func main() {
flag.Parse()
2015-05-08 03:09:40 +08:00
if version {
fmt.Printf("%s %s\n", app.Name, app.Version)
2015-05-08 03:09:40 +08:00
os.Exit(0)
}
// Set CPU cap
err := app.SetCPU(cpu)
if err != nil {
log.Fatal(err)
}
2015-04-16 04:11:32 +08:00
// Load config from file
allConfigs, err := loadConfigs()
2015-01-14 03:43:45 +08:00
if err != nil {
log.Fatal(err)
2015-04-18 23:53:43 +08:00
}
2015-01-14 03:43:45 +08:00
2015-04-18 23:53:43 +08:00
// Group by address (virtual hosts)
addresses, err := config.ArrangeBindings(allConfigs)
2015-04-16 04:11:32 +08:00
if err != nil {
log.Fatal(err)
}
// Start each server with its one or more configurations
for addr, configs := range addresses {
s, err := server.New(addr.String(), configs, configs[0].TLS.Enabled)
2015-01-14 03:43:45 +08:00
if err != nil {
log.Fatal(err)
}
s.HTTP2 = app.Http2 // TODO: This setting is temporary
app.Wg.Add(1)
2015-01-14 03:43:45 +08:00
go func(s *server.Server) {
defer app.Wg.Done()
2015-01-14 03:43:45 +08:00
err := s.Serve()
if err != nil {
log.Fatal(err) // kill whole process to avoid a half-alive zombie server
2015-01-14 03:43:45 +08:00
}
}(s)
app.Servers = append(app.Servers, s)
2015-05-21 14:40:05 +08:00
}
2015-05-21 14:40:05 +08: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-21 14:40:05 +08:00
}
2015-05-21 14:40:05 +08:00
// Warn if ulimit is too low for production sites
if (runtime.GOOS == "linux" || runtime.GOOS == "darwin") &&
!addr.IP.IsLoopback() && !checkedFdLimit {
2015-05-22 01:21:08 +08:00
out, err := exec.Command("sh", "-c", "ulimit -n").Output() // use sh because ulimit isn't in Linux $PATH
2015-05-21 14:40:05 +08:00
if err == nil {
// Note that an error here need not be reported
lim, err := strconv.Atoi(string(bytes.TrimSpace(out)))
if err == nil && lim < 4096 {
fmt.Printf("Warning: File descriptor limit is too low (%d) for production sites\n", lim)
}
2015-05-21 14:40:05 +08:00
checkedFdLimit = true
}
}
}
2015-01-14 03:43:45 +08:00
}
2015-05-21 14:40:05 +08:00
// Wait for all listeners to stop
app.Wg.Wait()
2015-01-14 03:43:45 +08:00
}
2015-04-16 04:11:32 +08:00
2015-05-21 14:40:58 +08:00
// isLocalhost returns true if the string looks explicitly like a localhost address.
func isLocalhost(s string) bool {
return s == "localhost" || s == "::1" || strings.HasPrefix(s, "127.")
}
// 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)
if err != nil {
return []server.Config{}, err
}
defer file.Close()
return config.Load(path.Base(conf), file)
}
// stdin
fi, err := os.Stdin.Stat()
if err == nil && fi.Mode()&os.ModeCharDevice == 0 {
2015-05-06 23:16:10 +08: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.
confBody, err := ioutil.ReadAll(os.Stdin)
if err != nil {
log.Fatal(err)
}
if len(confBody) > 0 {
return config.Load("stdin", bytes.NewReader(confBody))
}
}
// 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 23:16:10 +08:00
return config.Load(config.DefaultConfigFile, file)
}