caddy/main.go
2016-03-13 12:59:35 +01:00

243 lines
6.2 KiB
Go

package main
import (
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"runtime"
"strconv"
"strings"
"time"
"github.com/mholt/caddy/caddy"
"github.com/mholt/caddy/caddy/https"
"github.com/mholt/caddy/caddy/parse"
"github.com/xenolf/lego/acme"
"gopkg.in/natefinch/lumberjack.v2"
)
func init() {
caddy.TrapSignals()
setVersion()
flag.BoolVar(&https.Agreed, "agree", false, "Agree to Let's Encrypt Subscriber Agreement")
flag.StringVar(&https.CAUrl, "ca", "https://acme-v01.api.letsencrypt.org/directory", "Certificate authority ACME server")
flag.StringVar(&conf, "conf", "", "Configuration file to use (default="+caddy.DefaultConfigFile+")")
flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
flag.StringVar(&https.DefaultEmail, "email", "", "Default Let's Encrypt account email address")
flag.DurationVar(&caddy.GracefulTimeout, "grace", 5*time.Second, "Maximum duration of graceful shutdown")
flag.StringVar(&caddy.Host, "host", caddy.DefaultHost, "Default host")
flag.BoolVar(&caddy.HTTP2, "http2", true, "Use HTTP/2")
flag.StringVar(&logfile, "log", "", "Process log file")
flag.StringVar(&caddy.PidFile, "pidfile", "", "Path to write pid file")
flag.StringVar(&caddy.Port, "port", caddy.DefaultPort, "Default port")
flag.BoolVar(&caddy.Quiet, "quiet", false, "Quiet mode (no initialization output)")
flag.StringVar(&revoke, "revoke", "", "Hostname for which to revoke the certificate")
flag.StringVar(&caddy.Root, "root", caddy.DefaultRoot, "Root path to default site")
flag.BoolVar(&version, "version", false, "Show version")
flag.BoolVar(&directives, "directives", false, "List supported directives")
}
func main() {
flag.Parse() // called here in main() to allow other packages to set flags in their inits
caddy.AppName = appName
caddy.AppVersion = appVersion
acme.UserAgent = appName + "/" + appVersion
// set up process log before anything bad happens
switch logfile {
case "stdout":
log.SetOutput(os.Stdout)
case "stderr":
log.SetOutput(os.Stderr)
case "":
log.SetOutput(ioutil.Discard)
default:
log.SetOutput(&lumberjack.Logger{
Filename: logfile,
MaxSize: 100,
MaxAge: 14,
MaxBackups: 10,
})
}
if revoke != "" {
err := https.Revoke(revoke)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Revoked certificate for %s\n", revoke)
os.Exit(0)
}
if version {
fmt.Printf("%s %s\n", appName, appVersion)
if devBuild && gitShortStat != "" {
fmt.Printf("%s\n%s\n", gitShortStat, gitFilesModified)
}
os.Exit(0)
}
if directives {
for d := range parse.ValidDirectives {
fmt.Println(d)
}
os.Exit(0)
}
// Set CPU cap
err := setCPU(cpu)
if err != nil {
mustLogFatal(err)
}
// Get Caddyfile input
caddyfile, err := caddy.LoadCaddyfile(loadCaddyfile)
if err != nil {
mustLogFatal(err)
}
// Start your engines
err = caddy.Start(caddyfile)
if err != nil {
mustLogFatal(err)
}
// Twiddle your thumbs
caddy.Wait()
}
// mustLogFatal just wraps log.Fatal() in a way that ensures the
// output is always printed to stderr so the user can see it
// if the user is still there, even if the process log was not
// enabled. If this process is a restart, however, and the user
// might not be there anymore, this just logs to the process log
// and exits.
func mustLogFatal(args ...interface{}) {
if !caddy.IsRestart() {
log.SetOutput(os.Stderr)
}
log.Fatal(args...)
}
func loadCaddyfile() (caddy.Input, error) {
// Try -conf flag
if conf != "" {
if conf == "stdin" {
return caddy.CaddyfileFromPipe(os.Stdin)
}
contents, err := ioutil.ReadFile(conf)
if err != nil {
return nil, err
}
return caddy.CaddyfileInput{
Contents: contents,
Filepath: conf,
RealFile: true,
}, nil
}
// command line args
if flag.NArg() > 0 {
confBody := caddy.Host + ":" + caddy.Port + "\n" + strings.Join(flag.Args(), "\n")
return caddy.CaddyfileInput{
Contents: []byte(confBody),
Filepath: "args",
}, nil
}
// Caddyfile in cwd
contents, err := ioutil.ReadFile(caddy.DefaultConfigFile)
if err != nil {
if os.IsNotExist(err) {
return caddy.DefaultInput(), nil
}
return nil, err
}
return caddy.CaddyfileInput{
Contents: contents,
Filepath: caddy.DefaultConfigFile,
RealFile: true,
}, nil
}
// setCPU parses string cpu and sets GOMAXPROCS
// according to its value. It accepts either
// a number (e.g. 3) or a percent (e.g. 50%).
func setCPU(cpu string) error {
var numCPU int
availCPU := runtime.NumCPU()
if strings.HasSuffix(cpu, "%") {
// Percent
var percent float32
pctStr := cpu[:len(cpu)-1]
pctInt, err := strconv.Atoi(pctStr)
if err != nil || pctInt < 1 || pctInt > 100 {
return errors.New("invalid CPU value: percentage must be between 1-100")
}
percent = float32(pctInt) / 100
numCPU = int(float32(availCPU) * percent)
} else {
// Number
num, err := strconv.Atoi(cpu)
if err != nil || num < 1 {
return errors.New("invalid CPU value: provide a number or percent greater than 0")
}
numCPU = num
}
if numCPU > availCPU {
numCPU = availCPU
}
runtime.GOMAXPROCS(numCPU)
return nil
}
// setVersion figures out the version information based on
// variables set by -ldflags.
func setVersion() {
// A development build is one that's not at a tag or has uncommitted changes
devBuild = gitTag == "" || gitShortStat != ""
// Only set the appVersion if -ldflags was used
if gitNearestTag != "" || gitTag != "" {
if devBuild && gitNearestTag != "" {
appVersion = fmt.Sprintf("%s (+%s %s)",
strings.TrimPrefix(gitNearestTag, "v"), gitCommit, buildDate)
} else if gitTag != "" {
appVersion = strings.TrimPrefix(gitTag, "v")
}
}
}
const appName = "Caddy"
// Flags that control program flow or startup
var (
conf string
cpu string
logfile string
revoke string
version bool
directives bool
)
// Build information obtained with the help of -ldflags
var (
appVersion = "(untracked dev build)" // inferred at startup
devBuild = true // inferred at startup
buildDate string // date -u
gitTag string // git describe --exact-match HEAD 2> /dev/null
gitNearestTag string // git describe --abbrev=0 --tags HEAD
gitCommit string // git rev-parse HEAD
gitShortStat string // git diff-index --shortstat
gitFilesModified string // git diff-index --name-only HEAD
)