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

108
cmd/init.go Normal file
View File

@@ -0,0 +1,108 @@
package cmd
import (
"fmt"
"os"
"path/filepath"
"strings"
"git.wntrmute.dev/kyle/arca/internal/config"
"git.wntrmute.dev/kyle/arca/internal/udisks"
"github.com/godbus/dbus/v5"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)
var forceInit bool
var initCmd = &cobra.Command{
Use: "init",
Short: "Generate config from detected LUKS devices",
Long: "Scans for LUKS-encrypted devices, excludes the root filesystem, and writes a config file with passphrase as the default unlock method.",
RunE: runInit,
}
func init() {
initCmd.Flags().BoolVarP(&forceInit, "force", "f", false, "overwrite existing config file")
rootCmd.AddCommand(initCmd)
}
func runInit(cmd *cobra.Command, args []string) error {
cfgPath := config.Path()
if !forceInit {
if _, err := os.Stat(cfgPath); err == nil {
return fmt.Errorf("config already exists at %s (use --force to overwrite)", cfgPath)
}
}
client, err := udisks.NewClient()
if err != nil {
return fmt.Errorf("connecting to udisks2: %w", err)
}
defer client.Close()
encrypted, err := client.ListEncryptedDevices()
if err != nil {
return err
}
rootBacking, err := client.RootBackingDevices()
if err != nil {
return fmt.Errorf("detecting root device: %w", err)
}
cfg := config.Config{
Devices: make(map[string]config.DeviceConfig),
}
for _, dev := range encrypted {
if isRootBacking(dev.ObjectPath, rootBacking) {
fmt.Fprintf(os.Stderr, "Skipping %s (root filesystem)\n", dev.DevicePath)
continue
}
alias := aliasFromPath(dev.DevicePath)
cfg.Devices[alias] = config.DeviceConfig{
UUID: dev.UUID,
Methods: []string{"passphrase"},
}
fmt.Fprintf(os.Stderr, "Found %s (UUID %s) -> alias %q\n", dev.DevicePath, dev.UUID, alias)
}
if len(cfg.Devices) == 0 {
fmt.Println("No non-root LUKS devices found.")
return nil
}
data, err := yaml.Marshal(&cfg)
if err != nil {
return fmt.Errorf("marshaling config: %w", err)
}
if err := os.MkdirAll(filepath.Dir(cfgPath), 0o755); err != nil {
return fmt.Errorf("creating config directory: %w", err)
}
if err := os.WriteFile(cfgPath, data, 0o644); err != nil {
return fmt.Errorf("writing config: %w", err)
}
fmt.Printf("Config written to %s\n", cfgPath)
return nil
}
func isRootBacking(path dbus.ObjectPath, rootDevices []dbus.ObjectPath) bool {
for _, r := range rootDevices {
if path == r {
return true
}
}
return false
}
func aliasFromPath(devPath string) string {
// "/dev/sda1" -> "sda1", "/dev/nvme0n1p2" -> "nvme0n1p2"
base := filepath.Base(devPath)
return strings.TrimPrefix(base, "dm-")
}

75
cmd/mount.go Normal file
View File

@@ -0,0 +1,75 @@
package cmd
import (
"fmt"
"os"
"git.wntrmute.dev/kyle/arca/internal/config"
"git.wntrmute.dev/kyle/arca/internal/cryptsetup"
"git.wntrmute.dev/kyle/arca/internal/udisks"
"git.wntrmute.dev/kyle/arca/internal/unlock"
"github.com/spf13/cobra"
"golang.org/x/term"
)
var mountCmd = &cobra.Command{
Use: "mount <device|alias>",
Short: "Unlock and mount a LUKS volume",
Args: cobra.ExactArgs(1),
RunE: runMount,
}
func init() {
rootCmd.AddCommand(mountCmd)
}
func runMount(cmd *cobra.Command, args []string) error {
cfg := config.Load()
target := args[0]
client, err := udisks.NewClient()
if err != nil {
return fmt.Errorf("connecting to udisks2: %w", err)
}
defer client.Close()
devCfg := cfg.ResolveDevice(target)
dev, err := client.FindDevice(devCfg.UUID, target)
if err != nil {
return err
}
u := unlock.New(client, unlock.Options{
ReadPassphrase: readPassphrase,
KeyfilePath: devCfg.Keyfile,
})
result, err := u.Unlock(dev, devCfg.Methods)
if err != nil {
return err
}
var mountpoint string
if result.Privileged {
mountpoint, err = cryptsetup.Mount(result.Device.DevicePath, devCfg.Mountpoint)
} else {
mountpoint, err = client.Mount(result.Device)
}
if err != nil {
return fmt.Errorf("mounting: %w", err)
}
fmt.Println(mountpoint)
return nil
}
func readPassphrase() (string, error) {
fmt.Fprint(os.Stderr, "Passphrase: ")
pass, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Fprintln(os.Stderr)
if err != nil {
return "", fmt.Errorf("reading passphrase: %w", err)
}
return string(pass), nil
}

