M7: add verbose mode for debugging

Add -v/--verbose persistent flag that prints debug info to stderr:
D-Bus connection status, token plugin directory discovery, unlock method
sequencing with per-method success/failure, and full cryptsetup command
lines including LD_LIBRARY_PATH.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 08:37:08 -07:00
parent e44dd382dd
commit 0c19f94292
5 changed files with 36 additions and 0 deletions

View File

@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"git.wntrmute.dev/kyle/arca/internal/verbose"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -12,6 +13,10 @@ var rootCmd = &cobra.Command{
Short: "Mount and unmount LUKS-encrypted volumes", Short: "Mount and unmount LUKS-encrypted volumes",
} }
func init() {
rootCmd.PersistentFlags().BoolVarP(&verbose.Enabled, "verbose", "v", false, "print debug information to stderr")
}
func SetVersion(v string) { func SetVersion(v string) {
rootCmd.Version = v rootCmd.Version = v
} }

View File

@@ -5,6 +5,9 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings"
"git.wntrmute.dev/kyle/arca/internal/verbose"
) )
// Open opens a LUKS device using cryptsetup with token-based unlock. // Open opens a LUKS device using cryptsetup with token-based unlock.
@@ -12,6 +15,8 @@ func Open(devicePath, mapperName string) error {
args := withTokenPluginEnv([]string{"cryptsetup", "open", devicePath, mapperName, "--token-only"}) args := withTokenPluginEnv([]string{"cryptsetup", "open", devicePath, mapperName, "--token-only"})
args = withPrivilege(args) args = withPrivilege(args)
verbose.Printf("exec: %s", strings.Join(args, " "))
cmd := exec.Command(args[0], args[1:]...) cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
@@ -101,6 +106,7 @@ func findTokenPluginDir() string {
// NixOS stable symlink — survives rebuilds. // NixOS stable symlink — survives rebuilds.
const nixSystemPath = "/run/current-system/sw/lib/cryptsetup" const nixSystemPath = "/run/current-system/sw/lib/cryptsetup"
if hasTokenPlugins(nixSystemPath) { if hasTokenPlugins(nixSystemPath) {
verbose.Printf("token plugin dir: %s", nixSystemPath)
return nixSystemPath return nixSystemPath
} }
@@ -109,11 +115,13 @@ func findTokenPluginDir() string {
if resolved, err := filepath.EvalSymlinks(bin); err == nil { if resolved, err := filepath.EvalSymlinks(bin); err == nil {
dir := filepath.Join(filepath.Dir(filepath.Dir(resolved)), "lib", "cryptsetup") dir := filepath.Join(filepath.Dir(filepath.Dir(resolved)), "lib", "cryptsetup")
if hasTokenPlugins(dir) { if hasTokenPlugins(dir) {
verbose.Printf("token plugin dir (from systemd-cryptenroll): %s", dir)
return dir return dir
} }
} }
} }
verbose.Printf("no token plugin directory found")
return "" return ""
} }

View File

@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"git.wntrmute.dev/kyle/arca/internal/verbose"
"github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5"
) )
@@ -18,6 +19,7 @@ func NewClient() (*Client, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot connect to udisks2 — is the udisks2 service running? (%w)", err) return nil, fmt.Errorf("cannot connect to udisks2 — is the udisks2 service running? (%w)", err)
} }
verbose.Printf("connected to system D-Bus")
return &Client{conn: conn}, nil return &Client{conn: conn}, nil
} }

View File

@@ -8,6 +8,7 @@ import (
"git.wntrmute.dev/kyle/arca/internal/cryptsetup" "git.wntrmute.dev/kyle/arca/internal/cryptsetup"
"git.wntrmute.dev/kyle/arca/internal/udisks" "git.wntrmute.dev/kyle/arca/internal/udisks"
"git.wntrmute.dev/kyle/arca/internal/verbose"
) )
// Result holds the outcome of a successful unlock. // Result holds the outcome of a successful unlock.
@@ -35,12 +36,16 @@ func New(client *udisks.Client, opts Options) *Unlocker {
// Unlock tries each method in order and returns the result on first success. // Unlock tries each method in order and returns the result on first success.
func (u *Unlocker) Unlock(dev *udisks.BlockDevice, methods []string) (*Result, error) { func (u *Unlocker) Unlock(dev *udisks.BlockDevice, methods []string) (*Result, error) {
verbose.Printf("unlock %s: methods %v", dev.DevicePath, methods)
var errs []error var errs []error
for _, method := range methods { for _, method := range methods {
verbose.Printf("trying method: %s", method)
res, err := u.tryMethod(dev, method) res, err := u.tryMethod(dev, method)
if err == nil { if err == nil {
verbose.Printf("method %s succeeded", method)
return res, nil return res, nil
} }
verbose.Printf("method %s failed: %v", method, err)
errs = append(errs, fmt.Errorf("%s: %w", method, err)) errs = append(errs, fmt.Errorf("%s: %w", method, err))
} }
return nil, fmt.Errorf("all unlock methods failed for %s:\n%w", dev.DevicePath, errors.Join(errs...)) return nil, fmt.Errorf("all unlock methods failed for %s:\n%w", dev.DevicePath, errors.Join(errs...))

View File

@@ -0,0 +1,16 @@
package verbose
import (
"fmt"
"os"
)
// Enabled is set by the root command's --verbose flag.
var Enabled bool
// Printf prints to stderr if verbose mode is enabled.
func Printf(format string, args ...any) {
if Enabled {
fmt.Fprintf(os.Stderr, "arca: "+format+"\n", args...)
}
}