fix: address final-review findings before closing Plan 1

- privilege: wire sudo -v to real stdin/stdout/stderr so it can prompt
- cmd: validate --only/--skip IDs against registry (prevents silent
  run-all on typo); gate install to Arch-only (spec requirement);
  mark --only and --skip mutually exclusive
- platform: Pacman.Update now runs -Syu --noconfirm, not -Sy (avoids
  Arch partial-upgrade anti-pattern)
- test/integration: trap-clean the named docker volume on any exit
- go.mod: tidy — yaml.v3 is a direct dep
This commit is contained in:
MDietermann
2026-04-13 21:35:14 +02:00
parent 40ac730719
commit 8b43a0f273
7 changed files with 59 additions and 11 deletions

View File

@@ -12,6 +12,7 @@ import (
"github.com/MDietermann/ArduinoArchSetup/internal/engine"
"github.com/MDietermann/ArduinoArchSetup/internal/module"
"github.com/MDietermann/ArduinoArchSetup/internal/modules/hello"
"github.com/MDietermann/ArduinoArchSetup/internal/platform"
"github.com/MDietermann/ArduinoArchSetup/internal/privilege"
"github.com/MDietermann/ArduinoArchSetup/internal/state"
)
@@ -62,12 +63,28 @@ func newInstallCmd() *cobra.Command {
ctx = context.Background()
}
// Gate: v1 supports Arch only. Non-Arch hosts would silently run
// the hello module today but fail on Plan 3's pacman-dependent
// modules — fail fast with a clear message instead.
if !noSudo { // --no-sudo implies a test/container context; skip the gate
p, derr := platform.Detect()
if derr != nil {
return fmt.Errorf("platform detect: %w", derr)
}
if p.Distro != platform.DistroArch {
return fmt.Errorf("archduino requires Arch Linux (detected distro: %s)", p.Distro)
}
}
cfg, err := config.Load(overlayPath)
if err != nil {
return err
}
selected := resolveSelection(Registry, cfg, only, skip)
selected, err := resolveSelection(Registry, cfg, only, skip)
if err != nil {
return err
}
// Acquire sudo unless dry-run or explicitly skipped.
if !dryRun && !noSudo {
@@ -129,11 +146,30 @@ func newInstallCmd() *cobra.Command {
cmd.Flags().StringSliceVar(&only, "only", nil, "comma-separated module IDs to run (exclusive with --skip)")
cmd.Flags().StringSliceVar(&skip, "skip", nil, "comma-separated module IDs to skip")
cmd.Flags().StringVar(&overlayPath, "config", "", "path to overlay.yaml (default: ~/.config/archduino/overlay.yaml)")
cmd.MarkFlagsMutuallyExclusive("only", "skip")
return cmd
}
func resolveSelection(reg *module.Registry, cfg config.Config, only, skip []string) []string {
func resolveSelection(reg *module.Registry, cfg config.Config, only, skip []string) ([]string, error) {
all := reg.All()
known := map[string]bool{}
for _, m := range all {
known[m.Meta().ID] = true
}
// Validate every explicit ID the user passed so a typo in --only does not
// silently fall through to "run all modules".
for _, id := range only {
if !known[id] {
return nil, fmt.Errorf("unknown module in --only: %q", id)
}
}
for _, id := range skip {
if !known[id] {
return nil, fmt.Errorf("unknown module in --skip: %q", id)
}
}
disabled := map[string]bool{}
for _, d := range cfg.DisabledModules {
disabled[d] = true
@@ -156,7 +192,7 @@ func resolveSelection(reg *module.Registry, cfg config.Config, only, skip []stri
}
out = append(out, id)
}
return out
return out, nil
}
func writeState(started, ended time.Time, sum engine.Summary) error {