mount now detects already-unlocked and already-mounted devices, returning the existing mount point instead of failing. unmount handles already-locked devices gracefully and skips unmount if not mounted before locking. Adds IsMounted helper to udisks client. Updates PLAN.md with refined v1.0.0 milestones. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
159 lines
4.4 KiB
Markdown
159 lines
4.4 KiB
Markdown
# arca — v1.0.0 Plan
|
|
|
|
## Current State (v0.1.0)
|
|
|
|
Working proof of concept. FIDO2 unlock via cryptsetup with passphrase
|
|
fallback, privileged mount/unmount, config aliases with method
|
|
sequencing, init and status commands.
|
|
|
|
## M1: Idempotent mount/unmount
|
|
|
|
`mount` and `unmount` should be safe to run at any point in a device's
|
|
lifecycle without producing confusing errors.
|
|
|
|
### Files changed
|
|
|
|
- `cmd/mount.go`
|
|
- `cmd/unmount.go`
|
|
- `internal/udisks/client.go`
|
|
|
|
### Work
|
|
|
|
1. In `runMount`, after `FindDevice`, check if the device is already
|
|
unlocked (`CleartextDevice` succeeds). If the cleartext device is
|
|
already mounted, print the existing mount point and return success.
|
|
If unlocked but not mounted, skip unlock and go straight to mount.
|
|
|
|
2. In `runUnmount`, handle each failure case:
|
|
- `CleartextDevice` fails (already locked): print "already locked"
|
|
and return success.
|
|
- `Unmount` fails because not mounted: proceed to lock anyway.
|
|
- `Lock` fails because already locked: return success.
|
|
|
|
3. Add `client.IsMounted(dev)` helper that returns `(mountpoint, bool)`
|
|
to reduce duplicated mount-point checking logic.
|
|
|
|
---
|
|
|
|
## M2: Error messages
|
|
|
|
Replace generic D-Bus errors with actionable messages.
|
|
|
|
### Files changed
|
|
|
|
- `internal/udisks/client.go`
|
|
- `cmd/mount.go`, `cmd/unmount.go`, `cmd/init.go`, `cmd/status.go`
|
|
|
|
### Work
|
|
|
|
1. Wrap the `dbus.SystemBus()` error in `NewClient` to detect
|
|
"connection refused" or "no such file" and print:
|
|
`"cannot connect to udisks2 — is the udisks2 service running?"`
|
|
|
|
2. In `FindDevice`, when no device matches, include what was searched
|
|
and suggest `arca status` or `arca init`:
|
|
`"device /dev/sda1 not found (run 'arca status' to list devices)"`
|
|
|
|
3. In the unlock sequencer, prefix each method error with context:
|
|
`"fido2: cryptsetup open --token-only: exit status 5 (is the FIDO2 key plugged in?)"`
|
|
|
|
---
|
|
|
|
## M3: Unit tests
|
|
|
|
Cover pure logic that doesn't need D-Bus or real devices.
|
|
|
|
### Files changed
|
|
|
|
- `internal/config/config_test.go` (new)
|
|
- `internal/cryptsetup/cryptsetup_test.go` (new)
|
|
|
|
### Work
|
|
|
|
1. **config_test.go** — test cases:
|
|
- `ResolveDevice` with exact alias match
|
|
- `ResolveDevice` with device path match (`/dev/sda1` -> `sda1`)
|
|
- `ResolveDevice` with unknown name returns default methods
|
|
- `ResolveDevice` with empty methods list defaults to `[passphrase]`
|
|
- `AliasFor` finds alias by UUID
|
|
- `AliasFor` returns "" for unknown UUID
|
|
- `Load` with nonexistent file returns empty config
|
|
- `Load` with valid YAML parses correctly
|
|
|
|
2. **cryptsetup_test.go** — test cases:
|
|
- `MapperName("/dev/sda1")` == `"arca-sda1"`
|
|
- `MapperName("/dev/nvme0n1p2")` == `"arca-nvme0n1p2"`
|
|
- `hasTokenPlugins` with a temp dir containing a matching .so
|
|
- `hasTokenPlugins` with an empty temp dir
|
|
|
|
---
|
|
|
|
## M4: CLI polish
|
|
|
|
Small usability improvements.
|
|
|
|
### Files changed
|
|
|
|
- `cmd/root.go`
|
|
- `cmd/mount.go`
|
|
- `cmd/init.go`
|
|
- `main.go`
|
|
- `.gitignore` (new)
|
|
|
|
### Work
|
|
|
|
1. Add `var version = "dev"` to `main.go`. Set `rootCmd.Version` in
|
|
`cmd/root.go`. Build with `-ldflags "-X main.version=..."` in
|
|
`flake.nix`.
|
|
|
|
2. Add `--mountpoint` / `-m` flag to `mount` subcommand. When set,
|
|
pass it to `cryptsetup.Mount` (privileged path) or log a warning
|
|
that udisks2 doesn't support custom mount points.
|
|
|
|
3. In `init`, use first 8 chars of UUID as alias instead of device
|
|
path basename. Example: `b8b2f8e3` instead of `sda1`. UUIDs are
|
|
stable across boots; device paths are not.
|
|
|
|
4. Create `.gitignore` containing `/arca`.
|
|
|
|
---
|
|
|
|
## M5: Documentation and packaging
|
|
|
|
Make the project installable and the README trustworthy.
|
|
|
|
### Files changed
|
|
|
|
- `README.md`
|
|
- `flake.nix`
|
|
|
|
### Work
|
|
|
|
1. Update `README.md`:
|
|
- Replace placeholder UUIDs with realistic examples from actual
|
|
tested usage.
|
|
- Add "NixOS notes" section documenting the `LD_LIBRARY_PATH`
|
|
requirement for FIDO2/TPM2 and why `--external-tokens-path`
|
|
doesn't work.
|
|
- Add "Troubleshooting" section: no FIDO2 token enrolled, udisks2
|
|
not running, permission denied on mount.
|
|
- Document `init` subcommand.
|
|
|
|
2. Verify `flake.nix`:
|
|
- Run `nix build` and confirm it produces a working binary.
|
|
- Add `-ldflags` for version injection from `self.rev` or a
|
|
`version` variable.
|
|
- Test the flake output: `./result/bin/arca --version`.
|
|
|
|
3. Tag `v1.0.0`.
|
|
|
|
---
|
|
|
|
## Non-goals for v1.0.0
|
|
|
|
- `--dry-run` flag
|
|
- `--json` output for `status`
|
|
- udev auto-mount on plug
|
|
- Keyfile creation/management
|
|
- Multiple config files or config includes
|