Repository: caddyserver/caddy
File: cmd/commandfuncs.go (Go)

Code before:
func cmdExportStorage(fl Flags) (int, error) {
  	exportStorageCmdConfigFlag := fl.String("config")
	exportStorageCmdOutputFlag := fl.String("output")
	exportStorageCmdUserFlag := fl.String("user")
	
	var isFsStorage bool
	standard, _, _, err := getModules()
	if err != nil {
		return caddy.ExitCodeFailedStartup, err
	}	

	for _, mod := range standard {
		if mod.caddyModuleID == "caddy.storage.file_system" {
			isFsStorage = true
		}
	}

	u, err := user.Current()
	if err != nil {
		return caddy.ExitCodeFailedStartup, err 
	}

	// re-run command as the given user
	// to ensure storage files are accessible.
	// not sure this is necessary if sudo is needed
	// anyways for setuid / setguid?
	if exportStorageCmdUserFlag != "" && isFsStorage && u.Username != exportStorageCmdUserFlag {
		u, err = user.Lookup(exportStorageCmdUserFlag)
		if err != nil {
			return caddy.ExitCodeFailedStartup, err 
		}

		uid, err := strconv.Atoi(u.Uid)
		if err != nil {
			return caddy.ExitCodeFailedStartup, err 
		}
		gid, err := strconv.Atoi(u.Gid) 
		if err != nil {
			return caddy.ExitCodeFailedStartup, err 
		}

		cmd := exec.Command("caddy", "storage-export", "--config", exportStorageCmdConfigFlag)
		if exportStorageCmdOutputFlag != "" {
			f, err := os.Create(exportStorageCmdOutputFlag)
			if err != nil {
				return caddy.ExitCodeFailedStartup, err
			}
			defer f.Close()
		
			cmd.Stdout = f
		} else {

Review comment (suggestion):
```suggestion
	// re-run command as the given user
	// to ensure storage files are accessible,
	// and that $HOME is set correctly for FS access
```

Code after:

	if warning, diff := caddyfile.FormattingDifference(formatCmdConfigFile, input); diff {
		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,
		)
	}

	return caddy.ExitCodeSuccess, nil
}

// 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) {
	parsedAddr, err := caddy.ParseNetworkAddress(adminAddr)
	if err != nil || parsedAddr.PortRangeSize() > 1 {
		return nil, fmt.Errorf("invalid admin address %s: %v", adminAddr, err)
	}
	origin := "http://" + parsedAddr.JoinHostPort(0)
	if parsedAddr.IsUnixNetwork() {
		origin = "http://unixsocket" // hack so that http.NewRequest() is happy
	}

	// form the request
	req, err := http.NewRequest(method, origin+uri, body)
	if err != nil {
		return nil, fmt.Errorf("making request: %v", err)
	}
	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")