From 0d4c71d0f6ffda15df05480575d8f4c5750ce9b6 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Tue, 31 Dec 2024 22:56:51 +0800 Subject: [PATCH 01/23] Fixed #450 --- src/def.go | 2 +- src/mod/dynamicproxy/dynamicproxy.go | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/def.go b/src/def.go index 0ebeb5a..91a491b 100644 --- a/src/def.go +++ b/src/def.go @@ -42,7 +42,7 @@ import ( const ( /* Build Constants */ SYSTEM_NAME = "Zoraxy" - SYSTEM_VERSION = "3.1.6" + SYSTEM_VERSION = "3.1.7" DEVELOPMENT_BUILD = false /* Development: Set to false to use embedded web fs */ /* System Constants */ diff --git a/src/mod/dynamicproxy/dynamicproxy.go b/src/mod/dynamicproxy/dynamicproxy.go index 773910d..8e45be4 100644 --- a/src/mod/dynamicproxy/dynamicproxy.go +++ b/src/mod/dynamicproxy/dynamicproxy.go @@ -191,7 +191,24 @@ func (router *Router) StartProxyService() error { w.Write([]byte("400 - Bad Request")) } else { //No defined sub-domain - http.NotFound(w, r) + if router.Root.DefaultSiteOption == DefaultSite_NoResponse { + //No response. Just close the connection + hijacker, ok := w.(http.Hijacker) + if !ok { + w.Header().Set("Connection", "close") + return + } + conn, _, err := hijacker.Hijack() + if err != nil { + w.Header().Set("Connection", "close") + return + } + conn.Close() + } else { + //Default behavior + http.NotFound(w, r) + } + } } From 45f61b3053cc0c75df51734e756d3627d7de5bc7 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Wed, 15 Jan 2025 20:44:20 +0800 Subject: [PATCH 02/23] Optimized dark theme mode - Make dark theme mode less dark --- src/def.go | 2 +- src/mod/dynamicproxy/domainsniff/proxmox.go | 1 + src/web/darktheme.css | 38 +++++++++++++-------- src/web/snippet/upstreams.html | 4 +++ 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/def.go b/src/def.go index 91a491b..4a9ae97 100644 --- a/src/def.go +++ b/src/def.go @@ -43,7 +43,7 @@ const ( /* Build Constants */ SYSTEM_NAME = "Zoraxy" SYSTEM_VERSION = "3.1.7" - DEVELOPMENT_BUILD = false /* Development: Set to false to use embedded web fs */ + DEVELOPMENT_BUILD = true /* Development: Set to false to use embedded web fs */ /* System Constants */ TMP_FOLDER = "./tmp" diff --git a/src/mod/dynamicproxy/domainsniff/proxmox.go b/src/mod/dynamicproxy/domainsniff/proxmox.go index e5aa1cb..8c5a9ad 100644 --- a/src/mod/dynamicproxy/domainsniff/proxmox.go +++ b/src/mod/dynamicproxy/domainsniff/proxmox.go @@ -17,5 +17,6 @@ func IsProxmox(r *http.Request) bool { return true } } + return false } diff --git a/src/web/darktheme.css b/src/web/darktheme.css index 28646ea..3862adb 100644 --- a/src/web/darktheme.css +++ b/src/web/darktheme.css @@ -23,6 +23,10 @@ body:not(.darkTheme){ --text_color_inverted: #fcfcfc; --button_text_color: #878787; --button_border_color: #dedede; + --buttom_toggle_active: #01dc64; + --buttom_toggle_disabled: #f2f2f2; + --table_bg_default: transparent; + --status_dot_bg: #e8e8e8; --theme_background: linear-gradient(60deg, rgb(84, 58, 183) 0%, rgb(0, 172, 193) 100%); --theme_background_inverted: linear-gradient(215deg, rgba(38,60,71,1) 13%, rgba(2,3,42,1) 84%); @@ -31,10 +35,10 @@ body:not(.darkTheme){ } body.darkTheme{ - --theme_bg: #0a090e; - --theme_bg_primary: #060912; - --theme_bg_secondary:#172a41; - --theme_highlight: #4380b0; + --theme_bg: #1e1e1e; + --theme_bg_primary: #151517; + --theme_bg_secondary:#1b3572; + --theme_highlight: #6a7792; --theme_bg_active: #020101; --theme_bg_inverted: #f8f8f9; --theme_advance: #000000; @@ -47,8 +51,12 @@ body.darkTheme{ --text_color_inverted: #414141; --button_text_color: #e9e9e9; --button_border_color: #646464; + --buttom_toggle_active: #01dc64; + --buttom_toggle_disabled: #2b2b2b; + --table_bg_default: #121214; + --status_dot_bg: #232323; - --theme_background: linear-gradient(214deg, rgba(3,1,70,1) 17%, rgb(1, 55, 80) 78%); + --theme_background: linear-gradient(23deg, rgba(2,74,106,1) 17%, rgba(46,12,136,1) 86%); --theme_background_inverted: linear-gradient(215deg, rgba(38,60,71,1) 13%, rgba(2,3,42,1) 84%); --theme_green: linear-gradient(214deg, rgba(25,128,94,1) 17%, rgba(62,76,111,1) 78%); --theme_red: linear-gradient(203deg, rgba(250,172,38,1) 17%, rgba(202,0,37,1) 78%); @@ -195,10 +203,10 @@ body.darkTheme textarea:focus { } body.darkTheme .ui.toggle.checkbox input ~ label::before{ - background-color: var(--theme_bg_secondary) !important; + background-color: var(--buttom_toggle_disabled) !important; } body.darkTheme .ui.toggle.checkbox input:checked ~ label::before{ - background-color: var(--theme_highlight) !important; + background-color: var(--buttom_toggle_active) !important; } #sidemenuBtn{ @@ -444,7 +452,7 @@ body.darkTheme .ui.table{ body.darkTheme .ui.celled.sortable.unstackable.compact.table thead th, body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td, body.darkTheme .ui.celled.sortable.unstackable.compact.table tfoot td { - background-color: var(--theme_bg) !important; + background-color: var(--table_bg_default) !important; color: var(--text_color) !important; border-color: var(--divider_color) !important; } @@ -476,11 +484,11 @@ body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td .ui.toggle } body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td .ui.toggle.checkbox input ~ label::before { - background-color: var(--theme_bg_secondary) !important; + background-color: var(--buttom_toggle_disabled) !important; } body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td .ui.toggle.checkbox input:checked ~ label::before { - background-color: var(--theme_highlight) !important; + background-color: var(--buttom_toggle_active) !important; } body.darkTheme .ui.celled.sortable.unstackable.compact.table tbody td .ui.circular.mini.basic.icon.button { @@ -714,7 +722,7 @@ body.darkTheme #redirectset .ui.sortable.unstackable.celled.table thead th { } body.darkTheme #redirectset .ui.sortable.unstackable.celled.table tbody tr td { - background-color: var(--theme_bg) !important; + background-color: var(--table_bg_default) !important; color: var(--text_color) !important; border-color: var(--divider_color) !important; } @@ -833,7 +841,7 @@ body.darkTheme #access .ui.unstackable.basic.celled.table thead th { } body.darkTheme #access .ui.unstackable.basic.celled.table tbody tr td { - background-color: var(--theme_bg) !important; + background-color: var(--table_bg_default) !important; color: var(--text_color) !important; border-color: var(--divider_color) !important; } @@ -985,8 +993,8 @@ body.darkTheme #utm .standardContainer { } body.darkTheme #utm .standardContainer .padding.statusDot { - background-color: var(--theme_bg) !important; - border: 0.2px solid var(--text_color_inverted) !important; + background-color: var(--status_dot_bg) !important; + } body.darkTheme .ui.utmloading.segment { @@ -1116,7 +1124,7 @@ body.darkTheme .statistic .label { /* Other Tables */ body.darkTheme .ui.celled.compact.table { - background-color: var(--theme_bg) !important; + background-color: var(--table_bg_default) !important; color: var(--text_color) !important; border-color: var(--divider_color) !important; } diff --git a/src/web/snippet/upstreams.html b/src/web/snippet/upstreams.html index 39c9ccb..38a34d9 100644 --- a/src/web/snippet/upstreams.html +++ b/src/web/snippet/upstreams.html @@ -36,6 +36,10 @@ overflow-y: auto; } + body.darkTheme #upstreamTable{ + border: 1px solid var(--button_border_color); + } + .upstreamEntry.inactive{ background-color: #f3f3f3 !important; } From bfd64a885e94f0b2e4c9ac0ca81e2aa994f90a03 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Wed, 15 Jan 2025 20:59:09 +0800 Subject: [PATCH 03/23] Removed confirm from access - Removed troublesome confirm popup from black / whitelist - Minor fix to checkbox css --- src/web/components/access.html | 8 ++++---- src/web/darktheme.css | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/web/components/access.html b/src/web/components/access.html index 10f5437..6a59453 100644 --- a/src/web/components/access.html +++ b/src/web/components/access.html @@ -1174,7 +1174,7 @@

