mirror of
				https://github.com/caddyserver/caddy.git
				synced 2025-10-30 18:22:49 -04:00 
			
		
		
		
	
		
			Some checks failed
		
		
	
	Tests / test (./cmd/caddy/caddy, ~1.25.0, ubuntu-latest, 0, 1.25, linux) (push) Failing after 54s
				
			Tests / test (s390x on IBM Z) (push) Has been skipped
				
			Tests / goreleaser-check (push) Has been skipped
				
			Cross-Build / build (~1.25.0, 1.25, aix) (push) Failing after 15s
				
			Cross-Build / build (~1.25.0, 1.25, darwin) (push) Failing after 15s
				
			Cross-Build / build (~1.25.0, 1.25, dragonfly) (push) Failing after 16s
				
			Cross-Build / build (~1.25.0, 1.25, freebsd) (push) Failing after 15s
				
			Cross-Build / build (~1.25.0, 1.25, illumos) (push) Failing after 14s
				
			Cross-Build / build (~1.25.0, 1.25, linux) (push) Failing after 28s
				
			Cross-Build / build (~1.25.0, 1.25, netbsd) (push) Failing after 18s
				
			Cross-Build / build (~1.25.0, 1.25, openbsd) (push) Failing after 16s
				
			Cross-Build / build (~1.25.0, 1.25, solaris) (push) Failing after 14s
				
			Cross-Build / build (~1.25.0, 1.25, windows) (push) Failing after 13s
				
			Lint / lint (ubuntu-latest, linux) (push) Failing after 14s
				
			Lint / govulncheck (push) Successful in 1m38s
				
			Lint / dependency-review (push) Failing after 15s
				
			Tests / test (./cmd/caddy/caddy, ~1.25.0, macos-14, 0, 1.25, mac) (push) Has been cancelled
				
			Tests / test (./cmd/caddy/caddy.exe, ~1.25.0, windows-latest, True, 1.25, windows) (push) Has been cancelled
				
			Lint / lint (macos-14, mac) (push) Has been cancelled
				
			Lint / lint (windows-latest, windows) (push) Has been cancelled
				
			OpenSSF Scorecard supply-chain security / Scorecard analysis (push) Has started running
				
			
		
			
				
	
	
		
			232 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			232 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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 (
 | |
| 	"archive/tar"
 | |
| 	"context"
 | |
| 	"encoding/json"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"io/fs"
 | |
| 	"os"
 | |
| 
 | |
| 	"github.com/caddyserver/certmagic"
 | |
| 
 | |
| 	"github.com/caddyserver/caddy/v2"
 | |
| )
 | |
| 
 | |
| type storVal struct {
 | |
| 	StorageRaw json.RawMessage `json:"storage,omitempty" caddy:"namespace=caddy.storage inline_key=module"`
 | |
| }
 | |
| 
 | |
| // determineStorage returns the top-level storage module from the given config.
 | |
| // It may return nil even if no error.
 | |
