Compare commits

..

13 Commits

Author SHA1 Message Date
yusing
3aba5a1911 refactor(agent): simplify ReverseProxy method by directly modifying request URL 2025-09-17 14:07:06 +08:00
yusing
ca805edfe0 fix(agent): incorrect uri in reverse proxy 2025-09-17 14:03:35 +08:00
yusing
7205bf47de feat(autocert): add DNS resolver options to Config and update provider initialization 2025-09-16 15:43:49 +08:00
yusing
b12999210f feat(docker): add tmpfs caching for Next.js in compose files 2025-09-14 21:24:01 +08:00
yusing
8b8969f033 fix(auth): change userpass to redirect to login and update documentation 2025-09-14 21:11:20 +08:00
yusing
025ebab1ce refactor(api): remove unused ErrorCode type 2025-09-14 20:50:07 +08:00
yusing
ea7bd0d19a fix(docker): update dev docker compose 2025-09-14 18:39:40 +08:00
yusing
f889f5c08d fix(oidc): simplify LoginHandler to always redirect to IdP 2025-09-14 14:33:28 +08:00
yusing
932c20f32d chore(docker): update .gitignore to exclude all .env files and modify dev.compose.yml to include env_file for development 2025-09-14 13:47:02 +08:00
yusing
2a08c55e39 feat(auth): add GET endpoint for logout and update documentation 2025-09-14 13:07:24 +08:00
yusing
93e1d17090 fix(auth): revert userpass PostAuthCallback to respond http 200 2025-09-14 11:19:37 +08:00
yusing
d72d403e2c docs(README): update README files to include new Star History section and replace outdated screenshots
- Added "Star History" section with a chart link.
- Replaced outdated screenshots with new "Routes" and "Servers" images.
- Removed references to deleted screenshots for better clarity.
2025-09-14 01:30:37 +08:00
yusing
b5d70a0592 docs(README): remove WebUI announcement from README 2025-09-14 01:15:36 +08:00
27 changed files with 88 additions and 102 deletions

1
.gitignore vendored
View File

@@ -29,6 +29,7 @@ todo.md
.aider*
mtrace.json
.env
*.env
.cursorrules
.cursor/
.windsurfrules

View File