Quick Ban List

} function removeIpBlacklist(ipaddr){ - if (confirm("Confirm remove blacklist for " + ipaddr + " ?")){ + //if (confirm("Confirm remove blacklist for " + ipaddr + " ?")){ $.cjax({ url: "/api/blacklist/ip/remove", type: "POST", @@ -1191,7 +1191,7 @@

Quick Ban List

} }); - } + //} } /* @@ -1318,7 +1318,7 @@

Quick Ban List

} function removeIpWhitelist(ipaddr){ - if (confirm("Confirm remove whitelist for " + ipaddr + " ?")){ + //if (confirm("Confirm remove whitelist for " + ipaddr + " ?")){ $.cjax({ url: "/api/whitelist/ip/remove", type: "POST", @@ -1335,7 +1335,7 @@

Quick Ban List

} }); - } + //} } /* diff --git a/src/web/darktheme.css b/src/web/darktheme.css index 3862adb..16b9042 100644 --- a/src/web/darktheme.css +++ b/src/web/darktheme.css @@ -209,6 +209,10 @@ body.darkTheme .ui.toggle.checkbox input:checked ~ label::before{ background-color: var(--buttom_toggle_active) !important; } +body.darkTheme .ui.checkbox:not(.toggle) input[type="checkbox"]{ + opacity: 100% !important; +} + #sidemenuBtn{ border: 1px solid var(--button_border_color) !important; } From eeb438eb184f1791923e60acd546e40a4e0fff09 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Sat, 18 Jan 2025 15:19:55 +0800 Subject: [PATCH 04/23] Fixed #474 - Added automatic port check and reminder for beginners --- src/mod/netutils/netutils.go | 10 ++++++++++ src/reverseproxy.go | 26 ++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/mod/netutils/netutils.go b/src/mod/netutils/netutils.go index 22466f3..46ebcd4 100644 --- a/src/mod/netutils/netutils.go +++ b/src/mod/netutils/netutils.go @@ -157,3 +157,13 @@ func resolveIpFromDomain(targetIpOrDomain string) string { return targetIpAddrString } + +// Check if the given port is already used by another process +func CheckIfPortOccupied(portNumber int) bool { + listener, err := net.Listen("tcp", ":"+strconv.Itoa(portNumber)) + if err != nil { + return true + } + listener.Close() + return false +} diff --git a/src/reverseproxy.go b/src/reverseproxy.go index c55b6db..fb22cf6 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -14,6 +14,7 @@ import ( "imuslab.com/zoraxy/mod/dynamicproxy/loadbalance" "imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy" "imuslab.com/zoraxy/mod/dynamicproxy/rewrite" + "imuslab.com/zoraxy/mod/netutils" "imuslab.com/zoraxy/mod/uptime" "imuslab.com/zoraxy/mod/utils" ) @@ -28,10 +29,22 @@ func ReverseProxtInit() { Load Reverse Proxy Global Settings */ inboundPort := 443 + autoStartReverseProxy := true if sysdb.KeyExists("settings", "inbound") { + //Read settings from database sysdb.Read("settings", "inbound", &inboundPort) - SystemWideLogger.Println("Serving inbound port ", inboundPort) + if netutils.CheckIfPortOccupied(inboundPort) { + autoStartReverseProxy = false + SystemWideLogger.Println("Inbound port ", inboundPort, " is occupied. Change the listening port in the webmin panel and press \"Start Service\" to start reverse proxy service") + } else { + SystemWideLogger.Println("Serving inbound port ", inboundPort) + } } else { + //Default port + if netutils.CheckIfPortOccupied(inboundPort) { + inboundPort = 8743 + SystemWideLogger.Println("Port 443 is occupied. Switching to backup port 8743 instead") + } SystemWideLogger.Println("Inbound port not set. Using default (443)") } @@ -60,6 +73,9 @@ func ReverseProxtInit() { } listenOnPort80 := true + if netutils.CheckIfPortOccupied(80) { + listenOnPort80 = false + } sysdb.Read("settings", "listenP80", &listenOnPort80) if listenOnPort80 { SystemWideLogger.Println("Port 80 listener enabled") @@ -136,9 +152,11 @@ func ReverseProxtInit() { //Start Service //Not sure why but delay must be added if you have another //reverse proxy server in front of this service - time.Sleep(300 * time.Millisecond) - dynamicProxyRouter.StartProxyService() - SystemWideLogger.Println("Dynamic Reverse Proxy service started") + if autoStartReverseProxy { + time.Sleep(300 * time.Millisecond) + dynamicProxyRouter.StartProxyService() + SystemWideLogger.Println("Dynamic Reverse Proxy service started") + } //Add all proxy services to uptime monitor //Create a uptime monitor service From e20f81608016dc0a9efbdd9a5b6338d30e680e96 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Sat, 18 Jan 2025 21:49:35 +0800 Subject: [PATCH 05/23] Fixed #467 - Added status dot info in uptime monitor - simplified the no response record to no_resp in default site --- src/mod/dynamicproxy/Server.go | 2 +- src/web/components/uptime.html | 98 +++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/src/mod/dynamicproxy/Server.go b/src/mod/dynamicproxy/Server.go index 952cb7f..0922127 100644 --- a/src/mod/dynamicproxy/Server.go +++ b/src/mod/dynamicproxy/Server.go @@ -219,7 +219,7 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request) } case DefaultSite_NoResponse: //No response. Just close the connection - h.Parent.logRequest(r, false, 444, "root-noresponse", domainOnly) + h.Parent.logRequest(r, false, 444, "root-no_resp", domainOnly) hijacker, ok := w.(http.Hijacker) if !ok { w.Header().Set("Connection", "close") diff --git a/src/web/components/uptime.html b/src/web/components/uptime.html index 33306e0..e55ecb7 100644 --- a/src/web/components/uptime.html +++ b/src/web/components/uptime.html @@ -108,6 +108,36 @@

} } + function showStatusDotInfo(targetDot){ + $(".statusbar .selectedDotInfo").hide(); + let payload = $(targetDot).attr("payload"); + let statusData = JSON.parse(decodeURIComponent(payload)); + let statusDotInfoEle = $(targetDot).parent().parent().find(".selectedDotInfo"); + let statusInfoEle = $(statusDotInfoEle).find(".status_dot_status_info"); + //Fill in the data to the info box + $(statusDotInfoEle).find(".status_dot_timestamp").text(format_time(statusData.Timestamp)); + $(statusDotInfoEle).find(".status_dot_latency").text(statusData.Latency + "ms"); + $(statusDotInfoEle).find(".status_dot_status_code").text(statusData.StatusCode); + + + //Set the class of the info box if status code is 5xx + $(statusDotInfoEle).removeClass("yellow"); + $(statusDotInfoEle).removeClass("red"); + $(statusDotInfoEle).removeClass("green"); + if (statusData.StatusCode >= 500 && statusData.StatusCode < 600){ + $(statusDotInfoEle).addClass("yellow"); + $(statusInfoEle).text(httpErrorStatusCodeToText(statusData.StatusCode)); + }else if (statusData.StatusCode == 0 && !statusData.Online){ + $(statusDotInfoEle).addClass("red"); + $(statusInfoEle).text("Upstream is offline"); + }else{ + $(statusDotInfoEle).addClass("green"); + $(statusInfoEle).text("Upstream Online"); + } + + $(statusDotInfoEle).show(); + } + function renderUptimeData(key, value){ if (value.length == 0){ @@ -132,6 +162,7 @@

let thisStatus = value[i]; let dotType = ""; let statusCode = thisStatus.StatusCode; + let statusDotPayload = encodeURIComponent(JSON.stringify(thisStatus)); if (!thisStatus.Online && statusCode == 0){ dotType = "offline"; @@ -159,7 +190,7 @@

} let datetime = format_time(thisStatus.Timestamp); - statusDotList += `
` + statusDotList += `
` } ontimeRate = ontimeRate / value.length * 100; @@ -207,7 +238,7 @@

