Skip to content

Commit

Permalink
Add excludeFromBackup on macOS
Browse files Browse the repository at this point in the history
```yml
# Exclude from backup: "all", "disks" or "none"
# Specify which file in the instance's configuration to exclude from backup.
# Only available on macOS.
# 🟢 Builtin default: "disks"
excludeFromBackup: null
```

Sets the `NSURLIsExcludedFromBackupKey` attribute to:
- "all": the instance directory
- "disks": `basedisk` and `diffdisk` files
- "none": none

Signed-off-by: Norio Nomura <norio.nomura@gmail.com>

Update pkg/start/start.go

Fix typo

Co-authored-by: Jan Dubois <jan@jandubois.com>

Update pkg/limayaml/validate.go

Fix typo

Co-authored-by: Jan Dubois <jan@jandubois.com>
  • Loading branch information
norio-nomura and jandubois committed Jan 23, 2024
1 parent 4e1cfac commit db1e3fb
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 5 deletions.
6 changes: 6 additions & 0 deletions examples/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,12 @@ rosetta:
# 🟢 Builtin default: false
binfmt: null

# Exclude from backup: "all", "disks" or "none"
# Specify which file in the instance's configuration to exclude from backup.
# Only available on macOS.
# 🟢 Builtin default: "disks"
excludeFromBackup: null

# Specify the timezone name (as used by the zoneinfo database). Specify the empty string
# to not set a timezone in the instance.
# 🟢 Builtin default: use name from /etc/timezone or deduce from symlink target of /etc/localtime
Expand Down
14 changes: 14 additions & 0 deletions pkg/limayaml/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,20 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
y.Rosetta.BinFmt = ptr.Of(false)
}

if runtime.GOOS == "darwin" {
if y.ExcludeFromBackup == nil {
y.ExcludeFromBackup = d.ExcludeFromBackup
}
if o.ExcludeFromBackup != nil {
y.ExcludeFromBackup = o.ExcludeFromBackup
}
if y.ExcludeFromBackup == nil {
y.ExcludeFromBackup = ptr.Of(ExcludeFromBackupDisks)
}
} else {
y.ExcludeFromBackup = ptr.Of(ExcludeFromBackupNone)
}

if y.Plain == nil {
y.Plain = d.Plain
}
Expand Down
16 changes: 16 additions & 0 deletions pkg/limayaml/defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ func TestFillDefault(t *testing.T) {
}
}
}
if runtime.GOOS == "darwin" {
builtin.ExcludeFromBackup = ptr.Of(ExcludeFromBackupDisks)
} else {
builtin.ExcludeFromBackup = ptr.Of(ExcludeFromBackupNone)
}

defaultPortForward := PortForward{
GuestIP: api.IPv4loopback1,
Expand Down Expand Up @@ -444,6 +449,11 @@ func TestFillDefault(t *testing.T) {
}
}
expect.Plain = ptr.Of(false)
if runtime.GOOS == "darwin" {
expect.ExcludeFromBackup = ptr.Of(ExcludeFromBackupDisks)
} else {
expect.ExcludeFromBackup = ptr.Of(ExcludeFromBackupNone)
}

y = LimaYAML{}
FillDefault(&y, &d, &LimaYAML{}, filePath)
Expand Down Expand Up @@ -608,6 +618,7 @@ func TestFillDefault(t *testing.T) {
Enabled: ptr.Of(false),
BinFmt: ptr.Of(false),
},
ExcludeFromBackup: ptr.Of(ExcludeFromBackupAll),
}

y = filledDefaults
Expand Down Expand Up @@ -661,6 +672,11 @@ func TestFillDefault(t *testing.T) {
BinFmt: ptr.Of(false),
}
expect.Plain = ptr.Of(false)
if runtime.GOOS == "darwin" {
expect.ExcludeFromBackup = ptr.Of(ExcludeFromBackupAll)
} else {
expect.ExcludeFromBackup = ptr.Of(ExcludeFromBackupNone)
}

FillDefault(&y, &d, &o, filePath)
assert.DeepEqual(t, &y, &expect, opts...)
Expand Down
19 changes: 14 additions & 5 deletions pkg/limayaml/limayaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ type LimaYAML struct {
DNS []net.IP `yaml:"dns,omitempty" json:"dns,omitempty"`
HostResolver HostResolver `yaml:"hostResolver,omitempty" json:"hostResolver,omitempty"`
// `useHostResolver` was deprecated in Lima v0.8.1, removed in Lima v0.14.0. Use `hostResolver.enabled` instead.
PropagateProxyEnv *bool `yaml:"propagateProxyEnv,omitempty" json:"propagateProxyEnv,omitempty"`
CACertificates CACertificates `yaml:"caCerts,omitempty" json:"caCerts,omitempty"`
Rosetta Rosetta `yaml:"rosetta,omitempty" json:"rosetta,omitempty"`
Plain *bool `yaml:"plain,omitempty" json:"plain,omitempty"`
TimeZone *string `yaml:"timezone,omitempty" json:"timezone,omitempty"`
PropagateProxyEnv *bool `yaml:"propagateProxyEnv,omitempty" json:"propagateProxyEnv,omitempty"`
CACertificates CACertificates `yaml:"caCerts,omitempty" json:"caCerts,omitempty"`
Rosetta Rosetta `yaml:"rosetta,omitempty" json:"rosetta,omitempty"`
ExcludeFromBackup *ExcludeFromBackup `yaml:"excludeFromBackup,omitempty" json:"excludeFromBackup,omitempty"`
Plain *bool `yaml:"plain,omitempty" json:"plain,omitempty"`
TimeZone *string `yaml:"timezone,omitempty" json:"timezone,omitempty"`
}

