8.9 KiB
TASK.md — Controller Refactoring: Template Split, Server Decomposition, Domain Rename
Goal: Improve code organization for maintainability and Claude Code efficiency. No new features — purely structural refactoring + one config rename.
Version bump: v0.3.0 (structural refactor milestone)
Task 1: Split templates.go → go:embed (HIGH PRIORITY)
The templates.go file contains ALL HTML templates and CSS as Go string constants.
The file itself says: "As the UI grows, switch to go:embed for easier editing."
With 7 templates + full CSS, it's time.
1.1 — Create template files directory
Create controller/internal/web/templates/ with individual files:
internal/web/templates/
├── layout.html ← from layoutTmpl const
├── dashboard.html ← from dashboardTmpl const
├── stacks.html ← from stacksTmpl const (the stacks list page, NOT the old dashboard list)
├── login.html ← from loginTmpl const
├── logs.html ← from logsTmpl const
├── deploy.html ← from deployTmpl const
├── app_info.html ← from appInfoTmpl const
└── style.css ← from cssTemplate const
Each .html file should contain ONLY the template content (the {{define "name"}}...{{end}} block).
Keep the existing template names (layout_start, layout_end, dashboard, stacks, login, etc.).
1.2 — Create embed.go
Create controller/internal/web/embed.go:
package web
import "embed"
//go:embed templates/*.html templates/*.css
var templateFS embed.FS
1.3 — Update template loading in server.go (or the new funcmap.go, see Task 2)
Replace the current loadTemplates() method that parses the allTemplates const:
func (s *Server) loadTemplates() {
funcMap := template.FuncMap{ /* ... existing funcs ... */ }
s.tmpl = template.Must(
template.New("").Funcs(funcMap).ParseFS(templateFS, "templates/*.html"),
)
}
CSS serving: Instead of the cssTemplate const, read from templateFS:
func (s *Server) serveCSSHandler(w http.ResponseWriter, r *http.Request) {
data, err := templateFS.ReadFile("templates/style.css")
if err != nil {
http.Error(w, "CSS not found", 500)
return
}
w.Header().Set("Content-Type", "text/css; charset=utf-8")
w.Header().Set("Cache-Control", "public, max-age=3600")
w.Write(data)
}
Register this handler for /static/style.css in ServeHTTP (replace the current inline CSS serving).
1.4 — Delete old string constants
Remove from templates.go:
const allTemplates = ...const layoutTmpl = ...const dashboardTmpl = ...const stacksTmpl = ...const loginTmpl = ...const logsTmpl = ...const deployTmpl = ...const appInfoTmpl = ...const cssTemplate = ...
After this, templates.go should either be empty (delete it) or contain only the
felhom logo SVG constant if that's still embedded as a string (keep that one — it's small).
1.5 — Verify the build
go build ./cmd/controller/must succeedgo:embedrequires Go 1.16+ (we're on 1.22, fine)- Templates are still compiled into the binary — zero runtime file dependencies (same as before)
- Verify that the HTML files actually include the
{{define "name"}}...{{end}}wrappers (ParseFS needs them to register template names)
Important notes
- The
<link rel="stylesheet" href="/static/style.css">in layout.html already exists, so CSS loading via the/static/style.cssroute should already work — just make sure the handler reads from embed.FS instead of serving the const. - The felhom logo SVG can stay as a Go const (it's small) or move to
templates/felhom-logo.svgand be served from embed.FS too. Either approach is fine.
Task 2: Split server.go into focused files (MEDIUM PRIORITY)
Currently server.go handles: Server struct, auth/sessions, page handlers, template FuncMap,
asset serving, and HTTP routing. Split into:
2.1 — Create auth.go
Move from server.go to internal/web/auth.go:
type session structconst sessionCookieName,const sessionMaxAgeRequireAuth()middleware methodloginHandler(),loginPostHandler(),logoutHandler()createSession(),isValidSession(),cleanupSessions()renderLogin()helper
2.2 — Create handlers.go
Move from server.go to internal/web/handlers.go:
baseData()helperdashboardHandler()stacksHandler()deployPageHandler()deployPagePostHandler()(if it exists as separate handler)appDetailHandler()logsPageHandler()
2.3 — Create funcmap.go
Move from server.go to internal/web/funcmap.go:
- The entire
template.FuncMapdefinition fromloadTemplates() - Extract it as a standalone function:
func (s *Server) templateFuncMap() template.FuncMap - Then
loadTemplates()becomes a clean 3-liner callingtemplateFuncMap()+ParseFS
2.4 — Keep in server.go
After the split, server.go should contain only:
type Server structfunc NewServer()func (s *Server) loadTemplates()(now a 3-liner)func (s *Server) ServeHTTP()(HTTP routing dispatch)func (s *Server) render()helper- Static file/asset serving handlers (
serveStaticFile,serveCSSHandler,serveLogoHandler)
2.5 — Verify the split
All files are in package web — no import changes needed within the package.
The Server struct and all its methods are accessible across files in the same package.
Run go build ./cmd/controller/ to verify everything compiles.
Task 3: Rename controller domain from dashboard.* to felhom.* (LOW PRIORITY)
3.1 — Update controller's docker-compose.yml
In controller/docker-compose.yml, change the Traefik label:
# OLD:
- "traefik.http.routers.controller.rule=Host(`dashboard.${DOMAIN}`)"
# NEW:
- "traefik.http.routers.controller.rule=Host(`felhom.${DOMAIN}`)"
3.2 — Update docker-setup.sh
In controller/scripts/docker-setup.sh, update print_summary() output:
- Any reference to
dashboard.${BASE_DOMAIN}→felhom.${BASE_DOMAIN}
Also check install_controller() if it generates compose files or prints URLs.
3.3 — Update controller.yaml.example
If there's any reference to dashboard.* in the example config or comments, update to felhom.*.
3.4 — Update documentation
In CLAUDE.md build/deploy workflow sections, update any dashboard. references to felhom..
3.5 — Cloudflare Tunnel public hostname (MANUAL — not code)
Reminder for Viktor: After deploying, manually update the Cloudflare Tunnel public hostname in the Zero Trust dashboard:
- Old:
dashboard.demo-felhom.eu→ Traefik - New:
felhom.demo-felhom.eu→ Traefik
3.6 — Pi-hole DNS (MANUAL — not code)
Reminder for Viktor: If there's a pi-hole local DNS record for dashboard.demo-felhom.eu,
update it to felhom.demo-felhom.eu (or rely on the wildcard *.demo-felhom.eu record).
Task 4: Update documentation
4.1 — README.md
- Update directory structure to show
internal/web/templates/directory - Update "Tech stack" section: "Templates: go:embed HTML files" instead of "Go string constants"
- Mention the
felhom.*subdomain for the controller - Update file tree showing the new files (embed.go, auth.go, handlers.go, funcmap.go)
4.2 — CLAUDE.md
- Update workspace layout to reflect the new
internal/web/file structure - Update "Tech stack" section
- Update any
dashboard.*references tofelhom.* - Note the go:embed pattern for future template additions
4.3 — CONTEXT.md
- Add session entry documenting the refactoring
- Note: templates moved from Go string constants to go:embed HTML files
- Note: server.go split into auth.go, handlers.go, funcmap.go
- Note: controller domain changed from dashboard.* to felhom.*
- Update version to v0.3.0
4.4 — BUILDING.md
- Update the structure check in build.sh verification section if needed
(the
internal/web/templates/directory should exist now)
Implementation order
- Task 1 (templates.go → go:embed) — do this first, biggest impact
- Task 2 (server.go split) — do this second, leverages the cleaner templates
- Task 3 (domain rename) — small, do last
- Task 4 (docs) — update after all code changes
Verification checklist
go build ./cmd/controller/compiles successfully- All 7 HTML templates render correctly (login, dashboard, stacks, deploy, app_info, logs, layout)
- CSS loads at
/static/style.css - Felhom logo SVG loads at
/static/felhom-logo.svg - App logos/screenshots still serve from
/assets/ - Auth (login/logout/session) works unchanged
- Stack operations (start/stop/deploy) work unchanged
- Controller accessible at
felhom.demo-felhom.eu(after CF tunnel update) - No broken links or template errors in browser console
- Build + push via build.sh works
- Deploy on demo-felhom works