Go library
The pkg/s3site package provides the site manager, HTTP handler, hosted-site config loader, and local control server.
Install
go get github.com/rhnvrm/s3site@latest
SiteManager
SiteManager handles discovery, downloading, extraction, and hot-reloading of sites.
Discovery mode
sm := s3site.NewSiteManager(s3site.SiteManagerConfig{
S3: s3Client,
Bucket: "my-bucket",
Prefix: "sites/",
Interval: 30 * time.Second,
Logger: slog.Default(),
Storage: s3site.StorageMemory,
})
if err := sm.Validate(); err != nil {
panic(err)
}
go sm.Start(ctx)
Hosted mode
sites, err := s3site.LoadHostedSites("/etc/s3site/sites.json", "sites/")
if err != nil {
panic(err)
}
sm := s3site.NewSiteManager(s3site.SiteManagerConfig{
S3: s3Client,
Bucket: "my-bucket",
Prefix: "sites/",
Interval: 10 * time.Minute,
Logger: slog.Default(),
Storage: s3site.StorageDisk,
DataDir: "/var/lib/s3site",
HostedSites: sites,
})
if err := sm.Validate(); err != nil {
panic(err)
}
go sm.Start(ctx)
go s3site.StartControlServer(ctx, "/run/s3site/control.sock", sm, slog.Default())
Hosted config format:
{
"sites": [
{ "hostname": "rohanverma.net" },
{ "hostname": "oddship.net", "key": "sites/oddship.net.tar.gz" }
]
}
If key is omitted, it defaults to prefix + hostname + ".tar.gz".
Methods
| Method | Returns | Description |
|---|---|---|
Validate() |
error |
Validate manager configuration before starting. |
Start(ctx) |
error |
Run the sync loop. Blocks until context cancellation. |
Ready() |
bool |
Reports whether the initial sync attempt has completed. |
HostedMode() |
bool |
Reports whether explicit hosted sites are configured. |
RefreshHosts(hosts) |
error |
Force refresh for declared hosted sites. |
GetSite(hostname) |
fs.FS |
Get the filesystem for a hostname. Returns nil if not found. |
Hostnames() |
[]string |
List all loaded hostnames. |
S3() |
*simples3.S3 |
The underlying S3 client. |
Bucket() |
string |
The configured bucket. |
Prefix() |
string |
The configured key prefix. |
Handler
Handler returns an http.Handler that routes requests by normalized Host header to the matching site.
handler := s3site.Handler(sm, logger, "")
http.ListenAndServe(":8080", handler)
Parameters:
sm-- the SiteManagerlogger-- an*slog.Logger(nil for default)adminHost-- hostname for the legacy browser admin UI (empty string to disable)
When adminHost is set, requests to that host get the admin API. All other hosts are routed to their site's fs.FS via http.FileServerFS.
Local control server
StartControlServer exposes a local-only control plane over a unix socket.
Endpoints:
GET /healthPOST /refresh
go s3site.StartControlServer(ctx, "/run/s3site/control.sock", sm, logger)
Custom handler
If you need custom routing logic, use GetSite directly:
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
siteFS := sm.GetSite(r.Host)
if siteFS == nil {
http.Error(w, "not found", 404)
return
}
http.FileServerFS(siteFS).ServeHTTP(w, r)
})
Filesystem and extraction safety
In memory mode, each site is a memFS that implements fs.FS, fs.StatFS, and fs.ReadFileFS.
In disk mode, sites use os.DirFS backed by versioned extraction directories. The previous on-disk version is retained for one activation to reduce swap races.
Extraction rejects path traversal and enforces archive file-count and total-size limits.