cleanup: drop pre-v0.1 migration scaffolding + legacy-behavior refs

banger hasn't shipped a public release — every "legacy", "pre-opt-in",
"previously", "migration note", "no longer" reference in the tree is
pinning against a state no real user's install has ever been in.
That scaffolding has weight: it's a coordinate system future readers
have to decode, and it keeps dead code alive.

Removed (code):
- internal/daemon/ssh_client_config.go
    - vmSSHConfigIncludeBegin / vmSSHConfigIncludeEnd constants and
      every `removeManagedBlock(existing, vm...)` call they enabled
      (legacy inline `Host *.vm` block scrub)
    - cleanupLegacySSHConfigDir (+ its caller in syncVMSSHClientConfig)
      — wiped a pre-opt-in sibling file under $ConfigDir/ssh
    - sameDirOrParent + resolvePathForComparison — only ever used
      by cleanupLegacySSHConfigDir
    - the "also check legacy marker" fallback in
      UserSSHIncludeInstalled / UninstallUserSSHInclude
- internal/store/migrations.go
    - migrateDropDeadImageColumns (migration 2) + its slice entry
    - dropColumnIfExists (orphaned after the above)
    - addColumnIfMissing + the whole "columns added across the pre-
      versioning lifetime" block at the end of migrateBaseline —
      subsumed into the baseline CREATE TABLE
    - `packages_path TEXT` column on the images table (the
      throwaway migration 2 dropped it, but there was never any
      reader)
- internal/daemon/vm.go
    - vmDNSRecordName local wrapper — was justified as "avoid
      pulling vmdns into every file"; three of four callers already
      imported vmdns directly, so inline the one stray call
- internal/cli/cli_test.go
    - TestLegacyRemovedCommandIsRejected (`tui` subcommand never
      shipped)

Removed / simplified (tests):
- ssh_client_config_test.go: dropped TestSameDirOrParentHandlesSymlinks,
  TestSyncVMSSHClientConfigPreservesUserKeyInLegacyDir,
  TestSyncVMSSHClientConfigNarrowsCleanupToLegacyFile,
  TestSyncVMSSHClientConfigLeavesUnexpectedLegacyContents,
  TestInstallUserSSHIncludeMigratesLegacyInlineBlock, plus the
  "legacy posture" regression strings in the remaining happy-path
  test; TestUninstallUserSSHIncludeRemovesBothMarkerBlocks collapsed
  to a single-block test
- migrations_test.go: dropped TestMigrateDropDeadImageColumns_AcrossInstallPaths,
  TestDropColumnIfExistsIsIdempotent; TestOpenReadOnlyDoesNotRunMigrations
  simplified to test against the baseline marker

Removed (docs):
- README.md "**Migration note.**" blockquote about the SSH-key path move
- docs/advanced.md parenthetical "(the old behaviour)"

Reworded (comments):
- Dropped "Previously this file also contained LogLevel DEBUG3..."
  history from vm_disk.go's sshdGuestConfig doc
- Dropped "Call sites that previously read vm.Runtime.{PID,...}"
  from vm_handles.go; now documents the current contract
- Dropped "Pre-v0.1 the defaults are" scaffolding in doctor_test.go
- Dropped "no longer does its own git inspection" phrasing in vm_run.go
- Dropped the "(also cleans up legacy inline block from pre-opt-in
  builds)" aside on the `ssh-config` CLI docstring
- Renamed test var `legacyKey` → `existingKey` in vm_test.go; its
  purpose was "pre-existing authorized_keys line," not banger-legacy

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thales Maciel 2026-04-23 13:56:32 -03:00
parent 5791466498
commit 700a1e6e60
No known key found for this signature in database
GPG key ID: 33112E6833C34679
16 changed files with 54 additions and 735 deletions

View file

