mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-26 02:09:47 +08:00
f3596f734d
Turns out having each server block share a single server.Config during initialization when the Setup functions are being called was a bad idea. Sure, startup and shutdown functions were only executed once, but they had no idea what their hostname or port was. So here we revert to the old way of doing things where Setup may be called multiple times per server block (once per host associated with the block, to be precise), but the Setup functions now know their host and port since the config belongs to exactly one virtualHost. To have something happen just once per server block, use OncePerServerBlock, a new function available on each Controller.
175 lines
4.6 KiB
Go
175 lines
4.6 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/mholt/caddy/app"
|
|
"github.com/mholt/caddy/config"
|
|
"github.com/mholt/caddy/server"
|
|
)
|
|
|
|
var (
|
|
conf string
|
|
cpu string
|
|
version bool
|
|
)
|
|
|
|
func init() {
|
|
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")
|
|
flag.StringVar(&config.Host, "host", config.DefaultHost, "Default host")
|
|
flag.StringVar(&config.Port, "port", config.DefaultPort, "Default port")
|
|
flag.BoolVar(&version, "version", false, "Show version")
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
if version {
|
|
fmt.Printf("%s %s\n", app.Name, app.Version)
|
|
os.Exit(0)
|
|
}
|
|
|
|
// Set CPU cap
|
|
err := app.SetCPU(cpu)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Load config from file
|
|
addresses, err := loadConfigs()
|
|
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)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
s.HTTP2 = app.HTTP2 // TODO: This setting is temporary
|
|
app.Wg.Add(1)
|
|
go func(s *server.Server) {
|
|
defer app.Wg.Done()
|
|
err := s.Serve()
|
|
if err != nil {
|
|
log.Fatal(err) // kill whole process to avoid a half-alive zombie server
|
|
}
|
|
}(s)
|
|
|
|
app.Servers = append(app.Servers, s)
|
|
}
|
|
|
|
// 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())
|
|
}
|
|
if !checkedFdLimit && !addr.IP.IsLoopback() && !isLocalhost(conf.Host) {
|
|
checkFdlimit()
|
|
checkedFdLimit = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wait for all listeners to stop
|
|
app.Wg.Wait()
|
|
}
|
|
|
|
// 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 {
|
|
fmt.Printf("Warning: File descriptor limit %d is too low for production sites. At least %d is recommended. Set with \"ulimit -n %d\".\n", lim, min, min)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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).
|
|
// The configurations are grouped by bind address.
|
|
// Configuration is obtained from one of four sources, tried
|
|
// in this order: 1. -conf flag, 2. stdin, 3. command line argument 4. Caddyfile.
|
|
// If none of those are available, a default configuration is loaded.
|
|
func loadConfigs() (config.Group, error) {
|
|
// -conf flag
|
|
if conf != "" {
|
|
file, err := os.Open(conf)
|
|
if err != nil {
|
|
return nil, 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 {
|
|
// 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))
|
|
}
|
|
}
|
|
|
|
// Command line Arg
|
|
if flag.NArg() > 0 {
|
|
confBody := ":" + config.DefaultPort + "\n" + strings.Join(flag.Args(), "\n")
|
|
return config.Load("args", bytes.NewBufferString(confBody))
|
|
}
|
|
|
|
// Caddyfile
|
|
file, err := os.Open(config.DefaultConfigFile)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return config.Default()
|
|
}
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
|
|
return config.Load(config.DefaultConfigFile, file)
|
|
}
|