onlineStatusCss = `color: #f38020;`; reminderEle = `Target online but not accessible`; - + }else{ currentOnlineStatus = ` Offline`; onlineStatusCss = `color: #df484a;`; @@ -233,8 +264,71 @@

${name}

${statusDotList} ${reminderEle} +
`); } + function httpErrorStatusCodeToText(statusCode){ + switch(statusCode){ + case 400: + return "Bad Request"; + case 401: + return "Unauthorized"; + case 403: + return "Forbidden"; + case 404: + return "Not Found"; + case 405: + return "Method Not Allowed"; + case 500: + return "Internal Server Error"; + case 501: + return "Not Implemented"; + case 502: + return "Bad Gateway"; + case 503: + return "Service Unavailable"; + case 504: + return "Gateway Timeout"; + case 505: + return "HTTP Version Not Supported"; + case 506: + return "Variant Also Negotiates"; + case 507: + return "Insufficient Storage"; + case 508: + return "Loop Detected"; + case 510: + return "Not Extended"; + case 511: + return "Network Authentication Required"; + case 520: + return "Web Server Returned an Unknown Error (Cloudflare)"; + case 521: + return "Web Server is Down (Cloudflare)"; + case 522: + return "Connection Timed Out (Cloudflare)"; + case 523: + return "Origin is Unreachable (Cloudflare)"; + case 524: + return "A Timeout Occurred (Cloudflare)"; + case 525: + return "SSL Handshake Failed (Cloudflare)"; + case 526: + return "Invalid SSL Certificate (Cloudflare)"; + case 527: + return "Railgun Error (Cloudflare)"; + default: + return "Unknown Error"; + } + } \ No newline at end of file From 7d9f240d5608e8ab1a661dc569aebdb9b8093a2b Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Sat, 18 Jan 2025 22:10:45 +0800 Subject: [PATCH 06/23] Updated Close Conn resp for TLS - Use No Resp instead of 200 for close connection mode default site settings --- src/mod/dynamicproxy/Server.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/mod/dynamicproxy/Server.go b/src/mod/dynamicproxy/Server.go index 0922127..c1be285 100644 --- a/src/mod/dynamicproxy/Server.go +++ b/src/mod/dynamicproxy/Server.go @@ -209,25 +209,18 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request) http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect) case DefaultSite_NotFoundPage: //Serve the not found page, use template if exists - w.Header().Set("Content-Type", "text/html; charset=utf-8") - w.WriteHeader(http.StatusNotFound) - template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/notfound.html")) - if err != nil { - w.Write(page_hosterror) - } else { - w.Write(template) - } + h.serve404PageWithTemplate(w, r) case DefaultSite_NoResponse: //No response. Just close the connection h.Parent.logRequest(r, false, 444, "root-no_resp", domainOnly) hijacker, ok := w.(http.Hijacker) if !ok { - w.Header().Set("Connection", "close") + w.WriteHeader(http.StatusNoContent) return } conn, _, err := hijacker.Hijack() if err != nil { - w.Header().Set("Connection", "close") + w.WriteHeader(http.StatusNoContent) return } conn.Close() @@ -241,3 +234,15 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request) http.Error(w, "544 - No Route Defined", 544) } } + +// Serve 404 page with template if exists +func (h *ProxyHandler) serve404PageWithTemplate(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusNotFound) + template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/notfound.html")) + if err != nil { + w.Write(page_hosterror) + } else { + w.Write(template) + } +} From c49f2fd1db80290f14bab50178263c384c171d4e Mon Sep 17 00:00:00 2001 From: adoolaard Date: Thu, 30 Jan 2025 21:22:19 +0100 Subject: [PATCH 07/23] Changed dockerfile to better cache --- docker/Dockerfile | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 2099358..5c3c5cd 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,19 +1,27 @@ +# Stage 1: Build Zoraxy FROM docker.io/golang:alpine AS build-zoraxy +# Install dependencies RUN mkdir -p /opt/zoraxy/source/ &&\ mkdir -p /usr/local/bin/ -# If you build it yourself, you will need to add the src directory into the docker directory. -COPY ./src/ /opt/zoraxy/source/ - WORKDIR /opt/zoraxy/source/ -RUN go mod tidy &&\ - go build -o /usr/local/bin/zoraxy &&\ +# Copy go.mod and go.sum first to leverage Docker cache +COPY ./src/go.mod ./src/go.sum ./ +RUN go mod tidy + +# Copy the rest of the source code +COPY ./src/ . + +# Build the application +RUN go build -o /usr/local/bin/zoraxy &&\ chmod 755 /usr/local/bin/zoraxy +# Stage 2: Build ZeroTier FROM docker.io/ubuntu:latest AS build-zerotier +# Install dependencies RUN mkdir -p /opt/zerotier/source/ &&\ mkdir -p /usr/local/bin/ @@ -22,6 +30,7 @@ WORKDIR /opt/zerotier/source/ RUN apt-get update -y &&\ apt-get install -y curl jq build-essential pkg-config clang cargo libssl-dev +# Download and build ZeroTier RUN curl -Lo ZeroTierOne.tar.gz https://codeload.github.com/zerotier/ZeroTierOne/tar.gz/refs/tags/1.10.6 &&\ tar -xzvf ZeroTierOne.tar.gz &&\ cd ZeroTierOne-* &&\ @@ -29,19 +38,24 @@ RUN curl -Lo ZeroTierOne.tar.gz https://codeload.github.com/zerotier/ZeroTierOne mv ./zerotier-one /usr/local/bin/zerotier-one &&\ chmod 755 /usr/local/bin/zerotier-one +# Stage 3: Final image FROM docker.io/ubuntu:latest +# Install runtime dependencies RUN apt-get update -y &&\ apt-get install -y bash sudo netcat-openbsd libssl-dev ca-certificates +# Copy entrypoint script COPY --chmod=700 ./entrypoint.sh /opt/zoraxy/ + +# Copy built binaries from previous stages COPY --from=build-zoraxy /usr/local/bin/zoraxy /usr/local/bin/zoraxy COPY --from=build-zerotier /usr/local/bin/zerotier-one /usr/local/bin/zerotier-one WORKDIR /opt/zoraxy/config/ +# Set environment variables ENV ZEROTIER="false" - ENV AUTORENEW="86400" ENV CFGUPGRADE="true" ENV DB="auto" @@ -60,9 +74,12 @@ ENV WEBROOT="./www" ENV ZTAUTH="" ENV ZTPORT="9993" +# Define volumes VOLUME [ "/opt/zoraxy/config/" ] +# Set entrypoint ENTRYPOINT [ "/opt/zoraxy/entrypoint.sh" ] +# Healthcheck HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 CMD nc -vz 127.0.0.1 $PORT || exit 1 From 791fbfa1b45efc50e4f2f2d41cb74cf2405443f1 Mon Sep 17 00:00:00 2001 From: adoolaard Date: Thu, 30 Jan 2025 21:48:40 +0100 Subject: [PATCH 08/23] Updated gitignore --- .gitignore | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 5e9c044..8cdd224 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,9 @@ src/tmp/localhost.pem src/www/html/index.html src/sys.uuid src/zoraxy -src/log/ \ No newline at end of file +src/log/ + + +# dev-tags +/Dockerfile +/Entrypoint.sh \ No newline at end of file From a402c4f3269daf2f9baed5a2226b9716dd513db8 Mon Sep 17 00:00:00 2001 From: adoolaard Date: Thu, 30 Jan 2025 22:22:42 +0100 Subject: [PATCH 09/23] Tags are working, just not yet editable --- src/mod/dynamicproxy/typedef.go | 1 + src/reverseproxy.go | 15 +++++++++++++++ src/web/components/httprp.html | 4 ++++ src/web/components/rules.html | 7 +++++++ 4 files changed, 27 insertions(+) diff --git a/src/mod/dynamicproxy/typedef.go b/src/mod/dynamicproxy/typedef.go index c7a060d..2919a7e 100644 --- a/src/mod/dynamicproxy/typedef.go +++ b/src/mod/dynamicproxy/typedef.go @@ -194,6 +194,7 @@ type ProxyEndpoint struct { //Internal Logic Elements parent *Router `json:"-"` + Tags []string // Tags for the proxy endpoint } /* diff --git a/src/reverseproxy.go b/src/reverseproxy.go index c55b6db..ced72f5 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -287,6 +287,12 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) { } } + tagsStr, _ := utils.PostPara(r, "tags") + tags := strings.Split(tagsStr, ",") + for i := range tags { + tags[i] = strings.TrimSpace(tags[i]) + } + var proxyEndpointCreated *dynamicproxy.ProxyEndpoint if eptype == "host" { rootOrMatchingDomain, err := utils.PostPara(r, "rootname") @@ -357,6 +363,8 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) { // Rate Limit RequireRateLimit: requireRateLimit, RateLimit: int64(proxyRateLimit), + + Tags: tags, } preparedEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&thisProxyEndpoint) @@ -515,6 +523,12 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) { return } + tagsStr, _ := utils.PostPara(r, "tags") + tags := strings.Split(tagsStr, ",") + for i := range tags { + tags[i] = strings.TrimSpace(tags[i]) + } + //Generate a new proxyEndpoint from the new config newProxyEndpoint := dynamicproxy.CopyEndpoint(targetProxyEntry) newProxyEndpoint.BypassGlobalTLS = bypassGlobalTLS @@ -539,6 +553,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) { newProxyEndpoint.RateLimit = proxyRateLimit newProxyEndpoint.UseStickySession = useStickySession newProxyEndpoint.DisableUptimeMonitor = disbleUtm + newProxyEndpoint.Tags = tags //Prepare to replace the current routing rule readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint) diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html index 8ecd402..1464e38 100644 --- a/src/web/components/httprp.html +++ b/src/web/components/httprp.html @@ -19,6 +19,7 @@

HTTP Proxy

Host Destination Virtual Directory + Tags Advanced Settings Actions @@ -124,6 +125,7 @@

HTTP Proxy

${vdList} + ${subd.Tags.join(", ")} ${subd.AuthenticationProvider.AuthMethod == 0x1?` Basic Auth`:``} ${subd.AuthenticationProvider.AuthMethod == 0x2?` Authelia`:``} @@ -457,6 +459,7 @@

HTTP Proxy

let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked; let rateLimit = $(row).find(".RateLimit").val(); let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked; + let tags = $("#proxyTags").val().trim(); $.cjax({ url: "/api/proxy/edit", @@ -470,6 +473,7 @@

HTTP Proxy

"authprovider" :authProviderType, "rate" :requireRateLimit, "ratenum" :rateLimit, + "tags": tags, }, success: function(data){ if (data.error !== undefined){ diff --git a/src/web/components/rules.html b/src/web/components/rules.html index f6df6ce..dd37efd 100644 --- a/src/web/components/rules.html +++ b/src/web/components/rules.html @@ -63,6 +63,11 @@

New Proxy Rule

+
+ + + Comma-separated list of tags for this proxy host. +
Security @@ -198,6 +203,7 @@

New Proxy Rule

let skipWebSocketOriginCheck = $("#skipWebsocketOriginCheck")[0].checked; let accessRuleToUse = $("#newProxyRuleAccessFilter").val(); let useStickySessionLB = $("#useStickySessionLB")[0].checked; + let tags = $("#proxyTags").val().trim(); if (rootname.trim() == ""){ $("#rootname").parent().addClass("error"); @@ -231,6 +237,7 @@

New Proxy Rule

cred: JSON.stringify(credentials), access: accessRuleToUse, stickysess: useStickySessionLB, + tags: tags, }, success: function(data){ if (data.error != undefined){ From e4ad505f2ade0230263cf76c3a2352ecfcde9e1d Mon Sep 17 00:00:00 2001 From: adoolaard Date: Thu, 30 Jan 2025 22:42:06 +0100 Subject: [PATCH 10/23] Tags editor works! --- src/web/components/httprp.html | 9 ++++ src/web/snippet/tagEditor.html | 77 ++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 src/web/snippet/tagEditor.html diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html index 1464e38..cdb9873 100644 --- a/src/web/components/httprp.html +++ b/src/web/components/httprp.html @@ -417,6 +417,7 @@

HTTP Proxy

+ `); $(".hostAccessRuleSelector").dropdown(); @@ -613,4 +614,12 @@

