Added doc generator

- Added plugin doc generator
- Added getting start plugin doc
This commit is contained in:
Toby Chui
2025-05-25 22:17:38 +08:00
parent b4c2f6bf13
commit c56e317bfd
42 changed files with 2613 additions and 189 deletions

View File

@@ -0,0 +1,304 @@
<!DOCTYPE html>
<html lang="en" class="is-white">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/png" href="/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
What is Zoraxy Plugin | Zoraxy Documentation
</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.js"></script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Code highlight -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
<!-- additional languages -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/go.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/c.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/xml.min.js"></script>
<script>
hljs.highlightAll();
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/tomorrow-night-bright.css">
<style>
#msgbox{
position: fixed;
bottom: 1em;
right: 1em;
z-index: 9999;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
dialog[open] {
animation: fadeIn 0.3s ease-in-out;
}
code{
border-radius: 0.5rem;
}
</style>
<script src="/html/assets/theme.js"></script>
</head>
<body>
<div class="ts-content">
<div class="ts-container">
<div style="float: right;">
<button class="ts-button is-icon" id="darkModeToggle">
<span class="ts-icon is-moon-icon"></span>
</button>
</div>
<div class="ts-tab is-pilled">
<a href="" class="item" style="user-select: none;">
<img id="sysicon" class="ts-image" style="height: 30px" white_src="/html/assets/logo.png" dark_src="/html/assets/logo_white.png" src="/html/assets/logo.png"></img>
</a>
<a href="#!" class="is-active item">
Documents
</a>
<a href="#!" class="item">
Examples
</a>
</div>
</div>
</div>
<div class="ts-divider"></div>
<div>
<div class="has-padded">
<div class="ts-grid mobile:is-stacked">
<div class="column is-4-wide">
<div class="ts-box">
<div class="ts-menu is-end-icon">
<a class="item">
Introduction
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item is-active" href="/html/1. Introduction/1. What is Zoraxy Plugin.html">
What is Zoraxy Plugin
</a>
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
</div>
<a class="item">
Architecture
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/2. Architecture/1. Plugin Architecture.html">
Plugin Architecture
</a>
<a class="item" href="/html/2. Architecture/2. Introspect.html">
Introspect
</a>
<a class="item" href="/html/2. Architecture/3. Configure.html">
Configure
</a>
</div>
<a class="item">
Getting Started
<span class="ts-icon is-caret-down-icon"></span>
</a>
<a class="item" href="/html/index.html">
index
</a>
</div>
</div>
</div>
<div class="column is-12-wide">
<div class="ts-box">
<div class="ts-container is-padded has-top-padded-large">
<h1 id="what-is-zoraxy-plugin">
What is Zoraxy Plugin?
</h1>
<p>
<p class="ts-text">
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.
</p>
</p>
<p>
<p class="ts-text">
Zoraxy plugins are distributed as binaries, and developers have the flexibility to choose whether to open source them or not
<strong>
as the plugin library and interface are open source under the LGPL license
</strong>
.
</p>
</p>
<p>
<p class="ts-text">
There are two primary types of plugins:
</p>
</p>
<ul>
<li>
<strong>
Router plugins
</strong>
: Involved with connections from HTTP proxy rules.
<br>
</li>
<li>
<strong>
Utility plugins
</strong>
: Provide user interfaces for various network features that operate independently of the Zoraxy core.
<br>
</li>
</ul>
<div class="ts-divider"></div>
<h2 id="how-plugins-are-distributed-installed">
How plugins are distributed &amp; installed
</h2>
<p>
<p class="ts-text">
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
<code>
linux_amd64_foobar
</code>
,
<code>
windows_amd64_foobar.exe
</code>
, or
<code>
linux_arm64_foobar
</code>
.
</p>
</p>
<p>
<p class="ts-text">
To manually install a plugin for testing, place the binary file into the
<code>
/plugins/{plugin_name}/
</code>
folder within your Zoraxy installation directory.
</p>
</p>
<div class="ts-quote">
<p>
<p class="ts-text">
<strong>
Warning:
</strong>
The binary name inside the folder must match the plugin folder name. For example, the binary should be named
<code>
foobar
</code>
(or
<code>
foobar.exe
</code>
on Windows) if placed in the
<code>
/plugins/foobar/
</code>
folder. Avoid using names like
<code>
foobar_plugin.exe
</code>
.
</p>
</p>
</div>
<p>
<p class="ts-text">
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.
</p>
</p>
<div class="ts-divider"></div>
<h2 id="plugin-vs-pull-request">
<div class="ts-header is-large">
Plugin vs Pull Request
</div>
</h2>
<p>
<p class="ts-text">
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.
</p>
</p>
<ul>
<li>
Designed to handle features that are challenging to integrate directly into the Zoraxy core.
<br>
</li>
<li>
Caters to scenarios where certain features are only applicable in limited situations, avoiding unnecessary resource consumption for other users.
<br>
</li>
<li>
Allows for frequent updates to specific code components without impacting the core&rsquo;s stability or causing downtime.
<br>
</li>
</ul>
<div class="ts-divider"></div>
<h3 id="when-should-you-add-a-core-pr-or-a-plugin">
<div class="ts-header">
When should you add a core PR or a plugin?
</div>
</h3>
<p>
<p class="ts-text">
In certain situations, implementing a feature as a plugin is more reasonable than directly integrating it into the Zoraxy core:
</p>
</p>
<ul>
<li>
<strong>
Core PR
</strong>
: If the feature is relevant to most users and enhances Zoraxy&rsquo;s core functionality, consider submitting a core Pull Request (PR).
<br>
</li>
<li>
<strong>
Plugin
</strong>
: If the feature is targeted at a smaller user base or requires additional dependencies that not all users need, it should be developed as a plugin.
<br>
</li>
</ul>
<p>
The decision depends on the feature&rsquo;s general relevance and its impact on core stability. Plugins offer flexibility without burdening the core.
</p>
</div>
<br>
<br>
</div>
</div>
</div>
</div>
</div>
<div class="ts-container">
<div class="ts-divider"></div>
<div class="ts-content">
<div class="ts-text">
Zoraxy © tobychui
<span class="thisyear">
2025
</span>
</div>
</div>
</div>
<script>
$(".thisyear").text(new Date().getFullYear());
</script>
</body>
</html>