| func determineStorage(configFile string, configAdapter string) (*storVal, error) {
 | |
| 	cfg, _, _, err := LoadConfig(configFile, configAdapter)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	// storage defaults to FileStorage if not explicitly
 | |
| 	// defined in the config, so the config can be valid
 | |
| 	// json but unmarshaling will fail.
 | |
| 	if !json.Valid(cfg) {
 | |
| 		return nil, &json.SyntaxError{}
 | |
| 	}
 | |
| 	var tmpStruct storVal
 | |
| 	err = json.Unmarshal(cfg, &tmpStruct)
 | |
| 	if err != nil {
 | |
| 		// default case, ignore the error
 | |
| 		var jsonError *json.SyntaxError
 | |
| 		if errors.As(err, &jsonError) {
 | |
| 			return nil, nil
 | |
| 		}
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &tmpStruct, nil
 | |
| }
 | |
| 
 | |
| func cmdImportStorage(fl Flags) (int, error) {
 | |
| 	importStorageCmdConfigFlag := fl.String("config")
 | |
| 	importStorageCmdImportFile := fl.String("input")
 | |
| 
 | |
| 	if importStorageCmdConfigFlag == "" {
 | |
| 		return caddy.ExitCodeFailedStartup, errors.New("--config is required")
 | |
| 	}
 | |
| 	if importStorageCmdImportFile == "" {
 | |
| 		return caddy.ExitCodeFailedStartup, errors.New("--input is required")
 | |
| 	}
 | |
| 
 | |
| 	// extract storage from config if possible
 | |
| 	storageCfg, err := determineStorage(importStorageCmdConfigFlag, "")
 | |
| 	if err != nil {
 | |
| 		return caddy.ExitCodeFailedStartup, err
 | |
| 	}
 | |
| 
 | |
| 	// load specified storage or fallback to default
 | |
| 	var stor certmagic.Storage
 | |
| 	ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
 | |
| 	defer cancel()
 | |
| 	if storageCfg != nil && storageCfg.StorageRaw != nil {
 | |
| 		val, err := ctx.LoadModule(storageCfg, "StorageRaw")
 | |
| 		if err != nil {
 | |
| 			return caddy.ExitCodeFailedStartup, err
 | |
| 		}
 | |
| 		stor, err = val.(caddy.StorageConverter).CertMagicStorage()
 | |
| 		if err != nil {
 | |
| 			return caddy.ExitCodeFailedStartup, err
 | |
| 		}
 | |
| 	} else {
 | |
| 		stor = caddy.DefaultStorage
 | |
| 	}
 | |
| 
 | |
| 	// setup input
 | |
| 	var f *os.File
 | |
| 	if importStorageCmdImportFile == "-" {
 | |
| 		f = os.Stdin
 | |
| 	} else {
 | |
| 		f, err = os.Open(importStorageCmdImportFile)
 | |
| 		if err != nil {
 | |
| 			return caddy.ExitCodeFailedStartup, fmt.Errorf("opening input file: %v", err)
 | |
| 		}
 | |
| 		defer f.Close()
 | |
| 	}
 | |
| 
 | |
| 	// store each archive element
 | |
| 	tr := tar.NewReader(f)
 | |
| 	for {
 | |
| 		hdr, err := tr.Next()
 | |
| 		if err == io.EOF {
 | |
| 			break
 | |
| 		}
 | |
| 		if err != nil {
 | |
| 			return caddy.ExitCodeFailedQuit, fmt.Errorf("reading archive: %v", err)
 | |
| 		}
 | |
| 
 | |
| 		b, err := io.ReadAll(tr)
 | |
| 		if err != nil {
 | |
| 			return caddy.ExitCodeFailedQuit, fmt.Errorf("reading archive: %v", err)
 | |
| 		}
 | |
| 
 | |
| 		err = stor.Store(ctx, hdr.Name, b)
 | |
| 		if err != nil {
 | |
| 			return caddy.ExitCodeFailedQuit, fmt.Errorf("reading archive: %v", err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	fmt.Println("Successfully imported storage")
 | |
| 	return caddy.ExitCodeSuccess, nil
 | |
| }
 | |
| 
 | |
| func cmdExportStorage(fl Flags) (int, error) {
 | |
| 	exportStorageCmdConfigFlag := fl.String("config")
 | |
| 	exportStorageCmdOutputFlag := fl.String("output")
 | |
| 
 | |
| 	if exportStorageCmdConfigFlag == "" {
 | |
| 		return caddy.ExitCodeFailedStartup, errors.New("--config is required")
 | |
| 	}
 | |
| 	if exportStorageCmdOutputFlag == "" {
 | |
| 		return caddy.ExitCodeFailedStartup, errors.New("--output is required")
 | |
| 	}
 | |
| 
 | |
| 	// extract storage from config if possible
 | |
| 	storageCfg, err := determineStorage(exportStorageCmdConfigFlag, "")
 | |
| 	if err != nil {
 | |
| 		return caddy.ExitCodeFailedStartup, err
 | |
| 	}
 | |
| 
 | |
| 	// load specified storage or fallback to default
 | |
| 	var stor certmagic.Storage
 | |
| 	ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
 | |
| 	defer cancel()
 | |
| 	if storageCfg != nil && storageCfg.StorageRaw != nil {
 | |
| 		val, err := ctx.LoadModule(storageCfg, "StorageRaw")
 | |
| 		if err != nil {
 | |
| 			return caddy.ExitCodeFailedStartup, err
 | |
| 		}
 | |
| 		stor, err = val.(caddy.StorageConverter).CertMagicStorage()
 | |
| 		if err != nil {
 | |
| 			return caddy.ExitCodeFailedStartup, err
 | |
| 		}
 | |
| 	} else {
 | |
| 		stor = caddy.DefaultStorage
 | |
| 	}
 | |
| 
 | |
| 	// enumerate all keys
 | |
| 	keys, err := stor.List(ctx, "", true)
 | |
| 	if err != nil {
 | |
| 		return caddy.ExitCodeFailedStartup, err
 | |
| 	}
 | |
| 
 | |
| 	// setup output
 | |
| 	var f *os.File
 | |
| 	if exportStorageCmdOutputFlag == "-" {
 | |
| 		f = os.Stdout
 | |
| 	} else {
 | |
| 		f, err = os.Create(exportStorageCmdOutputFlag)
 | |
| 		if err != nil {
 | |
| 			return caddy.ExitCodeFailedStartup, fmt.Errorf("opening output file: %v", err)
 | |
| 		}
 | |
| 		defer f.Close()
 | |
| 	}
 | |
| 
 | |
| 	// `IsTerminal: true` keys hold the values we
 | |
| 	// care about, write them out
 | |
| 	tw := tar.NewWriter(f)
 | |
| 	for _, k := range keys {
 | |
| 		info, err := stor.Stat(ctx, k)
 | |
| 		if err != nil {
 | |
| 			if errors.Is(err, fs.ErrNotExist) {
 | |
| 				caddy.Log().Warn(fmt.Sprintf("key: %s removed while export is in-progress", k))
 | |
| 				continue
 | |
| 			}
 | |
| 			return caddy.ExitCodeFailedQuit, err
 | |
| 		}
 | |
| 
 | |
| 		if info.IsTerminal {
 | |
| 			v, err := stor.Load(ctx, k)
 | |
| 			if err != nil {
 | |
| 				if errors.Is(err, fs.ErrNotExist) {
 | |
| 					caddy.Log().Warn(fmt.Sprintf("key: %s removed while export is in-progress", k))
 | |
| 					continue
 | |
| 				}
 | |
| 				return caddy.ExitCodeFailedQuit, err
 | |
| 			}
 | |
| 
 | |
| 			hdr := &tar.Header{
 | |
| 				Name:    k,
 | |
| 				Mode:    0o600,
 | |
| 				Size:    int64(len(v)),
 | |
| 				ModTime: info.Modified,
 | |
| 			}
 | |
| 
 | |
| 			if err = tw.WriteHeader(hdr); err != nil {
 | |
| 				return caddy.ExitCodeFailedQuit, fmt.Errorf("writing archive: %v", err)
 | |
| 			}
 | |
| 			if _, err = tw.Write(v); err != nil {
 | |
| 				return caddy.ExitCodeFailedQuit, fmt.Errorf("writing archive: %v", err)
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if err = tw.Close(); err != nil {
 | |
| 		return caddy.ExitCodeFailedQuit, fmt.Errorf("writing archive: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	return caddy.ExitCodeSuccess, nil
 | |
| }
 |