mirror of
https://github.com/caddyserver/caddy.git
synced 2024-11-26 02:09:47 +08:00
e9b9432da5
Some programs (Node.js, supervisor, etc.) open a stdin pipe by default and don't use it, causing Caddy to block. It is their error, but we have to try to accommodate unfortunately. To fix this more universally, parent must explicitly set -conf to "stdin" to read from pipe.
196 lines
4.7 KiB
Go
196 lines
4.7 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/mholt/caddy/caddy"
|
|
"github.com/mholt/caddy/caddy/letsencrypt"
|
|
)
|
|
|
|
var (
|
|
conf string
|
|
cpu string
|
|
version bool
|
|
revoke string
|
|
logfile string
|
|
)
|
|
|
|
const (
|
|
appName = "Caddy"
|
|
appVersion = "0.8 beta 3"
|
|
)
|
|
|
|
func init() {
|
|
flag.StringVar(&conf, "conf", "", "Configuration file to use (default="+caddy.DefaultConfigFile+")")
|
|
flag.BoolVar(&caddy.HTTP2, "http2", true, "HTTP/2 support") // TODO: temporary flag until http2 merged into std lib
|
|
flag.BoolVar(&caddy.Quiet, "quiet", false, "Quiet mode (no initialization output)")
|
|
flag.StringVar(&cpu, "cpu", "100%", "CPU cap")
|
|
flag.StringVar(&caddy.Root, "root", caddy.DefaultRoot, "Root path to default site")
|
|
flag.StringVar(&caddy.Host, "host", caddy.DefaultHost, "Default host")
|
|
flag.StringVar(&caddy.Port, "port", caddy.DefaultPort, "Default port")
|
|
flag.BoolVar(&version, "version", false, "Show version")
|
|
// TODO: Boulder dev URL is: http://192.168.99.100:4000
|
|
// TODO: Staging API URL is: https://acme-staging.api.letsencrypt.org
|
|
// TODO: Production endpoint is: https://acme-v01.api.letsencrypt.org
|
|
flag.StringVar(&letsencrypt.CAUrl, "ca", "https://acme-staging.api.letsencrypt.org", "Certificate authority ACME server")
|
|
flag.BoolVar(&letsencrypt.Agreed, "agree", false, "Agree to Let's Encrypt Subscriber Agreement")
|
|
flag.StringVar(&letsencrypt.DefaultEmail, "email", "", "Default Let's Encrypt account email address")
|
|
flag.StringVar(&revoke, "revoke", "", "Hostname for which to revoke the certificate")
|
|
flag.StringVar(&logfile, "log", "", "Process log file")
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse() // called here in main() to allow other packages to set flags in their inits
|
|
|
|
caddy.AppName = appName
|
|
caddy.AppVersion = 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:
|
|
file, err := os.OpenFile(logfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
|
if err != nil {
|
|
log.Fatalf("Error opening log file: %v", err)
|
|
}
|
|
log.SetOutput(file)
|
|
}
|
|
|
|
if version {
|
|
fmt.Printf("%s %s\n", caddy.AppName, caddy.AppVersion)
|
|
os.Exit(0)
|
|
}
|
|
if revoke != "" {
|
|
err := letsencrypt.Revoke(revoke)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
fmt.Printf("Revoked certificate for %s\n", revoke)
|
|
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 {
|
|
if caddy.IsRestart() {
|
|
log.Printf("[ERROR] Upon starting %s: %v", appName, err)
|
|
} else {
|
|
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,
|
|
// even if the process log was not enabled.
|
|
func mustLogFatal(args ...interface{}) {
|
|
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
|
|
}
|