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:
168
PLAN.md
Normal file
168
PLAN.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# 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:
|
||||
|
||||
```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 <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`?
|
||||
Reference in New Issue
Block a user