2019-09-30 22:23:58 -05: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-29 15:21:55 -05:00
"context"
2019-09-30 22:23:58 -05:00
"crypto/rand"
"encoding/json"
2021-08-18 13:58:19 -05:00
"errors"
2019-09-30 22:23:58 -05:00
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"os/exec"
2019-12-31 18:56:19 -05:00
"runtime"
2019-10-01 00:43:39 -05:00
"runtime/debug"
2019-09-30 22:23:58 -05:00
"strings"
2022-04-12 13:49:19 -05:00
"github.com/aryann/difflib"
2019-09-30 22:23:58 -05:00
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig"
2020-02-29 12:12:16 -05:00
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
2019-11-15 17:45:18 -05:00
"go.uber.org/zap"
2019-09-30 22:23:58 -05:00
)
func cmdStart ( fl Flags ) ( int , error ) {
startCmdConfigFlag := fl . String ( "config" )
2019-10-01 00:43:39 -05:00
startCmdConfigAdapterFlag := fl . String ( "adapter" )
2020-05-13 12:28:15 -05:00
startCmdPidfileFlag := fl . String ( "pidfile" )
2020-03-22 23:58:24 -05:00
startCmdWatchFlag := fl . Bool ( "watch" )
2021-05-02 13:38:16 -05:00
startCmdEnvfileFlag := fl . String ( "envfile" )
2019-09-30 22:23:58 -05: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 )
}
2021-05-02 13:38:16 -05:00
if startCmdEnvfileFlag != "" {
cmd . Args = append ( cmd . Args , "--envfile" , startCmdEnvfileFlag )
}
2019-09-30 22:23:58 -05:00
if startCmdConfigAdapterFlag != "" {
2019-10-01 00:43:39 -05:00
cmd . Args = append ( cmd . Args , "--adapter" , startCmdConfigAdapterFlag )
2019-09-30 22:23:58 -05:00
}
2020-03-22 23:58:24 -05:00
if startCmdWatchFlag {
cmd . Args = append ( cmd . Args , "--watch" )
}
2020-05-13 12:28:15 -05:00
if startCmdPidfileFlag != "" {
cmd . Args = append ( cmd . Args , "--pidfile" , startCmdPidfileFlag )
}
2019-09-30 22:23:58 -05: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 02:16:47 -05:00
// started yet, and writing synchronously would result
2019-09-30 22:23:58 -05:00
// in a deadlock
go func ( ) {
2020-11-22 16:50:29 -05:00
_ , _ = stdinpipe . Write ( expect )
2019-09-30 22:23:58 -05: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-18 13:58:19 -05:00
if ! errors . Is ( err , net . ErrClosed ) {
2019-09-30 22:23:58 -05: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-13 14:02:47 -05:00
fmt . Printf ( "Successfully started Caddy (pid=%d) - Caddy is running in the background\n" , cmd . Process . Pid )
2019-09-30 22:23:58 -05: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-21 14:09:49 -05:00
caddy . TrapSignals ( )
2019-09-30 22:23:58 -05:00
runCmdConfigFlag := fl . String ( "config" )
2019-10-01 00:43:39 -05:00
runCmdConfigAdapterFlag := fl . String ( "adapter" )
2019-12-31 18:56:19 -05:00
runCmdResumeFlag := fl . Bool ( "resume" )
2020-05-15 16:49:51 -05:00
runCmdLoadEnvfileFlag := fl . String ( "envfile" )
2019-10-01 00:43:39 -05:00
runCmdPrintEnvFlag := fl . Bool ( "environ" )
2020-03-22 23:58:24 -05:00
runCmdWatchFlag := fl . Bool ( "watch" )
2020-05-13 12:28:15 -05:00
runCmdPidfileFlag := fl . String ( "pidfile" )
2019-09-30 22:23:58 -05:00
runCmdPingbackFlag := fl . String ( "pingback" )
2020-05-15 16:49:51 -05:00
// load all additional envs as soon as possible
if runCmdLoadEnvfileFlag != "" {
if err := loadEnvFromFile ( runCmdLoadEnvfileFlag ) ; err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "loading additional environment variables: %v" , err )
}
}
2019-09-30 22:23:58 -05:00
// if we are supposed to print the environment, do that first
if runCmdPrintEnvFlag {
printEnvironment ( )
}
2019-12-31 18:56:19 -05:00
// load the config, depending on flags
var config [ ] byte
var err error
if runCmdResumeFlag {
2021-09-29 12:17:48 -05:00
config , err = os . ReadFile ( caddy . ConfigAutosavePath )
2019-12-31 18:56:19 -05: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-04 14:29:25 -05: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 ) )
}
2019-12-31 18:56:19 -05:00
}
}
// we don't use 'else' here since this value might have been changed in 'if' block; i.e. not mutually exclusive
2020-03-22 23:58:24 -05:00
var configFile string
2019-12-31 18:56:19 -05:00
if ! runCmdResumeFlag {
2022-03-02 13:08:36 -05:00
config , configFile , err = LoadConfig ( runCmdConfigFlag , runCmdConfigAdapterFlag )
2019-12-31 18:56:19 -05:00
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
}
2019-11-04 14:05:20 -05:00
// run the initial config
err = caddy . Load ( config , true )
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "loading initial config: %v" , err )
2019-09-30 22:23:58 -05:00
}
2019-12-31 18:56:19 -05:00
caddy . Log ( ) . Info ( "serving initial configuration" )
2019-09-30 22:23:58 -05: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-29 12:17:48 -05:00
confirmationBytes , err := io . ReadAll ( os . Stdin )
2019-09-30 22:23:58 -05: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-22 23:58:24 -05:00
// if enabled, reload config file automatically on changes
// (this better only be used in dev!)
if runCmdWatchFlag {
go watchConfigFile ( configFile , runCmdConfigAdapterFlag )
}
2020-05-13 12:28:15 -05:00
// create pidfile
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-12-31 18:47:35 -05: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-09-30 22:23:58 -05:00
select { }
}
2019-11-15 17:45:18 -05:00
func cmdStop ( fl Flags ) ( int , error ) {
2022-03-02 13:08:36 -05:00
addrFlag := fl . String ( "address" )
configFlag := fl . String ( "config" )
configAdapterFlag := fl . String ( "adapter" )
2019-11-15 17:45:18 -05:00
2022-07-20 19:14:33 -05:00
adminAddr , err := DetermineAdminAPIAddress ( addrFlag , nil , configFlag , configAdapterFlag )
2022-03-02 13:08:36 -05: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-09-30 22:23:58 -05:00
if err != nil {
2020-05-29 15:21:55 -05:00
caddy . Log ( ) . Warn ( "failed using API to stop instance" , zap . Error ( err ) )
2019-12-23 14:41:05 -05:00
return caddy . ExitCodeFailedStartup , err
2019-09-30 22:23:58 -05:00
}
2022-03-02 13:08:36 -05:00
defer resp . Body . Close ( )
2019-11-15 17:45:18 -05:00
2019-09-30 22:23:58 -05:00
return caddy . ExitCodeSuccess , nil
}
func cmdReload ( fl Flags ) ( int , error ) {
2022-03-02 13:08:36 -05:00
configFlag := fl . String ( "config" )
configAdapterFlag := fl . String ( "adapter" )
addrFlag := fl . String ( "address" )
forceFlag := fl . Bool ( "force" )
2019-09-30 22:23:58 -05:00
// get the config in caddy's native format
2022-03-02 13:08:36 -05:00
config , configFile , err := LoadConfig ( configFlag , configAdapterFlag )
2019-09-30 22:23:58 -05:00
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
2020-03-22 23:58:24 -05:00
if configFile == "" {
2020-01-22 12:04:58 -05:00
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "no config file to load" )
}
2019-09-30 22:23:58 -05:00
2022-07-20 19:14:33 -05:00
adminAddr , err := DetermineAdminAPIAddress ( addrFlag , config , configFlag , configAdapterFlag )
2022-03-02 13:08:36 -05:00
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "couldn't determine admin API address: %v" , err )
2019-09-30 22:23:58 -05:00
}
2021-02-01 20:14:03 -05:00
// optionally force a config reload
headers := make ( http . Header )
2022-03-02 13:08:36 -05:00
if forceFlag {
2021-02-01 20:14:03 -05:00
headers . Set ( "Cache-Control" , "must-revalidate" )
}
2022-03-02 13:08:36 -05:00
resp , err := AdminAPIRequest ( adminAddr , http . MethodPost , "/load" , headers , bytes . NewReader ( config ) )
2019-11-15 17:45:18 -05:00
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "sending configuration to instance: %v" , err )
2019-09-30 22:23:58 -05:00
}
2022-03-02 13:08:36 -05:00
defer resp . Body . Close ( )
2019-09-30 22:23:58 -05:00
return caddy . ExitCodeSuccess , nil
}
func cmdVersion ( _ Flags ) ( int , error ) {
2022-08-04 12:16:59 -05:00
_ , full := caddy . Version ( )
fmt . Println ( full )
2019-09-30 22:23:58 -05:00
return caddy . ExitCodeSuccess , nil
}
2022-08-04 12:16:59 -05:00
func cmdBuildInfo ( _ Flags ) ( int , error ) {
2020-01-10 13:53:07 -05:00
bi , ok := debug . ReadBuildInfo ( )
if ! ok {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "no build information" )
}
2022-08-04 12:16:59 -05:00
fmt . Println ( bi )
2020-01-10 13:53:07 -05:00
return caddy . ExitCodeSuccess , nil
}
2019-10-01 00:43:39 -05:00
func cmdListModules ( fl Flags ) ( int , error ) {
2021-01-04 13:11:56 -05:00
packages := fl . Bool ( "packages" )
2019-10-01 00:43:39 -05:00
versions := fl . Bool ( "versions" )
2021-10-18 13:19:04 -05:00
skipStandard := fl . Bool ( "skip-standard" )
2019-10-01 00:43:39 -05:00
2021-01-04 13:11:56 -05: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-19 20:45:49 -05:00
standard , nonstandard , unknown , err := getModules ( )
if err != nil {
2021-01-04 13:11:56 -05:00
// oh well, just print the module IDs and exit
2019-10-01 00:43:39 -05:00
for _ , m := range caddy . Modules ( ) {
fmt . Println ( m )
}
return caddy . ExitCodeSuccess , nil
2019-09-30 22:23:58 -05:00
}
2019-10-01 00:43:39 -05:00
2021-10-18 13:19:04 -05:00
// Standard modules (always shipped with Caddy)
if ! skipStandard {
if len ( standard ) > 0 {
for _ , mod := range standard {
printModuleInfo ( mod )
}
2021-01-04 13:11:56 -05:00
}
2021-10-18 13:19:04 -05:00
fmt . Printf ( "\n Standard modules: %d\n" , len ( standard ) )
2021-01-04 13:11:56 -05:00
}
2021-10-18 13:19:04 -05:00
// Non-standard modules (third party plugins)
2021-01-04 13:11:56 -05:00
if len ( nonstandard ) > 0 {
2021-10-18 13:19:04 -05:00
if len ( standard ) > 0 && ! skipStandard {
2021-01-04 13:11:56 -05:00
fmt . Println ( )
}
for _ , mod := range nonstandard {
printModuleInfo ( mod )
}
}
fmt . Printf ( "\n Non-standard modules: %d\n" , len ( nonstandard ) )
2021-10-18 13:19:04 -05:00
// Unknown modules (couldn't get Caddy module info)
2021-01-04 13:11:56 -05:00
if len ( unknown ) > 0 {
2021-10-18 13:19:04 -05:00
if ( len ( standard ) > 0 && ! skipStandard ) || len ( nonstandard ) > 0 {
2021-01-04 13:11:56 -05:00
fmt . Println ( )
}
for _ , mod := range unknown {
printModuleInfo ( mod )
}
2019-10-01 00:43:39 -05:00
}
2021-01-04 13:11:56 -05:00
fmt . Printf ( "\n Unknown modules: %d\n" , len ( unknown ) )
2019-10-01 00:43:39 -05:00
2019-09-30 22:23:58 -05:00
return caddy . ExitCodeSuccess , nil
}
func cmdEnviron ( _ Flags ) ( int , error ) {
printEnvironment ( )
return caddy . ExitCodeSuccess , nil
}
func cmdAdaptConfig ( fl Flags ) ( int , error ) {
2019-10-01 00:43:39 -05:00
adaptCmdInputFlag := fl . String ( "config" )
2019-09-30 22:23:58 -05:00
adaptCmdAdapterFlag := fl . String ( "adapter" )
adaptCmdPrettyFlag := fl . Bool ( "pretty" )
2019-10-01 12:02:13 -05:00
adaptCmdValidateFlag := fl . Bool ( "validate" )
2019-09-30 22:23:58 -05:00
2020-02-04 12:48:02 -05:00
// if no input file was specified, try a default
// Caddyfile if the Caddyfile adapter is plugged in
if adaptCmdInputFlag == "" && caddyconfig . GetAdapter ( "caddyfile" ) != nil {
_ , err := os . Stat ( "Caddyfile" )
if err == nil {
// default Caddyfile exists
adaptCmdInputFlag = "Caddyfile"
caddy . Log ( ) . Info ( "using adjacent Caddyfile" )
} else if ! os . IsNotExist ( err ) {
// default Caddyfile exists, but error accessing it
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "accessing default Caddyfile: %v" , err )
}
}
if adaptCmdInputFlag == "" {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "input file required when there is no Caddyfile in current directory (use --config flag)" )
}
if adaptCmdAdapterFlag == "" {
2019-09-30 22:23:58 -05:00
return caddy . ExitCodeFailedStartup ,
2020-02-04 12:48:02 -05:00
fmt . Errorf ( "adapter name is required (use --adapt flag or leave unspecified for default)" )
2019-09-30 22:23:58 -05:00
}
cfgAdapter := caddyconfig . GetAdapter ( adaptCmdAdapterFlag )
if cfgAdapter == nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "unrecognized config adapter: %s" , adaptCmdAdapterFlag )
}
2021-09-29 12:17:48 -05:00
input , err := os . ReadFile ( adaptCmdInputFlag )
2019-09-30 22:23:58 -05:00
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "reading input file: %v" , err )
}
2022-08-02 15:39:09 -05:00
opts := map [ string ] any { "filename" : adaptCmdInputFlag }
2019-09-30 22:23:58 -05:00
adaptedConfig , warnings , err := cfgAdapter . Adapt ( input , opts )
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
2021-01-04 13:11:36 -05: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-19 16:21:11 -05:00
// print result to stdout
fmt . Println ( string ( adaptedConfig ) )
2019-09-30 22:23:58 -05: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-25 11:12:10 -05:00
caddy . Log ( ) . Named ( adaptCmdAdapterFlag ) . Warn ( msg ,
zap . String ( "file" , warn . File ) ,
zap . Int ( "line" , warn . Line ) )
2019-09-30 22:23:58 -05:00
}
2019-10-01 12:02:13 -05:00
// validate output if requested
if adaptCmdValidateFlag {
var cfg * caddy . Config
2023-02-22 13:39:40 -05:00
err = caddy . StrictUnmarshalJSON ( adaptedConfig , & cfg )
2019-10-01 12:02:13 -05: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-09-30 22:23:58 -05:00
return caddy . ExitCodeSuccess , nil
}
2019-10-01 00:43:39 -05:00
func cmdValidateConfig ( fl Flags ) ( int , error ) {
validateCmdConfigFlag := fl . String ( "config" )
validateCmdAdapterFlag := fl . String ( "adapter" )
2023-01-31 16:27:35 -05:00
runCmdLoadEnvfileFlag := fl . String ( "envfile" )
// load all additional envs as soon as possible
if runCmdLoadEnvfileFlag != "" {
if err := loadEnvFromFile ( runCmdLoadEnvfileFlag ) ; err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "loading additional environment variables: %v" , err )
}
}
2019-10-01 00:43:39 -05:00
2022-03-02 13:08:36 -05:00
input , _ , err := LoadConfig ( validateCmdConfigFlag , validateCmdAdapterFlag )
2019-10-01 00:43:39 -05:00
if err != nil {
2020-03-09 01:09:15 -05:00
return caddy . ExitCodeFailedStartup , err
2019-10-01 00:43:39 -05:00
}
2019-12-12 16:30:22 -05:00
input = caddy . RemoveMetaFields ( input )
2019-10-01 00:43:39 -05:00
var cfg * caddy . Config
2023-02-22 13:39:40 -05:00
err = caddy . StrictUnmarshalJSON ( input , & cfg )
2019-10-01 00:43:39 -05: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-15 22:18:31 -05:00
func cmdFmt ( fl Flags ) ( int , error ) {
2020-02-29 12:12:16 -05:00
formatCmdConfigFile := fl . Arg ( 0 )
if formatCmdConfigFile == "" {
formatCmdConfigFile = "Caddyfile"
}
2020-09-14 13:30:12 -05:00
// as a special case, read from stdin if the file name is "-"
if formatCmdConfigFile == "-" {
2021-09-29 12:17:48 -05:00
input , err := io . ReadAll ( os . Stdin )
2020-09-14 13:30:12 -05:00
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "reading stdin: %v" , err )
}
fmt . Print ( string ( caddyfile . Format ( input ) ) )
return caddy . ExitCodeSuccess , nil
}
2020-02-29 12:12:16 -05:00
2021-09-29 12:17:48 -05:00
input , err := os . ReadFile ( formatCmdConfigFile )
2020-02-29 12:12:16 -05:00
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "reading input file: %v" , err )
}
output := caddyfile . Format ( input )
2020-09-14 13:30:12 -05:00
if fl . Bool ( "overwrite" ) {
2021-09-29 12:17:48 -05:00
if err := os . WriteFile ( formatCmdConfigFile , output , 0600 ) ; err != nil {
2022-01-16 19:30:07 -05:00
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "overwriting formatted file: %v" , err )
2020-02-29 12:12:16 -05:00
}
2023-01-31 11:24:44 -05:00
return caddy . ExitCodeSuccess , nil
}
if fl . Bool ( "diff" ) {
2022-04-12 13:49:19 -05: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-02-29 12:12:16 -05:00
} else {
fmt . Print ( string ( output ) )
}
2023-01-21 23:28:37 -05:00
if warning , diff := caddyfile . FormattingDifference ( formatCmdConfigFile , input ) ; diff {
2023-02-16 18:34:12 -05: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-21 23:28:37 -05:00
}
2020-02-29 12:12:16 -05:00
return caddy . ExitCodeSuccess , nil
}
2022-03-02 13:08:36 -05: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-29 15:21:55 -05:00
parsedAddr , err := caddy . ParseNetworkAddress ( adminAddr )
if err != nil || parsedAddr . PortRangeSize ( ) > 1 {
2022-03-02 13:08:36 -05:00
return nil , fmt . Errorf ( "invalid admin address %s: %v" , adminAddr , err )
2020-05-29 15:21:55 -05:00
}
2022-03-19 23:51:32 -05:00
origin := "http://" + parsedAddr . JoinHostPort ( 0 )
2020-05-29 15:21:55 -05:00
if parsedAddr . IsUnixNetwork ( ) {
2022-04-28 09:31:59 -05:00
origin = "http://unixsocket" // hack so that http.NewRequest() is happy
2020-05-29 15:21:55 -05:00
}
// form the request
2022-03-19 23:51:32 -05:00
req , err := http . NewRequest ( method , origin + uri , body )
2020-05-29 15:21:55 -05:00
if err != nil {
2022-03-02 13:08:36 -05:00
return nil , fmt . Errorf ( "making request: %v" , err )
2020-05-29 15:21:55 -05:00
}
if parsedAddr . IsUnixNetwork ( ) {
// When listening on a unix socket, the admin endpoint doesn't
// accept any Host header because there is no host:port for
// a unix socket's address. The server's host check is fairly
// strict for security reasons, so we don't allow just any
// Host header. For unix sockets, the Host header must be
// empty. Unfortunately, Go makes it impossible to make HTTP
// requests with an empty Host header... except with this one
// weird trick. (Hopefully they don't fix it. It's already
// hard enough to use HTTP over unix sockets.)
//
// An equivalent curl command would be something like:
// $ curl --unix-socket caddy.sock http:/:$REQUEST_URI
req . URL . Host = " "
req . Host = ""
} else {
req . Header . Set ( "Origin" , origin )
}
if body != nil {
req . Header . Set ( "Content-Type" , "application/json" )
}
2021-02-01 20:14:03 -05:00
for k , v := range headers {
req . Header [ k ] = v
}
2020-05-29 15:21:55 -05: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-15 17:45:18 -05:00
if err != nil {
2022-03-02 13:08:36 -05:00
return nil , fmt . Errorf ( "performing request: %v" , err )
2019-11-15 17:45:18 -05:00
}
// if it didn't work, let the user know
if resp . StatusCode >= 400 {
2021-09-29 12:17:48 -05:00
respBody , err := io . ReadAll ( io . LimitReader ( resp . Body , 1024 * 10 ) )
2019-11-15 17:45:18 -05:00
if err != nil {
2022-03-02 13:08:36 -05: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-20 19:14:33 -05: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-02 13:08:36 -05: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-20 19:14:33 -05: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 00:24:16 -05:00
return "" , fmt . Errorf ( "no config file to load; either use --config flag or ensure Caddyfile exists in current directory" )
2022-07-20 19:14:33 -05:00
}
2022-03-02 13:08:36 -05:00
}
2022-07-20 19:14:33 -05:00
// get the address of the admin listener from the config
if len ( loadedConfig ) > 0 {
2022-03-02 13:08:36 -05:00
var tmpStruct struct {
Admin caddy . AdminConfig ` json:"admin" `
}
2022-07-20 19:14:33 -05:00
err := json . Unmarshal ( loadedConfig , & tmpStruct )
2022-03-02 13:08:36 -05:00
if err != nil {
return "" , fmt . Errorf ( "unmarshaling admin listener address from config: %v" , err )
}
2022-04-03 11:04:33 -05:00
if tmpStruct . Admin . Listen != "" {
return tmpStruct . Admin . Listen , nil
}
2019-11-15 17:45:18 -05:00
}
}
2022-03-02 13:08:36 -05:00
// Fallback to the default listen address otherwise
return caddy . DefaultAdminListen , nil
2019-11-15 17:45:18 -05:00
}
2021-01-19 20:45:49 -05:00
type moduleInfo struct {
caddyModuleID string
goModule * debug . Module
err error
}