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"
2023-08-09 12:40:37 -05:00
"io/fs"
2019-09-30 22:23:58 -05:00
"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"
2023-08-14 10:41:15 -05:00
"go.uber.org/zap"
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"
2023-08-05 19:09:16 -05:00
"github.com/caddyserver/caddy/v2/internal"
2019-09-30 22:23:58 -05:00
)
func cmdStart ( fl Flags ) ( int , error ) {
2023-10-11 10:46:18 -05:00
configFlag := fl . String ( "config" )
configAdapterFlag := fl . String ( "adapter" )
pidfileFlag := fl . String ( "pidfile" )
watchFlag := fl . Bool ( "watch" )
2023-09-06 21:19:24 -05:00
var err error
2023-10-11 10:46:18 -05:00
var envfileFlag [ ] string
envfileFlag , err = fl . GetStringSlice ( "envfile" )
2023-09-06 21:19:24 -05:00
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "reading envfile flag: %v" , err )
}
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 ( ) )
2024-08-12 21:26:20 -05:00
// we should be able to run caddy in relative paths
if errors . Is ( cmd . Err , exec . ErrDot ) {
cmd . Err = nil
}
2023-10-11 10:46:18 -05:00
if configFlag != "" {
cmd . Args = append ( cmd . Args , "--config" , configFlag )
2019-09-30 22:23:58 -05:00
}
2023-09-06 21:19:24 -05:00
2023-10-11 10:46:18 -05:00
for _ , envfile := range envfileFlag {
cmd . Args = append ( cmd . Args , "--envfile" , envfile )
2021-05-02 13:38:16 -05:00
}
2023-10-11 10:46:18 -05:00
if configAdapterFlag != "" {
cmd . Args = append ( cmd . Args , "--adapter" , configAdapterFlag )
2019-09-30 22:23:58 -05:00
}
2023-10-11 10:46:18 -05:00
if watchFlag {
2020-03-22 23:58:24 -05:00
cmd . Args = append ( cmd . Args , "--watch" )
}
2023-10-11 10:46:18 -05:00
if pidfileFlag != "" {
cmd . Args = append ( cmd . Args , "--pidfile" , pidfileFlag )
2020-05-13 12:28:15 -05:00
}
2023-10-11 10:46:18 -05:00
stdinPipe , err := cmd . StdinPipe ( )
2019-09-30 22:23:58 -05:00
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 {
2023-10-11 10:46:18 -05:00
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "generating random confirmation bytes: %v" , err )
2019-09-30 22:23:58 -05:00
}
// 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 ( ) {
2023-10-11 10:46:18 -05:00
_ , _ = stdinPipe . Write ( expect )
stdinPipe . Close ( )
2019-09-30 22:23:58 -05:00
} ( )
// start the process
err = cmd . Start ( )
if err != nil {
2023-10-11 10:46:18 -05:00
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "starting caddy process: %v" , err )
2019-09-30 22:23:58 -05:00
}
// 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 ( )
2023-10-11 10:46:18 -05:00
configFlag := fl . String ( "config" )
configAdapterFlag := fl . String ( "adapter" )
resumeFlag := fl . Bool ( "resume" )
printEnvFlag := fl . Bool ( "environ" )
watchFlag := fl . Bool ( "watch" )
pidfileFlag := fl . String ( "pidfile" )
pingbackFlag := fl . String ( "pingback" )
2023-09-06 21:19:24 -05:00
2020-05-15 16:49:51 -05:00
// load all additional envs as soon as possible
2023-10-11 10:46:18 -05:00
err := handleEnvFileFlag ( fl )
if err != nil {
return caddy . ExitCodeFailedStartup , err
2020-05-15 16:49:51 -05:00
}
2019-09-30 22:23:58 -05:00
// if we are supposed to print the environment, do that first
2023-10-11 10:46:18 -05:00
if printEnvFlag {
2019-09-30 22:23:58 -05:00
printEnvironment ( )
}
2019-12-31 18:56:19 -05:00
// load the config, depending on flags
var config [ ] byte
2023-10-11 10:46:18 -05:00
if resumeFlag {
2021-09-29 12:17:48 -05:00
config , err = os . ReadFile ( caddy . ConfigAutosavePath )
2024-01-02 00:48:55 -05:00
if errors . Is ( err , fs . ErrNotExist ) {
2019-12-31 18:56:19 -05:00
// 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 ) )
2023-10-11 10:46:18 -05:00
resumeFlag = false
2019-12-31 18:56:19 -05:00
} else if err != nil {
return caddy . ExitCodeFailedStartup , err
} else {
2023-10-11 10:46:18 -05:00
if configFlag == "" {
2020-04-04 14:29:25 -05:00
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
2023-10-11 10:46:18 -05:00
if ! resumeFlag {
config , configFile , err = LoadConfig ( configFlag , configAdapterFlag )
2019-12-31 18:56:19 -05:00
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
}
2023-04-03 12:57:16 -05:00
// create pidfile now, in case loading config takes a while (issue #5477)
2023-10-11 10:46:18 -05:00
if pidfileFlag != "" {
err := caddy . PIDFile ( pidfileFlag )
2023-04-03 12:57:16 -05:00
if err != nil {
caddy . Log ( ) . Error ( "unable to write PID file" ,
2023-10-11 10:46:18 -05:00
zap . String ( "pidfile" , pidfileFlag ) ,
2023-04-03 12:57:16 -05:00
zap . Error ( 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
2023-10-11 10:46:18 -05:00
if pingbackFlag != "" {
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 )
}
2023-10-11 10:46:18 -05:00
conn , err := net . Dial ( "tcp" , pingbackFlag )
2019-09-30 22:23:58 -05:00
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 ,
2023-10-11 10:46:18 -05:00
fmt . Errorf ( "writing confirmation bytes to %s: %v" , pingbackFlag , err )
2019-09-30 22:23:58 -05:00
}
}
2020-03-22 23:58:24 -05:00
// if enabled, reload config file automatically on changes
// (this better only be used in dev!)
2023-10-11 10:46:18 -05:00
if watchFlag {
go watchConfigFile ( configFile , configAdapterFlag )
2020-03-22 23:58:24 -05:00
}
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 ) {
2023-10-11 10:46:18 -05:00
addressFlag := fl . String ( "address" )
2022-03-02 13:08:36 -05:00
configFlag := fl . String ( "config" )
configAdapterFlag := fl . String ( "adapter" )
2019-11-15 17:45:18 -05:00
2023-10-11 10:46:18 -05:00
adminAddr , err := DetermineAdminAPIAddress ( addressFlag , 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" )
2023-10-11 10:46:18 -05:00
addressFlag := fl . String ( "address" )
2022-03-02 13:08:36 -05:00
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
2023-10-11 10:46:18 -05:00
adminAddr , err := DetermineAdminAPIAddress ( addressFlag , 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
}
2023-10-11 10:46:18 -05:00
func cmdEnviron ( fl Flags ) ( int , error ) {
// load all additional envs as soon as possible
err := handleEnvFileFlag ( fl )
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
2019-09-30 22:23:58 -05:00
printEnvironment ( )
return caddy . ExitCodeSuccess , nil
}
func cmdAdaptConfig ( fl Flags ) ( int , error ) {
2023-10-11 10:46:18 -05:00
inputFlag := fl . String ( "config" )
adapterFlag := fl . String ( "adapter" )
prettyFlag := fl . Bool ( "pretty" )
validateFlag := fl . Bool ( "validate" )
2019-09-30 22:23:58 -05:00
2023-08-09 12:40:37 -05:00
var err error
2023-10-11 10:46:18 -05:00
inputFlag , err = configFileWithRespectToDefault ( caddy . Log ( ) , inputFlag )
2023-08-09 12:40:37 -05:00
if err != nil {
return caddy . ExitCodeFailedStartup , err
2020-02-04 12:48:02 -05:00
}
2023-10-11 10:46:18 -05:00
// load all additional envs as soon as possible
err = handleEnvFileFlag ( fl )
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
if adapterFlag == "" {
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
}
2023-10-11 10:46:18 -05:00
cfgAdapter := caddyconfig . GetAdapter ( adapterFlag )
2019-09-30 22:23:58 -05:00
if cfgAdapter == nil {
return caddy . ExitCodeFailedStartup ,
2023-10-11 10:46:18 -05:00
fmt . Errorf ( "unrecognized config adapter: %s" , adapterFlag )
2019-09-30 22:23:58 -05:00
}
2023-10-11 10:46:18 -05:00
input , err := os . ReadFile ( inputFlag )
2019-09-30 22:23:58 -05:00
if err != nil {
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "reading input file: %v" , err )
}
2023-10-11 10:46:18 -05:00
opts := map [ string ] any { "filename" : inputFlag }
2019-09-30 22:23:58 -05:00
adaptedConfig , warnings , err := cfgAdapter . Adapt ( input , opts )
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
2023-10-11 10:46:18 -05:00
if prettyFlag {
2021-01-04 13:11:36 -05:00
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 )
}
2023-10-11 10:46:18 -05:00
caddy . Log ( ) . Named ( adapterFlag ) . Warn ( msg ,
2022-04-25 11:12:10 -05:00
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
2023-10-11 10:46:18 -05:00
if validateFlag {
2019-10-01 12:02:13 -05:00
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 ) {
2023-10-11 10:46:18 -05:00
configFlag := fl . String ( "config" )
adapterFlag := fl . String ( "adapter" )
2023-01-31 16:27:35 -05:00
// load all additional envs as soon as possible
2023-10-11 10:46:18 -05:00
err := handleEnvFileFlag ( fl )
if err != nil {
return caddy . ExitCodeFailedStartup , err
2023-01-31 16:27:35 -05:00
}
2019-10-01 00:43:39 -05:00
2023-08-09 12:40:37 -05:00
// use default config and ensure a config file is specified
2023-10-11 10:46:18 -05:00
configFlag , err = configFileWithRespectToDefault ( caddy . Log ( ) , configFlag )
2023-08-09 12:40:37 -05:00
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
2023-10-11 10:46:18 -05:00
if configFlag == "" {
2023-08-09 12:40:37 -05:00
return caddy . ExitCodeFailedStartup ,
fmt . Errorf ( "input file required when there is no Caddyfile in current directory (use --config flag)" )
}
2023-10-11 10:46:18 -05:00
input , _ , err := LoadConfig ( configFlag , adapterFlag )
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 ) {
2023-10-11 10:46:18 -05:00
configFile := fl . Arg ( 0 )
if configFile == "" {
configFile = "Caddyfile"
2020-02-29 12:12:16 -05:00
}
2020-09-14 13:30:12 -05:00
// as a special case, read from stdin if the file name is "-"
2023-10-11 10:46:18 -05:00
if configFile == "-" {
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
2023-10-11 10:46:18 -05:00
input , err := os . ReadFile ( configFile )
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" ) {
2023-10-11 10:46:18 -05:00
if err := os . WriteFile ( configFile , output , 0 o600 ) ; 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-10-11 10:46:18 -05:00
if warning , diff := caddyfile . FormattingDifference ( configFile , 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
}
2023-10-11 10:46:18 -05:00
// handleEnvFileFlag loads the environment variables from the given --envfile
// flag if specified. This should be called as early in the command function.
func handleEnvFileFlag ( fl Flags ) error {
var err error
var envfileFlag [ ] string
envfileFlag , err = fl . GetStringSlice ( "envfile" )
if err != nil {
return fmt . Errorf ( "reading envfile flag: %v" , err )
}
for _ , envfile := range envfileFlag {
if err := loadEnvFromFile ( envfile ) ; err != nil {
return fmt . Errorf ( "loading additional environment variables: %v" , err )
}
}
return 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 ( ) {
2023-08-02 12:13:52 -05:00
origin = "http://127.0.0.1" // bogus host is a hack so that http.NewRequest() is happy
2023-08-05 19:09:16 -05: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
2024-09-30 11:55:03 -05:00
} else if parsedAddr . IsFdNetwork ( ) {
origin = "http://127.0.0.1"
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
}
2024-09-30 11:55:03 -05:00
if parsedAddr . IsUnixNetwork ( ) || parsedAddr . IsFdNetwork ( ) {
2023-08-02 12:13:52 -05: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
2024-09-30 11:55:03 -05:00
// with unix sockets and socket fds. 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:
2023-08-02 12:13:52 -05:00
// https://github.com/golang/go/issues/60374
// See also the discussion here:
// https://github.com/golang/go/issues/61431
2020-05-29 15:21:55 -05:00
//
2023-08-02 12:13:52 -05: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-29 15:21:55 -05:00
} 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 {
2024-10-01 21:31:30 -05:00
respBody , err := io . ReadAll ( io . LimitReader ( resp . Body , 1024 * 1024 * 2 ) )
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
2023-08-09 12:40:37 -05: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-19 20:45:49 -05:00
type moduleInfo struct {
caddyModuleID string
goModule * debug . Module
err error
}