View File

@@ -0,0 +1,316 @@
<!DOCTYPE html>
<html lang="en" class="is-white">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/png" href="/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Getting Started | Zoraxy Documentation
</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.js"></script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Code highlight -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
<!-- additional languages -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/go.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/c.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/xml.min.js"></script>
<script>
hljs.highlightAll();
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/tomorrow-night-bright.css">
<style>
#msgbox{
position: fixed;
bottom: 1em;
right: 1em;
z-index: 9999;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
dialog[open] {
animation: fadeIn 0.3s ease-in-out;
}
code{
border-radius: 0.5rem;
}
</style>
<script src="/html/assets/theme.js"></script>
</head>
<body>
<div class="ts-content">
<div class="ts-container">
<div style="float: right;">
<button class="ts-button is-icon" id="darkModeToggle">
<span class="ts-icon is-moon-icon"></span>
</button>
</div>
<div class="ts-tab is-pilled">
<a href="" class="item" style="user-select: none;">
<img id="sysicon" class="ts-image" style="height: 30px" white_src="/html/assets/logo.png" dark_src="/html/assets/logo_white.png" src="/html/assets/logo.png"></img>
</a>
<a href="#!" class="is-active item">
Documents
</a>
<a href="#!" class="item">
Examples
</a>
</div>
</div>
</div>
<div class="ts-divider"></div>
<div>
<div class="has-padded">
<div class="ts-grid mobile:is-stacked">
<div class="column is-4-wide">
<div class="ts-box">
<div class="ts-menu is-end-icon">
<a class="item">
Introduction
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/1. Introduction/1. What is Zoraxy Plugin.html">
What is Zoraxy Plugin
</a>
<a class="item is-active" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
</div>
<a class="item">
Architecture
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/2. Architecture/1. Plugin Architecture.html">
Plugin Architecture
</a>
<a class="item" href="/html/2. Architecture/2. Introspect.html">
Introspect
</a>
<a class="item" href="/html/2. Architecture/3. Configure.html">
Configure
</a>
</div>
<a class="item">
Getting Started
<span class="ts-icon is-caret-down-icon"></span>
</a>
<a class="item" href="/html/index.html">
index
</a>
</div>
</div>
</div>
<div class="column is-12-wide">
<div class="ts-box">
<div class="ts-container is-padded has-top-padded-large">
<h1 id="getting-started">
Getting Started
</h1>
<p>
<p class="ts-text">
To start developing plugins, you will need the following installed on your computer
</p>
</p>
<ol>
<li>
The source code of Zoraxy
</li>
<li>
Go compiler
</li>
<li>
VSCode (recommended, or any editor of your choice)
</li>
</ol>
<div class="ts-divider"></div>
<h2 id="step-1-start-zoraxy-at-least-once">
<div class="ts-header is-large">
Step 1: Start Zoraxy at least once
</div>
</h2>
<p>
<p class="ts-text">
If you have just cloned Zoraxy from the Github repo, use the following to build and run it once.
</p>
</p>
<pre><code class="language-bash">cd src/
go mod tidy
go build
sudo ./zoraxy
</code></pre>
<p>
<p class="ts-text">
This would allow Zoraxy to generate all the required folder structure on startup.
</p>
</p>
<p>
After the startup process completes, you would see a folder named &ldquo;plugins&rdquo; in the working directory of Zoraxy.
</p>
<div class="ts-divider"></div>
<h2 id="steps-2-prepare-the-development-environment-for-zoraxy-plugin">
<div class="ts-header is-large">
Steps 2: Prepare the development environment for Zoraxy Plugin
</div>
</h2>
<p>
Next, you will need to think of a name for your plugin. Lets name our new plugin &ldquo;Lapwing&rdquo;.
</p>
<p>
<p class="ts-text">
<strong>
Notes: Plugin name described in Introspect (will discuss this in later sessions) can contains space, but the folder and compiled binary filename must not contains space and special characters for platform compatibilities reasons.
</strong>
</p>
</p>
<p>
<p class="ts-text">
Follow the steps below to create the folder structure
</p>
</p>
<h3 id="2-1-create-plugin-folder">
<div class="ts-header">
2.1 Create Plugin Folder
</div>
</h3>
<p>
<p class="ts-text">
Create a folder with your plugin name in the
<code>
plugins
</code>
folder. After creating the folder, you would have something like
<code>
plugins/Lapwing/
</code>
.
</p>
</p>
<h3 id="2-2-locate-and-copy-zoraxy-plugin-library">
<div class="ts-header">
2.2 Locate and copy Zoraxy Plugin library
</div>
</h3>
<p>
<p class="ts-text">
Locate the Zoraxy plugin library from the Zoraxy source code. You can find the
<code>
zoraxy_plugin
</code>
Go module under
<code>
src/mod/plugins/zoraxy_plugin
</code>
.
</p>
</p>
<p>
Copy the
<code>
zoraxy_plugin
</code>
folder from the Zoraxy source code mod folder into the your plugin&rsquo;s mod folder. Let assume you use the same mod folder name as Zoraxy as
<code>
mod
</code>
, then your copied library path should be
<code>
plugins/Lapwing/mod/zoraxy_plugin
</code>
.
</p>
<h3 id="2-3-prepare-go-project-structure">
<div class="ts-header">
2.3 Prepare Go Project structure
</div>
</h3>
<p>
<p class="ts-text">
Create the
<code>
main.go
</code>
file for your plugin. In the example above, it would be located at
<code>
plugins/Lapwing/main.go
</code>
.
</p>
</p>
<p>
<p class="ts-text">
Use
<code>
go mod init yourdomain.com/foo/plugin_name
</code>
to initiate your plugin. By default the
<code>
go.mod
</code>
file will be automatically generated by the go compiler. Assuming you are developing Lapwing with its source located on Github, this command would be
<code>
go mod init github.com/your_user_name/Lapwing
</code>
.
</p>
</p>
<div class="ts-divider"></div>
<h2 id="steps-3-open-plugin-folder-in-ide">
<div class="ts-header is-large">
Steps 3: Open plugin folder in IDE
</div>
</h2>
<p>
<p class="ts-text">
Now open your preferred IDE or text editor and use your plugin folder as the project folder
</p>
</p>
<p>
<p class="ts-text">
Now, you are ready to start developing Zoraxy plugin!
</p>
</p>
</div>
<br>
<br>
</div>
</div>
</div>
</div>
</div>
<div class="ts-container">
<div class="ts-divider"></div>
<div class="ts-content">
<div class="ts-text">
Zoraxy © tobychui
<span class="thisyear">
2025
</span>
</div>
</div>
</div>
<script>
$(".thisyear").text(new Date().getFullYear());
</script>
</body>
</html>

