func exitProcess(logger *zap.Logger) {
	if logger == nil {
		logger = Log()
	}
	logger.Warn("exiting; byeee!! 👋")
	exitCode := ExitCodeSuccess
	if err := Stop(); err != nil {
		logger.Error("failed to stop apps", zap.Error(err))
		exitCode = ExitCodeFailedQuit
	}
	certmagic.CleanUpOwnLocks(logger)
	if pidfile != "" {
		err := os.Remove(pidfile)
		if err != nil {
			logger.Error("cleaning up PID file:",
				zap.String("pidfile", pidfile),
				zap.Error(err))
			exitCode = ExitCodeFailedQuit
		}
	}
	go func() {
		defer func() {
			logger = logger.With(zap.Int("exit_code", exitCode))
			if exitCode == ExitCodeSuccess {
				logger.Info("shutdown complete")
			} else {
				logger.Error("unclean shutdown")
			}
			os.Exit(exitCode)
		}()
		if remoteAdminServer != nil {
			err := stopAdminServer(remoteAdminServer)
			if err != nil {
				exitCode = ExitCodeFailedQuit
				logger.Error("failed to stop remote admin server gracefully", zap.Error(err))
			}
		}
		if localAdminServer != nil {
			err := stopAdminServer(localAdminServer)
			if err != nil {
				exitCode = ExitCodeFailedQuit
				logger.Error("failed to stop local admin server gracefully", zap.Error(err))
			}
		}
	}()
}
exitProcess exits the process as gracefully as possible,
but it always exits, even if there are errors doing so.
It stops all apps, cleans up external locks, removes any
PID file, and shuts down admin endpoint(s) in a goroutine.
Errors are logged along the way, and an appropriate exit
code is emitted.
package caddy
import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"runtime/debug"
	"strconv"
	"strings"
	"sync"
	"time"
	"github.com/caddyserver/caddy/v2/notify"
	"github.com/caddyserver/certmagic"
	"github.com/google/uuid"
	"go.uber.org/zap"
)
type Config struct {
	Admin   *AdminConfig `json:"admin,omitempty"`
	Logging *Logging     `json:"logging,omitempty"`
	StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
	AppsRaw ModuleMap `json:"apps,omitempty" caddy:"namespace="`
	apps    map[string]App
	storage certmagic.Storage
	cancelFunc context.CancelFunc
}
type App interface {
	Start() error
	Stop() error
}
func Run(cfg *Config) error {
	cfgJSON, err := json.Marshal(cfg)
	if err != nil {
		return err
	}
	return Load(cfgJSON, true)
}
func Load(cfgJSON []byte, forceReload bool) error {
	if err := notify.NotifyReloading(); err != nil {
		Log().Error("unable to notify reloading to service manager", zap.Error(err))
	}
	defer func() {
		if err := notify.NotifyReadiness(); err != nil {
			Log().Error("unable to notify readiness to service manager", zap.Error(err))
		}
	}()
	return changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, forceReload)
}
func changeConfig(method, path string, input []byte, forceReload bool) error {
	switch method {
	case http.MethodGet,
		http.MethodHead,
		http.MethodOptions,
		http.MethodConnect,
		http.MethodTrace:
		return fmt.Errorf("method not allowed")
	}
	currentCfgMu.Lock()
	defer currentCfgMu.Unlock()
	err := unsyncedConfigAccess(method, path, input, nil)
	if err != nil {
		return err
	}
	newCfg, err := json.Marshal(rawCfg[rawConfigKey])
	if err != nil {
		return APIError{
			HTTPStatus: http.StatusBadRequest,
			Err:        fmt.Errorf("encoding new config: %v", err),
		}
	}
	if !forceReload && bytes.Equal(rawCfgJSON, newCfg) {
		Log().Named("admin.api").Info("config is unchanged")
		return nil
	}
	idx := make(map[string]string)
	err = indexConfigObjects(rawCfg[rawConfigKey], "/"+rawConfigKey, idx)
	if err != nil {
		return APIError{
			HTTPStatus: http.StatusInternalServerError,
			Err:        fmt.Errorf("indexing config: %v", err),
		}
	}
	err = unsyncedDecodeAndRun(newCfg, true)
	if err != nil {
		if len(rawCfgJSON) > 0 {
			var oldCfg interface{}
			err2 := json.Unmarshal(rawCfgJSON, &oldCfg)
			if err2 != nil {
				err = fmt.Errorf("%v; additionally, restoring old config: %v", err, err2)
			}
			rawCfg[rawConfigKey] = oldCfg
		}
		return fmt.Errorf("loading new config: %v", err)
	}
	rawCfgJSON = newCfg
	rawCfgIndex = idx
	return nil
}
func readConfig(path string, out io.Writer) error {
	currentCfgMu.RLock()
	defer currentCfgMu.RUnlock()
	return unsyncedConfigAccess(http.MethodGet, path, nil, out)
}
func indexConfigObjects(ptr interface{}, configPath string, index map[string]string) error {
	switch val := ptr.(type) {
	case map[string]interface{}:
		for k, v := range val {
			if k == idKey {
				switch idVal := v.(type) {
				case string:
					index[idVal] = configPath
				case float64:  
					index[fmt.Sprintf("%v", idVal)] = configPath
				default:
					return fmt.Errorf("%s: %s field must be a string or number", configPath, idKey)
				}
				continue
			}
			err := indexConfigObjects(val[k], path.Join(configPath, k), index)
			if err != nil {
				return err
			}
		}
	case []interface{}:
		for i := range val {
			err := indexConfigObjects(val[i], path.Join(configPath, strconv.Itoa(i)), index)
			if err != nil {
				return err
			}
		}
	}
	return nil
}
func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
	strippedCfgJSON := RemoveMetaFields(cfgJSON)
	var newCfg *Config
	err := strictUnmarshalJSON(strippedCfgJSON, &newCfg)
	if err != nil {
		return err
	}
	if !allowPersist &&
		newCfg != nil &&
		newCfg.Admin != nil &&
		newCfg.Admin.Config != nil &&
		newCfg.Admin.Config.LoadRaw != nil &&
		newCfg.Admin.Config.LoadInterval <= 0 {
		return fmt.Errorf("recursive config loading detected: pulled configs cannot pull other configs without positive load_interval")
	}
	err = run(newCfg, true)
	if err != nil {
		return err
	}
	oldCfg := currentCfg
	currentCfg = newCfg
	unsyncedStop(oldCfg)
	if allowPersist &&
		newCfg != nil &&
		(newCfg.Admin == nil ||
			newCfg.Admin.Config == nil ||
			newCfg.Admin.Config.Persist == nil ||
			*newCfg.Admin.Config.Persist) {
		dir := filepath.Dir(ConfigAutosavePath)
		err := os.MkdirAll(dir, 0700)
		if err != nil {
			Log().Error("unable to create folder for config autosave",
				zap.String("dir", dir),
				zap.Error(err))
		} else {
			err := os.WriteFile(ConfigAutosavePath, cfgJSON, 0600)
			if err == nil {
				Log().Info("autosaved config (load with --resume flag)", zap.String("file", ConfigAutosavePath))
			} else {
				Log().Error("unable to autosave config",
					zap.String("file", ConfigAutosavePath),
					zap.Error(err))
			}
		}
	}
	return nil
}
func run(newCfg *Config, start bool) error {
	var err error
	if newCfg == nil {
		newCfg = new(Config)
	}
	ctx, cancel := NewContext(Context{Context: context.Background(), cfg: newCfg})
	defer func() {
		if err != nil {
			cancel()
			if currentCfg != nil {
				certmagic.Default.Storage = currentCfg.storage
			}
		}
	}()
	newCfg.cancelFunc = cancel  
	if newCfg.Logging == nil {
		newCfg.Logging = new(Logging)
	}
	err = newCfg.Logging.openLogs(ctx)
	if err != nil {
		return err
	}
	if start {
		err = replaceLocalAdminServer(newCfg)
		if err != nil {
			return fmt.Errorf("starting caddy administration endpoint: %v", err)
		}
	}
	newCfg.apps = make(map[string]App)
	err = func() error {
		if newCfg.StorageRaw != nil {
			val, err := ctx.LoadModule(newCfg, "StorageRaw")
			if err != nil {
				return fmt.Errorf("loading storage module: %v", err)
			}
			stor, err := val.(StorageConverter).CertMagicStorage()
			if err != nil {
				return fmt.Errorf("creating storage value: %v", err)
			}
			newCfg.storage = stor
		}
		if newCfg.storage == nil {
			newCfg.storage = DefaultStorage
		}
		certmagic.Default.Storage = newCfg.storage
		return nil
	}()
	if err != nil {
		return err
	}
	err = func() error {
		for appName := range newCfg.AppsRaw {
			if _, err := ctx.App(appName); err != nil {
				return err
			}
		}
		return nil
	}()
	if err != nil {
		return err
	}
	if !start {
		return nil
	}
	err = func() error {
		var started []string
		for name, a := range newCfg.apps {
			err := a.Start()
			if err != nil {
				for _, otherAppName := range started {
					err2 := newCfg.apps[otherAppName].Stop()
					if err2 != nil {
						err = fmt.Errorf("%v; additionally, aborting app %s: %v",
							err, otherAppName, err2)
					}
				}
				return fmt.Errorf("%s app module: start: %v", name, err)
			}
			started = append(started, name)
		}
		return nil
	}()
	if err != nil {
		return err
	}
	return finishSettingUp(ctx, newCfg)
}
func finishSettingUp(ctx Context, cfg *Config) error {
	err := manageIdentity(ctx, cfg)
	if err != nil {
		return fmt.Errorf("provisioning remote admin endpoint: %v", err)
	}
	err = replaceRemoteAdminServer(ctx, cfg)
	if err != nil {
		return fmt.Errorf("provisioning remote admin endpoint: %v", err)
	}
	if cfg != nil && cfg.Admin != nil && cfg.Admin.Config != nil && cfg.Admin.Config.LoadRaw != nil {
		val, err := ctx.LoadModule(cfg.Admin.Config, "LoadRaw")
		if err != nil {
			return fmt.Errorf("loading config loader module: %s", err)
		}
		runLoadedConfig := func(config []byte) {
			Log().Info("applying dynamically-loaded config", zap.String("loader_module", val.(Module).CaddyModule().ID.Name()), zap.Int("pull_interval", int(cfg.Admin.Config.LoadInterval)))
			currentCfgMu.Lock()
			err := unsyncedDecodeAndRun(config, false)
			currentCfgMu.Unlock()
			if err == nil {
				Log().Info("dynamically-loaded config applied successfully")
			} else {
				Log().Error("running dynamically-loaded config failed", zap.Error(err))
			}
		}
		if cfg.Admin.Config.LoadInterval > 0 {
			go func() {
				select {
				case <-time.After(time.Duration(cfg.Admin.Config.LoadInterval)):
					loadedConfig, err := val.(ConfigLoader).LoadConfig(ctx)
					if err != nil {
						Log().Error("loading dynamic config failed", zap.Error(err))
						return
					}
					runLoadedConfig(loadedConfig)
				case <-ctx.Done():
					return
				}
			}()
		} else {
			loadedConfig, err := val.(ConfigLoader).LoadConfig(ctx)
			if err != nil {
				return fmt.Errorf("loading dynamic config from %T: %v", val, err)
			}
			go runLoadedConfig(loadedConfig)
		}
	}
	return nil
}
type ConfigLoader interface {
	LoadConfig(Context) ([]byte, error)
}
func Stop() error {
	currentCfgMu.Lock()
	defer currentCfgMu.Unlock()
	unsyncedStop(currentCfg)
	currentCfg = nil
	rawCfgJSON = nil
	rawCfgIndex = nil
	rawCfg[rawConfigKey] = nil
	return nil
}
func unsyncedStop(cfg *Config) {
	if cfg == nil {
		return
	}
	for name, a := range cfg.apps {
		err := a.Stop()
		if err != nil {
			log.Printf("[ERROR] stop %s: %v", name, err)
		}
	}
	cfg.cancelFunc()
}
func Validate(cfg *Config) error {
	err := run(cfg, false)
	if err == nil {
		cfg.cancelFunc()  
	}
	return err
}