HTTP Proxy

tabSwitchEventBind["httprp"] = function(){ listProxyEndpoints(); } + + function editTags(uuid){ + let payload = encodeURIComponent(JSON.stringify({ + ept: "host", + ep: uuid + })); + showSideWrapper("snippet/tagEditor.html?t=" + Date.now() + "#" + payload); + } \ No newline at end of file diff --git a/src/web/snippet/tagEditor.html b/src/web/snippet/tagEditor.html new file mode 100644 index 0000000..e4c5a32 --- /dev/null +++ b/src/web/snippet/tagEditor.html @@ -0,0 +1,77 @@ + + + + + + + + + + + +
+
+
+ Edit Tags +
+
+
+
+

Enter tags for this proxy host. Use commas to separate multiple tags.

+
+
+ + +
+ + +
+
+ + + \ No newline at end of file From 8df68f1f4eec0be0795350d38505fc4abf3fa7d5 Mon Sep 17 00:00:00 2001 From: adoolaard Date: Thu, 30 Jan 2025 22:48:48 +0100 Subject: [PATCH 11/23] Zoeken en filteren werkt ook! --- src/web/components/httprp.html | 48 ++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html index cdb9873..0e2ce06 100644 --- a/src/web/components/httprp.html +++ b/src/web/components/httprp.html @@ -12,6 +12,19 @@

HTTP Proxy

min-width: 200px; } +
+ + +
+
@@ -144,12 +157,42 @@

HTTP Proxy

