diff --git a/docs/configuration.md b/docs/configuration.md index 8137e72..8ddf74a 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -179,13 +179,14 @@ Rules: outside `10000-29999` — only the overlap with the usable domain counts, and a fully-outside range contributes nothing. - Values are **auto-aligned to whole hundreds**: the low end rounds up to a - block start and the high end rounds down then extends to `+99`, so the - effective range always ends in `...99`, never `...00`. For example - `10001-29998` becomes `10100-29999`. + block start and the high end rounds down to a block end (`...99 <= B`), so the + effective range always ends in `...99`, never `...00`, and never spills past + the requested upper bound. For example `10001-29998` becomes `10100-29899`, + and `10000-15000` becomes `10000-14999`. - At least one whole 100-port block must remain (the value is rejected otherwise) — a range that cannot host even one container is an error. - Capacity follows the ranges: `10000-29999` allows **200** containers; - `10000-20000, 25000-30000` allows **151**; and so on. The interactive prompt + `10000-20000, 25000-30000` allows **150**; and so on. The interactive prompt echoes the computed capacity for confirmation. - A narrowed set reserves far fewer host user ports, so an install's port-occupancy check scans only the configured ranges — not the whole diff --git a/scripts/00-ip-ask.sh b/scripts/00-ip-ask.sh index e2dfb50..618fc24 100644 --- a/scripts/00-ip-ask.sh +++ b/scripts/00-ip-ask.sh @@ -315,8 +315,8 @@ PY # comma-separated) and the container capacity on one line ("canon|cap"). Exit 0 # only when the value yields at least one usable 100-port block. Mirrors # cfg.ParseUserPorts: low end rounds up to a block start, high end rounds down -# then +99 (never ends in ...00); ranges outside 10000-29999 are clamped, and -# fully-outside ranges contribute nothing. +# to a block end <= b (never ends in ...00, never exceeds user's b); ranges +# outside 10000-29999 are clamped, and fully-outside ranges contribute nothing. normalize_user_ports(){ python3 - "$1" <<'PY' import sys @@ -337,7 +337,7 @@ def norm(s): if a > b: continue lo = ((a + 99) // 100) * 100 - hi = (b // 100) * 100 + 99 + hi = ((b + 1) // 100) * 100 - 1 if lo > hi: continue out.append([lo, hi]) diff --git a/src/internal/cfg/cfg.go b/src/internal/cfg/cfg.go index 0847f36..c2de403 100644 --- a/src/internal/cfg/cfg.go +++ b/src/internal/cfg/cfg.go @@ -386,9 +386,8 @@ func LegacySlotRangeToUserPorts(slotRange string) (string, error) { // - Lo/hi may extend outside [UserPortBase, UserPortMax] — only the overlap // with the usable domain counts (a range fully outside contributes nothing). // - Each range is aligned inward to whole hundreds: the low end rounds UP to -// a block start, the high end rounds DOWN to a block start then extends to -// +99 (so the effective range always ends in ...99, never ...00). A range -// narrower than one block is dropped. +// a block start, the high end rounds DOWN to a block end (never exceeding +// b, ending in ...99). A range narrower than one block is dropped. // - Overlapping/adjacent ranges are merged, so capacity is never double-counted. // - At least one usable 100-port block must remain, or the value is rejected // (a range that cannot host even one container is an error). @@ -421,9 +420,9 @@ func ParseUserPorts(s string) ([]PortRange, error) { if a > b { continue } - // Align inward: lo up to a block start, hi down to a block start then +99. + // Align inward: lo rounds UP to a block start, hi rounds DOWN to a block end <= b. lo := ((a + PortsPerUser - 1) / PortsPerUser) * PortsPerUser - hi := (b/PortsPerUser)*PortsPerUser + PortsPerUser - 1 + hi := ((b + 1) / PortsPerUser) * PortsPerUser - 1 if lo > hi { continue // narrower than one block } @@ -448,7 +447,7 @@ func ParseUserPorts(s string) ([]PortRange, error) { } // CanonicalUserPorts renders parsed ranges as their canonical comma-separated -// string form ("10000-20099, 25000-29999"). Returns the raw input unchanged if +// string form ("10000-19999, 25000-29999"). Returns the raw input unchanged if // it does not parse. func CanonicalUserPorts(s string) string { rs, err := ParseUserPorts(s) diff --git a/src/internal/cfg/cfg_test.go b/src/internal/cfg/cfg_test.go index 7e322d5..20c4bbb 100644 --- a/src/internal/cfg/cfg_test.go +++ b/src/internal/cfg/cfg_test.go @@ -266,17 +266,19 @@ func TestParseUserPorts(t *testing.T) { canon string // canonical form; "" = must be rejected }{ {"10000-29999", 200, "10000-29999"}, // default full range - {"10000-20000", 101, "10000-20099"}, // hi rounds down then +99 (never ends in 00) - {"10001-29998", 199, "10100-29999"}, // inward-aligned both ends + {"10000-20000", 100, "10000-19999"}, // hi rounds down to block end <= b (never exceeds b) + {"10000-15000", 50, "10000-14999"}, // boundary test: 15000 rounds down to 14999, not 15099 + {"10001-29998", 198, "10100-29899"}, // inward-aligned both ends (29998 rounds down to 29899) + {"10001-29999", 199, "10100-29999"}, // inward-aligned low end only {"10001-10099", 0, ""}, // narrower than one block {"10000-10099", 1, "10000-10099"}, // exactly one block - {"10000-20000, 25000-30000", 151, "10000-20099, 25000-29999"}, // discontiguous - {"10000-15000, 14000-20000", 101, "10000-20099"}, // overlapping → merged - {"5000-15000", 51, "10000-15099"}, // lo below domain clamps to 10000 + {"10000-20000, 25000-30000", 150, "10000-19999, 25000-29999"}, // discontiguous + {"10000-15000, 14000-20000", 100, "10000-19999"}, // overlapping → merged + {"5000-15000", 50, "10000-14999"}, // lo below domain clamps to 10000 {"20000-40000", 100, "20000-29999"}, // hi above domain clamps to 29999 - {"10000-10000", 1, "10000-10099"}, // single port still yields its block + {"10000-10000", 0, ""}, // single port has no whole block {"5000-6000", 0, ""}, // fully outside domain - {"10000-20000, 21000-21099", 102, "10000-20099, 21000-21099"}, // adjacent not merged (gap 100) + {"10000-20000, 21000-21099", 101, "10000-19999, 21000-21099"}, // adjacent not merged (gap 100) } for _, c := range cases { rs, err := ParseUserPorts(c.in) @@ -329,8 +331,8 @@ func TestUserPortCount(t *testing.T) { t.Errorf("default UserPortCount() = %d, want 200", n) } cfg.Net.UserPorts = "10000-20000, 25000-30000" - if n := cfg.UserPortCount(); n != 151 { - t.Errorf("UserPortCount() = %d, want 151", n) + if n := cfg.UserPortCount(); n != 150 { + t.Errorf("UserPortCount() = %d, want 150", n) } cfg.Net.UserPorts = "garbage" if n := cfg.UserPortCount(); n != 200 { @@ -346,8 +348,8 @@ func TestFillAutoUserPortsEnv(t *testing.T) { if err := c.FillAuto(); err != nil { t.Fatalf("FillAuto: %v", err) } - if c.Net.UserPorts != "10000-20099, 25000-29999" { - t.Errorf("user_ports = %q, want canonical 10000-20099, 25000-29999", c.Net.UserPorts) + if c.Net.UserPorts != "10000-19999, 25000-29999" { + t.Errorf("user_ports = %q, want canonical 10000-19999, 25000-29999", c.Net.UserPorts) } // A bad env value must fail loudly, not silently default.