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:
177
internal/udisks/client.go
Normal file
177
internal/udisks/client.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package udisks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
// Client communicates with udisks2 over D-Bus.
|
||||
type Client struct {
|
||||
conn *dbus.Conn
|
||||
}
|
||||
|
||||
// NewClient connects to the system D-Bus and returns a udisks2 client.
|
||||
func NewClient() (*Client, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connecting to system bus: %w", err)
|
||||
}
|
||||
return &Client{conn: conn}, nil
|
||||
}
|
||||
|
||||
// Close closes the D-Bus connection.
|
||||
func (c *Client) Close() {
|
||||
c.conn.Close()
|
||||
}
|
||||
|
||||
// FindDevice finds a block device by UUID (if non-empty) or device path.
|
||||
func (c *Client) FindDevice(uuid, pathOrName string) (*BlockDevice, error) {
|
||||
devices, err := c.listBlockDevices()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if uuid != "" {
|
||||
for i := range devices {
|
||||
if devices[i].UUID == uuid {
|
||||
return &devices[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no device with UUID %s", uuid)
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
if devices[i].DevicePath == pathOrName {
|
||||
return &devices[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("device %s not found", pathOrName)
|
||||
}
|
||||
|
||||
// ListEncryptedDevices returns all block devices with the Encrypted interface.
|
||||
func (c *Client) ListEncryptedDevices() ([]BlockDevice, error) {
|
||||
devices, err := c.listBlockDevices()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var encrypted []BlockDevice
|
||||
for _, d := range devices {
|
||||
if d.HasEncrypted {
|
||||
encrypted = append(encrypted, d)
|
||||
}
|
||||
}
|
||||
return encrypted, nil
|
||||
}
|
||||
|
||||
// CleartextDevice finds the unlocked cleartext device for a LUKS device.
|
||||
func (c *Client) CleartextDevice(dev *BlockDevice) (*BlockDevice, error) {
|
||||
devices, err := c.listBlockDevices()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range devices {
|
||||
if devices[i].CryptoBackingDevice == dev.ObjectPath {
|
||||
return &devices[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no cleartext device for %s (is it unlocked?)", dev.DevicePath)
|
||||
}
|
||||
|
||||
// MountPoint returns the current mount point of a device, or "" if not mounted.
|
||||
func (c *Client) MountPoint(dev *BlockDevice) (string, error) {
|
||||
obj := c.conn.Object(busName, dev.ObjectPath)
|
||||
variant, err := obj.GetProperty(ifaceFilesystem + ".MountPoints")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
mountPoints, ok := variant.Value().([][]byte)
|
||||
if !ok || len(mountPoints) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return string(bytes.TrimRight(mountPoints[0], "\x00")), nil
|
||||
}
|
||||
|
||||
// DeviceAtPath returns the block device at the given D-Bus object path.
|
||||
func (c *Client) DeviceAtPath(path dbus.ObjectPath) (*BlockDevice, error) {
|
||||
devices, err := c.listBlockDevices()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range devices {
|
||||
if devices[i].ObjectPath == path {
|
||||
return &devices[i], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("device at %s not found", path)
|
||||
}
|
||||
|
||||
// RootBackingDevices returns the object paths of LUKS devices that back
|
||||
// the root filesystem. Used to exclude them from init config generation.
|
||||
func (c *Client) RootBackingDevices() ([]dbus.ObjectPath, error) {
|
||||
devices, err := c.listBlockDevices()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var backing []dbus.ObjectPath
|
||||
for i := range devices {
|
||||
if !devices[i].HasFilesystem {
|
||||
continue
|
||||
}
|
||||
mp, err := c.MountPoint(&devices[i])
|
||||
if err != nil || mp != "/" {
|
||||
continue
|
||||
}
|
||||
if devices[i].CryptoBackingDevice != "" && devices[i].CryptoBackingDevice != "/" {
|
||||
backing = append(backing, devices[i].CryptoBackingDevice)
|
||||
}
|
||||
}
|
||||
return backing, nil
|
||||
}
|
||||
|
||||
func (c *Client) listBlockDevices() ([]BlockDevice, error) {
|
||||
obj := c.conn.Object(busName, dbus.ObjectPath(objectPrefix))
|
||||
var managed map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
||||
err := obj.Call(ifaceObjectManager+".GetManagedObjects", 0).Store(&managed)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing devices: %w", err)
|
||||
}
|
||||
|
||||
var devices []BlockDevice
|
||||
for path, ifaces := range managed {
|
||||
blockProps, ok := ifaces[ifaceBlock]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
dev := BlockDevice{ObjectPath: path}
|
||||
|
||||
if v, ok := blockProps["Device"]; ok {
|
||||
if bs, ok := v.Value().([]byte); ok {
|
||||
dev.DevicePath = string(bytes.TrimRight(bs, "\x00"))
|
||||
}
|
||||
}
|
||||
if v, ok := blockProps["IdUUID"]; ok {
|
||||
if s, ok := v.Value().(string); ok {
|
||||
dev.UUID = s
|
||||
}
|
||||
}
|
||||
if v, ok := blockProps["CryptoBackingDevice"]; ok {
|
||||
if p, ok := v.Value().(dbus.ObjectPath); ok {
|
||||
dev.CryptoBackingDevice = p
|
||||
}
|
||||
}
|
||||
|
||||
_, dev.HasEncrypted = ifaces[ifaceEncrypted]
|
||||
_, dev.HasFilesystem = ifaces[ifaceFilesystem]
|
||||
|
||||
devices = append(devices, dev)
|
||||
}
|
||||
return devices, nil
|
||||
}
|
||||
Reference in New Issue
Block a user