type (
Expand Down Expand Up @@ -260,6 +261,14 @@ type CACertificates struct {
Certs []string `yaml:"certs,omitempty" json:"certs,omitempty"`
}

type ExcludeFromBackup = string

const (
ExcludeFromBackupAll ExcludeFromBackup = "all"
ExcludeFromBackupDisks ExcludeFromBackup = "disks"
ExcludeFromBackupNone ExcludeFromBackup = "none"
)

// DEPRECATED types below

// Types have been renamed to turn all references to the old names into compiler errors,
Expand Down
10 changes: 10 additions & 0 deletions pkg/limayaml/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,16 @@ func Validate(y LimaYAML, warn bool) error {
if err := validateNetwork(y, warn); err != nil {
return err
}

switch *y.ExcludeFromBackup {
case ExcludeFromBackupAll, ExcludeFromBackupDisks, ExcludeFromBackupNone:
default:
return fmt.Errorf("field `excludeFromBackup` must be %q, %q, or %q; got %q", ExcludeFromBackupAll, ExcludeFromBackupDisks, ExcludeFromBackupNone, *y.ExcludeFromBackup)
}
if warn && runtime.GOOS != "darwin" && *y.ExcludeFromBackup != "none" {
logrus.Warn("field `excludeFromBackup` is only supported on macOS")
}

if warn {
warnExperimental(y)
}
Expand Down
38 changes: 38 additions & 0 deletions pkg/osutil/exclude_from_backup_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package osutil

/*
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c
#cgo darwin LDFLAGS: -lobjc -framework Foundation
#import <Foundation/Foundation.h>
void setExcludeFromBackup(const char *path, int exclude) {
@autoreleasepool {
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:path]];
NSError *error = nil;
if (![url setResourceValue: exclude == 1 ? @YES : @NO forKey:NSURLIsExcludedFromBackupKey error:&error]) {
NSLog(@"setResourceValue failed: %@", error);
}
}
}
*/
import "C" //nolint:gocritic
/*
The reason for separating the import statement is that CGO only recognizes the standalone import of "C".
However, the linter tool `gocritic` incorrectly identifies this separation as a duplicate import and raises
a `dupImport` warning. To avoid this warning, the `gocritic` check has been disabled.
Ref: https://github.com/go-critic/go-critic/issues/845
*/
import (
"unsafe" //nolint:gocritic
)

// SetExcludeFromBackup sets the `NSURLIsExcludedFromBackupKey` attribute of the specified file or directory.
func SetExcludeFromBackup(path string, exclude bool) {
cs := C.CString(path)
defer C.free(unsafe.Pointer(cs))
if exclude {
C.setExcludeFromBackup(cs, C.int(1))
} else {
C.setExcludeFromBackup(cs, C.int(0))
}
}
5 changes: 5 additions & 0 deletions pkg/osutil/exclude_from_backup_others.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//go:build !darwin

package osutil

func SetExcludeFromBackup(path string, exclude bool) {}
21 changes: 21 additions & 0 deletions pkg/start/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,26 @@ func ensureNerdctlArchiveCache(y *limayaml.LimaYAML, created bool) (string, erro
return "", fileutils.Errors(errs)
}

// applyExcludeFromBackup sets the NSURLIsExcludedFromBackupKey attribute of the instance directory.
func applyExcludeFromBackup(instDir string, excludeFromBackup limayaml.ExcludeFromBackup) {
baseDisk := filepath.Join(instDir, filenames.BaseDisk)
diffDisk := filepath.Join(instDir, filenames.DiffDisk)
var targets map[string]bool
switch excludeFromBackup {
case limayaml.ExcludeFromBackupAll:
// Exclude the instance directory from Time Machine backups
targets = map[string]bool{instDir: true, baseDisk: false, diffDisk: false}
case limayaml.ExcludeFromBackupDisks:
// Exclude disk files from Time Machine backup.
targets = map[string]bool{instDir: false, baseDisk: true, diffDisk: true}
default: // limayaml.ExcludeFromBackupNone
targets = map[string]bool{instDir: false, baseDisk: false, diffDisk: false}
}
for target, exclude := range targets {
osutil.SetExcludeFromBackup(target, exclude)
}
}

type Prepared struct {
Driver driver.Driver
NerdctlArchiveCache string
Expand Down Expand Up @@ -108,6 +128,7 @@ func Prepare(ctx context.Context, inst *store.Instance) (*Prepared, error) {
if err != nil {
return nil, err
}
applyExcludeFromBackup(inst.Dir, *y.ExcludeFromBackup)

return &Prepared{
Driver: limaDriver,
Expand Down

0 comments on commit db1e3fb

Please sign in to comment.