View File

@@ -0,0 +1,179 @@
<!DOCTYPE html>
<html lang="en" class="is-white">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/png" href="/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Plugin Architecture | Zoraxy Documentation
</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.js"></script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Code highlight -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
<!-- additional languages -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/go.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/c.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/xml.min.js"></script>
<script>
hljs.highlightAll();
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/tomorrow-night-bright.css">
<style>
#msgbox{
position: fixed;
bottom: 1em;
right: 1em;
z-index: 9999;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
dialog[open] {
animation: fadeIn 0.3s ease-in-out;
}
code{
border-radius: 0.5rem;
}
</style>
<script src="/html/assets/theme.js"></script>
</head>
<body>
<div class="ts-content">
<div class="ts-container">
<div style="float: right;">
<button class="ts-button is-icon" id="darkModeToggle">
<span class="ts-icon is-moon-icon"></span>
</button>
</div>
<div class="ts-tab is-pilled">
<a href="" class="item" style="user-select: none;">
<img id="sysicon" class="ts-image" style="height: 30px" white_src="/html/assets/logo.png" dark_src="/html/assets/logo_white.png" src="/html/assets/logo.png"></img>
</a>
<a href="#!" class="is-active item">
Documents
</a>
<a href="#!" class="item">
Examples
</a>
</div>
</div>
</div>
<div class="ts-divider"></div>
<div>
<div class="has-padded">
<div class="ts-grid mobile:is-stacked">
<div class="column is-4-wide">
<div class="ts-box">
<div class="ts-menu is-end-icon">
<a class="item">
Introduction
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/1. Introduction/1. What is Zoraxy Plugin.html">
What is Zoraxy Plugin
</a>
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
</div>
<a class="item">
Architecture
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item is-active" href="/html/2. Architecture/1. Plugin Architecture.html">
Plugin Architecture
</a>
<a class="item" href="/html/2. Architecture/2. Introspect.html">
Introspect
</a>
<a class="item" href="/html/2. Architecture/3. Configure.html">
Configure
</a>
</div>
<a class="item">
Getting Started
<span class="ts-icon is-caret-down-icon"></span>
</a>
<a class="item" href="/html/index.html">
index
</a>
</div>
</div>
</div>
<div class="column is-12-wide">
<div class="ts-box">
<div class="ts-container is-padded has-top-padded-large">
<h1 id="plugin-architecture">
Plugin Architecture
</h1>
<p>
<p class="ts-text">
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.
</p>
</p>
<ol>
<li>
Introspect
</li>
<li>
Configure
</li>
<li>
Forwarding
</li>
</ol>
<p>
<p class="ts-text">
The overall flow looks like this.
</p>
</p>
<p>
<div class="ts-image is-rounded" style="max-width: 800px">
<img src="img/1. Plugin Architecture/plugin_workflow.png" alt="" />
</div>
</p>
</div>
<br>
<br>
</div>
</div>
</div>
</div>
</div>
<div class="ts-container">
<div class="ts-divider"></div>
<div class="ts-content">
<div class="ts-text">
Zoraxy © tobychui
<span class="thisyear">
2025
</span>
</div>
</div>
</div>
<script>
$(".thisyear").text(new Date().getFullYear());
</script>
</body>
</html>

