M8: add command to append a single device to config

New 'arca add <device>' subcommand detects a LUKS device via udisks2 and
appends it to the config with passphrase as default method. Supports
--alias/-a to override the generated name. Skips if UUID already
configured. Adds Config.Save() and Config.HasUUID() to config package.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 08:38:32 -07:00
parent 0c19f94292
commit feb22db039
3 changed files with 113 additions and 11 deletions

87
cmd/add.go Normal file
View File

@@ -0,0 +1,87 @@
package cmd
import (
"fmt"
"strings"
"git.wntrmute.dev/kyle/arca/internal/config"
"git.wntrmute.dev/kyle/arca/internal/udisks"
"github.com/spf13/cobra"
)
var addAlias string
var addCmd = &cobra.Command{
Use: "add <device>",
Short: "Add a device to the config",
Long: "Detects a LUKS device via udisks2 and adds it to the config file with a default passphrase method.",
Args: cobra.ExactArgs(1),
RunE: runAdd,
}
func init() {
addCmd.Flags().StringVarP(&addAlias, "alias", "a", "", "alias name (default: first 8 chars of UUID)")
rootCmd.AddCommand(addCmd)
}
func runAdd(cmd *cobra.Command, args []string) error {
target := args[0]
client, err := udisks.NewClient()
if err != nil {
return fmt.Errorf("connecting to udisks2: %w", err)
}
defer client.Close()
// Find the device to get its UUID.
dev, err := client.FindDevice("", target)
if err != nil {
return err
}
if !dev.HasEncrypted {
return fmt.Errorf("%s is not a LUKS-encrypted device", target)
}
if dev.UUID == "" {
return fmt.Errorf("%s has no UUID", target)
}
cfg := config.Load()
// Check if already configured.
if existing := cfg.AliasFor(dev.UUID); existing != "" {
fmt.Printf("Device %s (UUID %s) already configured as %q\n", dev.DevicePath, dev.UUID, existing)
return nil
}
alias := addAlias
if alias == "" {
alias = aliasFromUUID(dev.UUID)
}
// Check for alias collision.
if _, exists := cfg.Devices[alias]; exists {
return fmt.Errorf("alias %q already in use — choose a different name with --alias", alias)
}
cfg.Devices[alias] = config.DeviceConfig{
UUID: dev.UUID,
Methods: []string{"passphrase"},
}
if err := cfg.Save(); err != nil {
return fmt.Errorf("saving config: %w", err)
}
fmt.Printf("Added %s (UUID %s) as %q\n", dev.DevicePath, dev.UUID, alias)
return nil
}
func aliasFromUUID(uuid string) string {
clean := strings.ReplaceAll(uuid, "-", "")
if len(clean) > 8 {
clean = clean[:8]
}
return clean
}

View File

@@ -4,8 +4,6 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"git.wntrmute.dev/kyle/arca/internal/config"
"git.wntrmute.dev/kyle/arca/internal/udisks"
@@ -102,12 +100,3 @@ func isRootBacking(path dbus.ObjectPath, rootDevices []dbus.ObjectPath) bool {
return false
}
func aliasFromUUID(uuid string) string {
// Use first 8 chars of UUID as a stable alias.
// "b8b2f8e3-4cde-4aca-a96e-df9274019f9f" -> "b8b2f8e3"
clean := strings.ReplaceAll(uuid, "-", "")
if len(clean) > 8 {
clean = clean[:8]
}
return clean
}