diff --git a/src/Makefile b/src/Makefile index c5dbbc2..e82e24e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -19,7 +19,8 @@ clean: $(PLATFORMS): @echo "Building $(os)/$(arch)" - GOROOT_FINAL=Git/ GOOS=$(os) GOARCH=$(arch) GOARM=6 go build -o './dist/zoraxy_$(os)_$(arch)' -ldflags "-s -w" -trimpath + GOROOT_FINAL=Git/ GOOS=$(os) GOARCH=$(arch) $(if $(filter linux/arm,$(os)/$(arch)),GOARM=6,) go build -o './dist/zoraxy_$(os)_$(arch)' -ldflags "-s -w" -trimpath +# GOROOT_FINAL=Git/ GOOS=$(os) GOARCH=$(arch) GOARM=6 go build -o './dist/zoraxy_$(os)_$(arch)' -ldflags "-s -w" -trimpath fixwindows: diff --git a/src/main.go b/src/main.go index 797dfc1..efe933c 100644 --- a/src/main.go +++ b/src/main.go @@ -30,6 +30,7 @@ import ( "imuslab.com/zoraxy/mod/tlscert" "imuslab.com/zoraxy/mod/uptime" "imuslab.com/zoraxy/mod/utils" + "imuslab.com/zoraxy/mod/webserv" ) // General flags @@ -41,6 +42,8 @@ var ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local no var ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port") var acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)") var enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)") +var staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters") + var ( name = "Zoraxy" version = "2.6.7" @@ -73,6 +76,7 @@ var ( tcpProxyManager *tcpprox.Manager //TCP Proxy Manager acmeHandler *acme.ACMEHandler //Handler for ACME Certificate renew acmeAutoRenewer *acme.AutoRenewer //Handler for ACME auto renew ticking + staticWebServer *webserv.WebServer //Static web server for hosting simple stuffs //Helper modules EmailSender *email.Sender //Email sender that handle email sending diff --git a/src/mod/webserv/handler.go b/src/mod/webserv/handler.go new file mode 100644 index 0000000..0a4eba6 --- /dev/null +++ b/src/mod/webserv/handler.go @@ -0,0 +1,9 @@ +package webserv + +/* + Handler.go + + Handler for web server options change + web server is directly listening to the TCP port + handlers in this script are for setting change only +*/ diff --git a/src/mod/webserv/middleware.go b/src/mod/webserv/middleware.go new file mode 100644 index 0000000..1dade78 --- /dev/null +++ b/src/mod/webserv/middleware.go @@ -0,0 +1,41 @@ +package webserv + +import ( + "net/http" + "path/filepath" + "strings" + + "imuslab.com/zoraxy/mod/utils" +) + +// Convert a request path (e.g. /index.html) into physical path on disk +func (ws *WebServer) resolveFileDiskPath(requestPath string) string { + fileDiskpath := filepath.Join(ws.option.WebRoot, "html", requestPath) + + //Force convert it to slash even if the host OS is on Windows + fileDiskpath = filepath.Clean(fileDiskpath) + fileDiskpath = strings.ReplaceAll(fileDiskpath, "\\", "/") + return fileDiskpath + +} + +// File server middleware to handle directory listing (and future expansion) +func (ws *WebServer) fsMiddleware(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if ws.option.EnableDirectoryListing { + if strings.HasSuffix(r.URL.Path, "/") { + //This is a folder. Let check if index exists + if utils.FileExists(filepath.Join(ws.resolveFileDiskPath(r.URL.Path), "index.html")) { + + } else if utils.FileExists(filepath.Join(ws.resolveFileDiskPath(r.URL.Path), "index.htm")) { + + } else { + http.NotFound(w, r) + return + } + } + } + + h.ServeHTTP(w, r) + }) +} diff --git a/src/mod/webserv/utils.go b/src/mod/webserv/utils.go new file mode 100644 index 0000000..856ac1a --- /dev/null +++ b/src/mod/webserv/utils.go @@ -0,0 +1,18 @@ +package webserv + +import ( + "net" +) + +// IsPortInUse checks if a port is in use. +func IsPortInUse(port string) bool { + listener, err := net.Listen("tcp", "localhost:"+port) + if err != nil { + // If there was an error, the port is in use. + return true + } + defer listener.Close() + + // No error means the port is available. + return false +} diff --git a/src/mod/webserv/webserv.go b/src/mod/webserv/webserv.go new file mode 100644 index 0000000..6548d92 --- /dev/null +++ b/src/mod/webserv/webserv.go @@ -0,0 +1,137 @@ +package webserv + +import ( + "errors" + "fmt" + "log" + "net/http" + "os" + "path/filepath" + "sync" + + "imuslab.com/zoraxy/mod/utils" +) + +/* + Static Web Server package + + This module host a static web server +*/ + +type WebServerOptions struct { + Port string //Port for listening + EnableDirectoryListing bool //Enable listing of directory + WebRoot string //Folder for stroing the static web folders +} +type WebServer struct { + mux *http.ServeMux + server *http.Server + option *WebServerOptions + isRunning bool + mu sync.Mutex +} + +// NewWebServer creates a new WebServer instance. +func NewWebServer(options *WebServerOptions) *WebServer { + if !utils.FileExists(options.WebRoot) { + //Web root folder not exists. Create one + os.MkdirAll(filepath.Join(options.WebRoot, "html"), 0775) + os.MkdirAll(filepath.Join(options.WebRoot, "templates"), 0775) + } + return &WebServer{ + mux: http.NewServeMux(), + option: options, + isRunning: false, + mu: sync.Mutex{}, + } +} + +// ChangePort changes the server's port. +func (ws *WebServer) ChangePort(port string) error { + ws.mu.Lock() + defer ws.mu.Unlock() + + if ws.isRunning { + if err := ws.Stop(); err != nil { + return err + } + } + + ws.option.Port = port + ws.server.Addr = ":" + port + + return nil +} + +// Start starts the web server. +func (ws *WebServer) Start() error { + ws.mu.Lock() + defer ws.mu.Unlock() + + //Check if server already running + if ws.isRunning { + return fmt.Errorf("web server is already running") + } + + //Check if the port is usable + if IsPortInUse(ws.option.Port) { + return errors.New("Port already in use or access denied by host OS") + } + + //Dispose the old mux and create a new one + ws.mux = http.NewServeMux() + + //Create a static web server + fs := http.FileServer(http.Dir(filepath.Join(ws.option.WebRoot, "html"))) + ws.mux.Handle("/", ws.fsMiddleware(fs)) + + ws.server = &http.Server{ + Addr: ":" + ws.option.Port, + Handler: ws.mux, + } + + go func() { + if err := ws.server.ListenAndServe(); err != nil { + if err != http.ErrServerClosed { + fmt.Printf("Web server error: %v\n", err) + } + } + }() + + log.Println("Static Web Server started. Listeing on :" + ws.option.Port) + ws.isRunning = true + + return nil +} + +// Stop stops the web server. +func (ws *WebServer) Stop() error { + ws.mu.Lock() + defer ws.mu.Unlock() + + if !ws.isRunning { + return fmt.Errorf("web server is not running") + } + + if err := ws.server.Close(); err != nil { + return err + } + + ws.isRunning = false + + return nil +} + +// UpdateDirectoryListing enables or disables directory listing. +func (ws *WebServer) UpdateDirectoryListing(enable bool) { + ws.mu.Lock() + defer ws.mu.Unlock() + + ws.option.EnableDirectoryListing = enable + +} + +// Close stops the web server without returning an error. +func (ws *WebServer) Close() { + ws.Stop() +} diff --git a/src/start.go b/src/start.go index 3da18e6..483d9d2 100644 --- a/src/start.go +++ b/src/start.go @@ -22,6 +22,7 @@ import ( "imuslab.com/zoraxy/mod/statistic/analytic" "imuslab.com/zoraxy/mod/tcpprox" "imuslab.com/zoraxy/mod/tlscert" + "imuslab.com/zoraxy/mod/webserv" ) /* @@ -208,6 +209,21 @@ func startupSequence() { if err != nil { log.Fatal(err) } + + /* + Static Web Server + + Start the static web server + */ + + staticWebServer = webserv.NewWebServer(&webserv.WebServerOptions{ + Port: "8081", + WebRoot: *staticWebServerRoot, + EnableDirectoryListing: true, + }) + + //TODO: Connect UI element to static web server + staticWebServer.Start() } // This sequence start after everything is initialized