2019-10-01 11:23:58 +08:00
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddycmd
import (
"bytes"
2020-05-30 04:21:55 +08:00
"context"
2019-10-01 11:23:58 +08:00
"crypto/rand"
"encoding/json"
2021-08-19 02:58:19 +08:00
"errors"
2019-10-01 11:23:58 +08:00
"fmt"
"io"
2023-08-10 01:40:37 +08:00
"io/fs"
2019-10-01 11:23:58 +08:00
"log"
"net"
"net/http"
"os"
"os/exec"
2020-01-01 07:56:19 +08:00
"runtime"
2019-10-01 13:43:39 +08:00
"runtime/debug"
2019-10-01 11:23:58 +08:00
"strings"
2022-04-13 02:49:19 +08:00
"github.com/aryann/difflib"
2023-08-14 23:41:15 +08:00
"go.uber.org/zap"
2019-10-01 11:23:58 +08:00
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
2020-03-01 01:12:16 +08:00
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
2023-08-06 08:09:16 +08:00
"github.com/caddyserver/caddy/v2/internal"
2019-10-01 11:23:58 +08:00
)
func cmdStart ( fl Flags ) ( int , error ) {
startCmdConfigFlag := fl . String ( "config" )
2019-10-01 13:43:39 +08:00
startCmdConfigAdapterFlag := fl . String ( "adapter" )
2020-05-14 01:28:15 +08:00
startCmdPidfileFlag := fl . String ( "pidfile" )
2020-03-23 12:58:24 +08:00
startCmdWatchFlag := fl . Bool ( "watch" )
2023-09-07 10:19:24 +08:00
var err error
var startCmdEnvfileFlag [ ] string
startCmdEnvfileFlag , err = fl . GetStringSlice ( "envfile" )
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "reading envfile flag: %v" , err )
}
2019-10-01 11:23:58 +08:00
// open a listener to which the child process will connect when
// it is ready to confirm that it has successfully started
ln , err := net . Listen ( "tcp" , "127.0.0.1:0" )
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "opening listener for success confirmation: %v" , err )
}
defer ln . Close ( )
// craft the command with a pingback address and with a
// pipe for its stdin, so we can tell it our confirmation
// code that we expect so that some random port scan at
// the most unfortunate time won't fool us into thinking
// the child succeeded (i.e. the alternative is to just
// wait for any connection on our listener, but better to
// ensure it's the process we're expecting - we can be
// sure by giving it some random bytes and having it echo
// them back to us)
cmd := exec . Command ( os . Args [ 0 ] , "run" , "--pingback" , ln . Addr ( ) . String ( ) )
if startCmdConfigFlag != "" {
cmd . Args = append ( cmd . Args , "--config" , startCmdConfigFlag )
}
2023-09-07 10:19:24 +08:00
for _ , envFile := range startCmdEnvfileFlag {
cmd . Args = append ( cmd . Args , "--envfile" , envFile )
2021-05-03 02:38:16 +08:00
}
2019-10-01 11:23:58 +08:00
if startCmdConfigAdapterFlag != "" {
2019-10-01 13:43:39 +08:00
cmd . Args = append ( cmd . Args , "--adapter" , startCmdConfigAdapterFlag )
2019-10-01 11:23:58 +08:00
}
2020-03-23 12:58:24 +08:00
if startCmdWatchFlag {
cmd . Args = append ( cmd . Args , "--watch" )
}
2020-05-14 01:28:15 +08:00
if startCmdPidfileFlag != "" {
cmd . Args = append ( cmd . Args , "--pidfile" , startCmdPidfileFlag )
}
2019-10-01 11:23:58 +08:00
stdinpipe , err := cmd . StdinPipe ( )
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "creating stdin pipe: %v" , err )
}
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
// generate the random bytes we'll send to the child process
expect := make ( [ ] byte , 32 )
_ , err = rand . Read ( expect )
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "generating random confirmation bytes: %v" , err )
}
// begin writing the confirmation bytes to the child's
// stdin; use a goroutine since the child hasn't been
2020-02-25 15:16:47 +08:00
// started yet, and writing synchronously would result
2019-10-01 11:23:58 +08:00
// in a deadlock
go func ( ) {
2020-11-23 05:50:29 +08:00
_ , _ = stdinpipe . Write ( expect )
2019-10-01 11:23:58 +08:00
stdinpipe . Close ( )
} ( )
// start the process
err = cmd . Start ( )
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "starting caddy process: %v" , err )
}
// there are two ways we know we're done: either
// the process will connect to our listener, or
// it will exit with an error
success , exit := make ( chan struct { } ) , make ( chan error )
// in one goroutine, we await the success of the child process
go func ( ) {
for {
conn , err := ln . Accept ( )
if err != nil {
2021-08-19 02:58:19 +08:00
if ! errors . Is ( err , net . ErrClosed ) {
2019-10-01 11:23:58 +08:00
log . Println ( err )
}
break
}
err = handlePingbackConn ( conn , expect )
if err == nil {
close ( success )
break
}
log . Println ( err )
}
} ( )
// in another goroutine, we await the failure of the child process
go func ( ) {
err := cmd . Wait ( ) // don't send on this line! Wait blocks, but send starts before it unblocks
exit <- err // sending on separate line ensures select won't trigger until after Wait unblocks
} ( )
// when one of the goroutines unblocks, we're done and can exit
select {
case <- success :
2020-03-14 03:02:47 +08:00
fmt . Printf ( "Successfully started Caddy (pid=%d) - Caddy is running in the background\n" , cmd . Process . Pid )
2019-10-01 11:23:58 +08:00
case err := <- exit :
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "caddy process exited with error: %v" , err )
}
return caddy . ExitCodeSuccess , nil
}
func cmdRun ( fl Flags ) ( int , error ) {
2020-05-22 03:09:49 +08:00
caddy . TrapSignals ( )
2019-10-01 11:23:58 +08:00
runCmdConfigFlag := fl . String ( "config" )
2019-10-01 13:43:39 +08:00
runCmdConfigAdapterFlag := fl . String ( "adapter" )
2020-01-01 07:56:19 +08:00
runCmdResumeFlag := fl . Bool ( "resume" )
2019-10-01 13:43:39 +08:00
runCmdPrintEnvFlag := fl . Bool ( "environ" )
2020-03-23 12:58:24 +08:00
runCmdWatchFlag := fl . Bool ( "watch" )
2020-05-14 01:28:15 +08:00
runCmdPidfileFlag := fl . String ( "pidfile" )
2019-10-01 11:23:58 +08:00
runCmdPingbackFlag := fl . String ( "pingback" )
2023-09-07 10:19:24 +08:00
var err error
var runCmdLoadEnvfileFlag [ ] string
runCmdLoadEnvfileFlag , err = fl . GetStringSlice ( "envfile" )
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "reading envfile flag: %v" , err )
}
2020-05-16 05:49:51 +08:00
// load all additional envs as soon as possible
2023-09-07 10:19:24 +08:00
for _ , envFile := range runCmdLoadEnvfileFlag {
if err := loadEnvFromFile ( envFile ) ; err != nil {
2020-05-16 05:49:51 +08:00
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "loading additional environment variables: %v" , err )
}
}
2019-10-01 11:23:58 +08:00
// if we are supposed to print the environment, do that first
if runCmdPrintEnvFlag {
printEnvironment ( )
}
2020-01-01 07:56:19 +08:00
// load the config, depending on flags
var config [ ] byte
if runCmdResumeFlag {
2021-09-30 01:17:48 +08:00
config , err = os . ReadFile ( caddy . ConfigAutosavePath )
2020-01-01 07:56:19 +08:00
if os . IsNotExist ( err ) {
// not a bad error; just can't resume if autosave file doesn't exist
caddy . Log ( ) . Info ( "no autosave file exists" , zap . String ( "autosave_file" , caddy . ConfigAutosavePath ) )
runCmdResumeFlag = false
} else if err != nil {
return caddy . ExitCodeFailedStartup , err
} else {
2020-04-05 03:29:25 +08:00
if runCmdConfigFlag == "" {
caddy . Log ( ) . Info ( "resuming from last configuration" ,
zap . String ( "autosave_file" , caddy . ConfigAutosavePath ) )
} else {
// if they also specified a config file, user should be aware that we're not
// using it (doing so could lead to data/config loss by overwriting!)
caddy . Log ( ) . Warn ( "--config and --resume flags were used together; ignoring --config and resuming from last configuration" ,
zap . String ( "autosave_file" , caddy . ConfigAutosavePath ) )
}
2020-01-01 07:56:19 +08:00
}
}
// we don't use 'else' here since this value might have been changed in 'if' block; i.e. not mutually exclusive
2020-03-23 12:58:24 +08:00
var configFile string
2020-01-01 07:56:19 +08:00
if ! runCmdResumeFlag {
2022-03-03 02:08:36 +08:00
config , configFile , err = LoadConfig ( runCmdConfigFlag , runCmdConfigAdapterFlag )
2020-01-01 07:56:19 +08:00
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
}
2023-04-04 01:57:16 +08:00
// create pidfile now, in case loading config takes a while (issue #5477)
if runCmdPidfileFlag != "" {
err := caddy . PIDFile ( runCmdPidfileFlag )
if err != nil {
caddy . Log ( ) . Error ( "unable to write PID file" ,
zap . String ( "pidfile" , runCmdPidfileFlag ) ,
zap . Error ( err ) )
}
}
2019-11-05 03:05:20 +08:00
// run the initial config
err = caddy . Load ( config , true )
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "loading initial config: %v" , err )
2019-10-01 11:23:58 +08:00
}
2020-01-01 07:56:19 +08:00
caddy . Log ( ) . Info ( "serving initial configuration" )
2019-10-01 11:23:58 +08:00
// if we are to report to another process the successful start
// of the server, do so now by echoing back contents of stdin
if runCmdPingbackFlag != "" {
2021-09-30 01:17:48 +08:00
confirmationBytes , err := io . ReadAll ( os . Stdin )
2019-10-01 11:23:58 +08:00
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "reading confirmation bytes from stdin: %v" , err )
}
conn , err := net . Dial ( "tcp" , runCmdPingbackFlag )
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "dialing confirmation address: %v" , err )
}
defer conn . Close ( )
_ , err = conn . Write ( confirmationBytes )
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "writing confirmation bytes to %s: %v" , runCmdPingbackFlag , err )
}
}
2020-03-23 12:58:24 +08:00
// if enabled, reload config file automatically on changes
// (this better only be used in dev!)
if runCmdWatchFlag {
go watchConfigFile ( configFile , runCmdConfigAdapterFlag )
}
2020-01-01 07:47:35 +08:00
// warn if the environment does not provide enough information about the disk
hasXDG := os . Getenv ( "XDG_DATA_HOME" ) != "" &&
os . Getenv ( "XDG_CONFIG_HOME" ) != "" &&
os . Getenv ( "XDG_CACHE_HOME" ) != ""
switch runtime . GOOS {
case "windows" :
if os . Getenv ( "HOME" ) == "" && os . Getenv ( "USERPROFILE" ) == "" && ! hasXDG {
caddy . Log ( ) . Warn ( "neither HOME nor USERPROFILE environment variables are set - please fix; some assets might be stored in ./caddy" )
}
case "plan9" :
if os . Getenv ( "home" ) == "" && ! hasXDG {
caddy . Log ( ) . Warn ( "$home environment variable is empty - please fix; some assets might be stored in ./caddy" )
}
default :
if os . Getenv ( "HOME" ) == "" && ! hasXDG {
caddy . Log ( ) . Warn ( "$HOME environment variable is empty - please fix; some assets might be stored in ./caddy" )
}
}
2019-10-01 11:23:58 +08:00
select { }
}
2019-11-16 06:45:18 +08:00
func cmdStop ( fl Flags ) ( int , error ) {
2022-03-03 02:08:36 +08:00
addrFlag := fl . String ( "address" )
configFlag := fl . String ( "config" )
configAdapterFlag := fl . String ( "adapter" )
2019-11-16 06:45:18 +08:00
2022-07-21 08:14:33 +08:00
adminAddr , err := DetermineAdminAPIAddress ( addrFlag , nil , configFlag , configAdapterFlag )
2022-03-03 02:08:36 +08:00
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "couldn't determine admin API address: %v" , err )
}
resp , err := AdminAPIRequest ( adminAddr , http . MethodPost , "/stop" , nil , nil )
2019-10-01 11:23:58 +08:00
if err != nil {
2020-05-30 04:21:55 +08:00
caddy . Log ( ) . Warn ( "failed using API to stop instance" , zap . Error ( err ) )
2019-12-24 03:41:05 +08:00
return caddy . ExitCodeFailedStartup , err
2019-10-01 11:23:58 +08:00
}
2022-03-03 02:08:36 +08:00
defer resp . Body . Close ( )
2019-11-16 06:45:18 +08:00
2019-10-01 11:23:58 +08:00
return caddy . ExitCodeSuccess , nil
}
func cmdReload ( fl Flags ) ( int , error ) {
2022-03-03 02:08:36 +08:00
configFlag := fl . String ( "config" )
configAdapterFlag := fl . String ( "adapter" )
addrFlag := fl . String ( "address" )
forceFlag := fl . Bool ( "force" )
2019-10-01 11:23:58 +08:00
// get the config in caddy's native format
2022-03-03 02:08:36 +08:00
config , configFile , err := LoadConfig ( configFlag , configAdapterFlag )
2019-10-01 11:23:58 +08:00
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
2020-03-23 12:58:24 +08:00
if configFile == "" {
2020-01-23 01:04:58 +08:00
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "no config file to load" )
}
2019-10-01 11:23:58 +08:00
2022-07-21 08:14:33 +08:00
adminAddr , err := DetermineAdminAPIAddress ( addrFlag , config , configFlag , configAdapterFlag )
2022-03-03 02:08:36 +08:00
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "couldn't determine admin API address: %v" , err )
2019-10-01 11:23:58 +08:00
}
2021-02-02 09:14:03 +08:00
// optionally force a config reload
headers := make ( http . Header )
2022-03-03 02:08:36 +08:00
if forceFlag {
2021-02-02 09:14:03 +08:00
headers . Set ( "Cache-Control" , "must-revalidate" )
}
2022-03-03 02:08:36 +08:00
resp , err := AdminAPIRequest ( adminAddr , http . MethodPost , "/load" , headers , bytes . NewReader ( config ) )
2019-11-16 06:45:18 +08:00
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "sending configuration to instance: %v" , err )
2019-10-01 11:23:58 +08:00
}
2022-03-03 02:08:36 +08:00
defer resp . Body . Close ( )
2019-10-01 11:23:58 +08:00
return caddy . ExitCodeSuccess , nil
}
func cmdVersion ( _ Flags ) ( int , error ) {
2022-08-05 01:16:59 +08:00
_ , full := caddy . Version ( )
fmt . Println ( full )
2019-10-01 11:23:58 +08:00
return caddy . ExitCodeSuccess , nil
}
2022-08-05 01:16:59 +08:00
func cmdBuildInfo ( _ Flags ) ( int , error ) {
2020-01-11 02:53:07 +08:00
bi , ok := debug . ReadBuildInfo ( )
if ! ok {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "no build information" )
}
2022-08-05 01:16:59 +08:00
fmt . Println ( bi )
2020-01-11 02:53:07 +08:00
return caddy . ExitCodeSuccess , nil
}
2019-10-01 13:43:39 +08:00
func cmdListModules ( fl Flags ) ( int , error ) {
2021-01-05 02:11:56 +08:00
packages := fl . Bool ( "packages" )
2019-10-01 13:43:39 +08:00
versions := fl . Bool ( "versions" )
2021-10-19 02:19:04 +08:00
skipStandard := fl . Bool ( "skip-standard" )
2019-10-01 13:43:39 +08:00
2021-01-05 02:11:56 +08:00
printModuleInfo := func ( mi moduleInfo ) {
fmt . Print ( mi . caddyModuleID )
if versions && mi . goModule != nil {
fmt . Print ( " " + mi . goModule . Version )
}
if packages && mi . goModule != nil {
fmt . Print ( " " + mi . goModule . Path )
if mi . goModule . Replace != nil {
fmt . Print ( " => " + mi . goModule . Replace . Path )
}
}
if mi . err != nil {
fmt . Printf ( " [%v]" , mi . err )
}
fmt . Println ( )
}
// organize modules by whether they come with the standard distribution
2021-01-20 09:45:49 +08:00
standard , nonstandard , unknown , err := getModules ( )
if err != nil {
2021-01-05 02:11:56 +08:00
// oh well, just print the module IDs and exit
2019-10-01 13:43:39 +08:00
for _ , m := range caddy . Modules ( ) {
fmt . Println ( m )
}
return caddy . ExitCodeSuccess , nil
2019-10-01 11:23:58 +08:00
}
2019-10-01 13:43:39 +08:00
2021-10-19 02:19:04 +08:00
// Standard modules (always shipped with Caddy)
if ! skipStandard {
if len ( standard ) > 0 {
for _ , mod := range standard {
printModuleInfo ( mod )
}
2021-01-05 02:11:56 +08:00
}
2021-10-19 02:19:04 +08:00
fmt . Printf ( "\n Standard modules: %d\n" , len ( standard ) )
2021-01-05 02:11:56 +08:00
}
2021-10-19 02:19:04 +08:00
// Non-standard modules (third party plugins)
2021-01-05 02:11:56 +08:00
if len ( nonstandard ) > 0 {
2021-10-19 02:19:04 +08:00
if len ( standard ) > 0 && ! skipStandard {
2021-01-05 02:11:56 +08:00
fmt . Println ( )
}
for _ , mod := range nonstandard {
printModuleInfo ( mod )
}
}
fmt . Printf ( "\n Non-standard modules: %d\n" , len ( nonstandard ) )
2021-10-19 02:19:04 +08:00
// Unknown modules (couldn't get Caddy module info)
2021-01-05 02:11:56 +08:00
if len ( unknown ) > 0 {
2021-10-19 02:19:04 +08:00
if ( len ( standard ) > 0 && ! skipStandard ) || len ( nonstandard ) > 0 {
2021-01-05 02:11:56 +08:00
fmt . Println ( )
}
for _ , mod := range unknown {
printModuleInfo ( mod )
}
2019-10-01 13:43:39 +08:00
}
2021-01-05 02:11:56 +08:00
fmt . Printf ( "\n Unknown modules: %d\n" , len ( unknown ) )
2019-10-01 13:43:39 +08:00
2019-10-01 11:23:58 +08:00
return caddy . ExitCodeSuccess , nil
}
func cmdEnviron ( _ Flags ) ( int , error ) {
printEnvironment ( )
return caddy . ExitCodeSuccess , nil
}
func cmdAdaptConfig ( fl Flags ) ( int , error ) {
2019-10-01 13:43:39 +08:00
adaptCmdInputFlag := fl . String ( "config" )
2019-10-01 11:23:58 +08:00
adaptCmdAdapterFlag := fl . String ( "adapter" )
adaptCmdPrettyFlag := fl . Bool ( "pretty" )
2019-10-02 01:02:13 +08:00
adaptCmdValidateFlag := fl . Bool ( "validate" )
2019-10-01 11:23:58 +08:00
2023-08-10 01:40:37 +08:00
var err error
adaptCmdInputFlag , err = configFileWithRespectToDefault ( caddy . Log ( ) , adaptCmdInputFlag )
if err != nil {
return caddy . ExitCodeFailedStartup , err
2020-02-05 01:48:02 +08:00
}
if adaptCmdAdapterFlag == "" {
2019-10-01 11:23:58 +08:00
return caddy . ExitCodeFailedStartup ,
2020-02-05 01:48:02 +08:00
fmt . Errorf ( "adapter name is required (use --adapt flag or leave unspecified for default)" )
2019-10-01 11:23:58 +08:00
}
cfgAdapter := caddyconfig . GetAdapter ( adaptCmdAdapterFlag )
if cfgAdapter == nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "unrecognized config adapter: %s" , adaptCmdAdapterFlag )
}
2021-09-30 01:17:48 +08:00
input , err := os . ReadFile ( adaptCmdInputFlag )
2019-10-01 11:23:58 +08:00
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "reading input file: %v" , err )
}
2022-08-03 04:39:09 +08:00
opts := map [ string ] any { "filename" : adaptCmdInputFlag }
2019-10-01 11:23:58 +08:00
adaptedConfig , warnings , err := cfgAdapter . Adapt ( input , opts )
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
2021-01-05 02:11:36 +08:00
if adaptCmdPrettyFlag {
var prettyBuf bytes . Buffer
err = json . Indent ( & prettyBuf , adaptedConfig , "" , "\t" )
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
adaptedConfig = prettyBuf . Bytes ( )
}
2021-01-20 05:21:11 +08:00
// print result to stdout
fmt . Println ( string ( adaptedConfig ) )
2019-10-01 11:23:58 +08:00
// print warnings to stderr
for _ , warn := range warnings {
msg := warn . Message
if warn . Directive != "" {
msg = fmt . Sprintf ( "%s: %s" , warn . Directive , warn . Message )
}
2022-04-26 00:12:10 +08:00
caddy . Log ( ) . Named ( adaptCmdAdapterFlag ) . Warn ( msg ,
zap . String ( "file" , warn . File ) ,
zap . Int ( "line" , warn . Line ) )
2019-10-01 11:23:58 +08:00
}
2019-10-02 01:02:13 +08:00
// validate output if requested
if adaptCmdValidateFlag {
var cfg * caddy . Config
2023-02-23 02:39:40 +08:00
err = caddy . StrictUnmarshalJSON ( adaptedConfig , & cfg )
2019-10-02 01:02:13 +08:00
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "decoding config: %v" , err )
}
err = caddy . Validate ( cfg )
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "validation: %v" , err )
}
}
2019-10-01 11:23:58 +08:00
return caddy . ExitCodeSuccess , nil
}
2019-10-01 13:43:39 +08:00
func cmdValidateConfig ( fl Flags ) ( int , error ) {
validateCmdConfigFlag := fl . String ( "config" )
validateCmdAdapterFlag := fl . String ( "adapter" )
2023-09-07 10:19:24 +08:00
var err error
var runCmdLoadEnvfileFlag [ ] string
runCmdLoadEnvfileFlag , err = fl . GetStringSlice ( "envfile" )
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "reading envfile flag: %v" , err )
}
2023-02-01 05:27:35 +08:00
// load all additional envs as soon as possible
2023-09-07 10:19:24 +08:00
for _ , envFile := range runCmdLoadEnvfileFlag {
if err := loadEnvFromFile ( envFile ) ; err != nil {
2023-02-01 05:27:35 +08:00
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "loading additional environment variables: %v" , err )
}
}
2019-10-01 13:43:39 +08:00
2023-08-10 01:40:37 +08:00
// use default config and ensure a config file is specified
validateCmdConfigFlag , err = configFileWithRespectToDefault ( caddy . Log ( ) , validateCmdConfigFlag )
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
if validateCmdConfigFlag == "" {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "input file required when there is no Caddyfile in current directory (use --config flag)" )
}
2022-03-03 02:08:36 +08:00
input , _ , err := LoadConfig ( validateCmdConfigFlag , validateCmdAdapterFlag )
2019-10-01 13:43:39 +08:00
if err != nil {
2020-03-09 14:09:15 +08:00
return caddy . ExitCodeFailedStartup , err
2019-10-01 13:43:39 +08:00
}
2019-12-13 05:30:22 +08:00
input = caddy . RemoveMetaFields ( input )
2019-10-01 13:43:39 +08:00
var cfg * caddy . Config
2023-02-23 02:39:40 +08:00
err = caddy . StrictUnmarshalJSON ( input , & cfg )
2019-10-01 13:43:39 +08:00
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "decoding config: %v" , err )
}
err = caddy . Validate ( cfg )
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
fmt . Println ( "Valid configuration" )
return caddy . ExitCodeSuccess , nil
}
2020-03-16 11:18:31 +08:00
func cmdFmt ( fl Flags ) ( int , error ) {
2020-03-01 01:12:16 +08:00
formatCmdConfigFile := fl . Arg ( 0 )
if formatCmdConfigFile == "" {
formatCmdConfigFile = "Caddyfile"
}
2020-09-15 02:30:12 +08:00
// as a special case, read from stdin if the file name is "-"
if formatCmdConfigFile == "-" {
2021-09-30 01:17:48 +08:00
input , err := io . ReadAll ( os . Stdin )
2020-09-15 02:30:12 +08:00
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "reading stdin: %v" , err )
}
fmt . Print ( string ( caddyfile . Format ( input ) ) )
return caddy . ExitCodeSuccess , nil
}
2020-03-01 01:12:16 +08:00
2021-09-30 01:17:48 +08:00
input , err := os . ReadFile ( formatCmdConfigFile )
2020-03-01 01:12:16 +08:00
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "reading input file: %v" , err )
}
output := caddyfile . Format ( input )
2020-09-15 02:30:12 +08:00
if fl . Bool ( "overwrite" ) {
2023-08-08 03:40:31 +08:00
if err := os . WriteFile ( formatCmdConfigFile , output , 0 o600 ) ; err != nil {
2022-01-17 08:30:07 +08:00
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "overwriting formatted file: %v" , err )
2020-03-01 01:12:16 +08:00
}
2023-02-01 00:24:44 +08:00
return caddy . ExitCodeSuccess , nil
}
if fl . Bool ( "diff" ) {
2022-04-13 02:49:19 +08:00
diff := difflib . Diff (
strings . Split ( string ( input ) , "\n" ) ,
strings . Split ( string ( output ) , "\n" ) )
for _ , d := range diff {
switch d . Delta {
case difflib . Common :
fmt . Printf ( " %s\n" , d . Payload )
case difflib . LeftOnly :
fmt . Printf ( "- %s\n" , d . Payload )
case difflib . RightOnly :
fmt . Printf ( "+ %s\n" , d . Payload )
}
}
2020-03-01 01:12:16 +08:00
} else {
fmt . Print ( string ( output ) )
}
2023-01-22 12:28:37 +08:00
if warning , diff := caddyfile . FormattingDifference ( formatCmdConfigFile , input ) ; diff {
2023-02-17 07:34:12 +08:00
return caddy . ExitCodeFailedStartup , fmt . Errorf ( ` %s:%d: Caddyfile input is not formatted; Tip: use '--overwrite' to update your Caddyfile in-place instead of previewing it. Consult '--help' for more options ` ,
warning . File ,
warning . Line ,
)
2023-01-22 12:28:37 +08:00
}
2020-03-01 01:12:16 +08:00
return caddy . ExitCodeSuccess , nil
}
2022-03-03 02:08:36 +08:00
// AdminAPIRequest makes an API request according to the CLI flags given,
// with the given HTTP method and request URI. If body is non-nil, it will
// be assumed to be Content-Type application/json. The caller should close
// the response body. Should only be used by Caddy CLI commands which
// need to interact with a running instance of Caddy via the admin API.
func AdminAPIRequest ( adminAddr , method , uri string , headers http . Header , body io . Reader ) ( * http . Response , error ) {
2020-05-30 04:21:55 +08:00
parsedAddr , err := caddy . ParseNetworkAddress ( adminAddr )
if err != nil || parsedAddr . PortRangeSize ( ) > 1 {
2022-03-03 02:08:36 +08:00
return nil , fmt . Errorf ( "invalid admin address %s: %v" , adminAddr , err )
2020-05-30 04:21:55 +08:00
}
2022-03-20 12:51:32 +08:00
origin := "http://" + parsedAddr . JoinHostPort ( 0 )
2020-05-30 04:21:55 +08:00
if parsedAddr . IsUnixNetwork ( ) {
2023-08-03 01:13:52 +08:00
origin = "http://127.0.0.1" // bogus host is a hack so that http.NewRequest() is happy
2023-08-06 08:09:16 +08:00
// the unix address at this point might still contain the optional
// unix socket permissions, which are part of the address/host.
// those need to be removed first, as they aren't part of the
// resulting unix file path
addr , _ , err := internal . SplitUnixSocketPermissionsBits ( parsedAddr . Host )
if err != nil {
return nil , err
}
parsedAddr . Host = addr
2020-05-30 04:21:55 +08:00
}
// form the request
2022-03-20 12:51:32 +08:00
req , err := http . NewRequest ( method , origin + uri , body )
2020-05-30 04:21:55 +08:00
if err != nil {
2022-03-03 02:08:36 +08:00
return nil , fmt . Errorf ( "making request: %v" , err )
2020-05-30 04:21:55 +08:00
}
if parsedAddr . IsUnixNetwork ( ) {
2023-08-03 01:13:52 +08:00
// We used to conform to RFC 2616 Section 14.26 which requires
// an empty host header when there is no host, as is the case
// with unix sockets. However, Go required a Host value so we
// used a hack of a space character as the host (it would see
// the Host was non-empty, then trim the space later). As of
// Go 1.20.6 (July 2023), this hack no longer works. See:
// https://github.com/golang/go/issues/60374
// See also the discussion here:
// https://github.com/golang/go/issues/61431
2020-05-30 04:21:55 +08:00
//
2023-08-03 01:13:52 +08:00
// After that, we now require a Host value of either 127.0.0.1
// or ::1 if one is set. Above I choose to use 127.0.0.1. Even
// though the value should be completely irrelevant (it could be
// "srldkjfsd"), if for some reason the Host *is* used, at least
// we can have some reasonable assurance it will stay on the local
// machine and that browsers, if they ever allow access to unix
// sockets, can still enforce CORS, ensuring it is still coming
// from the local machine.
2020-05-30 04:21:55 +08:00
} else {
req . Header . Set ( "Origin" , origin )
}
if body != nil {
req . Header . Set ( "Content-Type" , "application/json" )
}
2021-02-02 09:14:03 +08:00
for k , v := range headers {
req . Header [ k ] = v
}
2020-05-30 04:21:55 +08:00
// make an HTTP client that dials our network type, since admin
// endpoints aren't always TCP, which is what the default transport
// expects; reuse is not of particular concern here
client := http . Client {
Transport : & http . Transport {
DialContext : func ( _ context . Context , _ , _ string ) ( net . Conn , error ) {
return net . Dial ( parsedAddr . Network , parsedAddr . JoinHostPort ( 0 ) )
} ,
} ,
}
resp , err := client . Do ( req )
2019-11-16 06:45:18 +08:00
if err != nil {
2022-03-03 02:08:36 +08:00
return nil , fmt . Errorf ( "performing request: %v" , err )
2019-11-16 06:45:18 +08:00
}
// if it didn't work, let the user know
if resp . StatusCode >= 400 {
2021-09-30 01:17:48 +08:00
respBody , err := io . ReadAll ( io . LimitReader ( resp . Body , 1024 * 10 ) )
2019-11-16 06:45:18 +08:00
if err != nil {
2022-03-03 02:08:36 +08:00
return nil , fmt . Errorf ( "HTTP %d: reading error message: %v" , resp . StatusCode , err )
}
return nil , fmt . Errorf ( "caddy responded with error: HTTP %d: %s" , resp . StatusCode , respBody )
}
return resp , nil
}
// DetermineAdminAPIAddress determines which admin API endpoint address should
// be used based on the inputs. By priority: if `address` is specified, then
2022-07-21 08:14:33 +08:00
// it is returned; if `config` is specified, then that config will be used for
// finding the admin address; if `configFile` (and `configAdapter`) are specified,
// then that config will be loaded to find the admin address; otherwise, the
// default admin listen address will be returned.
func DetermineAdminAPIAddress ( address string , config [ ] byte , configFile , configAdapter string ) ( string , error ) {
2022-03-03 02:08:36 +08:00
// Prefer the address if specified and non-empty
if address != "" {
return address , nil
}
// Try to load the config from file if specified, with the given adapter name
if configFile != "" {
2022-07-21 08:14:33 +08:00
var loadedConfigFile string
var err error
// use the provided loaded config if non-empty
// otherwise, load it from the specified file/adapter
loadedConfig := config
if len ( loadedConfig ) == 0 {
// get the config in caddy's native format
loadedConfig , loadedConfigFile , err = LoadConfig ( configFile , configAdapter )
if err != nil {
return "" , err
}
if loadedConfigFile == "" {
2022-09-15 13:24:16 +08:00
return "" , fmt . Errorf ( "no config file to load; either use --config flag or ensure Caddyfile exists in current directory" )
2022-07-21 08:14:33 +08:00
}
2022-03-03 02:08:36 +08:00
}
2022-07-21 08:14:33 +08:00
// get the address of the admin listener from the config
if len ( loadedConfig ) > 0 {
2022-03-03 02:08:36 +08:00
var tmpStruct struct {
Admin caddy . AdminConfig ` json:"admin" `
}
2022-07-21 08:14:33 +08:00
err := json . Unmarshal ( loadedConfig , & tmpStruct )
2022-03-03 02:08:36 +08:00
if err != nil {
return "" , fmt . Errorf ( "unmarshaling admin listener address from config: %v" , err )
}
2022-04-04 00:04:33 +08:00
if tmpStruct . Admin . Listen != "" {
return tmpStruct . Admin . Listen , nil
}
2019-11-16 06:45:18 +08:00
}
}
2022-03-03 02:08:36 +08:00
// Fallback to the default listen address otherwise
return caddy . DefaultAdminListen , nil
2019-11-16 06:45:18 +08:00
}
2021-01-20 09:45:49 +08:00
2023-08-10 01:40:37 +08:00
// configFileWithRespectToDefault returns the filename to use for loading the config, based
// on whether a config file is already specified and a supported default config file exists.
func configFileWithRespectToDefault ( logger * zap . Logger , configFile string ) ( string , error ) {
const defaultCaddyfile = "Caddyfile"
// if no input file was specified, try a default Caddyfile if the Caddyfile adapter is plugged in
if configFile == "" && caddyconfig . GetAdapter ( "caddyfile" ) != nil {
_ , err := os . Stat ( defaultCaddyfile )
if err == nil {
// default Caddyfile exists
if logger != nil {
logger . Info ( "using adjacent Caddyfile" )
}
return defaultCaddyfile , nil
}
if ! errors . Is ( err , fs . ErrNotExist ) {
// problem checking
return configFile , fmt . Errorf ( "checking if default Caddyfile exists: %v" , err )
}
}
// default config file does not exist or is irrelevant
return configFile , nil
}
2021-01-20 09:45:49 +08:00
type moduleInfo struct {
caddyModuleID string
goModule * debug . Module
err error
}