# 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/` - 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 ` - TPM2: `cryptsetup open --token-type systemd-tpm2 ` - `--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: ```yaml 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 ` 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 ` 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`?