`); }); + populateTagFilterDropdown(data); } resolveAccessRuleNameOnHostRPlist(); }); } + // Function to populate the tag filter dropdown + function populateTagFilterDropdown(data) { + let tags = new Set(); + data.forEach(subd => { + subd.Tags.forEach(tag => tags.add(tag)); + }); + let dropdownMenu = $("#tagFilterDropdown .menu"); + dropdownMenu.html('
All
'); + tags.forEach(tag => { + dropdownMenu.append(`
${tag}
`); + }); + $('#tagFilterDropdown').dropdown(); + } + + // Function to filter the proxy list + function filterProxyList() { + let searchInput = $("#searchInput").val().toLowerCase(); + let selectedTag = $("#tagFilterDropdown").dropdown('get value'); + $("#httpProxyList tr").each(function() { + let host = $(this).find("td[data-label='']").text().toLowerCase(); + let tags = $(this).find("td[data-label='tags']").text().toLowerCase(); + if ((host.includes(searchInput) || searchInput === "") && (tags.includes(selectedTag) || selectedTag === "")) { + $(this).show(); + } else { + $(this).hide(); + } + }); + } + //Perform realtime alias update without refreshing the whole page function updateAliasListForEndpoint(endpointName, newAliasDomainList){ let targetEle = $(`.aliasDomains[eptuuid='${endpointName}']`); @@ -622,4 +665,9 @@

HTTP Proxy

})); showSideWrapper("snippet/tagEditor.html?t=" + Date.now() + "#" + payload); } + + // Initialize the proxy list on page load + $(document).ready(function() { + listProxyEndpoints(); + }); \ No newline at end of file From 97a6cf016a5431805ec8197e67e729e731b7ddb6 Mon Sep 17 00:00:00 2001 From: adoolaard Date: Fri, 31 Jan 2025 00:17:10 +0100 Subject: [PATCH 12/23] Point on the I --- src/reverseproxy.go | 22 ++++++++++++++-------- src/web/components/httprp.html | 16 +++++++++++++--- src/web/components/rules.html | 1 + 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/reverseproxy.go b/src/reverseproxy.go index ced72f5..c0f3fe9 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -287,10 +287,13 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) { } } - tagsStr, _ := utils.PostPara(r, "tags") - tags := strings.Split(tagsStr, ",") - for i := range tags { - tags[i] = strings.TrimSpace(tags[i]) + tagStr, _ := utils.PostPara(r, "tags") + tags := []string{} + if tagStr != "" { + tags = strings.Split(tagStr, ",") + for i := range tags { + tags[i] = strings.TrimSpace(tags[i]) + } } var proxyEndpointCreated *dynamicproxy.ProxyEndpoint @@ -523,10 +526,13 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) { return } - tagsStr, _ := utils.PostPara(r, "tags") - tags := strings.Split(tagsStr, ",") - for i := range tags { - tags[i] = strings.TrimSpace(tags[i]) + tagStr, _ := utils.PostPara(r, "tags") + tags := []string{} + if tagStr != "" { + tags = strings.Split(tagStr, ",") + for i := range tags { + tags[i] = strings.TrimSpace(tags[i]) + } } //Generate a new proxyEndpoint from the new config diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html index 0e2ce06..1624fc5 100644 --- a/src/web/components/httprp.html +++ b/src/web/components/httprp.html @@ -138,7 +138,9 @@

HTTP Proxy

- + - + @@ -163,13 +163,21 @@

Add Redirection Rule

$("#redirectionRuleList").html(""); $.get("/api/redirect/list", function(data){ data.forEach(function(entry){ - $("#redirectionRuleList").append(` - - - - - - `); + let encodedEntry = encodeURIComponent(JSON.stringify(entry)); + let hrefURL = entry.RedirectURL; + if (!hrefURL.startsWith("http")){ + hrefURL = "https://" + hrefURL; + } + $("#redirectionRuleList").append(` + + + + + + `); }); if (data.length == 0){ @@ -180,6 +188,68 @@

Add Redirection Rule

} initRedirectionRuleList(); + function editRule(obj){ + $(".redirectEditBtn").addClass("disabled"); + let payload = JSON.parse(decodeURIComponent($(obj).attr("payload"))); + let row = $(obj).closest("tr"); + let redirectUrl = payload.RedirectURL; + let destUrl = payload.TargetURL; + let forwardChildpath = payload.ForwardChildpath; + let statusCode = payload.StatusCode; + + row.html(` + + + + + + `); + + $(".checkbox").checkbox(); + } + + function saveEditRule(obj){ + let payload = JSON.parse(decodeURIComponent($(obj).attr("payload"))); + let redirectUrl = $("#editRedirectUrl").val(); + let destUrl = $("#editDestUrl").val(); + let forwardChildpath = $("#editForwardChildpath").is(":checked"); + let statusCode = parseInt($("input[name='editStatusCode']:checked").val()); + + $.cjax({ + url: "/api/redirect/edit", + method: "POST", + data: { + originalRedirectUrl: payload.RedirectURL, + newRedirectUrl: redirectUrl, + destUrl: destUrl, + forwardChildpath: forwardChildpath, + redirectType: statusCode, + }, + success: function(data){ + if (data.error != undefined){ + msgbox(data.error, false); + }else{ + msgbox("Redirection rule updated", true); + initRedirectionRuleList(); + } + } + }); + } + function initRegexpSupportToggle(){ $.get("/api/redirect/regex", function(data){ //Set the checkbox initial state From 1863af0d632a7e4457d53357d0e0e0afec9209c0 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Wed, 5 Feb 2025 20:33:38 +0800 Subject: [PATCH 17/23] Minor css update - Changed inline edit button for redirection rule to circular to match http proxy rule page --- src/web/components/redirection.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/web/components/redirection.html b/src/web/components/redirection.html index 9b7c4f8..1d8c853 100644 --- a/src/web/components/redirection.html +++ b/src/web/components/redirection.html @@ -174,8 +174,8 @@

Add Redirection Rule

`); }); @@ -214,8 +214,8 @@

Add Redirection Rule

