diff --git a/cmd/restic/cmd_init.go b/cmd/restic/cmd_init.go index bbab3711d..f5e43bbc9 100644 --- a/cmd/restic/cmd_init.go +++ b/cmd/restic/cmd_init.go @@ -1,10 +1,13 @@ package main import ( + "strconv" + "github.com/restic/chunker" "github.com/restic/restic/internal/backend/location" "github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/repository" + "github.com/restic/restic/internal/restic" "github.com/spf13/cobra" ) @@ -30,6 +33,7 @@ Exit status is 0 if the command was successful, and non-zero if there was any er type InitOptions struct { secondaryRepoOptions CopyChunkerParameters bool + RepositoryVersion string } var initOptions InitOptions @@ -40,9 +44,26 @@ func init() { f := cmdInit.Flags() initSecondaryRepoOptions(f, &initOptions.secondaryRepoOptions, "secondary", "to copy chunker parameters from") f.BoolVar(&initOptions.CopyChunkerParameters, "copy-chunker-params", false, "copy chunker parameters from the secondary repository (useful with the copy command)") + f.StringVar(&initOptions.RepositoryVersion, "repository-version", "stable", "repository format version to use, allowed values are a format version, 'latest' and 'stable'") } func runInit(opts InitOptions, gopts GlobalOptions, args []string) error { + var version uint + if opts.RepositoryVersion == "latest" || opts.RepositoryVersion == "" { + version = restic.MaxRepoVersion + } else if opts.RepositoryVersion == "stable" { + version = restic.StableRepoVersion + } else { + v, err := strconv.ParseUint(opts.RepositoryVersion, 10, 32) + if err != nil { + return errors.Fatal("invalid repository version") + } + version = uint(v) + } + if version < restic.MinRepoVersion || version > restic.MaxRepoVersion { + return errors.Fatalf("only repository versions between %v and %v are allowed", restic.MinRepoVersion, restic.MaxRepoVersion) + } + chunkerPolynomial, err := maybeReadChunkerPolynomial(opts, gopts) if err != nil { return err @@ -67,7 +88,7 @@ func runInit(opts InitOptions, gopts GlobalOptions, args []string) error { s := repository.New(be) - err = s.Init(gopts.ctx, gopts.password, chunkerPolynomial) + err = s.Init(gopts.ctx, version, gopts.password, chunkerPolynomial) if err != nil { return errors.Fatalf("create key in repository at %s failed: %v\n", location.StripPassword(gopts.Repo), err) } diff --git a/internal/repository/repository.go b/internal/repository/repository.go index eb2d0a109..b00dfa39a 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -661,7 +661,15 @@ func (r *Repository) SearchKey(ctx context.Context, password string, maxKeys int // Init creates a new master key with the supplied password, initializes and // saves the repository config. -func (r *Repository) Init(ctx context.Context, password string, chunkerPolynomial *chunker.Pol) error { +func (r *Repository) Init(ctx context.Context, version uint, password string, chunkerPolynomial *chunker.Pol) error { + if version > restic.MaxRepoVersion { + return fmt.Errorf("repo version %v too high", version) + } + + if version < restic.MinRepoVersion { + return fmt.Errorf("repo version %v too low", version) + } + has, err := r.be.Test(ctx, restic.Handle{Type: restic.ConfigFile}) if err != nil { return err @@ -670,7 +678,7 @@ func (r *Repository) Init(ctx context.Context, password string, chunkerPolynomia return errors.New("repository master key and config already initialized") } - cfg, err := restic.CreateConfig() + cfg, err := restic.CreateConfig(version) if err != nil { return err } diff --git a/internal/restic/config.go b/internal/restic/config.go index ded98ac1b..3a6bab746 100644 --- a/internal/restic/config.go +++ b/internal/restic/config.go @@ -18,9 +18,12 @@ type Config struct { ChunkerPolynomial chunker.Pol `json:"chunker_polynomial"` } -// RepoVersion is the version that is written to the config when a repository +const MinRepoVersion = 1 +const MaxRepoVersion = 2 + +// StableRepoVersion is the version that is written to the config when a repository // is newly created with Init(). -const RepoVersion = 1 +const StableRepoVersion = 1 // JSONUnpackedLoader loads unpacked JSON. type JSONUnpackedLoader interface { @@ -29,7 +32,7 @@ type JSONUnpackedLoader interface { // CreateConfig creates a config file with a randomly selected polynomial and // ID. -func CreateConfig() (Config, error) { +func CreateConfig(version uint) (Config, error) { var ( err error cfg Config @@ -41,7 +44,7 @@ func CreateConfig() (Config, error) { } cfg.ID = NewRandomID().String() - cfg.Version = RepoVersion + cfg.Version = version debug.Log("New config: %#v", cfg) return cfg, nil @@ -52,7 +55,7 @@ func TestCreateConfig(t testing.TB, pol chunker.Pol) (cfg Config) { cfg.ChunkerPolynomial = pol cfg.ID = NewRandomID().String() - cfg.Version = RepoVersion + cfg.Version = StableRepoVersion return cfg } @@ -77,7 +80,7 @@ func LoadConfig(ctx context.Context, r JSONUnpackedLoader) (Config, error) { return Config{}, err } - if cfg.Version != RepoVersion { + if cfg.Version < MinRepoVersion || cfg.Version > MaxRepoVersion { return Config{}, errors.Errorf("unsupported repository version %v", cfg.Version) } diff --git a/internal/restic/config_test.go b/internal/restic/config_test.go index 506381965..fd8e4aeed 100644 --- a/internal/restic/config_test.go +++ b/internal/restic/config_test.go @@ -32,7 +32,7 @@ func TestConfig(t *testing.T) { return restic.ID{}, nil } - cfg1, err := restic.CreateConfig() + cfg1, err := restic.CreateConfig(restic.MaxRepoVersion) rtest.OK(t, err) _, err = saver(save).SaveJSONUnpacked(restic.ConfigFile, cfg1)