1 Commits
Author SHA1 Message Date
sneak bbe39fa073 Print remote info and verify status lines through internal/ui (closes #410)
check / check (push) Successful in 18m52s
The status and progress lines of `remote info` and `snapshot verify`
(shallow and deep) now use the `internal/ui` Begin, Complete and
Progress methods, so they carry its marker and, on a terminal, color.
The reports themselves stay plain on stdout.

Model: opus-5-5
2026-11-10 19:59:37 +00:00
4 changed files with 13 additions and 75 deletions

No files matched your search

+2 -2
View File
@@ -32,8 +32,8 @@ mkdir /Volumes/usbstick/mybackup
# (System Settings → Privacy & Security → Full Disk Access), otherwise
# the backup will abort with a permission error on protected directories
# run your first backup (the default config backs up ~, and /Applications
# on macOS only, with sensible excludes)
# run your first backup (the default config backs up ~ and /Applications
# with sensible excludes)
vaultik snapshot create
# see what you have
-5
View File
@@ -30,11 +30,6 @@ owner's; a `v*` tag runs the release build in
([issue #410](https://git.eeqj.de/sneak/vaultik/issues/410)). Their
reports stay plain.
- 2026-11-10: Made `config init` write the `apps` snapshot, which backs
up `/Applications`, only on macOS
([issue #408](https://git.eeqj.de/sneak/vaultik/issues/408)). On
Linux the first `snapshot create` stopped on the missing directory.
- 2026-10-15: Stopped `snapshot create --prune` pruning after a backup
in which a volume mounted below a configured path in an earlier
snapshot of the same name was not mounted
+4 -27
View File
@@ -8,7 +8,6 @@ import (
"os/exec"
"path/filepath"
"reflect"
"runtime"
"slices"
"strconv"
"strings"
@@ -31,7 +30,7 @@ const configSetArgs = 2
// parent config dirs (e.g. ~/.config) are conventionally traversable.
const configDirMode = 0o755
// configYAMLIndent matches the 2-space indentation of the default config,
// configYAMLIndent matches the 2-space indentation of defaultConfigTemplate,
// so `config set` writes the file back with the same indentation rather than
// yaml.Marshal's 4-space default.
const configYAMLIndent = 2
@@ -51,22 +50,7 @@ var (
"so the file was not changed")
)
// macOS is the value of runtime.GOOS on macOS.
const macOS = "darwin"
// defaultConfigText returns the file `config init` writes on the operating
// system goos, named as runtime.GOOS names it. Only macOS gets the apps
// snapshot, since /Applications exists nowhere else.
func defaultConfigText(goos string) string {
if goos == macOS {
return defaultConfigHead + appsSnapshotConfig + defaultConfigTail
}
return defaultConfigHead + defaultConfigTail
}
// defaultConfigHead is the default config up to the end of the home snapshot.
const defaultConfigHead = `# vaultik configuration
const defaultConfigTemplate = `# vaultik configuration
# Documentation: https://sneak.berlin/go/vaultik
# ─── REQUIRED ────────────────────────────────────────────────────────────────
@@ -184,10 +168,7 @@ snapshots:
- "/Movies/ProxyMedia"
- "/Music/iTunes/Album Artwork"
- "/Pictures/iPod Photo Cache"
`
// appsSnapshotConfig is the apps snapshot of the default config.
const appsSnapshotConfig = `
# Third-party applications. OS-provided apps live in /System/Applications
# on modern macOS and are never in /Applications, but Apple-installed
# App Store apps (Safari, GarageBand, iWork, iMovie) are excluded since
@@ -208,10 +189,7 @@ const appsSnapshotConfig = `
- "/Steam.app"
- "/VirtualBox.app"
- "/Utilities/Adobe Installers"
`
// defaultConfigTail is the default config after the snapshots.
const defaultConfigTail = `
# Storage backend (pick ONE of the three forms below).
#
# S3-compatible:
@@ -333,8 +311,7 @@ on macOS, ~/.config/ on Linux, /etc/vaultik/ as root).`,
return fmt.Errorf("creating config directory %s: %w", dir, err)
}
err = os.WriteFile(path, []byte(defaultConfigText(runtime.GOOS)),
configFileMode)
err = os.WriteFile(path, []byte(defaultConfigTemplate), configFileMode)
if err != nil {
return fmt.Errorf("writing config file: %w", err)
}
@@ -583,7 +560,7 @@ func yamlPathOnly(root *yaml.Node, keys []string) (*yaml.Node, error) {
}
// marshalConfigYAML renders a config document tree with 2-space indentation,
// matching the default config. yaml.Marshal defaults to 4 spaces, which
// matching defaultConfigTemplate. yaml.Marshal defaults to 4 spaces, which
// would reindent the whole file on the first `config set` despite the promise
// to preserve formatting.
func marshalConfigYAML(root *yaml.Node) ([]byte, error) {
+7 -41
View File
@@ -13,15 +13,14 @@ import (
"sneak.berlin/go/vaultik/internal/ui"
)
// TestDefaultConfigParsesOnMacOS ensures the config `config init` writes on
// macOS is valid YAML that unmarshals into the Config struct with the
// expected snapshots.
func TestDefaultConfigParsesOnMacOS(t *testing.T) {
// TestDefaultConfigTemplateParses ensures the init template is valid YAML
// that unmarshals into the Config struct with the expected snapshots.
func TestDefaultConfigTemplateParses(t *testing.T) {
t.Parallel()
var cfg config.Config
err := yaml.Unmarshal([]byte(defaultConfigText("darwin")), &cfg)
err := yaml.Unmarshal([]byte(defaultConfigTemplate), &cfg)
if err != nil {
t.Fatalf("default config template is not valid YAML: %v", err)
}
@@ -59,39 +58,6 @@ func TestDefaultConfigParsesOnMacOS(t *testing.T) {
}
}
// TestDefaultConfigOnLinux checks that the config `config init` writes on
// Linux loads with its home snapshot and without the apps snapshot, whose
// /Applications exists only on macOS.
func TestDefaultConfigOnLinux(t *testing.T) {
t.Parallel()
path := filepath.Join(t.TempDir(), "config.yml")
err := os.WriteFile(path, []byte(defaultConfigText("linux")), configFileMode)
if err != nil {
t.Fatalf("write config: %v", err)
}
err = writeConfigSet(ui.NewWithColor(&bytes.Buffer{}, false),
path, "storage_url", "file:///mnt/backups")
if err != nil {
t.Fatalf("config set storage_url: %v", err)
}
cfg, err := config.Load(path)
if err != nil {
t.Fatalf("config.Load: %v", err)
}
if _, ok := cfg.Snapshots["home"]; !ok {
t.Error("expected 'home' snapshot in the Linux default config")
}
if _, ok := cfg.Snapshots["apps"]; ok {
t.Error("the Linux default config should have no 'apps' snapshot")
}
}
// testRecipient is a valid age public key.
const testRecipient = "age1278m9q7dp3chsh2dcy82qk27v047zywyvtxwnj4cvt0z65jw6a7q5dqhfj"
@@ -105,7 +71,7 @@ func TestConfigSetRecipientOnFreshConfig(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.yml")
err := os.WriteFile(path, []byte(defaultConfigText(macOS)), configFileMode)
err := os.WriteFile(path, []byte(defaultConfigTemplate), configFileMode)
if err != nil {
t.Fatalf("write config: %v", err)
}
@@ -254,7 +220,7 @@ func TestConfigSetKeepsFileThatWouldNotLoad(t *testing.T) {
{levelReference, "blob_size_limit", "1MB",
"blob_size_limit must be at least"},
// The type check on a config that does not load yet.
{defaultConfigText(macOS), "snapshots.apps.paths", "/srv",
{defaultConfigTemplate, "snapshots.apps.paths", "/srv",
"into []string"},
// The type check on a config that cannot be read here.
{configWithUnsetReference, "snapshots.home.paths", "/x",
@@ -414,7 +380,7 @@ func loadAfterConfigSet(t *testing.T, key, value string) *config.Config {
path := filepath.Join(t.TempDir(), "config.yml")
err := os.WriteFile(path, []byte(defaultConfigText(macOS)), configFileMode)
err := os.WriteFile(path, []byte(defaultConfigTemplate), configFileMode)
if err != nil {
t.Fatalf("write config: %v", err)
}