Overview
I’ve come across Homer in my homelabbing adventures. It’s great for navigating the various services you can run in a homelab, and I plan on using it to organize the links I use for work and share it with my team.
The documented methods on github to get started running homer are:
- Use docker
- Unzip the prebuilt tarball and host the assets with an HTTP server
Running docker on a Mac just for this felt a bit heavy-handed, so I chose to serve the assets with an HTTP server instead. Of the documented HTTP server options, the GitHub docs suggest using pnpm http-server
, python -m http.server
, or any web server. Since I work with Go a lot and it’s lightweight, I decided to use a Go HTTP server.
Serving static assets in Go is straightforward. Most boilerplate examples suggest something like the following:
package main
import (
"log"
"net/http"
)
const (
address = "localhost:1323"
dir = "./docs"
)
func main() {
fs := http.FileServer(http.Dir(dir))
http.Handle("/", fs)
log.Printf("Listening on %s...\n", address)
err := http.ListenAndServe(address, nil)
if err != nil {
log.Fatal(err)
}
}
Troubleshooting
Running the Go HTTP server didn’t work out of the box. The dashboard kept redirecting to itself for a bit before stalling. Switching between python3 -m http.server --bind localhost --directory ./docs
and running go run main.go
, the difference seemed to stem from requests to /index.html
.
The Homer dashboard relies on HEAD requests to /index.html
with a timestamp query parameter for cache invalidation. As of Go 1.24.3, the HTTP File Server implementation redirects requests ending in index.html
to /
(docs link); truncating the timestamp and causing the Homer dashboard to load improperly.
Adding the following handler to override the default index.html
behavior allows the Homer dashboard to load correctly:
http.HandleFunc("/index.html", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "HEAD" {
f, err := os.Open(path.Join(dir, "index.html"))
if err != nil {
panic(err)
}
http.ServeContent(w, r, "index.html", time.Now(), f)
} else {
http.Redirect(w, r, "/", http.StatusMovedPermanently)
}
})
Final solution
Putting it all together, here’s a complete Go HTTP server that can serve the Homer dashboard:
package main
import (
"log"
"net/http"
"os"
"path"
"time"
)
const (
address = "localhost:1323"
dir = "./docs"
)
func main() {
fs := http.FileServer(http.Dir(dir))
http.Handle("/", fs)
http.HandleFunc("/index.html", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "HEAD" {
f, err := os.Open(path.Join(dir, "index.html"))
if err != nil {
panic(err)
}
http.ServeContent(w, r, "index.html", time.Now(), f)
} else {
http.Redirect(w, r, "/", http.StatusMovedPermanently)
}
})
log.Printf("Listening on %s...\n", address)
err := http.ListenAndServe(address, nil)
if err != nil {
log.Fatal(err)
}
}