View File

@@ -0,0 +1,268 @@
<!DOCTYPE html>
<html lang="en" class="is-white">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/png" href="/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Introspect | Zoraxy Documentation
</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.js"></script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Code highlight -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
<!-- additional languages -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/go.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/c.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/xml.min.js"></script>
<script>
hljs.highlightAll();
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/tomorrow-night-bright.css">
<style>
#msgbox{
position: fixed;
bottom: 1em;
right: 1em;
z-index: 9999;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
dialog[open] {
animation: fadeIn 0.3s ease-in-out;
}
code{
border-radius: 0.5rem;
}
</style>
<script src="/html/assets/theme.js"></script>
</head>
<body>
<div class="ts-content">
<div class="ts-container">
<div style="float: right;">
<button class="ts-button is-icon" id="darkModeToggle">
<span class="ts-icon is-moon-icon"></span>
</button>
</div>
<div class="ts-tab is-pilled">
<a href="" class="item" style="user-select: none;">
<img id="sysicon" class="ts-image" style="height: 30px" white_src="/html/assets/logo.png" dark_src="/html/assets/logo_white.png" src="/html/assets/logo.png"></img>
</a>
<a href="#!" class="is-active item">
Documents
</a>
<a href="#!" class="item">
Examples
</a>
</div>
</div>
</div>
<div class="ts-divider"></div>
<div>
<div class="has-padded">
<div class="ts-grid mobile:is-stacked">
<div class="column is-4-wide">
<div class="ts-box">
<div class="ts-menu is-end-icon">
<a class="item">
Introduction
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/1. Introduction/1. What is Zoraxy Plugin.html">
What is Zoraxy Plugin
</a>
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
</div>
<a class="item">
Architecture
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/2. Architecture/1. Plugin Architecture.html">
Plugin Architecture
</a>
<a class="item is-active" href="/html/2. Architecture/2. Introspect.html">
Introspect
</a>
<a class="item" href="/html/2. Architecture/3. Configure.html">
Configure
</a>
</div>
<a class="item">
Getting Started
<span class="ts-icon is-caret-down-icon"></span>
</a>
<a class="item" href="/html/index.html">
index
</a>
</div>
</div>
</div>
<div class="column is-12-wide">
<div class="ts-box">
<div class="ts-container is-padded has-top-padded-large">
<h1 id="introspect">
Introspect
</h1>
<p>
<p class="ts-text">
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).
</p>
</p>
<p>
<p class="ts-text">
<strong>
This is a pre-defined structure where the plugin must provide to Zoraxy
</strong>
when the plugin is being started with the
<code>
-introspect
</code>
flag.
</p>
</p>
<p>
<p class="ts-text">
The introspect structure is defined under the
<code>
zoraxy_plugin
</code>
library, where both Zoraxy and plugin should use. As of writing, the structure of introspect is like this.
</p>
</p>
<pre><code class="language-go">type IntroSpect struct {
/* Plugin metadata */
ID string `json:&quot;id&quot;` //Unique ID of your plugin, recommended using your own domain in reverse like com.yourdomain.pluginname
Name string `json:&quot;name&quot;` //Name of your plugin
Author string `json:&quot;author&quot;` //Author name of your plugin
AuthorContact string `json:&quot;author_contact&quot;` //Author contact of your plugin, like email
Description string `json:&quot;description&quot;` //Description of your plugin
URL string `json:&quot;url&quot;` //URL of your plugin
Type PluginType `json:&quot;type&quot;` //Type of your plugin, Router(0) or Utilities(1)
VersionMajor int `json:&quot;version_major&quot;` //Major version of your plugin
VersionMinor int `json:&quot;version_minor&quot;` //Minor version of your plugin
VersionPatch int `json:&quot;version_patch&quot;` //Patch version of your plugin
/*
Endpoint Settings
*/
/*
Static Capture Settings
Once plugin is enabled these rules always applies to the enabled HTTP Proxy rule
This is faster than dynamic capture, but less flexible
*/
StaticCapturePaths []StaticCaptureRule `json:&quot;static_capture_paths&quot;` //Static capture paths of your plugin, see Zoraxy documentation for more details
StaticCaptureIngress string `json:&quot;static_capture_ingress&quot;` //Static capture ingress path of your plugin (e.g. /s_handler)
/*
Dynamic Capture Settings
Once plugin is enabled, these rules will be captured and forward to plugin sniff
if the plugin sniff returns 280, the traffic will be captured
otherwise, the traffic will be forwarded to the next plugin
This is slower than static capture, but more flexible
*/
DynamicCaptureSniff string `json:&quot;dynamic_capture_sniff&quot;` //Dynamic capture sniff path of your plugin (e.g. /d_sniff)
DynamicCaptureIngress string `json:&quot;dynamic_capture_ingress&quot;` //Dynamic capture ingress path of your plugin (e.g. /d_handler)
/* UI Path for your plugin */
UIPath string `json:&quot;ui_path&quot;` //UI path of your plugin (e.g. /ui), will proxy the whole subpath tree to Zoraxy Web UI as plugin UI
/* Subscriptions Settings */
SubscriptionPath string `json:&quot;subscription_path&quot;` //Subscription event path of your plugin (e.g. /notifyme), a POST request with SubscriptionEvent as body will be sent to this path when the event is triggered
SubscriptionsEvents map[string]string `json:&quot;subscriptions_events&quot;` //Subscriptions events of your plugin, see Zoraxy documentation for more details
}
</code></pre>
<h2 id="introspect-triggering">
<div class="ts-header is-large">
Introspect Triggering
</div>
</h2>
<p>
<p class="ts-text">
To manually test if the introspect return is correct, you can try using the
<code>
-introspect
</code>
flag on any Zoraxy plugin. You should be able to see an output like so.
</p>
</p>
<pre><code class="language-json">$ ./debugger -introspect
{
&quot;id&quot;: &quot;org.aroz.zoraxy.debugger&quot;,
&quot;name&quot;: &quot;Plugin Debugger&quot;,
&quot;author&quot;: &quot;aroz.org&quot;,
&quot;author_contact&quot;: &quot;https://aroz.org&quot;,
&quot;description&quot;: &quot;A debugger for Zoraxy \u003c-\u003e plugin communication pipeline&quot;,
&quot;url&quot;: &quot;https://zoraxy.aroz.org&quot;,
&quot;type&quot;: 0,
&quot;version_major&quot;: 1,
&quot;version_minor&quot;: 0,
&quot;version_patch&quot;: 0,
&quot;static_capture_paths&quot;: [
{
&quot;capture_path&quot;: &quot;/test_a&quot;
},
{
&quot;capture_path&quot;: &quot;/test_b&quot;
}
],
&quot;static_capture_ingress&quot;: &quot;/s_capture&quot;,
&quot;dynamic_capture_sniff&quot;: &quot;/d_sniff&quot;,
&quot;dynamic_capture_ingress&quot;: &quot;/d_capture&quot;,
&quot;ui_path&quot;: &quot;/debug&quot;,
&quot;subscription_path&quot;: &quot;&quot;,
&quot;subscriptions_events&quot;: null
}
</code></pre>
</div>
<br>
<br>
</div>
</div>
</div>
</div>
</div>
<div class="ts-container">
<div class="ts-divider"></div>
<div class="ts-content">
<div class="ts-text">
Zoraxy © tobychui
<span class="thisyear">
2025
</span>
</div>
</div>
</div>
<script>
$(".thisyear").text(new Date().getFullYear());
</script>
</body>
</html>

