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>
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 frameworkgithub.com/godbus/dbus/v5— D-Bus clientgopkg.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)
- Connect to the system bus.
- Enumerate block devices under
/org/freedesktop/UDisks2/block_devices/. - Find a block device by device path or UUID.
Unlock(passphrase)— callorg.freedesktop.UDisks2.Encrypted.Unlockon the LUKS partition. Returns the cleartext device object path.Mount()— callorg.freedesktop.UDisks2.Filesystem.Mounton the cleartext device. Returns the mount point path.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-onlycan 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:
- Try the first method (e.g.,
fido2). - If it fails (device not plugged in, timeout, etc.), try the next.
- Continue until one succeeds or all are exhausted.
Default when no config exists: [passphrase].
Phase 2: Config File
Build internal/config/:
- Load
~/.config/arca/config.yaml(respect$XDG_CONFIG_HOME). - Resolve an alias to a UUID and unlock methods.
- 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>
- Resolve argument: if it matches a config alias, look up UUID and methods list; otherwise treat as a device path with default methods.
- Find the block device via udisks2.
- Walk the methods list in order, attempting each unlock strategy:
passphrase/keyfile: via udisks2 D-Bus (no root).fido2/tpm2: viacryptsetupCLI (requires doas/sudo).- Stop at the first success; if all fail, report each error.
- Mount the resulting cleartext device via udisks2.
- Print the mount point.
arca unmount <device|alias>
- Resolve argument to the LUKS block device.
- Find the associated cleartext (mapped) device.
- Call Unmount on the cleartext device.
- Call Lock on the LUKS device.
arca status
- Enumerate all block devices with the Encrypted interface.
- For each, check if unlocked (has a cleartext device).
- If mounted, show mount point.
- 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:
cryptsetuphandles the token interaction (tap prompt comes from libfido2). - TPM2:
cryptsetuphandles TPM unsealing automatically.
Phase 5: Packaging
flake.nixwithbuildGoModule.- Add as a flake input to the NixOS config repo.
Open Questions
- Should
mountaccept a--mountpointflag to override the default? - Should there be a
--dry-runflag that shows what D-Bus calls would be made? - Is there value in a
--jsonoutput flag forstatus?