Initial implementation of arca, a LUKS volume manager.

Go CLI using cobra with mount, unmount, status, and init subcommands.
Unlocks via udisks2 D-Bus (passphrase/keyfile) or cryptsetup (FIDO2/TPM2)
with ordered method fallback. Includes NixOS-specific LD_LIBRARY_PATH
injection for systemd cryptsetup token plugins.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 07:42:38 -07:00
commit c835358829
538 changed files with 259597 additions and 0 deletions

100
internal/config/config.go Normal file
View File

@@ -0,0 +1,100 @@
package config
import (
"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,
}
}
// 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")
}