View File

@@ -0,0 +1,204 @@
<!DOCTYPE html>
<html lang="en" class="is-white">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/png" href="/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Configure | Zoraxy Documentation
</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.js"></script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Code highlight -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
<!-- additional languages -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/go.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/c.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/xml.min.js"></script>
<script>
hljs.highlightAll();
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/tomorrow-night-bright.css">
<style>
#msgbox{
position: fixed;
bottom: 1em;
right: 1em;
z-index: 9999;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
dialog[open] {
animation: fadeIn 0.3s ease-in-out;
}
code{
border-radius: 0.5rem;
}
</style>
<script src="/html/assets/theme.js"></script>
</head>
<body>
<div class="ts-content">
<div class="ts-container">
<div style="float: right;">
<button class="ts-button is-icon" id="darkModeToggle">
<span class="ts-icon is-moon-icon"></span>
</button>
</div>
<div class="ts-tab is-pilled">
<a href="" class="item" style="user-select: none;">
<img id="sysicon" class="ts-image" style="height: 30px" white_src="/html/assets/logo.png" dark_src="/html/assets/logo_white.png" src="/html/assets/logo.png"></img>
</a>
<a href="#!" class="is-active item">
Documents
</a>
<a href="#!" class="item">
Examples
</a>
</div>
</div>
</div>
<div class="ts-divider"></div>
<div>
<div class="has-padded">
<div class="ts-grid mobile:is-stacked">
<div class="column is-4-wide">
<div class="ts-box">
<div class="ts-menu is-end-icon">
<a class="item">
Introduction
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/1. Introduction/1. What is Zoraxy Plugin.html">
What is Zoraxy Plugin
</a>
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
</div>
<a class="item">
Architecture
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/2. Architecture/1. Plugin Architecture.html">
Plugin Architecture
</a>
<a class="item" href="/html/2. Architecture/2. Introspect.html">
Introspect
</a>
<a class="item is-active" href="/html/2. Architecture/3. Configure.html">
Configure
</a>
</div>
<a class="item">
Getting Started
<span class="ts-icon is-caret-down-icon"></span>
</a>
<a class="item" href="/html/index.html">
index
</a>
</div>
</div>
</div>
<div class="column is-12-wide">
<div class="ts-box">
<div class="ts-container is-padded has-top-padded-large">
<h1 id="configure">
Configure
</h1>
<p>
<p class="ts-text">
Configure or Configure Spec is the
<code>
exec
</code>
call where Zoraxy start the plugin. The configure spec JSON structure is defined in
<code>
zoraxy_plugin
</code>
library.
</p>
</p>
<p>
<p class="ts-text">
As the time of writing, the
<code>
ConfigureSpec
</code>
only contains information on some basic info.
</p>
</p>
<pre><code class="language-go">type ConfigureSpec struct {
Port int `json:&quot;port&quot;` //Port to listen
RuntimeConst RuntimeConstantValue `json:&quot;runtime_const&quot;` //Runtime constant values
//To be expanded
}
</code></pre>
<p>
<p class="ts-text">
The
<code>
ConfigureSpec
</code>
struct will be parsed to JSON and pass to your plugin via the
<code>
-configure=(json payload here)
</code>
.
</p>
</p>
<p>
<p class="ts-text">
In your plugin, you can use the
<code>
zoraxy_plugin
</code>
library to parse it or parse it manually (if you are developing a plugin with other languages).
</p>
</p>
</div>
<br>
<br>
</div>
</div>
</div>
</div>
</div>
<div class="ts-container">
<div class="ts-divider"></div>
<div class="ts-content">
<div class="ts-text">
Zoraxy © tobychui
<span class="thisyear">
2025
</span>
</div>
</div>
</div>
<script>
$(".thisyear").text(new Date().getFullYear());
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,51 @@
/* Things to do before body loads */
function restoreDarkMode(){
if (localStorage.getItem("darkMode") === "enabled") {
$("html").addClass("is-dark");
$("html").removeClass("is-white");
} else {
$("html").removeClass("is-dark");
$("html").addClass("is-white");
}
}
restoreDarkMode();
function updateElementToTheme(isDarkTheme=false){
if (!isDarkTheme){
let whiteSrc = $("#sysicon").attr("white_src");
$("#sysicon").attr("src", whiteSrc);
$("#darkModeToggle").html(`<span class="ts-icon is-sun-icon"></span>`);
// Update the rendering text color in the garphs
if (typeof(changeScaleTextColor) != "undefined"){
changeScaleTextColor("black");
}
}else{
let darkSrc = $("#sysicon").attr("dark_src");
$("#sysicon").attr("src", darkSrc);
$("#darkModeToggle").html(`<span class="ts-icon is-moon-icon"></span>`);
// Update the rendering text color in the garphs
if (typeof(changeScaleTextColor) != "undefined"){
changeScaleTextColor("white");
}
}
}
/* Things to do after body loads */
$(document).ready(function(){
$("#darkModeToggle").on("click", function() {
$("html").toggleClass("is-dark");
$("html").toggleClass("is-white");
if ($("html").hasClass("is-dark")) {
localStorage.setItem("darkMode", "enabled");
updateElementToTheme(true);
} else {
localStorage.setItem("darkMode", "disabled");
updateElementToTheme(false);
}
});
updateElementToTheme(localStorage.getItem("darkMode") === "enabled");
});