20
cmd/root.go Normal file
View File

@@ -0,0 +1,20 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "arca",
Short: "Mount and unmount LUKS-encrypted volumes",
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

59
cmd/status.go Normal file
View File

@@ -0,0 +1,59 @@
package cmd
import (
"fmt"
"os"
"text/tabwriter"
"git.wntrmute.dev/kyle/arca/internal/config"
"git.wntrmute.dev/kyle/arca/internal/udisks"
"github.com/spf13/cobra"
)
var statusCmd = &cobra.Command{
Use: "status",
Short: "Show LUKS volume status",
RunE: runStatus,
}
func init() {
rootCmd.AddCommand(statusCmd)
}
func runStatus(cmd *cobra.Command, args []string) error {
cfg := config.Load()
client, err := udisks.NewClient()
if err != nil {
return fmt.Errorf("connecting to udisks2: %w", err)
}
defer client.Close()
devices, err := client.ListEncryptedDevices()
if err != nil {
return err
}
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
fmt.Fprintln(w, "DEVICE\tUUID\tALIAS\tSTATE\tMOUNTPOINT")
for _, dev := range devices {
alias := cfg.AliasFor(dev.UUID)
state := "locked"
mountpoint := ""
if ct, err := client.CleartextDevice(&dev); err == nil {
state = "unlocked"
if mp, err := client.MountPoint(ct); err == nil && mp != "" {
mountpoint = mp
state = "mounted"
}
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
dev.DevicePath, dev.UUID, alias, state, mountpoint)
}
w.Flush()
return nil
}

73
cmd/unmount.go Normal file
View File

@@ -0,0 +1,73 @@
package cmd
import (
"fmt"
"strings"
"git.wntrmute.dev/kyle/arca/internal/config"
"git.wntrmute.dev/kyle/arca/internal/cryptsetup"
"git.wntrmute.dev/kyle/arca/internal/udisks"
"github.com/spf13/cobra"
)
var unmountCmd = &cobra.Command{
Use: "unmount <device|alias>",
Aliases: []string{"umount"},
Short: "Unmount and lock a LUKS volume",
Args: cobra.ExactArgs(1),
RunE: runUnmount,
}
func init() {
rootCmd.AddCommand(unmountCmd)
}
func runUnmount(cmd *cobra.Command, args []string) error {
cfg := config.Load()
target := args[0]
client, err := udisks.NewClient()
if err != nil {
return fmt.Errorf("connecting to udisks2: %w", err)
}
defer client.Close()
devCfg := cfg.ResolveDevice(target)
dev, err := client.FindDevice(devCfg.UUID, target)
if err != nil {
return err
}
cleartext, err := client.CleartextDevice(dev)
if err != nil {
return fmt.Errorf("finding cleartext device: %w", err)
}
// Unmount: try udisks2 first, fall back to privileged umount.
mp, _ := client.MountPoint(cleartext)
if err := client.Unmount(cleartext); err != nil {
if mp == "" {
return fmt.Errorf("unmounting: %w", err)
}
if err := cryptsetup.Unmount(mp); err != nil {
return fmt.Errorf("unmounting: %w", err)
}
}
// Lock: try udisks2 first, fall back to cryptsetup close if it's
// an arca-managed mapping.
if err := client.Lock(dev); err != nil {
mapperName := cryptsetup.MapperName(dev.DevicePath)
if strings.HasPrefix(mapperName, "arca-") {
if err := cryptsetup.Close(mapperName); err != nil {
return fmt.Errorf("locking: %w", err)
}
} else {
return fmt.Errorf("locking: %w", err)
}
}
fmt.Printf("Unmounted and locked %s\n", target)
return nil
}