Files
arca/internal/config/config.go
Kyle Isom feb22db039 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>
2026-03-24 08:38:32 -07:00

127 lines
2.9 KiB
Go

package config
import (
"fmt"
"os"
"path/filepath"
"gopkg.in/yaml.v3"
)
// Config holds the arca configuration.
type Config struct {
Devices map[string]DeviceConfig `yaml:"devices"`
}
// DeviceConfig holds per-device settings.
type DeviceConfig struct {
UUID string `yaml:"uuid"`
Mountpoint string `yaml:"mountpoint,omitempty"`
Methods []string `yaml:"methods,omitempty"`
Keyfile string `yaml:"keyfile,omitempty"`
}
// ResolvedDevice holds the effective settings for a device lookup.
type ResolvedDevice struct {
UUID string
Mountpoint string
Methods []string
Keyfile string
}
// Load reads the config file. Returns an empty config if the file doesn't exist.
func Load() *Config {
cfg := &Config{
Devices: make(map[string]DeviceConfig),
}
data, err := os.ReadFile(configPath())
if err != nil {
return cfg
}
yaml.Unmarshal(data, cfg)
return cfg
}
// ResolveDevice looks up by alias name first, then checks if the argument
// is a device path matching a known alias (e.g. "/dev/sda1" matches "sda1").
// If nothing matches, returns defaults for a bare device path.
func (c *Config) ResolveDevice(nameOrPath string) ResolvedDevice {
// Direct alias match.
if dev, ok := c.Devices[nameOrPath]; ok {
return resolvedFrom(dev)
}
// Match device path against aliases: "/dev/sda1" matches alias "sda1".
base := filepath.Base(nameOrPath)
if dev, ok := c.Devices[base]; ok {
return resolvedFrom(dev)
}
return ResolvedDevice{
Methods: []string{"passphrase"},
}
}
func resolvedFrom(dev DeviceConfig) ResolvedDevice {
methods := dev.Methods
if len(methods) == 0 {
methods = []string{"passphrase"}
}
return ResolvedDevice{
UUID: dev.UUID,
Mountpoint: dev.Mountpoint,
Methods: methods,
Keyfile: dev.Keyfile,
}
}
// HasUUID returns true if a device with the given UUID is already configured.
func (c *Config) HasUUID(uuid string) bool {
for _, dev := range c.Devices {
if dev.UUID == uuid {
return true
}
}
return false
}
// Save writes the config to the config file.
func (c *Config) Save() error {
path := configPath()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return fmt.Errorf("creating config directory: %w", err)
}
data, err := yaml.Marshal(c)
if err != nil {
return fmt.Errorf("marshaling config: %w", err)
}
return os.WriteFile(path, data, 0o644)
}
// AliasFor returns the config alias for a given UUID, or "" if none.
func (c *Config) AliasFor(uuid string) string {
for name, dev := range c.Devices {
if dev.UUID == uuid {
return name
}
}
return ""
}
// Path returns the config file path, respecting XDG_CONFIG_HOME.
func Path() string {
return configPath()
}
func configPath() string {
if dir := os.Getenv("XDG_CONFIG_HOME"); dir != "" {
return filepath.Join(dir, "arca", "config.yaml")
}
home, _ := os.UserHomeDir()
return filepath.Join(home, ".config", "arca", "config.yaml")
}