# Design Decisions ## Why Go? - Single binary — no external dependencies. - Small memory footprint — ~10 MB for a typical deployment. - Fast startup — < 1 second. - Simple to compile and distribute. ## Why not NGINX auth_basic? NGINX's auth_basic is simpler but doesn't support: - IP whitelisting (you'd need the geo module or a third-party module). - Temporary whitelisting (requires config reload). - API-based management. Our auth-gate service fills these gaps while keeping the architecture simple. ## Why not Authelia? Authelia is a full-featured self-hosted auth portal with: - SSO integration - 2FA - MFA - LDAP - OIDC But for our use case: - It's heavier (requires Postgres, Redis, etc.). - It doesn't support temporary whitelisting out of the box. - It requires a login portal. We wanted something simpler. ## Why not Authentik? Authentik is a full-featured identity provider with: - SSO - MFA - OIDC - LDAP - SCIM But it's too heavy for our use case. We just need IP whitelisting + Basic Auth. ## Why not Pomerium? Pomerium is a zero-trust proxy with: - OIDC - MFA - IP-based access control - Policy engine But it's overkill for our use case. We just need IP whitelisting + Basic Auth. ## Why not oauth2-proxy? oauth2-proxy is a reverse proxy with: - OIDC - SSO - MFA But it's designed for OIDC, not for IP whitelisting. ## Why not a custom script? A shell script would work but: - No type safety. - No built-in HTTP server (need to use Python/Node). - No standard library (need to install packages). - No built-in JSON encoding/decoding. Go's standard library is sufficient for our needs. ## Why SQLite? SQLite provides: - Single file — no external dependencies. - Persistence across restarts. - ACID transactions. - Simple WAL mode for concurrent reads. For a handful of IPs (dozens at most), SQLite is: - Simple to deploy. - Fast (single-file, no network). - No external dependencies. ## Why in-memory temporary whitelist? For a handful of IPs (dozens at most), an in-memory map is: - Simple to implement. - Fast (O(1) lookup). - No external dependencies. If you need persistence across restarts, add a disk-backed store later. ## Why a background cleanup goroutine? Checking expiry on every request adds latency (two map lookups per request). The cleanup goroutine is a one-time cost that keeps the hot path fast. ## Why poll instead of inotify? Polling is simpler and works inside Docker containers where inotify may not be available (no host filesystem access). ## Why not use Redis? For our use case, Redis is overkill. We chose an in-memory map + periodic cleanup.