mirror of
https://github.com/kopia/kopia.git
synced 2026-03-27 18:42:26 -04:00
* content: fixed repo upgrade version Previously upgrade would enable epoch manager and index v2 but would not set the version of the format itself. Everything worked fine but it would not protect from old kopia opening the repository. * ci: added compatibility test that uses real 0.8 and current binaries
176 lines
4.8 KiB
Go
176 lines
4.8 KiB
Go
package repo
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/kopia/kopia/internal/gather"
|
|
"github.com/kopia/kopia/repo/blob"
|
|
"github.com/kopia/kopia/repo/content"
|
|
"github.com/kopia/kopia/repo/encryption"
|
|
"github.com/kopia/kopia/repo/hashing"
|
|
"github.com/kopia/kopia/repo/object"
|
|
"github.com/kopia/kopia/repo/splitter"
|
|
)
|
|
|
|
// BuildInfo is the build information of Kopia.
|
|
var (
|
|
BuildInfo = "unknown"
|
|
BuildVersion = "v0-unofficial"
|
|
BuildGitHubRepo = ""
|
|
)
|
|
|
|
const (
|
|
hmacSecretLength = 32
|
|
masterKeyLength = 32
|
|
uniqueIDLength = 32
|
|
)
|
|
|
|
// NewRepositoryOptions specifies options that apply to newly created repositories.
|
|
// All fields are optional, when not provided, reasonable defaults will be used.
|
|
type NewRepositoryOptions struct {
|
|
UniqueID []byte `json:"uniqueID"` // force the use of particular unique ID
|
|
BlockFormat content.FormattingOptions `json:"blockFormat"`
|
|
DisableHMAC bool `json:"disableHMAC"`
|
|
ObjectFormat object.Format `json:"objectFormat"` // object format
|
|
}
|
|
|
|
// ErrAlreadyInitialized indicates that repository has already been initialized.
|
|
var ErrAlreadyInitialized = errors.Errorf("repository already initialized")
|
|
|
|
// Initialize creates initial repository data structures in the specified storage with given credentials.
|
|
func Initialize(ctx context.Context, st blob.Storage, opt *NewRepositoryOptions, password string) error {
|
|
if opt == nil {
|
|
opt = &NewRepositoryOptions{}
|
|
}
|
|
|
|
// get the blob - expect ErrNotFound
|
|
var tmp gather.WriteBuffer
|
|
defer tmp.Close()
|
|
|
|
err := st.GetBlob(ctx, FormatBlobID, 0, -1, &tmp)
|
|
if err == nil {
|
|
return ErrAlreadyInitialized
|
|
}
|
|
|
|
if !errors.Is(err, blob.ErrBlobNotFound) {
|
|
return errors.Wrap(err, "unexpected error when checking for format blob")
|
|
}
|
|
|
|
format := formatBlobFromOptions(opt)
|
|
|
|
formatEncryptionKey, err := format.deriveFormatEncryptionKeyFromPassword(password)
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to derive format encryption key")
|
|
}
|
|
|
|
f, err := repositoryObjectFormatFromOptions(opt)
|
|
if err != nil {
|
|
return errors.Wrap(err, "invalid parameters")
|
|
}
|
|
|
|
if err := f.MutableParameters.Validate(); err != nil {
|
|
return errors.Wrap(err, "invalid parameters")
|
|
}
|
|
|
|
if err := encryptFormatBytes(format, f, formatEncryptionKey, format.UniqueID); err != nil {
|
|
return errors.Wrap(err, "unable to encrypt format bytes")
|
|
}
|
|
|
|
if err := writeFormatBlob(ctx, st, format); err != nil {
|
|
return errors.Wrap(err, "unable to write format blob")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func formatBlobFromOptions(opt *NewRepositoryOptions) *formatBlob {
|
|
return &formatBlob{
|
|
Tool: "https://github.com/kopia/kopia",
|
|
BuildInfo: BuildInfo,
|
|
BuildVersion: BuildVersion,
|
|
KeyDerivationAlgorithm: defaultKeyDerivationAlgorithm,
|
|
UniqueID: applyDefaultRandomBytes(opt.UniqueID, uniqueIDLength),
|
|
Version: "1",
|
|
EncryptionAlgorithm: defaultFormatEncryption,
|
|
}
|
|
}
|
|
|
|
func repositoryObjectFormatFromOptions(opt *NewRepositoryOptions) (*repositoryObjectFormat, error) {
|
|
fv := opt.BlockFormat.Version
|
|
if fv == 0 {
|
|
switch os.Getenv("KOPIA_REPOSITORY_FORMAT_VERSION") {
|
|
case "1":
|
|
fv = content.FormatVersion1
|
|
case "2":
|
|
fv = content.FormatVersion2
|
|
default:
|
|
fv = content.FormatVersion2
|
|
}
|
|
}
|
|
|
|
f := &repositoryObjectFormat{
|
|
FormattingOptions: content.FormattingOptions{
|
|
Hash: applyDefaultString(opt.BlockFormat.Hash, hashing.DefaultAlgorithm),
|
|
Encryption: applyDefaultString(opt.BlockFormat.Encryption, encryption.DefaultAlgorithm),
|
|
HMACSecret: applyDefaultRandomBytes(opt.BlockFormat.HMACSecret, hmacSecretLength),
|
|
MasterKey: applyDefaultRandomBytes(opt.BlockFormat.MasterKey, masterKeyLength),
|
|
MutableParameters: content.MutableParameters{
|
|
Version: fv,
|
|
MaxPackSize: applyDefaultInt(opt.BlockFormat.MaxPackSize, 20<<20), //nolint:gomnd
|
|
IndexVersion: applyDefaultInt(opt.BlockFormat.IndexVersion, content.DefaultIndexVersion),
|
|
EpochParameters: opt.BlockFormat.EpochParameters,
|
|
},
|
|
EnablePasswordChange: opt.BlockFormat.EnablePasswordChange,
|
|
},
|
|
Format: object.Format{
|
|
Splitter: applyDefaultString(opt.ObjectFormat.Splitter, splitter.DefaultAlgorithm),
|
|
},
|
|
}
|
|
|
|
if opt.DisableHMAC {
|
|
f.HMACSecret = nil
|
|
}
|
|
|
|
if err := f.FormattingOptions.ResolveFormatVersion(); err != nil {
|
|
return nil, errors.Wrap(err, "error resolving format version")
|
|
}
|
|
|
|
return f, nil
|
|
}
|
|
|
|
func randomBytes(n int) []byte {
|
|
b := make([]byte, n)
|
|
io.ReadFull(rand.Reader, b) //nolint:errcheck
|
|
|
|
return b
|
|
}
|
|
|
|
func applyDefaultInt(v, def int) int {
|
|
if v == 0 {
|
|
return def
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
func applyDefaultString(v, def string) string {
|
|
if v == "" {
|
|
return def
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
func applyDefaultRandomBytes(b []byte, n int) []byte {
|
|
if b == nil {
|
|
return randomBytes(n)
|
|
}
|
|
|
|
return b
|
|
}
|