`); From 70abfe6fcf90028c3c9f56c55da99ef0347487cb Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Thu, 6 Feb 2025 20:36:23 +0800 Subject: [PATCH 18/23] Restore dockerfile - The docker file change shd be included in another PR --- docker/Dockerfile | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 5c3c5cd..2099358 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,27 +1,19 @@ -# Stage 1: Build Zoraxy FROM docker.io/golang:alpine AS build-zoraxy -# Install dependencies RUN mkdir -p /opt/zoraxy/source/ &&\ mkdir -p /usr/local/bin/ -WORKDIR /opt/zoraxy/source/ - -# Copy go.mod and go.sum first to leverage Docker cache -COPY ./src/go.mod ./src/go.sum ./ -RUN go mod tidy +# If you build it yourself, you will need to add the src directory into the docker directory. +COPY ./src/ /opt/zoraxy/source/ -# Copy the rest of the source code -COPY ./src/ . +WORKDIR /opt/zoraxy/source/ -# Build the application -RUN go build -o /usr/local/bin/zoraxy &&\ +RUN go mod tidy &&\ + go build -o /usr/local/bin/zoraxy &&\ chmod 755 /usr/local/bin/zoraxy -# Stage 2: Build ZeroTier FROM docker.io/ubuntu:latest AS build-zerotier -# Install dependencies RUN mkdir -p /opt/zerotier/source/ &&\ mkdir -p /usr/local/bin/ @@ -30,7 +22,6 @@ WORKDIR /opt/zerotier/source/ RUN apt-get update -y &&\ apt-get install -y curl jq build-essential pkg-config clang cargo libssl-dev -# Download and build ZeroTier RUN curl -Lo ZeroTierOne.tar.gz https://codeload.github.com/zerotier/ZeroTierOne/tar.gz/refs/tags/1.10.6 &&\ tar -xzvf ZeroTierOne.tar.gz &&\ cd ZeroTierOne-* &&\ @@ -38,24 +29,19 @@ RUN curl -Lo ZeroTierOne.tar.gz https://codeload.github.com/zerotier/ZeroTierOne mv ./zerotier-one /usr/local/bin/zerotier-one &&\ chmod 755 /usr/local/bin/zerotier-one -# Stage 3: Final image FROM docker.io/ubuntu:latest -# Install runtime dependencies RUN apt-get update -y &&\ apt-get install -y bash sudo netcat-openbsd libssl-dev ca-certificates -# Copy entrypoint script COPY --chmod=700 ./entrypoint.sh /opt/zoraxy/ - -# Copy built binaries from previous stages COPY --from=build-zoraxy /usr/local/bin/zoraxy /usr/local/bin/zoraxy COPY --from=build-zerotier /usr/local/bin/zerotier-one /usr/local/bin/zerotier-one WORKDIR /opt/zoraxy/config/ -# Set environment variables ENV ZEROTIER="false" + ENV AUTORENEW="86400" ENV CFGUPGRADE="true" ENV DB="auto" @@ -74,12 +60,9 @@ ENV WEBROOT="./www" ENV ZTAUTH="" ENV ZTPORT="9993" -# Define volumes VOLUME [ "/opt/zoraxy/config/" ] -# Set entrypoint ENTRYPOINT [ "/opt/zoraxy/entrypoint.sh" ] -# Healthcheck HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 CMD nc -vz 127.0.0.1 $PORT || exit 1 From 05511ed4ca0ae85f71f0b757a8f874d1d6cdd4aa Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Fri, 7 Feb 2025 22:08:56 +0800 Subject: [PATCH 19/23] Updated tag system design - Added search-able tag dropdown - Implemented realtime quick search - Added better tag coloring --- src/web/components/httprp.html | 188 ++++++++++++++++++++++++--------- src/web/darktheme.css | 15 +++ src/web/snippet/tagEditor.html | 7 +- 3 files changed, 161 insertions(+), 49 deletions(-) diff --git a/src/web/components/httprp.html b/src/web/components/httprp.html index 1624fc5..871d0eb 100644 --- a/src/web/components/httprp.html +++ b/src/web/components/httprp.html @@ -11,20 +11,47 @@

HTTP Proxy

.subdEntry td:not(.ignoremw){ min-width: 200px; } + + .httpProxyListTools{ + width: 100%; + } + + .tag-select{ + cursor: pointer; + } + + .tag-select:hover{ + text-decoration: underline; + opacity: 0.8; + } -
- - -
-
${vdList}${subd.Tags.join(", ")} + ${subd.Tags.map(tag => `${tag}`).join("")} + ${subd.AuthenticationProvider.AuthMethod == 0x1?` Basic Auth`:``} ${subd.AuthenticationProvider.AuthMethod == 0x2?` Authelia`:``} @@ -170,10 +172,11 @@

HTTP Proxy

data.forEach(subd => { subd.Tags.forEach(tag => tags.add(tag)); }); + tags = Array.from(tags).sort((a, b) => a.localeCompare(b)); let dropdownMenu = $("#tagFilterDropdown .menu"); - dropdownMenu.html('
All
'); + dropdownMenu.html('
All
'); tags.forEach(tag => { - dropdownMenu.append(`
${tag}
`); + dropdownMenu.append(`
${tag}
`); }); $('#tagFilterDropdown').dropdown(); } @@ -669,5 +672,12 @@

HTTP Proxy

// Initialize the proxy list on page load $(document).ready(function() { listProxyEndpoints(); + + // Event listener for clicking on tags + $(document).on('click', '.tag-select', function() { + let tag = $(this).text().trim(); + $('#tagFilterDropdown').dropdown('set selected', tag); + filterProxyList(); + }); }); \ No newline at end of file diff --git a/src/web/components/rules.html b/src/web/components/rules.html index dd37efd..642798f 100644 --- a/src/web/components/rules.html +++ b/src/web/components/rules.html @@ -246,6 +246,7 @@

New Proxy Rule

//Clear old data $("#rootname").val(""); $("#proxyDomain").val(""); + $("#proxyTags").val(""); credentials = []; updateTable(); reloadUptimeList(); From 07dc63a82cd50b2e378ea390eff878735f40a615 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Mon, 3 Feb 2025 20:36:34 +0800 Subject: [PATCH 13/23] Added H2C (experimental) - Added experimental H2C transporter - Exposed default listening port and web server listen state to start parameters #474 --- src/def.go | 4 ++++ src/mod/dynamicproxy/dpcore/dpcore.go | 14 +++++++++++++- src/reverseproxy.go | 8 ++++---- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/def.go b/src/def.go index 4a9ae97..5c54c26 100644 --- a/src/def.go +++ b/src/def.go @@ -87,6 +87,10 @@ var ( allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder") enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected") + /* Default Configuration Flags */ + defaultInboundPort = flag.Int("default_inbound_port", 443, "Default web server listening port") + defaultEnableInboundTraffic = flag.Bool("default_inbound_enabled", true, "If web server is enabled by default") + /* Path Configuration Flags */ //path_database = flag.String("dbpath", "./sys.db", "Database path") //path_conf = flag.String("conf", "./conf", "Configuration folder path") diff --git a/src/mod/dynamicproxy/dpcore/dpcore.go b/src/mod/dynamicproxy/dpcore/dpcore.go index 5ae55bc..14a425e 100644 --- a/src/mod/dynamicproxy/dpcore/dpcore.go +++ b/src/mod/dynamicproxy/dpcore/dpcore.go @@ -2,6 +2,7 @@ package dpcore import ( "context" + "crypto/tls" "errors" "io" "log" @@ -11,6 +12,7 @@ import ( "strings" "time" + "golang.org/x/net/http2" "imuslab.com/zoraxy/mod/dynamicproxy/domainsniff" "imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy" ) @@ -84,6 +86,7 @@ type requestCanceler interface { type DpcoreOptions struct { IgnoreTLSVerification bool //Disable all TLS verification when request pass through this proxy router FlushInterval time.Duration //Duration to flush in normal requests. Stream request or keep-alive request will always flush with interval of -1 (immediately) + UseH2CRoundTripper bool //Use H2C RoundTripper for HTTP/2.0 connection } func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOptions) *ReverseProxy { @@ -100,8 +103,17 @@ func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOp } - //Hack the default transporter to handle more connections thisTransporter := http.DefaultTransport + if dpcOptions.UseH2CRoundTripper { + thisTransporter = &http2.Transport{ + DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) { + return net.Dial(network, addr) + }, + AllowHTTP: true, + } + } + + //Hack the default transporter to handle more connections optimalConcurrentConnection := 32 thisTransporter.(*http.Transport).MaxIdleConns = optimalConcurrentConnection * 2 thisTransporter.(*http.Transport).MaxIdleConnsPerHost = optimalConcurrentConnection diff --git a/src/reverseproxy.go b/src/reverseproxy.go index fb22cf6..3ecdcb2 100644 --- a/src/reverseproxy.go +++ b/src/reverseproxy.go @@ -28,8 +28,8 @@ func ReverseProxtInit() { /* Load Reverse Proxy Global Settings */ - inboundPort := 443 - autoStartReverseProxy := true + inboundPort := *defaultInboundPort + autoStartReverseProxy := *defaultEnableInboundTraffic if sysdb.KeyExists("settings", "inbound") { //Read settings from database sysdb.Read("settings", "inbound", &inboundPort) @@ -42,8 +42,8 @@ func ReverseProxtInit() { } else { //Default port if netutils.CheckIfPortOccupied(inboundPort) { - inboundPort = 8743 - SystemWideLogger.Println("Port 443 is occupied. Switching to backup port 8743 instead") + autoStartReverseProxy = false + SystemWideLogger.Println("Port 443 is occupied. Change the listening port in the webmin panel and press \"Start Service\" to start reverse proxy service") } SystemWideLogger.Println("Inbound port not set. Using default (443)") } From bb2d0d5b46d5d6408991e5cc2a75f03976167344 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Mon, 3 Feb 2025 21:10:24 +0800 Subject: [PATCH 14/23] Fixed #507 --- src/mod/dynamicproxy/dynamicproxy.go | 2 +- src/mod/dynamicproxy/endpoints.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mod/dynamicproxy/dynamicproxy.go b/src/mod/dynamicproxy/dynamicproxy.go index 8e45be4..f5c906b 100644 --- a/src/mod/dynamicproxy/dynamicproxy.go +++ b/src/mod/dynamicproxy/dynamicproxy.go @@ -354,7 +354,7 @@ func (router *Router) LoadProxy(matchingDomain string) (*ProxyEndpoint, error) { return true } - if key == matchingDomain { + if key == strings.ToLower(matchingDomain) { targetProxyEndpoint = v } return true diff --git a/src/mod/dynamicproxy/endpoints.go b/src/mod/dynamicproxy/endpoints.go index 62c5535..9a18fc1 100644 --- a/src/mod/dynamicproxy/endpoints.go +++ b/src/mod/dynamicproxy/endpoints.go @@ -267,7 +267,8 @@ func (ep *ProxyEndpoint) Clone() *ProxyEndpoint { // Remove this proxy endpoint from running proxy endpoint list func (ep *ProxyEndpoint) Remove() error { - ep.parent.ProxyEndpoints.Delete(ep.RootOrMatchingDomain) + lookupHostname := strings.ToLower(ep.RootOrMatchingDomain) + ep.parent.ProxyEndpoints.Delete(lookupHostname) return nil } From f753becd6662198ff076d517c37be2c17ced8baf Mon Sep 17 00:00:00 2001 From: adoolaard Date: Mon, 3 Feb 2025 15:10:13 +0100 Subject: [PATCH 15/23] The proxy hosts broke on import, because the tags were missing. This is now fixed. --- src/config.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/config.go b/src/config.go index eacea02..62468f8 100644 --- a/src/config.go +++ b/src/config.go @@ -54,6 +54,11 @@ func LoadReverseProxyConfig(configFilepath string) error { return err } + //Make sure the tags are not nil + if thisConfigEndpoint.Tags == nil { + thisConfigEndpoint.Tags = []string{} + } + //Matching domain not set. Assume root if thisConfigEndpoint.RootOrMatchingDomain == "" { thisConfigEndpoint.RootOrMatchingDomain = "/" @@ -175,8 +180,8 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) { // Set the Content-Type header to indicate it's a zip file w.Header().Set("Content-Type", "application/zip") - // Set the Content-Disposition header to specify the file name - w.Header().Set("Content-Disposition", "attachment; filename=\"config.zip\"") + // Set the Content-Disposition header to specify the file name, add timestamp to the filename + w.Header().Set("Content-Disposition", "attachment; filename=\"zoraxy-config-"+time.Now().Format("2006-01-02-15-04-05")+".zip\"") // Create a zip writer zipWriter := zip.NewWriter(w) From 2a9d87787d841e43381aea08d5fdc2d4a43eb5a7 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Wed, 5 Feb 2025 20:24:42 +0800 Subject: [PATCH 16/23] Fixed #510 - Added inline edit for redirection rule --- src/api.go | 1 + .../dynamicproxy/redirection/redirection.go | 38 +++++++- src/redirect.go | 43 ++++++++++ src/web/components/redirection.html | 86 +++++++++++++++++-- 4 files changed, 158 insertions(+), 10 deletions(-) diff --git a/src/api.go b/src/api.go index 4e266eb..1507289 100644 --- a/src/api.go +++ b/src/api.go @@ -88,6 +88,7 @@ func RegisterRedirectionAPIs(authRouter *auth.RouterDef) { authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules) authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule) authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule) + authRouter.HandleFunc("/api/redirect/edit", handleEditRedirectionRule) authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport) } diff --git a/src/mod/dynamicproxy/redirection/redirection.go b/src/mod/dynamicproxy/redirection/redirection.go index 8cb0b15..abd163e 100644 --- a/src/mod/dynamicproxy/redirection/redirection.go +++ b/src/mod/dynamicproxy/redirection/redirection.go @@ -2,7 +2,6 @@ package redirection import ( "encoding/json" - "fmt" "log" "os" "path" @@ -111,6 +110,42 @@ func (t *RuleTable) AddRedirectRule(redirectURL string, destURL string, forwardP return nil } +// Edit an existing redirection rule, the oldRedirectURL is used to find the rule to be edited +func (t *RuleTable) EditRedirectRule(oldRedirectURL string, newRedirectURL string, destURL string, forwardPathname bool, statusCode int) error { + newRule := &RedirectRules{ + RedirectURL: newRedirectURL, + TargetURL: destURL, + ForwardChildpath: forwardPathname, + StatusCode: statusCode, + } + + //Remove the old rule + t.DeleteRedirectRule(oldRedirectURL) + + // Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_" + filename := utils.ReplaceSpecialCharacters(newRedirectURL) + ".json" + filepath := path.Join(t.configPath, filename) + + // Create a new file for writing the JSON data + file, err := os.Create(filepath) + if err != nil { + t.log("Error creating file "+filepath, err) + return err + } + defer file.Close() + + err = json.NewEncoder(file).Encode(newRule) + if err != nil { + t.log("Error encoding JSON to file "+filepath, err) + return err + } + + // Update the runtime map + t.rules.Store(newRedirectURL, newRule) + + return nil +} + func (t *RuleTable) DeleteRedirectRule(redirectURL string) error { // Convert the redirectURL to a valid filename by replacing "/" with "-" and "." with "_" filename := utils.ReplaceSpecialCharacters(redirectURL) + ".json" @@ -118,7 +153,6 @@ func (t *RuleTable) DeleteRedirectRule(redirectURL string) error { // Create the full file path by joining the t.configPath with the filename filepath := path.Join(t.configPath, filename) - fmt.Println(redirectURL, filename, filepath) // Check if the file exists if _, err := os.Stat(filepath); os.IsNotExist(err) { return nil // File doesn't exist, nothing to delete diff --git a/src/redirect.go b/src/redirect.go index fa0a5c2..4e0920b 100644 --- a/src/redirect.go +++ b/src/redirect.go @@ -78,6 +78,49 @@ func handleDeleteRedirectionRule(w http.ResponseWriter, r *http.Request) { utils.SendOK(w) } +func handleEditRedirectionRule(w http.ResponseWriter, r *http.Request) { + originalRedirectUrl, err := utils.PostPara(r, "originalRedirectUrl") + if err != nil { + utils.SendErrorResponse(w, "original redirect url cannot be empty") + return + } + + newRedirectUrl, err := utils.PostPara(r, "newRedirectUrl") + if err != nil { + utils.SendErrorResponse(w, "redirect url cannot be empty") + return + } + destUrl, err := utils.PostPara(r, "destUrl") + if err != nil { + utils.SendErrorResponse(w, "destination url cannot be empty") + } + + forwardChildpath, err := utils.PostPara(r, "forwardChildpath") + if err != nil { + //Assume true + forwardChildpath = "true" + } + + redirectTypeString, err := utils.PostPara(r, "redirectType") + if err != nil { + redirectTypeString = "307" + } + + redirectionStatusCode, err := strconv.Atoi(redirectTypeString) + if err != nil { + utils.SendErrorResponse(w, "invalid status code number") + return + } + + err = redirectTable.EditRedirectRule(originalRedirectUrl, newRedirectUrl, destUrl, forwardChildpath == "true", redirectionStatusCode) + if err != nil { + utils.SendErrorResponse(w, err.Error()) + return + } + + utils.SendOK(w) +} + // Toggle redirection regex support. Note that this cost another O(n) time complexity to each page load func handleToggleRedirectRegexpSupport(w http.ResponseWriter, r *http.Request) { enabled, err := utils.PostPara(r, "enable") diff --git a/src/web/components/redirection.html b/src/web/components/redirection.html index 98088e0..9b7c4f8 100644 --- a/src/web/components/redirection.html +++ b/src/web/components/redirection.html @@ -13,7 +13,7 @@

Redirection Rules

Destination URL Copy Pathname Status CodeRemoveActions
${entry.RedirectURL}${entry.TargetURL}${entry.ForwardChildpath?"":""}${entry.StatusCode==307?"Temporary Redirect (307)":"Moved Permanently (301)"}
${entry.RedirectURL}${entry.TargetURL}${entry.ForwardChildpath?"":""}${entry.StatusCode==307?"Temporary Redirect (307)":"Moved Permanently (301)"} + + +
+
+ +
+
+
+ +
+
+

+
+
+ + + ${entry.ForwardChildpath?"":""} ${entry.StatusCode==307?"Temporary Redirect (307)":"Moved Permanently (301)"} - - + +
- - + +
@@ -32,7 +59,7 @@

HTTP Proxy

- + @@ -138,8 +165,10 @@

HTTP Proxy

-
Host Destination Virtual DirectoryTags Tags Advanced Settings Actions
${vdList} - ${subd.Tags.map(tag => `${tag}`).join("")} + +
+ ${subd.Tags.length >0 ? subd.Tags.map(tag => `${tag}`).join(""):"No Tags"} +
${subd.AuthenticationProvider.AuthMethod == 0x1?` Basic Auth`:``} @@ -166,36 +195,6 @@

HTTP Proxy

}); } - // Function to populate the tag filter dropdown - function populateTagFilterDropdown(data) { - let tags = new Set(); - data.forEach(subd => { - subd.Tags.forEach(tag => tags.add(tag)); - }); - tags = Array.from(tags).sort((a, b) => a.localeCompare(b)); - let dropdownMenu = $("#tagFilterDropdown .menu"); - dropdownMenu.html('
All
'); - tags.forEach(tag => { - dropdownMenu.append(`
${tag}
`); - }); - $('#tagFilterDropdown').dropdown(); - } - - // Function to filter the proxy list - function filterProxyList() { - let searchInput = $("#searchInput").val().toLowerCase(); - let selectedTag = $("#tagFilterDropdown").dropdown('get value'); - $("#httpProxyList tr").each(function() { - let host = $(this).find("td[data-label='']").text().toLowerCase(); - let tags = $(this).find("td[data-label='tags']").text().toLowerCase(); - if ((host.includes(searchInput) || searchInput === "") && (tags.includes(selectedTag) || selectedTag === "")) { - $(this).show(); - } else { - $(this).hide(); - } - }); - } - //Perform realtime alias update without refreshing the whole page function updateAliasListForEndpoint(endpointName, newAliasDomainList){ let targetEle = $(`.aliasDomains[eptuuid='${endpointName}']`); @@ -333,7 +332,11 @@

HTTP Proxy

column.append(``); - + }else if (datatype == "tags"){ + column.append(` +
+ + `); }else if (datatype == "advanced"){ let authProvider = payload.AuthenticationProvider.AuthMethod; @@ -463,7 +466,6 @@

HTTP Proxy

- `); $(".hostAccessRuleSelector").dropdown(); @@ -506,8 +508,12 @@

