Files
arca/PLAN.md
Kyle Isom c835358829 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>
2026-03-24 07:42:38 -07:00

5.6 KiB

arca — Implementation Plan

Overview

A Go CLI that manages LUKS volumes. Uses udisks2 D-Bus for passphrase/keyfile unlock (no root needed), and falls back to cryptsetup for FIDO2/TPM2 tokens (requires root/doas). Three subcommands: mount, unmount, status.

Dependencies

  • github.com/spf13/cobra — CLI framework
  • github.com/godbus/dbus/v5 — D-Bus client
  • gopkg.in/yaml.v3 — config file parsing

Project Structure

arca/
├── main.go
├── cmd/
│   ├── root.go          # root command, global flags
│   ├── mount.go         # mount subcommand
│   ├── unmount.go       # unmount subcommand
│   └── status.go        # status subcommand
├── internal/
│   ├── udisks/          # udisks2 D-Bus client
│   │   ├── client.go    # connection + device discovery
│   │   ├── encrypt.go   # Unlock / Lock operations
│   │   ├── mount.go     # Mount / Unmount operations
│   │   └── types.go     # object path helpers, constants
│   ├── cryptsetup/      # cryptsetup CLI wrapper (FIDO2/TPM2)
│   │   └── cryptsetup.go
│   ├── unlock/          # method sequencing logic
│   │   └── unlock.go
│   └── config/
│       └── config.go    # YAML config loading + device resolution
├── flake.nix
├── go.mod
├── go.sum
├── README.md
└── PLAN.md

Phase 1: Core Unlock/Lock Backend

Build internal/udisks/ for D-Bus operations and internal/cryptsetup/ for FIDO2/TPM2 fallback.

udisks2 D-Bus (passphrase + keyfile)

  1. Connect to the system bus.
  2. Enumerate block devices under /org/freedesktop/UDisks2/block_devices/.
  3. Find a block device by device path or UUID.
  4. Unlock(passphrase) — call org.freedesktop.UDisks2.Encrypted.Unlock on the LUKS partition. Returns the cleartext device object path.
  5. Mount() — call org.freedesktop.UDisks2.Filesystem.Mount on the cleartext device. Returns the mount point path.
  6. Unmount() + Lock() — reverse operations.

Key D-Bus details:

  • Bus name: org.freedesktop.UDisks2
  • Object paths: /org/freedesktop/UDisks2/block_devices/<name>
  • Interfaces: org.freedesktop.UDisks2.Encrypted (Unlock, Lock), org.freedesktop.UDisks2.Filesystem (Mount, Unmount), org.freedesktop.UDisks2.Block (device properties, UUID lookup)

cryptsetup fallback (FIDO2 + TPM2)

udisks2 does not support FIDO2/TPM2 token-based keyslots. For these, fall back to invoking cryptsetup (via doas/sudo):

  • FIDO2: cryptsetup open --token-type systemd-fido2 <device> <name>
  • TPM2: cryptsetup open --token-type systemd-tpm2 <device> <name>
  • --token-only can attempt all enrolled tokens automatically.

After cryptsetup open, mount via udisks2 D-Bus or plain mount.

Unlock method sequencing

Each device has an ordered list of methods to try. The unlock logic walks the list in order, stopping at the first success:

  1. Try the first method (e.g., fido2).
  2. If it fails (device not plugged in, timeout, etc.), try the next.
  3. Continue until one succeeds or all are exhausted.

Default when no config exists: [passphrase].

Phase 2: Config File

Build internal/config/:

  1. Load ~/.config/arca/config.yaml (respect $XDG_CONFIG_HOME).
  2. Resolve an alias to a UUID and unlock methods.
  3. If no config file exists, that's fine — device path arguments still work with the default method sequence [passphrase].

Config schema:

devices:
  backup:
    uuid: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    mountpoint: "/mnt/backup"        # optional
    methods:                         # optional, default: [passphrase]
      - fido2
      - passphrase
  media:
    uuid: "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
    methods:
      - keyfile
      - passphrase
    keyfile: "/path/to/media.key"    # required if keyfile is in methods

Supported methods: passphrase, keyfile, fido2, tpm2.

Phase 3: CLI Commands

Wire up cobra subcommands:

arca mount <device|alias>

  1. Resolve argument: if it matches a config alias, look up UUID and methods list; otherwise treat as a device path with default methods.
  2. Find the block device via udisks2.
  3. Walk the methods list in order, attempting each unlock strategy:
    • passphrase / keyfile: via udisks2 D-Bus (no root).
    • fido2 / tpm2: via cryptsetup CLI (requires doas/sudo).
    • Stop at the first success; if all fail, report each error.
  4. Mount the resulting cleartext device via udisks2.
  5. Print the mount point.

arca unmount <device|alias>

  1. Resolve argument to the LUKS block device.
  2. Find the associated cleartext (mapped) device.
  3. Call Unmount on the cleartext device.
  4. Call Lock on the LUKS device.

arca status

  1. Enumerate all block devices with the Encrypted interface.
  2. For each, check if unlocked (has a cleartext device).
  3. If mounted, show mount point.
  4. Print a table: device, UUID, alias (if configured), state, mount point.

Phase 4: Credential Input

  • Passphrase: read from terminal with echo disabled (golang.org/x/term). Alternatively, accept from stdin for scripting.
  • Keyfile: read path from config, pass contents to udisks2.
  • FIDO2: cryptsetup handles the token interaction (tap prompt comes from libfido2).
  • TPM2: cryptsetup handles TPM unsealing automatically.

Phase 5: Packaging

  • flake.nix with buildGoModule.
  • Add as a flake input to the NixOS config repo.

Open Questions

  • Should mount accept a --mountpoint flag to override the default?
  • Should there be a --dry-run flag that shows what D-Bus calls would be made?
  • Is there value in a --json output flag for status?