package cli import ( "fmt" "os" "path/filepath" "github.com/spf13/cobra" ) const defaultConfigTemplate = `# vaultik configuration # Documentation: https://git.eeqj.de/sneak/vaultik # ─── REQUIRED ──────────────────────────────────────────────────────────────── # Age recipient public keys for encryption. # Backups are encrypted to ALL listed recipients. Any one of the corresponding # private keys can decrypt. Generate a keypair with: # age-keygen -o key.txt && grep 'public key' key.txt age_recipients: - age1REPLACE_WITH_YOUR_PUBLIC_KEY # Named snapshots. Each snapshot backs up one or more paths and can have its # own exclude patterns in addition to the global excludes below. # # Exclude pattern semantics: # - Patterns starting with / are anchored to the snapshot path root # (e.g. "/Library/Caches" matches only ~/Library/Caches in a ~ snapshot) # - Patterns without a leading / match anywhere in the tree # (e.g. ".cache" matches any directory named .cache at any depth) # - Globs are supported: *, **, ? snapshots: home: paths: - "~" exclude: # Trash, temp, and filesystem metadata - "/.Trash" - "/.Trashes" - "/.fseventsd" - "/.Spotlight-V100" - "/.TemporaryItems" - "/tmp" - "/.rnd" - ".DS_Store" # Caches and package manager state (rebuildable) - ".cache" - ".bundle" - "/.cpan/build" - "/.cpan/sources" - "/.gradle/caches" - "/.docker" - "/.dropbox" - "/.minikube/cache" - "/.local/share/containers/podman/machine" - "/.persepolis" - "/Library/Caches" - "/Library/Logs" - "/Library/Cookies" - "/Library/Metadata" - "/Library/Suggestions" - "/Library/PubSub" - "/Library/Homebrew" - "/Library/Developer" - "/Library/Parallels" - "/Library/Google/GoogleSoftwareUpdate" - "/Library/Preferences/Macromedia/Flash Player" - "/Library/Preferences/SDMHelpData" - "/Library/VoiceTrigger/SAT" # Cloud-synced or restorable-from-server data - "/Library/Mail" - "/Library/Mail Downloads" - "/Library/Safari" - "/Library/Application Support/Evernote" - "/Library/Application Support/MobileSync" - "/Library/Application Support/SyncServices" - "/Library/Application Support/protonmail/bridge/cache" - "/Library/Application Support/Syncthing/index-*" - "/Library/Syncthing/folders" - "/Documents/Dropbox/.dropbox.cache" # Large rebuildable app data (games, media caches, device backups) - "/Applications/Fortnite" - "/Documents/Steam Content" - "/Library/Application Support/Ableton" - "/Library/Application Support/CrossOver Games" - "/Library/Application Support/SecondLife/cache" - "/Library/Application Support/Steam/SteamApps" - "/Library/Containers/com.docker.docker" - "/Library/Group Containers/group.com.apple.secure-control-center-preferences" - "/Library/iTunes/iPad Software Updates" - "/Library/iTunes/iPhone Software Updates" - "/Movies/CacheClip" - "/Movies/ProxyMedia" - "/Music/iTunes/Album Artwork" - "/Pictures/iPod Photo Cache" # 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 # they are re-downloadable. apps: paths: - /Applications exclude: - ".DS_Store" - "/Safari.app" - "/GarageBand.app" - "/iMovie.app" - "/Keynote.app" - "/Numbers.app" - "/Pages.app" - "/Xcode.app" - "/Spotify.app" - "/Steam.app" - "/VirtualBox.app" - "/Utilities/Adobe Installers" # Storage backend (pick ONE of the three forms below). # # S3-compatible: # storage_url: "s3://mybucket/backups?endpoint=s3.example.com®ion=us-east-1" # (also set s3.access_key_id and s3.secret_access_key below) # # Local filesystem: # storage_url: "file:///mnt/backups/vaultik" # # Rclone (requires rclone configured separately): # storage_url: "rclone://myremote/path/to/backups" storage_url: "" # ─── S3 CREDENTIALS (required for s3:// storage_url) ──────────────────────── # s3: # access_key_id: YOUR_ACCESS_KEY # secret_access_key: YOUR_SECRET_KEY # # region: us-east-1 # Default: us-east-1 # # use_ssl: true # Default: true # # part_size: 5MB # Multipart upload part size. Default: 5MB # ─── OPTIONAL ──────────────────────────────────────────────────────────────── # Global exclude patterns applied to ALL snapshots. # Snapshot-specific excludes are additive. # exclude: # - "*.log" # - "*.tmp" # - ".git" # - "node_modules" # Average chunk size for content-defined chunking (FastCDC). # Smaller = better deduplication but more metadata overhead. # Accepts: 1MB, 10M, 64KB, etc. # Default: 10MB # chunk_size: 10MB # Maximum blob size before splitting into a new blob. # Accepts: 1GB, 10G, 500MB, etc. # Default: 10GB # blob_size_limit: 10GB # Zstd compression level (1-19). Higher = better ratio but slower. # Default: 3 # compression_level: 3 # Hostname used in snapshot IDs. Default: system hostname. # hostname: myserver # Path to the local SQLite index database. # Default: ~/.local/share/berlin.sneak.app.vaultik/index.sqlite # index_path: /path/to/index.sqlite ` // NewInitCommand creates the init command that writes a default config file. func NewInitCommand() *cobra.Command { cmd := &cobra.Command{ Use: "init", Short: "Write a default config file", Long: `Creates a default configuration file with commented explanations for every setting. If a config file already exists at the target path, the command refuses to overwrite it. The config is written to the path from --config, $VAULTIK_CONFIG, or the platform default config directory (e.g. ~/Library/Application Support/ on macOS, ~/.config/ on Linux, /etc/vaultik/ as root).`, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { path := configPathForInit() if _, err := os.Stat(path); err == nil { return fmt.Errorf("config file already exists: %s", path) } dir := filepath.Dir(path) if err := os.MkdirAll(dir, 0o755); err != nil { return fmt.Errorf("creating config directory %s: %w", dir, err) } if err := os.WriteFile(path, []byte(defaultConfigTemplate), 0o600); err != nil { return fmt.Errorf("writing config file: %w", err) } fmt.Printf("Config written to %s\n", path) fmt.Println("Edit it to set your age_recipients, snapshots, and storage_url.") return nil }, } return cmd } // configPathForInit returns the config path to write, checking --config flag, // VAULTIK_CONFIG env, and the platform default. func configPathForInit() string { if rootFlags.ConfigPath != "" { return rootFlags.ConfigPath } if envPath := os.Getenv("VAULTIK_CONFIG"); envPath != "" { return envPath } return DefaultConfigPath() }