View File

@@ -0,0 +1,203 @@
<!DOCTYPE html>
<html lang="en" class="is-white">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/png" href="/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
index | Zoraxy Documentation
</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.js"></script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Code highlight -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
<!-- additional languages -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/go.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/c.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/xml.min.js"></script>
<script>
hljs.highlightAll();
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/tomorrow-night-bright.css">
<style>
#msgbox{
position: fixed;
bottom: 1em;
right: 1em;
z-index: 9999;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
dialog[open] {
animation: fadeIn 0.3s ease-in-out;
}
code{
border-radius: 0.5rem;
}
</style>
<script src="/html/assets/theme.js"></script>
</head>
<body>
<div class="ts-content">
<div class="ts-container">
<div style="float: right;">
<button class="ts-button is-icon" id="darkModeToggle">
<span class="ts-icon is-moon-icon"></span>
</button>
</div>
<div class="ts-tab is-pilled">
<a href="" class="item" style="user-select: none;">
<img id="sysicon" class="ts-image" style="height: 30px" white_src="/html/assets/logo.png" dark_src="/html/assets/logo_white.png" src="/html/assets/logo.png"></img>
</a>
<a href="#!" class="is-active item">
Documents
</a>
<a href="#!" class="item">
Examples
</a>
</div>
</div>
</div>
<div class="ts-divider"></div>
<div>
<div class="has-padded">
<div class="ts-grid mobile:is-stacked">
<div class="column is-4-wide">
<div class="ts-box">
<div class="ts-menu is-end-icon">
<a class="item">
Introduction
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/1. Introduction/1. What is Zoraxy Plugin.html">
What is Zoraxy Plugin
</a>
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
</div>
<a class="item">
Architecture
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/2. Architecture/1. Plugin Architecture.html">
Plugin Architecture
</a>
<a class="item" href="/html/2. Architecture/2. Introspect.html">
Introspect
</a>
<a class="item" href="/html/2. Architecture/3. Configure.html">
Configure
</a>
</div>
<a class="item">
Getting Started
<span class="ts-icon is-caret-down-icon"></span>
</a>
<a class="item is-active" href="/html/index.html">
index
</a>
</div>
</div>
</div>
<div class="column is-12-wide">
<div class="ts-box">
<div class="ts-container is-padded has-top-padded-large">
<h1 id="index">
Index
</h1>
<p>
Welcome to the Zoraxy Plugin Documentation!
<br>
Click on a topic in the side menu to begin navigating through the available resources and guides for developing and managing plugins.
</p>
<h2 id="faq">
<div class="ts-header is-large">
FAQ
</div>
</h2>
<h3 id="what-skills-do-i-need-for-developing-a-plugin">
<div class="ts-header">
What skills do I need for developing a plugin?
</div>
</h3>
<p>
<p class="ts-text">
Basic HTML, JavaScript, and CSS skills are required, with Go (Golang) being the preferred backend language. However, any programming language that can be compiled into a binary and provide a web server interface will work.
</p>
</p>
<h3 id="will-a-plugin-crash-the-whole-zoraxy">
<div class="ts-header">
Will a plugin crash the whole Zoraxy?
</div>
</h3>
<p>
<p class="ts-text">
No. Plugins operate in a separate process from Zoraxy. If a plugin crashes, Zoraxy will terminate and disable that plugin without affecting the core operations. This is by design to ensure stability.
</p>
</p>
<h3 id="can-i-sell-my-plugin">
<div class="ts-header">
Can I sell my plugin?
</div>
</h3>
<p>
<p class="ts-text">
Yes, the plugin library and interface design are open source under the LGPL license. You are not required to disclose the source code of your plugin as long as you do not modify the plugin library and use it as-is. For more details on how to comply with the license, refer to the licensing documentation.
</p>
</p>
<h3 id="how-can-i-add-my-plugin-to-the-official-plugin-store">
<div class="ts-header">
How can I add my plugin to the official plugin store?
</div>
</h3>
<p>
<p class="ts-text">
To add your plugin to the official plugin store, open a pull request (PR) in the plugin repository.
</p>
</p>
</div>
<br>
<br>
</div>
</div>
</div>
</div>
</div>
<div class="ts-container">
<div class="ts-divider"></div>
<div class="ts-content">
<div class="ts-text">
Zoraxy © tobychui
<span class="thisyear">
2025
</span>
</div>
</div>
</div>
<script>
$(".thisyear").text(new Date().getFullYear());
</script>
</body>
</html>