@ -12,19 +12,9 @@ import (
"banger/internal/paths"
)
// Marker sentinels.
//
// vmSSHConfigIncludeBegin / vmSSHConfigIncludeEnd used to wrap the full
// Host *.vm stanza when banger wrote directly into ~/.ssh/config.
// We keep the sentinel strings only so uninstall can find and remove
// legacy blocks on systems that upgraded from that behaviour.
//
// The new opt-in flow writes a short Include block with its own marker
// pair; the daemon itself no longer touches ~/.ssh/config at all.
// Marker sentinels that fence the `Include` block banger writes into
// ~/.ssh/config when the user runs `banger ssh-config --install`.
const (
vmSSHConfigIncludeBegin = "# BEGIN BANGER MANAGED VM SSH"
vmSSHConfigIncludeEnd = "# END BANGER MANAGED VM SSH"
bangerSSHIncludeBegin = "# BEGIN BANGER SSH INCLUDE"
bangerSSHIncludeEnd = "# END BANGER SSH INCLUDE"
)
@ -78,11 +68,6 @@ func (d *Daemon) ensureVMSSHClientConfig() {
//
// The file lives in the banger config dir so users who manage their
// SSH config declaratively can decide how (or whether) to pull it in.
// A narrow migration step also runs here: the pre-opt-in daemon
// wrote a sibling file at $ConfigDir/ssh/ssh_config. Remove only
// that specific legacy file, then remove the enclosing directory
// only if it's empty — never os.RemoveAll, because the user may
// have pointed ssh_key_path at a key under that directory.
func syncVMSSHClientConfig(layout paths.Layout, keyPath string) error {
keyPath = strings.TrimSpace(keyPath)
if keyPath == "" {
@ -96,79 +81,12 @@ func syncVMSSHClientConfig(layout paths.Layout, keyPath string) error {
return err
}
block := renderManagedVMSSHBlock(keyPath, layout.KnownHostsPath)
if err := writeTextFileIfChanged(target, block, 0o644); err != nil {
return err
}
cleanupLegacySSHConfigDir(layout, keyPath)
return nil
}
// cleanupLegacySSHConfigDir removes the pre-opt-in sibling file at
// $ConfigDir/ssh/ssh_config and, if the directory is then empty, the
// directory itself. Skips the whole operation when ssh_key_path
// resolves under that directory — users who explicitly configured a
// key there must not have the enclosing dir yanked out from under
// them. All errors are swallowed: this is best-effort migration, not
// a hard failure mode.
func cleanupLegacySSHConfigDir(layout paths.Layout, keyPath string) {
legacyDir := filepath.Join(layout.ConfigDir, "ssh")
if sameDirOrParent(legacyDir, keyPath) {
return
}
_ = os.Remove(filepath.Join(legacyDir, "ssh_config"))
// Remove the dir only if it's now empty. os.Remove returns
// ENOTEMPTY when it isn't, which is the signal we want.
_ = os.Remove(legacyDir)
}
// sameDirOrParent reports whether dir contains path (or equals it)
// after resolving symlinks. Used to gate destructive cleanup against
// a configured key that lives inside the cleanup target — either
// directly or via a symlinked spelling of the same physical
// location. Lexical comparison alone would miss the symlink case
// and let the scrub delete a user key aliased through an symlinked
// directory.
func sameDirOrParent(dir, path string) bool {
if strings.TrimSpace(dir) == "" || strings.TrimSpace(path) == "" {
return false
}
absDir, err := resolvePathForComparison(dir)
if err != nil {
return false
}
absPath, err := resolvePathForComparison(path)
if err != nil {
return false
}
rel, err := filepath.Rel(absDir, absPath)
if err != nil {
return false
}
// filepath.Rel returns "../..." when absPath is outside absDir.
// A path inside (or equal to) the dir starts with "." or a
// non-".." prefix.
return rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator))
}
// resolvePathForComparison returns an absolute, symlink-resolved
// version of p. Falls back to filepath.Abs when EvalSymlinks errors
// — typically because p refers to a file or directory that doesn't
// exist yet, which is fine for comparison purposes: two non-existent
// paths compared lexically is the best we can do and matches the
// pre-symlink-aware behaviour.
func resolvePathForComparison(p string) (string, error) {
if resolved, err := filepath.EvalSymlinks(p); err == nil {
return resolved, nil
}
return filepath.Abs(p)
return writeTextFileIfChanged(target, block, 0o644)
}
// InstallUserSSHInclude adds an `Include <bangerSSHConfigPath>` line
// to ~/.ssh/config inside a banger-owned marker block. Idempotent:
// running it twice leaves a single block. Also strips any legacy
// inline `Host *.vm` banger block left over from the pre-opt-in
// era so the user ends up with the Include-only layout.
// running it twice leaves a single block.
func InstallUserSSHInclude(layout paths.Layout) error {
bangerConfig := BangerSSHConfigPath(layout)
if bangerConfig == "" {
@ -182,21 +100,17 @@ func InstallUserSSHInclude(layout paths.Layout) error {
if err != nil {
return err
}
stripped, err := removeManagedBlock(existing, vmSSHConfigIncludeBegin, vmSSHConfigIncludeEnd)
if err != nil {
return err
}
block := renderBangerSSHIncludeBlock(bangerConfig)
updated, err := upsertManagedBlock(stripped, bangerSSHIncludeBegin, bangerSSHIncludeEnd, block)
updated, err := upsertManagedBlock(existing, bangerSSHIncludeBegin, bangerSSHIncludeEnd, block)
if err != nil {
return err
}
return writeTextFileIfChanged(userConfigPath, updated, 0o600)
}
// UninstallUserSSHInclude removes the Include block (and any legacy
// inline Host *.vm block) from ~/.ssh/config. Idempotent: missing
// file or missing block is a no-op.
// UninstallUserSSHInclude removes the Include block from
// ~/.ssh/config. Idempotent: missing file or missing block is a
// no-op.
func UninstallUserSSHInclude() error {
userConfigPath, err := userSSHConfigPath()
if err != nil {
@ -213,16 +127,12 @@ func UninstallUserSSHInclude() error {
if err != nil {
return err
}
updated, err = removeManagedBlock(updated, vmSSHConfigIncludeBegin, vmSSHConfigIncludeEnd)
if err != nil {
return err
}
return writeTextFileIfChanged(userConfigPath, updated, 0o600)
}
// UserSSHIncludeInstalled reports whether ~/.ssh/config contains
// either the new Include block or a legacy inline banger block.
// Used by `ssh-config` (status readout) and `doctor`.
// UserSSHIncludeInstalled reports whether ~/.ssh/config contains the
// banger Include block. Used by `ssh-config` (status readout) and
// `doctor`.
func UserSSHIncludeInstalled() (bool, error) {
userConfigPath, err := userSSHConfigPath()
if err != nil {
@ -232,13 +142,7 @@ func UserSSHIncludeInstalled() (bool, error) {
if err != nil {
return false, err
}
if strings.Contains(existing, bangerSSHIncludeBegin) {
return true, nil
}
if strings.Contains(existing, vmSSHConfigIncludeBegin) {
return true, nil
}
return false, nil
return strings.Contains(existing, bangerSSHIncludeBegin), nil
}
func userSSHConfigPath() (string, error) {