diff --git a/docs/plugins/cssOptimizer.go b/docs/plugins/cssOptimizer.go index f6147a0..fe6dbb9 100644 --- a/docs/plugins/cssOptimizer.go +++ b/docs/plugins/cssOptimizer.go @@ -106,7 +106,6 @@ func optimizeCss(htmlContent []byte) ([]byte, error) { endIndex += startIndex + 6 codeSegment := originalHTMLContent[startIndex : endIndex+7] // Include - fmt.Println(">>>>", codeSegment) if !strings.Contains(codeSegment, "class=") { replacement := strings.Replace(codeSegment, "", "", 1) replacement = strings.Replace(replacement, "", "", 1) diff --git a/docs/plugins/docs/1. Introduction/1. What is Zoraxy Plugin.md b/docs/plugins/docs/1. Introduction/1. What is Zoraxy Plugin.md index 1ef835a..3e79889 100644 --- a/docs/plugins/docs/1. Introduction/1. What is Zoraxy Plugin.md +++ b/docs/plugins/docs/1. Introduction/1. What is Zoraxy Plugin.md @@ -1,4 +1,9 @@ # What is Zoraxy Plugin? + +Last Update: 25/05/2025 + +--- + Zoraxy Plugin is a powerful extension feature designed to enhance the functionality of the Zoraxy system. It provides additional features and capabilities that are not part of the core system, allowing users to customize their experience and optimize performance. The plugin is built to be modular and flexible, enabling users to tailor their Zoraxy environment to meet specific needs. Zoraxy plugins are distributed as binaries, and developers have the flexibility to choose whether to open source them or not **as the plugin library and interface are open source under the LGPL license**. @@ -7,7 +12,7 @@ There are two primary types of plugins: - **Router plugins**: Involved with connections from HTTP proxy rules. - **Utility plugins**: Provide user interfaces for various network features that operate independently of the Zoraxy core. ---- +--- ## How plugins are distributed & installed Zoraxy plugins are distributed as platform-dependent binaries, tailored to specific operating systems and CPU architectures. These binaries follow a naming convention that includes the operating system, CPU architecture, and plugin name, such as `linux_amd64_foobar`, `windows_amd64_foobar.exe`, or `linux_arm64_foobar`. @@ -18,7 +23,7 @@ To manually install a plugin for testing, place the binary file into the `/plugi For distribution, a plugin store system is used. The plugin store architecture is similar to the one built into the Arduino IDE, with a manager URL (a JSON file) listing all the plugins supported by that store. See the documentation section for more details on how to implement your own plugin store. ---- +--- ## Plugin vs Pull Request The Zoraxy plugin was introduced to address specific use cases that enhance its functionality. It serves as an extension to the core Zoraxy system, providing additional features and capabilities while maintaining the integrity of the core system. diff --git a/docs/plugins/docs/1. Introduction/2. Getting Started.md b/docs/plugins/docs/1. Introduction/2. Getting Started.md index 4be9aa9..1d02cab 100644 --- a/docs/plugins/docs/1. Introduction/2. Getting Started.md +++ b/docs/plugins/docs/1. Introduction/2. Getting Started.md @@ -1,5 +1,9 @@ # Getting Started +Last Update: 25/05/2025 + +--- + To start developing plugins, you will need the following installed on your computer 1. The source code of Zoraxy diff --git a/docs/plugins/docs/1. Introduction/3. Installing Plugin.md b/docs/plugins/docs/1. Introduction/3. Installing Plugin.md new file mode 100644 index 0000000..cf35531 --- /dev/null +++ b/docs/plugins/docs/1. Introduction/3. Installing Plugin.md @@ -0,0 +1,26 @@ +# Installing Plugin + +Last Update: 25/05/2025 + +--- + +### Install via Plugin Store + +(Work in progress) + +### Manual Install + +The plugin shall be placed inside the `plugins/{{plugin_name}}/` directory where the binary executable name must be matching with the plugin name. + +If you are on Linux, also make sure Zoraxy have the execution permission of the plugin. You can use the following command to enable execution of the plugin binary on Linux with the current user (Assume Zoraxy is run by the current user) + +```bash +cd ./plugins/{{plugin_name}}/ +chmod +x ./{{plugin_name}} +``` + + + +Sometime plugins might come with additional assets other than the binary file. If that is the case, extract all of the plugins content into the folder with the plugin's name. + +After the folder structure is ready, restart Zoraxy. If you are using systemd for Zoraxy, use `sudo systemctl restart zoraxy` to restart Zoraxy via systemd service. \ No newline at end of file diff --git a/docs/plugins/docs/1. Introduction/4. Enable Plugins.md b/docs/plugins/docs/1. Introduction/4. Enable Plugins.md new file mode 100644 index 0000000..d1aa702 --- /dev/null +++ b/docs/plugins/docs/1. Introduction/4. Enable Plugins.md @@ -0,0 +1,40 @@ +# Enable Plugins + +Last Update: 25/05/2025 + +--- + +To enable and assign a plugin to a certain HTTP Proxy Rule, you will need to do the following steps + +## 1. Create a tag for your HTTP Proxy Rules + +Let say you want to enable debugger on some of your HTTP Proxy Rules. You can do that by first creating a tag in the tag editor. In the example below, we will be using the tag "debug". After adding the tag to the HTTP Proxy rule, you will see something like this. + + + +![image-20250527193748456](img/3. Enable Plugins/image-20250527193748456.png) + +--- + +## 2. Enable Plugin + +Click on the "Enable" button on the plugin which you want to enable + +![image-20250527193601017](img/3. Enable Plugins/image-20250527193601017.png) + +--- + +## 3. Assign Plugin to HTTP Proxy Rule + +Finally, select the tag that you just created in the dropdown menu + +![image-20250527194052408](img/3. Enable Plugins/image-20250527194052408.png) + + + +Afterward, you will see the plugin is attached to the target tag + +![image-20250527195703464](img/3. Enable Plugins/image-20250527195703464.png) + +It means the plugin is enabled on the HTTP proxy rule + diff --git a/docs/plugins/docs/1. Introduction/img/3. Enable Plugins/image-20250527193601017.png b/docs/plugins/docs/1. Introduction/img/3. Enable Plugins/image-20250527193601017.png new file mode 100644 index 0000000..d32e67b Binary files /dev/null and b/docs/plugins/docs/1. Introduction/img/3. Enable Plugins/image-20250527193601017.png differ diff --git a/docs/plugins/docs/1. Introduction/img/3. Enable Plugins/image-20250527193748456.png b/docs/plugins/docs/1. Introduction/img/3. Enable Plugins/image-20250527193748456.png new file mode 100644 index 0000000..40e248d Binary files /dev/null and b/docs/plugins/docs/1. Introduction/img/3. Enable Plugins/image-20250527193748456.png differ diff --git a/docs/plugins/docs/1. Introduction/img/3. Enable Plugins/image-20250527194052408.png b/docs/plugins/docs/1. Introduction/img/3. Enable Plugins/image-20250527194052408.png new file mode 100644 index 0000000..4336e78 Binary files /dev/null and b/docs/plugins/docs/1. Introduction/img/3. Enable Plugins/image-20250527194052408.png differ diff --git a/docs/plugins/docs/1. Introduction/img/3. Enable Plugins/image-20250527195703464.png b/docs/plugins/docs/1. Introduction/img/3. Enable Plugins/image-20250527195703464.png new file mode 100644 index 0000000..7dac9fa Binary files /dev/null and b/docs/plugins/docs/1. Introduction/img/3. Enable Plugins/image-20250527195703464.png differ diff --git a/docs/plugins/docs/2. Architecture/1. Plugin Architecture.md b/docs/plugins/docs/2. Architecture/1. Plugin Architecture.md index 16995da..8975975 100644 --- a/docs/plugins/docs/2. Architecture/1. Plugin Architecture.md +++ b/docs/plugins/docs/2. Architecture/1. Plugin Architecture.md @@ -1,5 +1,9 @@ # Plugin Architecture +Last Update: 25/05/2025 + +--- + The Zoraxy Plugin uses a 3 steps approach to get information from plugin, setup the plugin and forward request to plugin. The name of the steps are partially referred from dbus designs as followings. 1. Introspect @@ -10,3 +14,4 @@ The overall flow looks like this. ![](img/1. Plugin Architecture/plugin_workflow.png) +This design make sure that the Zoraxy plugins do not depends on platform dependent implementations that uses, for example, unix socket. This also avoided protocol that require complex conversion to and from HTTP request (data structure) like gRPC, while making sure the plugin can be cross compile into different CPU architecture or OS environment with little to no effect on its performance. diff --git a/docs/plugins/docs/2. Architecture/2. Introspect.md b/docs/plugins/docs/2. Architecture/2. Introspect.md index 00d3186..16fa8ab 100644 --- a/docs/plugins/docs/2. Architecture/2. Introspect.md +++ b/docs/plugins/docs/2. Architecture/2. Introspect.md @@ -1,5 +1,9 @@ # Introspect +Last Update: 25/05/2025 + +--- + Introspect, similar to the one in dbus design, is used to get the information from plugin when Zoraxy starts (or manually triggered in development mode or force reload plugin list). **This is a pre-defined structure where the plugin must provide to Zoraxy** when the plugin is being started with the `-introspect` flag. @@ -55,6 +59,12 @@ type IntroSpect struct { } ``` + + +The introspect provide Zoraxy the required information to start the plugin and how to interact with it. For more details on what those capture settings are for, see "Capture Mode" section. + +--- + ## Introspect Manual Triggering To manually test if the introspect return is correct, you can try using the `-introspect` flag on any Zoraxy plugin. You should be able to see an output like so. diff --git a/docs/plugins/docs/2. Architecture/5. Plugin UI.md b/docs/plugins/docs/2. Architecture/5. Plugin UI.md new file mode 100644 index 0000000..b4e64b4 --- /dev/null +++ b/docs/plugins/docs/2. Architecture/5. Plugin UI.md @@ -0,0 +1,23 @@ +# Plugin UI + +Last Update: 25/05/2025 + +--- + +A plugin can optionally expose a Web UI interface for user configuration. + +**It is generally recommended that a plugin have such UI exposed for easy configurations.** As plugin installed via plugin store provides limited ways for a user to configure the plugin, the plugin web UI will be the best way for user to setup your plugin. + +## Plugin Web UI Access + +If a plugin provide a Web UI endpoint for Zoraxy during the introspect process, a new item will be shown in the Plugins section on Zoraxy side menu. Below is an example of the Web UI of UPnP Port Forwarder plugin. + +![image-20250527201750613](img/5. Plugin UI/image-20250527201750613.png) + + + +## Front-end Developer Notes + +The Web UI is implemented as a reverse proxy and embed in an iframe. So you do not need to handle CORS issues with the web UI (as it will be proxy internally by Zoraxy as exposed as something like a virtual directory mounted website). + +However, the plugin web UI is exposed via the path `/plugin.ui/{{plugin_uuid}}/`, for example, `/plugin.ui/org.aroz.zoraxy.plugins.upnp/`. **When developing the plugin web UI, do not use absolute path for any resources used in the HTML file**, unless you are trying to re-use Zoraxy components like css or image elements stored in Zoraxy embedded web file system (e.g. `/img/logo.svg`). \ No newline at end of file diff --git a/docs/plugins/docs/2. Architecture/img/5. Plugin UI/image-20250527201750613.png b/docs/plugins/docs/2. Architecture/img/5. Plugin UI/image-20250527201750613.png new file mode 100644 index 0000000..4792a4e Binary files /dev/null and b/docs/plugins/docs/2. Architecture/img/5. Plugin UI/image-20250527201750613.png differ diff --git a/docs/plugins/docs/3. Basic Examples/1. Hello World.md b/docs/plugins/docs/3. Basic Examples/1. Hello World.md new file mode 100644 index 0000000..6729085 --- /dev/null +++ b/docs/plugins/docs/3. Basic Examples/1. Hello World.md @@ -0,0 +1,325 @@ +# Hello World! + +Last Update: 25/05/2025 + +--- + +Let start with a really simple Hello World plugin. This only function of this plugin is to print "Hello World" in the plugin web UI. + +--- + +## 1. Name your plugin + +First things first, give your plugin a name. In this example, we are using the name "helloworld". + +**Plugin name cannot contain space or special characters**, so you must use a file name that satisfy the requirement. No worry, the plugin file name is not the same as the plugin display name in the introspect. + +--- + +## 2. Create the plugin folder + + If your zoraxy root folder do not contains a folder named "plugins", it might implies that your Zoraxy is freshly clone from Github. **You will need to build and run it once to start working on your plugin**, so if you have a newly cloned source code of Zoraxy, do the followings. + +```bash +git clone https://github.com/tobychui/zoraxy +cd src +go mod tidy +go build +sudo ./zoraxy +``` + +Afterward, create a plugin folder under your Zoraxy development environment that is exactly matching your plugin name. In the above example, the folder name should be "helloworld". + +```bash +# Assume you are already inside the src/ folder +mkdir helloworld +cd ./helloworld +``` + +--- + +## 3. Create a go project + +Similar to any Go project, you can start by creating a `main.go` file. Next, you would want to let the go compiler knows your plugin name so when generating a binary file, it knows what to name it. This can be done via using the `go mod init` command. + +```bash +touch main.go +go mod init example.com/zoraxy/helloworld +ls +# After you are done, you should see the followings +# go.mod main.go +``` + +--- + +## 4. Copy the Zoraxy plugin lib from Zoraxy source code + + Locate the Zoraxy plugin library from the Zoraxy source code. You can find the `zoraxy_plugin` Go module under `src/mod/plugins/zoraxy_plugin` + +Copy the `zoraxy_plugin` folder from the Zoraxy source code mod folder into the your plugin’s mod folder. Let assume you use the same mod folder name as Zoraxy as `mod`, then your copied library path should be `plugins/helloword/mod/zoraxy_plugin` + +```bash +mkdir ./mod +cp -r "mod/plugins/zoraxy_plugin" ./mod/ +ls ./mod/zoraxy_plugin/ +# You should see something like this (might be different in future versions) +# dev_webserver.go dynamic_router.go embed_webserver.go README.txt static_router.go zoraxy_plugin.go +``` + +--- + +## 5. Create a web resources folder + +Lets create a www folder and put all our web resources, we need to create an `index.html` file as our plugin web ui homepage. This can be done by creating a HTML file in the www folder. + +```bash +# Assuming you are currently in the src/plugins/helloworld/ folder +mkdir www +cd www +touch index.html +``` + + + +And here is an example `index.html` file that uses the Zoraxy internal resources like css and dark theme toggle mechanism. That csrf token template is not straightly needed in this example as helloworld plugin do not make any POST request to Zoraxy webmin interface, but it might come in handy later. + +```html + + + + + + + + + + + + + Hello World + + + + + + +
+

Hello World

+

Welcome to your first Zoraxy plugin

+
+ + +``` + +--- + +## 6. Creating a handler for Introspect + +To create a handler for introspect, you can first start your plugin with a few constants. + +1. Plugin ID, this must be unique. You can use a domain you own like `com.example.helloworld` +2. UI Path, for now we uses "/" as this plugin do not have any other endpoints, so we can use the whole root just for web UI +3. Web root, for trimming off from the embedded web folder so when user can visit your `index.html` by accessing `/` instead of needing to navigate to `/www` + + + +After you have defined these constant, we can use `plugin.ServeAndRecvSpec` function to handle the handshake between Zoraxy and your plugin. + +```go +const ( + PLUGIN_ID = "com.example.helloworld" + UI_PATH = "/" + WEB_ROOT = "/www" +) + +func main(){ + runtimeCfg, err := plugin.ServeAndRecvSpec(&plugin.IntroSpect{ + ID: "com.example.helloworld", + Name: "Hello World Plugin", + Author: "foobar", + AuthorContact: "admin@example.com", + Description: "A simple hello world plugin", + URL: "https://example.com", + Type: plugin.PluginType_Utilities, + VersionMajor: 1, + VersionMinor: 0, + VersionPatch: 0, + + // As this is a utility plugin, we don't need to capture any traffic + // but only serve the UI, so we set the UI (relative to the plugin path) to "/" + UIPath: UI_PATH, + }) + if err != nil { + //Terminate or enter standalone mode here + panic(err) + } +} +``` + + + +**Notes: If some post processing is needed between Introspect and Configure, you can use two seperate function to handle the first start and the second starting of your plugin. The "seperated version" of `ServeAndRecvSpec` is defined as ` ServeIntroSpect(pluginSpect *IntroSpect) ` and `RecvConfigureSpec() (*ConfigureSpec, error)`. See `zoraxy_plugin.go` for more information.** + +--- + +## 7. Creating a web server from embedded web fs + +After that, we need to create a web server to serve our plugin UI to Zoraxy via HTTP. This can be done via the `http.FileServer` but for simplicity and ease of upgrade, the Zoraxy plugin library provided an easy to use embedded web FS server API for plugin developers. + +To use the Zoraxy plugin embedded web server, you first need to embed your web fs into Zoraxy as such. + +```go +import ( + _ "embed" + "fmt" + + plugin "example.com/zoraxy/helloworld/mod/zoraxy_plugin" +) + +//go:embed www/* +var content embed.FS +``` + +Then call to the `NewPluginEmbedUIRouter` to create a new UI router from the embedded Fs. + +```go +// Create a new PluginEmbedUIRouter that will serve the UI from web folder +// The router will also help to handle the termination of the plugin when +// a user wants to stop the plugin via Zoraxy Web UI +embedWebRouter := plugin.NewPluginEmbedUIRouter(PLUGIN_ID, &content, WEB_ROOT, UI_PATH) +``` + + + +Here is the tricky part. since not all platform support cross process signaling, Zoraxy plugin uses HTTP request to request a plugin to shutdown. The `embedWebRouter` object has a function named `RegisterTerminateHandler` where you can easily use this function to register actions that needed to be done before shutdown. + +```go +embedWebRouter.RegisterTerminateHandler(func() { + // Do cleanup here if needed + fmt.Println("Hello World Plugin Exited") +}, nil) +``` + +**Notes: This is a blocking function. That is why Zoraxy has a build-in timeout context where if the terminate request takes more than 3 seconds, the plugin process will be treated as "freezed" and forcefully terminated. So please make sure the terminate handler complete its shutdown procedures within 3 seconds.** + +--- + +## 8. Register & Serve the Web UI + +After you have created a embedded web router, you can register it to the UI PATH as follows. + +```go +// Serve the hello world page in the www folder +http.Handle(UI_PATH, embedWebRouter.Handler()) +fmt.Println("Hello World started at http://127.0.0.1:" + strconv.Itoa(runtimeCfg.Port)) +err = http.ListenAndServe("127.0.0.1:"+strconv.Itoa(runtimeCfg.Port), nil) +if err != nil { + panic(err) +} +``` + + + +As this is just the standard golang net/http package, you can of course add more Function Handlers to it based on your needs. There are something that you need to know about adding API endpoints, we will discuss this in the later sections. + +--- + +## 9. Build and Test + +After saving the `main.go` file, you can now build your plugin with `go build`. It should generate the plugin in your platform architecture and OS. If you are on Linux, it will be `helloworld` and if you are on Windows, it will be `helloworld.exe`. + +After you are done, restart Zoraxy and enable your plugin in the Plugin List. Now you can test and debug your plugin with your HTTP Proxy Rules. All the STDOUT and STDERR of your plugin will be forwarded to the STDOUT of Zoraxy as well as the log file. + + + +**Tips** + +You can also enable the Developer Option - Plugin Auto Reload function if you are too lazy to restart Zoraxy everytime the plugin binary changed. + +![image-20250527210849767](img/1. Hello World/image-20250527210849767.png) + + + +--- + +## 10. Full Code + +This is the full code of the helloworld plugin main.go file. + +```go +package main + +import ( + "embed" + _ "embed" + "fmt" + "net/http" + "strconv" + + plugin "example.com/zoraxy/helloworld/mod/zoraxy_plugin" +) + +const ( + PLUGIN_ID = "com.example.helloworld" + UI_PATH = "/" + WEB_ROOT = "/www" +) + +//go:embed www/* +var content embed.FS + +func main() { + // Serve the plugin intro spect + // This will print the plugin intro spect and exit if the -introspect flag is provided + runtimeCfg, err := plugin.ServeAndRecvSpec(&plugin.IntroSpect{ + ID: "com.example.helloworld", + Name: "Hello World Plugin", + Author: "foobar", + AuthorContact: "admin@example.com", + Description: "A simple hello world plugin", + URL: "https://example.com", + Type: plugin.PluginType_Utilities, + VersionMajor: 1, + VersionMinor: 0, + VersionPatch: 0, + + // As this is a utility plugin, we don't need to capture any traffic + // but only serve the UI, so we set the UI (relative to the plugin path) to "/" + UIPath: UI_PATH, + }) + if err != nil { + //Terminate or enter standalone mode here + panic(err) + } + + // Create a new PluginEmbedUIRouter that will serve the UI from web folder + // The router will also help to handle the termination of the plugin when + // a user wants to stop the plugin via Zoraxy Web UI + embedWebRouter := plugin.NewPluginEmbedUIRouter(PLUGIN_ID, &content, WEB_ROOT, UI_PATH) + embedWebRouter.RegisterTerminateHandler(func() { + // Do cleanup here if needed + fmt.Println("Hello World Plugin Exited") + }, nil) + + // Serve the hello world page in the www folder + http.Handle(UI_PATH, embedWebRouter.Handler()) + fmt.Println("Hello World started at http://127.0.0.1:" + strconv.Itoa(runtimeCfg.Port)) + err = http.ListenAndServe("127.0.0.1:"+strconv.Itoa(runtimeCfg.Port), nil) + if err != nil { + panic(err) + } + +} + +``` + diff --git a/docs/plugins/docs/3. Basic Examples/img/1. Hello World/image-20250527210849767.png b/docs/plugins/docs/3. Basic Examples/img/1. Hello World/image-20250527210849767.png new file mode 100644 index 0000000..0928b98 Binary files /dev/null and b/docs/plugins/docs/3. Basic Examples/img/1. Hello World/image-20250527210849767.png differ diff --git a/docs/plugins/go.mod b/docs/plugins/go.mod index 935824e..1e6edf7 100644 --- a/docs/plugins/go.mod +++ b/docs/plugins/go.mod @@ -3,9 +3,14 @@ module imuslab.com/zoraxy/docs go 1.24.1 require ( - github.com/PuerkitoBio/goquery v1.10.3 // indirect - github.com/andybalholm/cascadia v1.3.3 // indirect - github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b // indirect - github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 // indirect - golang.org/x/net v0.40.0 // indirect + github.com/PuerkitoBio/goquery v1.10.3 + github.com/fsnotify/fsnotify v1.9.0 + github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b + github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 +) + +require ( + github.com/andybalholm/cascadia v1.3.3 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect ) diff --git a/docs/plugins/go.sum b/docs/plugins/go.sum index 074de83..fef935a 100644 --- a/docs/plugins/go.sum +++ b/docs/plugins/go.sum @@ -2,6 +2,8 @@ github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiU github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk= github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -48,6 +50,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/docs/plugins/html/1. Introduction/1. What is Zoraxy Plugin.html b/docs/plugins/html/1. Introduction/1. What is Zoraxy Plugin.html index 490ff7d..86ea8ef 100644 --- a/docs/plugins/html/1. Introduction/1. What is Zoraxy Plugin.html +++ b/docs/plugins/html/1. Introduction/1. What is Zoraxy Plugin.html @@ -95,6 +95,12 @@ Getting Started + + Installing Plugin + + + Enable Plugins + Architecture @@ -113,11 +119,19 @@ Capture Modes + + Plugin UI + - Getting Started + Basic Examples +
+ + Hello World + +
index @@ -130,6 +144,12 @@

What is Zoraxy Plugin?

+

+

+ Last Update: 25/05/2025 +

+

+

Zoraxy Plugin is a powerful extension feature designed to enhance the functionality of the Zoraxy system. It provides additional features and capabilities that are not part of the core system, allowing users to customize their experience and optimize performance. The plugin is built to be modular and flexible, enabling users to tailor their Zoraxy environment to meet specific needs. diff --git a/docs/plugins/html/1. Introduction/2. Getting Started.html b/docs/plugins/html/1. Introduction/2. Getting Started.html index e0b2111..3721d96 100644 --- a/docs/plugins/html/1. Introduction/2. Getting Started.html +++ b/docs/plugins/html/1. Introduction/2. Getting Started.html @@ -95,6 +95,12 @@ Getting Started + + Installing Plugin + + + Enable Plugins + Architecture @@ -113,11 +119,19 @@ Capture Modes + + Plugin UI + - Getting Started + Basic Examples +

+ + Hello World + +
index @@ -130,6 +144,12 @@

Getting Started

+

+

+ Last Update: 25/05/2025 +

+

+

To start developing plugins, you will need the following installed on your computer diff --git a/docs/plugins/html/1. Introduction/3. Installing Plugin.html b/docs/plugins/html/1. Introduction/3. Installing Plugin.html new file mode 100644 index 0000000..90828c9 --- /dev/null +++ b/docs/plugins/html/1. Introduction/3. Installing Plugin.html @@ -0,0 +1,216 @@ + + + + + + + + Installing Plugin | Zoraxy Documentation + + + + + + + + + + + + + + + + + + + + + + + + + +

+
+
+ +
+ +
+
+
+
+
+
+ +
+
+
+

+ Installing Plugin +

+

+

+ Last Update: 25/05/2025 +

+

+
+

+ Install via Plugin Store +

+

+

+ (Work in progress) +

+

+

+ Manual Install +

+

+

+ The plugin shall be placed inside the + + plugins/{{plugin_name}}/ + + directory where the binary executable name must be matching with the plugin name. +

+

+

+

+ If you are on Linux, also make sure Zoraxy have the execution permission of the plugin. You can use the following command to enable execution of the plugin binary on Linux with the current user (Assume Zoraxy is run by the current user) +

+

+
cd ./plugins/{{plugin_name}}/
+chmod +x ./{{plugin_name}}
+
+

+ Sometime plugins might come with additional assets other than the binary file. If that is the case, extract all of the plugins content into the folder with the plugin’s name. +

+

+

+ After the folder structure is ready, restart Zoraxy. If you are using systemd for Zoraxy, use + + sudo systemctl restart zoraxy + + to restart Zoraxy via systemd service. +

+

+
+
+
+
+
+
+
+
+
+
+
+
+ Zoraxy © tobychui + + 2025 + +
+
+
+ + + \ No newline at end of file diff --git a/docs/plugins/html/1. Introduction/4. Enable Plugins.html b/docs/plugins/html/1. Introduction/4. Enable Plugins.html new file mode 100644 index 0000000..e4e4613 --- /dev/null +++ b/docs/plugins/html/1. Introduction/4. Enable Plugins.html @@ -0,0 +1,233 @@ + + + + + + + + Enable Plugins | Zoraxy Documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ +
+
+
+
+
+
+ +
+
+
+

+ Enable Plugins +

+

+

+ Last Update: 25/05/2025 +

+

+
+

+

+ To enable and assign a plugin to a certain HTTP Proxy Rule, you will need to do the following steps +

+

+

+ 1. Create a tag for your HTTP Proxy Rules +

+

+ Let say you want to enable debugger on some of your HTTP Proxy Rules. You can do that by first creating a tag in the tag editor. In the example below, we will be using the tag “debug”. After adding the tag to the HTTP Proxy rule, you will see something like this. +

+

+

+ image-20250527193748456 +
+

+
+

+ 2. Enable Plugin +

+

+ Click on the “Enable” button on the plugin which you want to enable +

+

+

+ image-20250527193601017 +
+

+
+

+ 3. Assign Plugin to HTTP Proxy Rule +

+

+

+ Finally, select the tag that you just created in the dropdown menu +

+

+

+

+ image-20250527194052408 +
+

+

+

+ Afterward, you will see the plugin is attached to the target tag +

+

+

+

+ image-20250527195703464 +
+

+

+

+ It means the plugin is enabled on the HTTP proxy rule +

+

+
+
+
+
+
+
+
+
+
+
+
+
+ Zoraxy © tobychui + + 2025 + +
+
+
+ + + \ No newline at end of file diff --git a/docs/plugins/html/1. Introduction/img/3. Enable Plugins/image-20250527193601017.png b/docs/plugins/html/1. Introduction/img/3. Enable Plugins/image-20250527193601017.png new file mode 100644 index 0000000..d32e67b Binary files /dev/null and b/docs/plugins/html/1. Introduction/img/3. Enable Plugins/image-20250527193601017.png differ diff --git a/docs/plugins/html/1. Introduction/img/3. Enable Plugins/image-20250527193748456.png b/docs/plugins/html/1. Introduction/img/3. Enable Plugins/image-20250527193748456.png new file mode 100644 index 0000000..40e248d Binary files /dev/null and b/docs/plugins/html/1. Introduction/img/3. Enable Plugins/image-20250527193748456.png differ diff --git a/docs/plugins/html/1. Introduction/img/3. Enable Plugins/image-20250527194052408.png b/docs/plugins/html/1. Introduction/img/3. Enable Plugins/image-20250527194052408.png new file mode 100644 index 0000000..4336e78 Binary files /dev/null and b/docs/plugins/html/1. Introduction/img/3. Enable Plugins/image-20250527194052408.png differ diff --git a/docs/plugins/html/1. Introduction/img/3. Enable Plugins/image-20250527195703464.png b/docs/plugins/html/1. Introduction/img/3. Enable Plugins/image-20250527195703464.png new file mode 100644 index 0000000..7dac9fa Binary files /dev/null and b/docs/plugins/html/1. Introduction/img/3. Enable Plugins/image-20250527195703464.png differ diff --git a/docs/plugins/html/2. Architecture/1. Plugin Architecture.html b/docs/plugins/html/2. Architecture/1. Plugin Architecture.html index baa6b4e..2cd3baa 100644 --- a/docs/plugins/html/2. Architecture/1. Plugin Architecture.html +++ b/docs/plugins/html/2. Architecture/1. Plugin Architecture.html @@ -95,6 +95,12 @@ Getting Started + + Installing Plugin + + + Enable Plugins + Architecture @@ -113,11 +119,19 @@ Capture Modes + + Plugin UI + - Getting Started + Basic Examples +
+ + Hello World + +
index @@ -130,6 +144,12 @@

Plugin Architecture

+

+

+ Last Update: 25/05/2025 +

+

+

The Zoraxy Plugin uses a 3 steps approach to get information from plugin, setup the plugin and forward request to plugin. The name of the steps are partially referred from dbus designs as followings. @@ -156,6 +176,11 @@

+

+

+ This design make sure that the Zoraxy plugins do not depends on platform dependent implementations that uses, for example, unix socket. This also avoided protocol that require complex conversion to and from HTTP request (data structure) like gRPC, while making sure the plugin can be cross compile into different CPU architecture or OS environment with little to no effect on its performance. +

+



diff --git a/docs/plugins/html/2. Architecture/2. Introspect.html b/docs/plugins/html/2. Architecture/2. Introspect.html index 3e2154d..5c92c2a 100644 --- a/docs/plugins/html/2. Architecture/2. Introspect.html +++ b/docs/plugins/html/2. Architecture/2. Introspect.html @@ -95,6 +95,12 @@ Getting Started + + Installing Plugin + + + Enable Plugins + Architecture @@ -113,11 +119,19 @@ Capture Modes + + Plugin UI + - Getting Started + Basic Examples +
+ + Hello World + +
index @@ -130,6 +144,12 @@

Introspect

+

+

+ Last Update: 25/05/2025 +

+

+

Introspect, similar to the one in dbus design, is used to get the information from plugin when Zoraxy starts (or manually triggered in development mode or force reload plugin list). @@ -203,6 +223,10 @@ SubscriptionsEvents map[string]string `json:"subscriptions_events"` //Subscriptions events of your plugin, see Zoraxy documentation for more details } +

+ The introspect provide Zoraxy the required information to start the plugin and how to interact with it. For more details on what those capture settings are for, see “Capture Mode” section. +

+

Introspect Manual Triggering

diff --git a/docs/plugins/html/2. Architecture/3. Configure.html b/docs/plugins/html/2. Architecture/3. Configure.html index 6b62dae..189a066 100644 --- a/docs/plugins/html/2. Architecture/3. Configure.html +++ b/docs/plugins/html/2. Architecture/3. Configure.html @@ -95,6 +95,12 @@ Getting Started + + Installing Plugin + + + Enable Plugins + Architecture @@ -113,11 +119,19 @@ Capture Modes + + Plugin UI + - Getting Started + Basic Examples +
+ + Hello World + +
index diff --git a/docs/plugins/html/2. Architecture/4. Capture Modes.html b/docs/plugins/html/2. Architecture/4. Capture Modes.html index 800155e..c826fdf 100644 --- a/docs/plugins/html/2. Architecture/4. Capture Modes.html +++ b/docs/plugins/html/2. Architecture/4. Capture Modes.html @@ -95,6 +95,12 @@ Getting Started + + Installing Plugin + + + Enable Plugins + Architecture @@ -113,11 +119,19 @@ Capture Modes + + Plugin UI + - Getting Started + Basic Examples +
+ + Hello World + +
index diff --git a/docs/plugins/html/2. Architecture/5. Plugin UI.html b/docs/plugins/html/2. Architecture/5. Plugin UI.html new file mode 100644 index 0000000..1164e87 --- /dev/null +++ b/docs/plugins/html/2. Architecture/5. Plugin UI.html @@ -0,0 +1,231 @@ + + + + + + + + Plugin UI | Zoraxy Documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ +
+
+
+
+
+
+ +
+
+
+

+ Plugin UI +

+

+

+ Last Update: 25/05/2025 +

+

+
+

+

+ A plugin can optionally expose a Web UI interface for user configuration. +

+

+

+

+ + It is generally recommended that a plugin have such UI exposed for easy configurations. + + As plugin installed via plugin store provides limited ways for a user to configure the plugin, the plugin web UI will be the best way for user to setup your plugin. +

+

+

+ Plugin Web UI Access +

+

+

+ If a plugin provide a Web UI endpoint for Zoraxy during the introspect process, a new item will be shown in the Plugins section on Zoraxy side menu. Below is an example of the Web UI of UPnP Port Forwarder plugin. +

+

+

+

+ image-20250527201750613 +
+

+

+ Front-end Developer Notes +

+

+

+ The Web UI is implemented as a reverse proxy and embed in an iframe. So you do not need to handle CORS issues with the web UI (as it will be proxy internally by Zoraxy as exposed as something like a virtual directory mounted website). +

+

+

+

+ However, the plugin web UI is exposed via the path + + /plugin.ui/{{plugin_uuid}}/ + + , for example, + + /plugin.ui/org.aroz.zoraxy.plugins.upnp/ + + . + + When developing the plugin web UI, do not use absolute path for any resources used in the HTML file + + , unless you are trying to re-use Zoraxy components like css or image elements stored in Zoraxy embedded web file system (e.g. + + /img/logo.svg + + ). +

+

+
+
+
+
+
+
+
+
+
+
+
+
+ Zoraxy © tobychui + + 2025 + +
+
+
+ + + \ No newline at end of file diff --git a/docs/plugins/html/2. Architecture/img/5. Plugin UI/image-20250527201750613.png b/docs/plugins/html/2. Architecture/img/5. Plugin UI/image-20250527201750613.png new file mode 100644 index 0000000..4792a4e Binary files /dev/null and b/docs/plugins/html/2. Architecture/img/5. Plugin UI/image-20250527201750613.png differ diff --git a/docs/plugins/html/3. Basic Examples/1. Hello World.html b/docs/plugins/html/3. Basic Examples/1. Hello World.html new file mode 100644 index 0000000..d880c31 --- /dev/null +++ b/docs/plugins/html/3. Basic Examples/1. Hello World.html @@ -0,0 +1,643 @@ + + + + + + + + Hello World | Zoraxy Documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ +
+
+
+
+
+
+ +
+
+
+

+ Hello World! +

+

+

+ Last Update: 25/05/2025 +

+

+
+

+ Let start with a really simple Hello World plugin. This only function of this plugin is to print “Hello World” in the plugin web UI. +

+
+

+ 1. Name your plugin +

+

+ First things first, give your plugin a name. In this example, we are using the name “helloworld”. +

+

+

+ + Plugin name cannot contain space or special characters + + , so you must use a file name that satisfy the requirement. No worry, the plugin file name is not the same as the plugin display name in the introspect. +

+

+
+

+ 2. Create the plugin folder +

+

+ If your zoraxy root folder do not contains a folder named “plugins”, it might implies that your Zoraxy is freshly clone from Github. + + You will need to build and run it once to start working on your plugin + + , so if you have a newly cloned source code of Zoraxy, do the followings. +

+
git clone https://github.com/tobychui/zoraxy
+cd src
+go mod tidy
+go build
+sudo ./zoraxy
+
+

+ Afterward, create a plugin folder under your Zoraxy development environment that is exactly matching your plugin name. In the above example, the folder name should be “helloworld”. +

+
# Assume you are already inside the src/ folder
+mkdir helloworld
+cd ./helloworld
+
+
+

+ 3. Create a go project +

+

+

+ Similar to any Go project, you can start by creating a + + main.go + + file. Next, you would want to let the go compiler knows your plugin name so when generating a binary file, it knows what to name it. This can be done via using the + + go mod init + + command. +

+

+
touch main.go
+go mod init example.com/zoraxy/helloworld
+ls
+# After you are done, you should see the followings
+# go.mod  main.go
+
+
+

+ 4. Copy the Zoraxy plugin lib from Zoraxy source code +

+

+

+ Locate the Zoraxy plugin library from the Zoraxy source code. You can find the + + zoraxy_plugin + + Go module under + + src/mod/plugins/zoraxy_plugin + +

+

+

+

+ Copy the + + zoraxy_plugin + + folder from the Zoraxy source code mod folder into the your plugin’s mod folder. Let assume you use the same mod folder name as Zoraxy as + + mod + + , then your copied library path should be + + plugins/helloword/mod/zoraxy_plugin + +

+

+
mkdir ./mod
+cp -r "mod/plugins/zoraxy_plugin" ./mod/
+ls ./mod/zoraxy_plugin/
+# You should see something like this (might be different in future versions)
+# dev_webserver.go  dynamic_router.go  embed_webserver.go  README.txt  static_router.go  zoraxy_plugin.go
+
+
+

+ 5. Create a web resources folder +

+

+

+ Lets create a www folder and put all our web resources, we need to create an + + index.html + + file as our plugin web ui homepage. This can be done by creating a HTML file in the www folder. +

+

+
# Assuming you are currently in the src/plugins/helloworld/ folder
+mkdir www
+cd www
+touch index.html
+
+

+

+ And here is an example + + index.html + + file that uses the Zoraxy internal resources like css and dark theme toggle mechanism. That csrf token template is not straightly needed in this example as helloworld plugin do not make any POST request to Zoraxy webmin interface, but it might come in handy later. +

+

+
<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <!-- CSRF token, if your plugin need to make POST request to backend -->
+    <meta name="zoraxy.csrf.Token" content="{{.csrfToken}}">
+    <link rel="stylesheet" href="/script/semantic/semantic.min.css">
+    <script src="/script/jquery-3.6.0.min.js"></script>
+    <script src="/script/semantic/semantic.min.js"></script>
+    <script src="/script/utils.js"></script>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <link rel="stylesheet" href="/main.css">
+    <title>Hello World</title>
+    <style>
+        body {
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            height: 100vh;
+            margin: 0;
+            font-family: Arial, sans-serif;
+            background:none;
+        }
+    </style>
+</head>
+<body>
+    <!-- Dark theme script must be included after body tag-->
+    <link rel="stylesheet" href="/darktheme.css">
+    <script src="/script/darktheme.js"></script>
+    <div style="text-align: center;">
+        <h1>Hello World</h1>
+        <p>Welcome to your first Zoraxy plugin</p>
+    </div>
+</body>
+</html>
+
+
+

+ 6. Creating a handler for Introspect +

+

+

+ To create a handler for introspect, you can first start your plugin with a few constants. +

+

+
+
+ Plugin ID, this must be unique. You can use a domain you own like + + com.example.helloworld + +
+
+ UI Path, for now we uses “/” as this plugin do not have any other endpoints, so we can use the whole root just for web UI +
+
+ Web root, for trimming off from the embedded web folder so when user can visit your + + index.html + + by accessing + + / + + instead of needing to navigate to + + /www + +
+
+

+

+ After you have defined these constant, we can use + + plugin.ServeAndRecvSpec + + function to handle the handshake between Zoraxy and your plugin. +

+

+
const (
+	PLUGIN_ID = "com.example.helloworld"
+	UI_PATH   = "/"
+	WEB_ROOT  = "/www"
+)
+
+func main(){
+    runtimeCfg, err := plugin.ServeAndRecvSpec(&plugin.IntroSpect{
+		ID:            "com.example.helloworld",
+		Name:          "Hello World Plugin",
+		Author:        "foobar",
+		AuthorContact: "admin@example.com",
+		Description:   "A simple hello world plugin",
+		URL:           "https://example.com",
+		Type:          plugin.PluginType_Utilities,
+		VersionMajor:  1,
+		VersionMinor:  0,
+		VersionPatch:  0,
+
+		// As this is a utility plugin, we don't need to capture any traffic
+		// but only serve the UI, so we set the UI (relative to the plugin path) to "/"
+		UIPath: UI_PATH,
+	})
+	if err != nil {
+		//Terminate or enter standalone mode here
+		panic(err)
+	}
+}
+
+

+ + Notes: If some post processing is needed between Introspect and Configure, you can use two seperate function to handle the first start and the second starting of your plugin. The “seperated version” of + + ServeAndRecvSpec + + is defined as + + ServeIntroSpect(pluginSpect *IntroSpect) + + and + + RecvConfigureSpec() (*ConfigureSpec, error) + + . See + + zoraxy_plugin.go + + for more information. + +

+
+

+ 7. Creating a web server from embedded web fs +

+

+

+ After that, we need to create a web server to serve our plugin UI to Zoraxy via HTTP. This can be done via the + + http.FileServer + + but for simplicity and ease of upgrade, the Zoraxy plugin library provided an easy to use embedded web FS server API for plugin developers. +

+

+

+

+ To use the Zoraxy plugin embedded web server, you first need to embed your web fs into Zoraxy as such. +

+

+
import (
+	_ "embed"
+	"fmt"
+
+	plugin "example.com/zoraxy/helloworld/mod/zoraxy_plugin"
+)
+
+//go:embed www/*
+var content embed.FS
+
+

+

+ Then call to the + + NewPluginEmbedUIRouter + + to create a new UI router from the embedded Fs. +

+

+
// Create a new PluginEmbedUIRouter that will serve the UI from web folder
+// The router will also help to handle the termination of the plugin when
+// a user wants to stop the plugin via Zoraxy Web UI
+embedWebRouter := plugin.NewPluginEmbedUIRouter(PLUGIN_ID, &content, WEB_ROOT, UI_PATH)
+
+

+

+ Here is the tricky part. since not all platform support cross process signaling, Zoraxy plugin uses HTTP request to request a plugin to shutdown. The + + embedWebRouter + + object has a function named + + RegisterTerminateHandler + + where you can easily use this function to register actions that needed to be done before shutdown. +

+

+
embedWebRouter.RegisterTerminateHandler(func() {
+	// Do cleanup here if needed
+	fmt.Println("Hello World Plugin Exited")
+}, nil)
+
+

+ + Notes: This is a blocking function. That is why Zoraxy has a build-in timeout context where if the terminate request takes more than 3 seconds, the plugin process will be treated as “freezed” and forcefully terminated. So please make sure the terminate handler complete its shutdown procedures within 3 seconds. + +

+
+

+ 8. Register & Serve the Web UI +

+

+

+ After you have created a embedded web router, you can register it to the UI PATH as follows. +

+

+
// Serve the hello world page in the www folder
+http.Handle(UI_PATH, embedWebRouter.Handler())
+fmt.Println("Hello World started at http://127.0.0.1:" + strconv.Itoa(runtimeCfg.Port))
+err = http.ListenAndServe("127.0.0.1:"+strconv.Itoa(runtimeCfg.Port), nil)
+if err != nil {
+	panic(err)
+}
+
+

+

+ As this is just the standard golang net/http package, you can of course add more Function Handlers to it based on your needs. There are something that you need to know about adding API endpoints, we will discuss this in the later sections. +

+

+
+

+ 9. Build and Test +

+

+

+ After saving the + + main.go + + file, you can now build your plugin with + + go build + + . It should generate the plugin in your platform architecture and OS. If you are on Linux, it will be + + helloworld + + and if you are on Windows, it will be + + helloworld.exe + + . +

+

+

+

+ After you are done, restart Zoraxy and enable your plugin in the Plugin List. Now you can test and debug your plugin with your HTTP Proxy Rules. All the STDOUT and STDERR of your plugin will be forwarded to the STDOUT of Zoraxy as well as the log file. +

+

+

+

+ + Tips + +

+

+

+

+ You can also enable the Developer Option - Plugin Auto Reload function if you are too lazy to restart Zoraxy everytime the plugin binary changed. +

+

+

+

+ image-20250527210849767 +
+

+
+

+ 10. Full Code +

+

+

+ This is the full code of the helloworld plugin main.go file. +

+

+
package main
+
+import (
+	"embed"
+	_ "embed"
+	"fmt"
+	"net/http"
+	"strconv"
+
+	plugin "example.com/zoraxy/helloworld/mod/zoraxy_plugin"
+)
+
+const (
+	PLUGIN_ID = "com.example.helloworld"
+	UI_PATH   = "/"
+	WEB_ROOT  = "/www"
+)
+
+//go:embed www/*
+var content embed.FS
+
+func main() {
+	// Serve the plugin intro spect
+	// This will print the plugin intro spect and exit if the -introspect flag is provided
+	runtimeCfg, err := plugin.ServeAndRecvSpec(&plugin.IntroSpect{
+		ID:            "com.example.helloworld",
+		Name:          "Hello World Plugin",
+		Author:        "foobar",
+		AuthorContact: "admin@example.com",
+		Description:   "A simple hello world plugin",
+		URL:           "https://example.com",
+		Type:          plugin.PluginType_Utilities,
+		VersionMajor:  1,
+		VersionMinor:  0,
+		VersionPatch:  0,
+
+		// As this is a utility plugin, we don't need to capture any traffic
+		// but only serve the UI, so we set the UI (relative to the plugin path) to "/"
+		UIPath: UI_PATH,
+	})
+	if err != nil {
+		//Terminate or enter standalone mode here
+		panic(err)
+	}
+
+	// Create a new PluginEmbedUIRouter that will serve the UI from web folder
+	// The router will also help to handle the termination of the plugin when
+	// a user wants to stop the plugin via Zoraxy Web UI
+	embedWebRouter := plugin.NewPluginEmbedUIRouter(PLUGIN_ID, &content, WEB_ROOT, UI_PATH)
+	embedWebRouter.RegisterTerminateHandler(func() {
+		// Do cleanup here if needed
+		fmt.Println("Hello World Plugin Exited")
+	}, nil)
+
+	// Serve the hello world page in the www folder
+	http.Handle(UI_PATH, embedWebRouter.Handler())
+	fmt.Println("Hello World started at http://127.0.0.1:" + strconv.Itoa(runtimeCfg.Port))
+	err = http.ListenAndServe("127.0.0.1:"+strconv.Itoa(runtimeCfg.Port), nil)
+	if err != nil {
+		panic(err)
+	}
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Zoraxy © tobychui + + 2025 + +
+
+
+ + + \ No newline at end of file diff --git a/docs/plugins/html/3. Basic Examples/img/1. Hello World/image-20250527210849767.png b/docs/plugins/html/3. Basic Examples/img/1. Hello World/image-20250527210849767.png new file mode 100644 index 0000000..0928b98 Binary files /dev/null and b/docs/plugins/html/3. Basic Examples/img/1. Hello World/image-20250527210849767.png differ diff --git a/docs/plugins/html/index.html b/docs/plugins/html/index.html index 634a058..d040620 100644 --- a/docs/plugins/html/index.html +++ b/docs/plugins/html/index.html @@ -95,6 +95,12 @@ Getting Started + + Installing Plugin + + + Enable Plugins + Architecture @@ -113,11 +119,19 @@ Capture Modes + + Plugin UI + - Getting Started + Basic Examples +
+ + Hello World + +
index diff --git a/docs/plugins/index.html b/docs/plugins/index.html index bbbb580..08bc4ae 100644 --- a/docs/plugins/index.html +++ b/docs/plugins/index.html @@ -7,6 +7,6 @@ Redirecting... -

If you are not redirected automatically, follow this link.

+

If you are not redirected automatically, follow this link.

\ No newline at end of file diff --git a/docs/plugins/index.json b/docs/plugins/index.json index ca463b9..3bf1d86 100644 --- a/docs/plugins/index.json +++ b/docs/plugins/index.json @@ -17,6 +17,16 @@ "filename": "1. Introduction/2. Getting Started.md", "title": "Getting Started", "type": "file" + }, + { + "filename": "1. Introduction/3. Installing Plugin.md", + "title": "Installing Plugin", + "type": "file" + }, + { + "filename": "1. Introduction/4. Enable Plugins.md", + "title": "Enable Plugins", + "type": "file" } ] }, @@ -44,13 +54,25 @@ "filename": "2. Architecture/4. Capture Modes.md", "title": "Capture Modes", "type": "file" + }, + { + "filename": "2. Architecture/5. Plugin UI.md", + "title": "Plugin UI", + "type": "file" } ] }, { - "title": "Getting Started", - "path": "3. Getting Started", - "type": "folder" + "title": "Basic Examples", + "path": "3. Basic Examples", + "type": "folder", + "files": [ + { + "filename": "3. Basic Examples/1. Hello World.md", + "title": "Hello World", + "type": "file" + } + ] }, { "filename": "index.md", diff --git a/docs/plugins/main.go b/docs/plugins/main.go index cf405dc..10a21b8 100644 --- a/docs/plugins/main.go +++ b/docs/plugins/main.go @@ -5,6 +5,9 @@ import ( "fmt" "log" "net/http" + "time" + + "github.com/fsnotify/fsnotify" ) type FileInfo struct { @@ -17,6 +20,8 @@ type FileInfo struct { var ( mode = flag.String("m", "web", "Mode to run the application: 'web' or 'build'") root_url = flag.String("root", "/html/", "Root URL for the web server") + + webserver_stop_chan = make(chan bool, 1) ) func main() { @@ -27,12 +32,67 @@ func main() { case "build": build() default: + go watchDocsChange() + fmt.Println("Running in web mode") + startWebServerInBackground() + select {} + } +} + +func startWebServerInBackground() { + go func() { + server := &http.Server{Addr: ":8080", Handler: http.DefaultServeMux} + http.DefaultServeMux = http.NewServeMux() http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.FileServer(http.Dir("./")).ServeHTTP(w, r) }) + + go func() { + <-webserver_stop_chan + fmt.Println("Stopping server at :8080") + if err := server.Close(); err != nil { + log.Println("Error stopping server:", err) + } + }() + fmt.Println("Starting server at :8080") - if err := http.ListenAndServe(":8080", nil); err != nil { + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatal(err) } + }() +} + +func watchDocsChange() { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + + err = watcher.Add("./docs") + if err != nil { + log.Fatal(err) + } + + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + if event.Op&(fsnotify.Write|fsnotify.Create|fsnotify.Remove|fsnotify.Rename) != 0 { + log.Println("Change detected in docs folder:", event) + webserver_stop_chan <- true + time.Sleep(1 * time.Second) // Allow server to stop gracefully + build() + startWebServerInBackground() + log.Println("Static html files regenerated") + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Println("Watcher error:", err) + } } }