HTTP Proxy

let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked; let rateLimit = $(row).find(".RateLimit").val(); let bypassGlobalTLS = $(row).find(".BypassGlobalTLS")[0].checked; - let tags = $("#proxyTags").val().trim(); - + let tags = getTagsArrayFromEndpoint(uuid); + if (tags.length > 0){ + tags = tags.join(","); + }else{ + tags = ""; + } $.cjax({ url: "/api/proxy/edit", method: "POST", @@ -661,6 +667,75 @@

HTTP Proxy

listProxyEndpoints(); } + /* Tags & Search */ + function handleSearchInput(event){ + if (event.key == "Escape"){ + $("#searchInput").val(""); + } + filterProxyList(); + } + + // Function to filter the proxy list + function filterProxyList() { + let searchInput = $("#searchInput").val().toLowerCase(); + let selectedTag = $("#tagFilterDropdown").dropdown('get value'); + $("#httpProxyList tr").each(function() { + let host = $(this).find("td[data-label='']").text().toLowerCase(); + let tagElements = $(this).find("td[data-label='tags']"); + let tags = tagElements.attr("payload"); + tags = JSON.parse(decodeURIComponent(tags)); + if ((host.includes(searchInput) || searchInput === "") && (tags.includes(selectedTag) || selectedTag === "")) { + $(this).show(); + } else { + $(this).hide(); + } + }); + } + + // Function to generate a color based on a tag name + function getTagColorByName(tagName) { + function hashCode(str) { + return str.split('').reduce((prevHash, currVal) => + ((prevHash << 5) - prevHash) + currVal.charCodeAt(0), 0); + } + let hash = hashCode(tagName); + let color = '#' + ((hash >> 24) & 0xFF).toString(16).padStart(2, '0') + + ((hash >> 16) & 0xFF).toString(16).padStart(2, '0') + + ((hash >> 8) & 0xFF).toString(16).padStart(2, '0'); + return color; + } + + function getTagTextColor(tagName){ + let color = getTagColorByName(tagName); + let r = parseInt(color.substr(1, 2), 16); + let g = parseInt(color.substr(3, 2), 16); + let b = parseInt(color.substr(5, 2), 16); + let brightness = Math.round(((r * 299) + (g * 587) + (b * 114)) / 1000); + return brightness > 125 ? "#000000" : "#ffffff"; + } + + // Populate the tag filter dropdown + function populateTagFilterDropdown(data) { + let tags = new Set(); + data.forEach(subd => { + subd.Tags.forEach(tag => tags.add(tag)); + }); + tags = Array.from(tags).sort((a, b) => a.localeCompare(b)); + let dropdownMenu = $("#tagFilterDropdown .tagList"); + dropdownMenu.html(`
+
+ Show all +
`); + tags.forEach(tag => { + let thisTagColor = getTagColorByName(tag); + dropdownMenu.append(`
+
+ ${tag} +
`); + }); + } + + // Edit tags for a specific endpoint function editTags(uuid){ let payload = encodeURIComponent(JSON.stringify({ ept: "host", @@ -669,6 +744,23 @@

HTTP Proxy

showSideWrapper("snippet/tagEditor.html?t=" + Date.now() + "#" + payload); } + // Render the tags preview from tag editing snippet + function renderTagsPreview(endpoint, tags){ + let targetProxyRuleEle = $(".subdEntry[eptuuid='" + endpoint + "'] td[data-label='tags']"); + //Update the tag DOM + let newTagDOM = tags.map(tag => `${tag}`).join(""); + $(targetProxyRuleEle).find(".tags-list").html(newTagDOM); + + //Update the tag payload + $(targetProxyRuleEle).attr("payload", encodeURIComponent(JSON.stringify(tags))); + } + + function getTagsArrayFromEndpoint(endpoint){ + let targetProxyRuleEle = $(".subdEntry[eptuuid='" + endpoint + "'] td[data-label='tags']"); + let tags = $(targetProxyRuleEle).attr("payload"); + return JSON.parse(decodeURIComponent(tags)); + } + // Initialize the proxy list on page load $(document).ready(function() { listProxyEndpoints(); diff --git a/src/web/darktheme.css b/src/web/darktheme.css index 16b9042..a2d52ae 100644 --- a/src/web/darktheme.css +++ b/src/web/darktheme.css @@ -121,6 +121,9 @@ body.darkTheme .ui.basic.button:not(.red) { body.darkTheme .ui.basic.button:not(.red):hover { border: 1px solid var(--button_border_color) !important; background-color: var(--theme_bg) !important; +} + +body.darkTheme .ui.basic.button:not(.red):not(.dropdown):hover { opacity: 0.8; } @@ -549,6 +552,18 @@ body.darkTheme .RateLimit input { border-color: var(--theme_highlight) !important; } +body.darkTheme .menu.transition{ + background-color: var(--theme_bg) !important; + color: var(--text_color) !important; +} + +body.darkTheme .ui.dropdown .menu{ + background: var(--theme_bg_primary) !important; +} + +body.darkTheme .ui.dropdown .menu .item{ + color: var(--text_color) !important; +} /* Virtual Directorie Table */ diff --git a/src/web/snippet/tagEditor.html b/src/web/snippet/tagEditor.html index e4c5a32..d93145d 100644 --- a/src/web/snippet/tagEditor.html +++ b/src/web/snippet/tagEditor.html @@ -9,6 +9,9 @@ + + +
@@ -67,7 +70,9 @@ parent.msgbox(data.error, false); } else { parent.msgbox("Tags updated"); - parent.hideSideWrapper(); + //Update the preview on parent page + parent.renderTagsPreview(editingEndpoint.ep, tags); + //parent.hideSideWrapper(); } } }); From 99728144b3e850e0dc07e6f12740b6e11679d18a Mon Sep 17 00:00:00 2001 From: PassiveLemon Date: Sat, 8 Feb 2025 01:37:03 -0500 Subject: [PATCH 20/23] Refactor: Launch services in background and trap Docker TERM signal --- docker/entrypoint.sh | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 106acc5..51901e9 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,5 +1,14 @@ #!/usr/bin/env bash +trap cleanup TERM INT + +cleanup() { + echo "Shutting down..." + kill -TERM "$(pidof zoraxy)" &> /dev/null && echo "Zoraxy stopped." + kill -TERM "$(pidof zerotier-one)" &> /dev/null && echo "ZeroTier-One stopped." + exit 0 +} + update-ca-certificates echo "CA certificates updated." @@ -11,12 +20,13 @@ if [ "$ZEROTIER" = "true" ]; then mkdir -p /opt/zoraxy/config/zerotier/ fi ln -s /opt/zoraxy/config/zerotier/ /var/lib/zerotier-one - zerotier-one -d + zerotier-one -d & + zerotierpid=$! echo "ZeroTier daemon started." fi echo "Starting Zoraxy..." -exec zoraxy \ +zoraxy \ -autorenew="$AUTORENEW" \ -cfgupgrade="$CFGUPGRADE" \ -db="$DB" \ @@ -33,5 +43,10 @@ exec zoraxy \ -webfm="$WEBFM" \ -webroot="$WEBROOT" \ -ztauth="$ZTAUTH" \ - -ztport="$ZTPORT" + -ztport="$ZTPORT" \ + & + +zoraxypid=$! +wait $zoraxypid +wait $zerotierpid From 3320b56b198f59a01215d0641fa4c8d8c5939633 Mon Sep 17 00:00:00 2001 From: Toby Chui Date: Sat, 8 Feb 2025 15:19:36 +0800 Subject: [PATCH 21/23] Update tagEditor.html - Optimized UX for tag editor - Finished integration of tag system --- src/web/snippet/tagEditor.html | 192 +++++++++++++++++++++++++++++++-- 1 file changed, 185 insertions(+), 7 deletions(-) diff --git a/src/web/snippet/tagEditor.html b/src/web/snippet/tagEditor.html index d93145d..853207d 100644 --- a/src/web/snippet/tagEditor.html +++ b/src/web/snippet/tagEditor.html @@ -7,6 +7,14 @@ + @@ -20,15 +28,49 @@
-

Enter tags for this proxy host. Use commas to separate multiple tags.

+

Tags currently applied to this host name / proxy rule

+
+ + + + + + + + + + +
Tag NameAction
+
+
+

Add New Tags

+

Create new tag or add this proxy rule to an existing tag

- +
- - + +
+ Or +
+
+ + + +
+
+
+ +