package felhomsshd import "fmt" // Candidates is the ordered OOB-port candidate list (spike §2). First free wins; NEVER :22 or a // random port. Package-var (not const) so tests can shrink it. var Candidates = []int{8822, 2222, 8022, 62222} // ErrPortsExhausted is returned when every candidate is busy — a LOUD failure [SF-4/trap 6], never a // silent fallback to :22 or a random high port. var ErrPortsExhausted = fmt.Errorf("felhomsshd: all candidate OOB ports are busy — refusing to fall back to :22 or a random port") // portProbe reports whether a TCP port is free (nothing listening AND a real bind succeeds). Injected // for tests; production impl = probeFree (ss + net.Listen). type portProbe func(port int) bool // claimPort returns the OOB port, mirroring the spike's shell algorithm: // - if a persisted port exists AND is still free → keep it (idempotent, no thrash), // - else the FIRST free candidate → persist + return, // - else ErrPortsExhausted (LOUD). // // persist writes PortFile; readPersisted reads it. isFree is the probe. All injected for tests. func claimPort(candidates []int, isFree portProbe, readPersisted func() (int, bool), persist func(int) error) (int, error) { if cur, ok := readPersisted(); ok && cur != 22 && isFree(cur) { return cur, nil } for _, p := range candidates { if p == 22 { continue // defensive: never :22 } if isFree(p) { if err := persist(p); err != nil { return 0, fmt.Errorf("felhomsshd: persist claimed port %d: %w", p, err) } return p, nil } } return 0, ErrPortsExhausted }