@@ -21,8 +21,6 @@ A lightweight, simple, and performant reverse proxy with WebUI.
Have questions? Ask [ChatGPT](https://chatgpt.com/g/g-6825390374b481919ad482f2e48936a1-godoxy-assistant)! (Thanks to [@ismesid](https://github.com/arevindh))
**New WebUI and is now available in nightly tag [(Demo)](https://nightly.demo.godoxy.dev), feedbacks are welcomed!**
</div>
## Table of content
@@ -41,6 +39,7 @@ Have questions? Ask [ChatGPT](https://chatgpt.com/g/g-6825390374b481919ad482f2e4
- [Manual Setup](#manual-setup)
- [Folder structrue](#folder-structrue)
- [Build it yourself](#build-it-yourself)
- [Star History](#star-history)
## Running demo
@@ -140,22 +139,12 @@ Configure Wildcard DNS Record(s) to point to machine running `GoDoxy`, e.g.
<div align="center">
<table>
<tr>
<td align="center"><img src="screenshots/uptime.png" alt="Uptime Monitor" width="250"/></td>
<td align="center"><img src="screenshots/docker-logs.jpg" alt="Docker Logs" width="250"/></td>
<td align="center"><img src="screenshots/docker.jpg" alt="Server Overview" width="250"/></td>
<td align="center"><img src="screenshots/routes.jpg" alt="Routes" width="350"/></td>
<td align="center"><img src="screenshots/servers.jpg" alt="Servers" width="350"/></td>
</tr>
<tr>
<td align="center"><b>Uptime Monitor</b></td>
<td align="center"><b>Docker Logs</b></td>
<td align="center"><b>Server Overview</b></td>
</tr>
<tr>
<td align="center"><img src="screenshots/system-monitor.jpg" alt="System Monitor" width="250"/></td>
<td align="center"><img src="screenshots/system-info-graphs.jpg" alt="Graphs" width="250"/></td>
</tr>
<tr>
<td align="center"><b>System Monitor</b></td>
<td align="center"><b>Graphs</b></td>
<td align="center"><b>Routes</b></td>
<td align="center"><b>Servers</b></td>
</tr>
</table>
</div>
@@ -207,4 +196,8 @@ Configure Wildcard DNS Record(s) to point to machine running `GoDoxy`, e.g.
5. build binary with `make build`
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=yusing/godoxy&type=Date)](https://www.star-history.com/#yusing/godoxy&Date)
[🔼Back to top](#table-of-content)

View File

@@ -38,6 +38,7 @@
- [閒置休眠](#閒置休眠)
- [監控](#監控)
- [自行編譯](#自行編譯)
- [Star History](#star-history)
## 運行示例
@@ -84,8 +85,6 @@
- **高效能**
-**[Go](https://go.dev)** 語言編寫
[🔼 回到頂部](#目錄)
## 前置需求
設置 DNS 記錄指向運行 `GoDoxy` 的機器,例如:
@@ -110,8 +109,6 @@
3. 現在可以在 WebUI `https://godoxy.yourdomain.com` 進行額外配置
[🔼 回到頂部](#目錄)
### 手動安裝
1. 建立 `config` 目錄,然後將 `config.example.yml` 下載到 `config/config.yml`
@@ -153,29 +150,17 @@
![閒置休眠](screenshots/idlesleeper.webp)
[🔼 回到頂部](#目錄)
### 監控
<div align="center">
<table>
<tr>
<td align="center"><img src="screenshots/uptime.png" alt="Uptime Monitor" width="250"/></td>
<td align="center"><img src="screenshots/docker-logs.jpg" alt="Docker Logs" width="250"/></td>
<td align="center"><img src="screenshots/docker.jpg" alt="Server Overview" width="250"/></td>
<td align="center"><img src="screenshots/routes.jpg" alt="Routes" width="350"/></td>
<td align="center"><img src="screenshots/servers.jpg" alt="Servers" width="350"/></td>
</tr>
<tr>
<td align="center"><b>運行時間監控</b></td>
<td align="center"><b>Docker 日誌</b></td>
<td align="center"><b>伺服器概覽</b></td>
</tr>
<tr>
<td align="center"><img src="screenshots/system-monitor.jpg" alt="System Monitor" width="250"/></td>
<td align="center"><img src="screenshots/system-info-graphs.jpg" alt="Graphs" width="250"/></td>
</tr>
<tr>
<td align="center"><b>系統監控</b></td>
<td align="center"><b>圖表</b></td>
<td align="center"><b>路由</b></td>
<td align="center"><b>伺服器</b></td>
</tr>
</table>
</div>
@@ -192,4 +177,8 @@
5. 使用 `make build` 編譯二進制檔案
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=yusing/godoxy&type=Date)](https://www.star-history.com/#yusing/godoxy&Date)
[🔼 回到頂部](#目錄)

View File

@@ -19,7 +19,6 @@ func (cfg *AgentConfig) Do(ctx context.Context, method, endpoint string, body io
}
func (cfg *AgentConfig) Forward(req *http.Request, endpoint string) (*http.Response, error) {
req = req.WithContext(req.Context())
req.URL.Host = AgentHost
req.URL.Scheme = "https"
req.URL.Path = APIEndpointBase + endpoint
@@ -56,17 +55,11 @@ func (cfg *AgentConfig) Websocket(ctx context.Context, endpoint string) (*websoc
//
// It will create a new request with the same context, method, and body, but with the agent host and scheme, and the endpoint
// If the request has a query, it will be added to the proxy request's URL
func (cfg *AgentConfig) ReverseProxy(w http.ResponseWriter, req *http.Request, endpoint string) error {
func (cfg *AgentConfig) ReverseProxy(w http.ResponseWriter, req *http.Request, endpoint string) {
rp := reverseproxy.NewReverseProxy("agent", nettypes.NewURL(AgentURL), cfg.Transport())
uri := APIEndpointBase + endpoint
if req.URL.RawQuery != "" {
uri += "?" + req.URL.RawQuery
}
r, err := http.NewRequestWithContext(req.Context(), req.Method, uri, req.Body)
if err != nil {
return err
}
r.Header = req.Header
rp.ServeHTTP(w, r)
return nil
req.URL.Host = AgentHost
req.URL.Scheme = "https"
req.URL.Path = endpoint
req.RequestURI = ""
rp.ServeHTTP(w, req)
}

View File

@@ -28,6 +28,8 @@ services:
env_file: .env
user: ${GODOXY_UID:-1000}:${GODOXY_GID:-1000}
read_only: true
tmpfs:
- /app/.next/cache # next image caching
security_opt:
- no-new-privileges:true
cap_drop:

View File

@@ -8,13 +8,13 @@ services:
- TARGET=godoxy
container_name: godoxy-proxy-dev
restart: unless-stopped
env_file: dev.env
environment:
TZ: Asia/Hong_Kong
API_ADDR: :8999
API_ADDR: 127.0.0.1:8999
API_USER: dev
API_PASSWORD: 1234
API_SKIP_ORIGIN_CHECK: true
API_JWT_SECURE: false
API_JWT_TTL: 24h
DEBUG: true
API_SECRET: 1234567891234567
@@ -30,8 +30,7 @@ services:
- ./dev-data/error_pages:/app/error_pages:ro
- ./dev-data/data:/app/data
- ./dev-data/logs:/app/logs
depends_on:
- tinyauth
- ~/certs/myCA.pem:/etc/ssl/certs/ca.crt:ro
tinyauth:
image: ghcr.io/steveiliop56/tinyauth:v3
container_name: tinyauth

View File

@@ -56,6 +56,7 @@ func NewHandler() *gin.Engine {
v1Auth.GET("/callback", authApi.Callback)
v1Auth.POST("/callback", authApi.Callback)
v1Auth.POST("/logout", authApi.Logout)
v1Auth.GET("/logout", authApi.Logout)
}
}

View File

@@ -1,17 +0,0 @@
package apitypes
type ErrorCode int
const (
ErrorCodeUnauthorized ErrorCode = iota + 1
ErrorCodeNotFound
ErrorCodeInternalServerError
)
func (e ErrorCode) String() string {
return []string{
"Unauthorized",
"Not Found",
"Internal Server Error",
}[e]
}

View File

@@ -5,14 +5,14 @@ import (
"github.com/yusing/go-proxy/internal/auth"
)
// @x-id "check"
// @x-id "check"
// @Base /api/v1
// @Summary Check authentication status
// @Description Checks if the user is authenticated by validating their token
// @Tags auth
// @Produce plain
// @Success 200 {string} string "OK"
// @Failure 403 {string} string "Forbidden: use X-Redirect-To header to redirect to login page"
// @Failure 302 {string} string "Redirects to login page or IdP"
// @Router /auth/check [head]
func Check(c *gin.Context) {
auth.AuthCheckHandler(c.Writer, c.Request)

View File

@@ -12,7 +12,6 @@ import (
// @Tags auth
// @Produce plain
// @Success 302 {string} string "Redirects to login page or IdP"
// @Failure 403 {string} string "Forbidden(webui): follow X-Redirect-To header"
// @Failure 429 {string} string "Too Many Requests"
// @Router /auth/login [post]
func Login(c *gin.Context) {

View File

@@ -13,6 +13,7 @@ import (
// @Produce plain
// @Success 302 {string} string "Redirects to home page"
// @Router /auth/logout [post]
// @Router /auth/logout [get]
func Logout(c *gin.Context) {
auth.GetDefaultAuth().LogoutHandler(c.Writer, c.Request)
}

View File

@@ -239,8 +239,8 @@
"type": "string"
}
},
"403": {
"description": "Forbidden: use X-Redirect-To header to redirect to login page",
"302": {
"description": "Redirects to login page or IdP",
"schema": {
"type": "string"
}
@@ -267,12 +267,6 @@
"type": "string"
}
},
"403": {
"description": "Forbidden(webui): follow X-Redirect-To header",
"schema": {
"type": "string"
}
},
"429": {
"description": "Too Many Requests",
"schema": {
@@ -285,6 +279,26 @@
}
},
"/auth/logout": {
"get": {
"description": "Logs out the user by invalidating the token",
"produces": [
"text/plain"
],
"tags": [
"auth"
],
"summary": "Logout",
"responses": {
"302": {
"description": "Redirects to home page",
"schema": {
"type": "string"
}
}
},
"x-id": "logout",
"operationId": "logout"
},
"post": {
"description": "Logs out the user by invalidating the token",
"produces": [

View File

@@ -1581,8 +1581,8 @@ paths:
description: OK
schema:
type: string
"403":
description: 'Forbidden: use X-Redirect-To header to redirect to login page'
"302":
description: Redirects to login page or IdP
schema:
type: string
summary: Check authentication status
@@ -1600,10 +1600,6 @@ paths:
description: Redirects to login page or IdP
schema:
type: string
"403":
description: 'Forbidden(webui): follow X-Redirect-To header'
schema:
type: string
"429":
description: Too Many Requests
schema:
@@ -1613,6 +1609,19 @@ paths:
- auth
x-id: login
/auth/logout:
get:
description: Logs out the user by invalidating the token
produces:
- text/plain
responses:
"302":
description: Redirects to home page
schema:
type: string
summary: Logout
tags:
- auth
x-id: logout
post:
description: Logs out the user by invalidating the token
produces:

View File

@@ -46,6 +46,7 @@ func SystemInfo(c *gin.Context) {
systeminfo.Poller.ServeHTTP(c)
return
}
c.Request.URL.RawQuery = query.Encode()
agent, ok := agentPkg.GetAgent(agentAddr)
if !ok {
@@ -69,10 +70,6 @@ func SystemInfo(c *gin.Context) {
c.Status(resp.StatusCode)
io.Copy(c.Writer, resp.Body)
} else {
err := agent.ReverseProxy(c.Writer, c.Request, agentPkg.EndpointSystemInfo+"?"+query.Encode())
if err != nil {
c.Error(apitypes.InternalServerError(err, "failed to reverse proxy"))
return
}
agent.ReverseProxy(c.Writer, c.Request, agentPkg.EndpointSystemInfo)
}
}

View File

@@ -247,12 +247,7 @@ func (auth *OIDCProvider) LoginHandler(w http.ResponseWriter, r *http.Request) {
SetTokenCookie(w, r, auth.getAppScopedCookieName(CookieOauthState), state, 300*time.Second)
// redirect user to Idp
url := auth.oauthConfig.AuthCodeURL(state, optRedirectPostAuth(r))
if IsFrontend(r) {
w.Header().Set("X-Redirect-To", url)
w.WriteHeader(http.StatusForbidden)
} else {
http.Redirect(w, r, url, http.StatusFound)
}
http.Redirect(w, r, url, http.StatusFound)
}
func parseClaims(idToken *oidc.IDToken) (*IDTokenClaims, error) {

View File

@@ -125,12 +125,11 @@ func (auth *UserPassAuth) PostAuthCallbackHandler(w http.ResponseWriter, r *http
return
}
SetTokenCookie(w, r, auth.TokenCookieName(), token, auth.tokenTTL)
http.Redirect(w, r, "/", http.StatusFound)
w.WriteHeader(http.StatusOK)
}
func (auth *UserPassAuth) LoginHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Redirect-To", "/login")
w.WriteHeader(http.StatusForbidden)
http.Redirect(w, r, "/login", http.StatusFound)
}
func (auth *UserPassAuth) LogoutHandler(w http.ResponseWriter, r *http.Request) {

View File

@@ -11,6 +11,7 @@ import (
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/lego"
"github.com/rs/zerolog/log"
"github.com/yusing/go-proxy/internal/common"
@@ -27,6 +28,8 @@ type Config struct {
Provider string `json:"provider,omitempty"`
Options map[string]any `json:"options,omitempty"`
Resolvers []string `json:"resolvers,omitempty"`
// Custom ACME CA
CADirURL string `json:"ca_dir_url,omitempty"`
CACerts []string `json:"ca_certs,omitempty"`
@@ -111,6 +114,12 @@ func (cfg *Config) Validate() gperr.Error {
return b.Error()
}
func (cfg *Config) dns01Options() []dns01.ChallengeOption {
return []dns01.ChallengeOption{
dns01.CondOption(len(cfg.Resolvers) > 0, dns01.AddRecursiveNameservers(cfg.Resolvers)),
}
}
func (cfg *Config) GetLegoConfig() (*User, *lego.Config, gperr.Error) {
if err := cfg.Validate(); err != nil {
return nil, nil, err

View File

@@ -286,7 +286,7 @@ func (p *Provider) initClient() error {
return err
}
err = legoClient.Challenge.SetDNS01Provider(p.cfg.challengeProvider)
err = legoClient.Challenge.SetDNS01Provider(p.cfg.challengeProvider, p.cfg.dns01Options()...)
if err != nil {
return err
}

View File

@@ -26,6 +26,8 @@ services:
restart: unless-stopped
env_file: .env
read_only: true
tmpfs:
- /app/.next/cache # next image caching
security_opt:
- no-new-privileges:true
cap_drop:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 516 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

BIN
screenshots/routes.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

BIN
screenshots/servers.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 KiB

After

Width:  |  Height:  |  Size: 476 KiB