| name | navigator |
| description | Working with the Navigator Go submodule for web server fixes and enhancements. Use when deployment plans require Navigator changes, config parsing issues arise, or new routing/proxy behavior is needed. |
Navigator Submodule Development
Why Navigator is a Submodule
Navigator is included as a Git submodule because showcase routinely needs Navigator fixes to implement deployment plans. Changes are tested in showcase context before being pushed upstream.
Location: navigator/ (Git submodule)
Language: Go
Purpose: Multi-tenant web server with framework independence
Project Structure
navigator/
├── cmd/navigator/ # Production Navigator implementation
├── internal/ # Modular packages
│ ├── config/ # Configuration loading and parsing
│ ├── server/ # HTTP handling, routing, static files
│ ├── auth/ # Authentication (htpasswd)
│ ├── process/ # Web app lifecycle management
│ └── proxy/ # Reverse proxy and Fly-Replay
└── docs/ # MkDocs documentation
Critical Architecture: Configuration Flow
Understanding this flow is essential for fixing config-related bugs:
- YAML file (user-facing config)
- YAMLConfig struct in
types.go (mirrors YAML structure with yaml tags)
- ConfigParser in
parser.go (converts YAML to internal format)
- Config struct in
types.go (optimized internal representation)
Common Bug Pattern: When adding config fields, developers often:
- ✅ Add field to
Config struct (internal)
- ❌ FORGET to add field to
YAMLConfig struct
- ❌ FORGET to add parsing logic in
parser.go
- Result: YAML config is valid but silently ignored!
Request Flow in Handler (Order Matters!)
1. Health checks
2. Authentication
3. Rewrites/redirects
4. CGI scripts
5. Reverse proxies
6. Static files
7. Maintenance mode
8. Web app proxy
Security Critical: Health checks MUST come before auth. Auth MUST come before tenant routing.
Common Development Tasks
Adding a New Config Option
Example: Health Check Config (Recent Fix)
Step 1: Add to YAMLConfig in internal/config/types.go:
type YAMLConfig struct {
Server struct {
HealthCheck HealthCheckConfig `yaml:"health_check"`
} `yaml:"server"`
}
Step 2: Add to internal Config (if different structure needed):
type Config struct {
Server struct {
HealthCheck HealthCheckConfig
}
}
Step 3: Add parsing logic in internal/config/parser.go:
func (p *ConfigParser) parseServerConfig() {
p.config.Server.HealthCheck = p.yamlConfig.Server.HealthCheck
}
Step 4: Add tests in internal/config/parser_test.go:
func TestConfigParser_ParseHealthCheck(t *testing.T) {
yamlConfig := YAMLConfig{}
yamlConfig.Server.HealthCheck = HealthCheckConfig{
Path: "/up",
Response: &HealthCheckResponse{Status: 200, Body: "OK"},
}
parser := NewConfigParser(&yamlConfig)
config, err := parser.Parse()
if config.Server.HealthCheck.Path != "/up" {
t.Errorf("HealthCheck not parsed correctly")
}
}
Step 5: Add integration tests in internal/server/handler_test.go (or appropriate file).
Adding Handler Behavior
Example: Health Check Handler (Recent Fix)
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if h.config.Server.HealthCheck.Path != "" && r.URL.Path == h.config.Server.HealthCheck.Path {
h.handleHealthCheck(recorder, r)
return
}
}
Integration Test (Security Critical):
func TestHandler_ServeHTTP_HealthCheckBeforeAuth(t *testing.T) {
cfg := &config.Config{}
cfg.Server.HealthCheck = config.HealthCheckConfig{
Path: "/up",
Response: &config.HealthCheckResponse{Status: 200, Body: "OK"},
}
cfg.Auth.Enabled = true
basicAuth := &auth.BasicAuth{}
handler := CreateTestHandler(cfg, nil, basicAuth, nil)
req := httptest.NewRequest("GET", "/up", nil)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, req)
if recorder.Code != 200 {
t.Errorf("Health check should bypass auth")
}
}
Testing Requirements
Test Checklist
Running Tests
go test ./...
go test -v ./internal/config/
go test -v ./internal/server/
go test -cover ./...
gofmt -s -l . && \
golangci-lint run && \
go vet ./... && \
go test -race -cover -timeout=3m ./... && \
go build ./cmd/navigator-refactored && \
echo "✓ All CI checks passed!"
Recent Fixes (Nov 2024)
Fix 1: CGI reload_config Not Parsed
Problem: reload_config field in CGI scripts ignored
Cause: Field missing from YAMLConfig.Server.CGIScripts
Fix: Added field to CGIScriptConfig struct
Lesson: Always check YAMLConfig has all fields with yaml tags
Fix 2: Health Checks Not Working
Problem: /up returned 401 (auth) or started index tenant unnecessarily
Cause:
YAMLConfig.Server missing HealthCheck field
parseServerConfig() not copying health check config
Fix:
HealthCheck HealthCheckConfig `yaml:"health_check"`
p.config.Server.HealthCheck = p.yamlConfig.Server.HealthCheck
Test Coverage: 270 lines added
- Parser tests: YAML → Config transformation
- Handler tests: before auth, different paths, custom headers
- Security test: health checks bypass authentication
Common Bug Patterns
Pattern 1: Config Parser Forgot to Copy Field
type YAMLConfig struct {
Server struct {
HealthCheck HealthCheckConfig `yaml:"health_check"`
}
}
func (p *ConfigParser) parseServerConfig() {
}
p.config.Server.HealthCheck = p.yamlConfig.Server.HealthCheck
How to catch: Write parser tests that verify field is copied.
Pattern 2: Missing yaml Tag
type ServerConfig struct {
Listen string
}
type ServerConfig struct {
Listen string `yaml:"listen"`
}
Pattern 3: Wrong Handler Order
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.handleWebAppProxy(w, r)
if !h.auth.CheckAuth(r) {
return
}
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if h.config.Server.HealthCheck.Path != "" { }
isPublic := auth.ShouldExcludeFromAuth(r.URL.Path, h.config)
needsAuth := h.auth.IsEnabled() && !isPublic
if needsAuth && !h.auth.CheckAuth(r) {
h.auth.RequireAuth(recorder)
return
}
h.handleWebAppProxy(w, r)
}
How to catch: Write integration tests that verify security (auth bypass scenarios).
Deployment Workflow
- Rebase submodule on main before making changes:
cd navigator
git fetch origin
git rebase origin/main
- Work in Navigator submodule:
go test ./...
golangci-lint run
git add -A
git commit -m "Fix: health check parsing"
- Test in showcase context:
cd ..
- Push to both repos:
cd navigator
git push origin HEAD:refs/heads/main
cd ..
git add navigator
git commit -m "Update navigator: fix health check parsing"
git push
Note: If submodule is in detached HEAD state (common after git submodule update), the rebase step ensures you're building on the latest main branch before making changes.
Quick Reference Commands
cd navigator
go build -o bin/navigator cmd/navigator
make build
./bin/navigator config/navigator.yml
kill -HUP $(cat /tmp/navigator.pid)
LOG_LEVEL=debug ./bin/navigator config/navigator.yml
When to Fix Navigator
You need Navigator changes when:
- ✅ Deployment plan requires new config options
- ✅ New routing/proxy behavior needed
- ✅ Authentication/security enhancements required
- ✅ Config not being parsed correctly (check YAMLConfig + parser)
- ✅ Request flow order needs adjustment
Pattern: Fix Navigator first, test in showcase, then continue deployment plan.
Essential Files
navigator/CLAUDE.md - Comprehensive development guide (read first!)
internal/config/types.go - All config structures (YAMLConfig + Config)
internal/config/parser.go - YAML → Config conversion
internal/server/handler.go - Main HTTP request routing
Getting Help
- Read
navigator/CLAUDE.md for comprehensive guide
- Check
navigator/docs/ for user documentation
- Search tests for similar patterns
- Review Git history for "Fix:" commits
- Run with
LOG_LEVEL=debug to see what's happening