mirror of
https://github.com/yusing/godoxy.git
synced 2026-01-16 00:23:38 +01:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aec937a114 | ||
|
|
bab9471bde | ||
|
|
4ebd1dbf32 | ||
|
|
82a4a61df0 | ||
|
|
9e56ea5db1 | ||
|
|
719682c99f | ||
|
|
f81a2b6607 | ||
|
|
f47ba0a9b5 |
@@ -47,6 +47,7 @@ FROM scratch
|
||||
|
||||
LABEL maintainer="yusing@6uo.me"
|
||||
LABEL proxy.exclude=1
|
||||
LABEL proxy.#1.healthcheck.disable=true
|
||||
|
||||
# copy timezone data
|
||||
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
||||
|
||||
1
Makefile
1
Makefile
@@ -2,7 +2,6 @@ shell := /bin/sh
|
||||
export VERSION ?= $(shell git describe --tags --abbrev=0)
|
||||
export BUILD_DATE ?= $(shell date -u +'%Y%m%d-%H%M')
|
||||
export GOOS = linux
|
||||
export GOARCH ?= amd64
|
||||
|
||||
WEBUI_DIR ?= ../godoxy-frontend
|
||||
DOCS_DIR ?= ../godoxy-wiki
|
||||
|
||||
32
README.md
32
README.md
@@ -1,10 +1,11 @@
|
||||
<div align="center">
|
||||
|
||||
# GoDoxy
|
||||
<img src="assets/godoxy.png" width="200">
|
||||
|
||||
[](https://sonarcloud.io/summary/new_code?id=yusing_go-proxy)
|
||||

|
||||
[](https://sonarcloud.io/summary/new_code?id=go-proxy)
|
||||
|
||||

|
||||
[](https://discord.gg/umReR62nRd)
|
||||
|
||||
@@ -16,10 +17,10 @@ A lightweight, simple, and performant reverse proxy with WebUI.
|
||||
|
||||
<h5>EN | <a href="README_CHT.md">中文</a></h5>
|
||||
|
||||
Have questions? Ask [ChatGPT](https://chatgpt.com/g/g-6825390374b481919ad482f2e48936a1-godoxy-assistant)! (Thanks to [@ismesid](https://github.com/arevindh))
|
||||
|
||||
<img src="screenshots/webui.jpg" style="max-width: 650">
|
||||
|
||||
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>
|
||||
@@ -28,19 +29,18 @@ Have questions? Ask [ChatGPT](https://chatgpt.com/g/g-6825390374b481919ad482f2e4
|
||||
|
||||
<!-- TOC -->
|
||||
|
||||
- [GoDoxy](#godoxy)
|
||||
- [Table of content](#table-of-content)
|
||||
- [Running demo](#running-demo)
|
||||
- [Key Features](#key-features)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Setup](#setup)
|
||||
- [How does GoDoxy work](#how-does-godoxy-work)
|
||||
- [Screenshots](#screenshots)
|
||||
- [idlesleeper](#idlesleeper)
|
||||
- [Metrics and Logs](#metrics-and-logs)
|
||||
- [Manual Setup](#manual-setup)
|
||||
- [Folder structrue](#folder-structrue)
|
||||
- [Build it yourself](#build-it-yourself)
|
||||
- [Table of content](#table-of-content)
|
||||
- [Running demo](#running-demo)
|
||||
- [Key Features](#key-features)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Setup](#setup)
|
||||
- [How does GoDoxy work](#how-does-godoxy-work)
|
||||
- [Screenshots](#screenshots)
|
||||
- [idlesleeper](#idlesleeper)
|
||||
- [Metrics and Logs](#metrics-and-logs)
|
||||
- [Manual Setup](#manual-setup)
|
||||
- [Folder structrue](#folder-structrue)
|
||||
- [Build it yourself](#build-it-yourself)
|
||||
|
||||
## Running demo
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<div align="center">
|
||||
|
||||
# GoDoxy
|
||||
<img src="assets/godoxy.png" width="200">
|
||||
|
||||
[](https://sonarcloud.io/summary/new_code?id=yusing_go-proxy)
|
||||

|
||||
[](https://sonarcloud.io/summary/new_code?id=yusing_go-proxy)
|
||||
|
||||

|
||||
[](https://discord.gg/umReR62nRd)
|
||||
|
||||
@@ -16,28 +17,27 @@
|
||||
|
||||
<h5><a href="README.md">EN</a> | 中文</h5>
|
||||
|
||||
有疑問? 問 [ChatGPT](https://chatgpt.com/g/g-6825390374b481919ad482f2e48936a1-godoxy-assistant)!(鳴謝 [@ismesid](https://github.com/arevindh))
|
||||
|
||||
<img src="https://github.com/user-attachments/assets/4bb371f4-6e4c-425c-89b2-b9e962bdd46f" style="max-width: 650">
|
||||
|
||||
有疑問? 問 [ChatGPT](https://chatgpt.com/g/g-6825390374b481919ad482f2e48936a1-godoxy-assistant)!(鳴謝 [@ismesid](https://github.com/arevindh))
|
||||
|
||||
</div>
|
||||
|
||||
## 目錄
|
||||
|
||||
<!-- TOC -->
|
||||
|
||||
- [GoDoxy](#godoxy)
|
||||
- [目錄](#目錄)
|
||||
- [運行示例](#運行示例)
|
||||
- [主要特點](#主要特點)
|
||||
- [前置需求](#前置需求)
|
||||
- [安裝](#安裝)
|
||||
- [手動安裝](#手動安裝)
|
||||
- [資料夾結構](#資料夾結構)
|
||||
- [截圖](#截圖)
|
||||
- [閒置休眠](#閒置休眠)
|
||||
- [監控](#監控)
|
||||
- [自行編譯](#自行編譯)
|
||||
- [目錄](#目錄)
|
||||
- [運行示例](#運行示例)
|
||||
- [主要特點](#主要特點)
|
||||
- [前置需求](#前置需求)
|
||||
- [安裝](#安裝)
|
||||
- [手動安裝](#手動安裝)
|
||||
- [資料夾結構](#資料夾結構)
|
||||
- [截圖](#截圖)
|
||||
- [閒置休眠](#閒置休眠)
|
||||
- [監控](#監控)
|
||||
- [自行編譯](#自行編譯)
|
||||
|
||||
## 運行示例
|
||||
|
||||
|
||||
BIN
assets/godoxy.png
Normal file
BIN
assets/godoxy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -215,10 +216,23 @@ func (cfg *Config) StartServers(opts ...*StartServersOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
var envRegex = regexp.MustCompile(`\$\{([^}]+)\}`) // e.g. ${CLOUDFLARE_API_KEY}
|
||||
var readFile = os.ReadFile
|
||||
|
||||
func (cfg *Config) readConfigFile() ([]byte, error) {
|
||||
data, err := readFile(common.ConfigPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return envRegex.ReplaceAllFunc(data, func(match []byte) []byte {
|
||||
return strconv.AppendQuote(nil, os.Getenv(string(match[2:len(match)-1])))
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (cfg *Config) load() gperr.Error {
|
||||
const errMsg = "config load error"
|
||||
|
||||
data, err := os.ReadFile(common.ConfigPath)
|
||||
data, err := cfg.readConfigFile()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
log.Warn().Msg("config file not found, using default config")
|
||||
|
||||
38
internal/config/config_test.go
Normal file
38
internal/config/config_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConfigEnvSubstitution(t *testing.T) {
|
||||
os.Setenv("CLOUDFLARE_AUTH_TOKEN", "test")
|
||||
readFile = func(_ string) ([]byte, error) {
|
||||
return []byte(`
|
||||
---
|
||||
autocert:
|
||||
email: "test@test.com"
|
||||
domains:
|
||||
- "*.test.com"
|
||||
provider: cloudflare
|
||||
options:
|
||||
auth_token: ${CLOUDFLARE_AUTH_TOKEN}
|
||||
`), nil
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
out, err := cfg.readConfigFile()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, `
|
||||
---
|
||||
autocert:
|
||||
email: "test@test.com"
|
||||
domains:
|
||||
- "*.test.com"
|
||||
provider: cloudflare
|
||||
options:
|
||||
auth_token: "test"
|
||||
`, string(out))
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"maps"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -21,6 +22,8 @@ import (
|
||||
|
||||
var DummyContainer = new(types.Container)
|
||||
|
||||
var EnvDockerHost = os.Getenv("DOCKER_HOST")
|
||||
|
||||
var (
|
||||
ErrNetworkNotFound = errors.New("network not found")
|
||||
ErrNoNetwork = errors.New("no network found")
|
||||
@@ -160,6 +163,10 @@ func isLocal(c *types.Container) bool {
|
||||
if strings.HasPrefix(c.DockerHost, "unix://") {
|
||||
return true
|
||||
}
|
||||
// treat it as local if the docker host is the same as the environment variable
|
||||
if c.DockerHost == EnvDockerHost {
|
||||
return true
|
||||
}
|
||||
url, err := url.Parse(c.DockerHost)
|
||||
if err != nil {
|
||||
return false
|
||||
|
||||
@@ -31,6 +31,7 @@ type Manager struct {
|
||||
err error
|
||||
|
||||
writeLock sync.Mutex
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
var defaultUpgrader = websocket.Upgrader{
|
||||
@@ -111,6 +112,12 @@ func NewManagerWithUpgrade(c *gin.Context) (*Manager, error) {
|
||||
go cm.pingCheckRoutine()
|
||||
go cm.readRoutine()
|
||||
|
||||
// Ensure resources are released when parent context is canceled.
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
cm.Close()
|
||||
}()
|
||||
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
@@ -209,6 +216,10 @@ func (cm *Manager) ReadJSON(out any, timeout time.Duration) error {
|
||||
|
||||
// Close closes the connection and cancels the context
|
||||
func (cm *Manager) Close() {
|
||||
cm.closeOnce.Do(cm.close)
|
||||
}
|
||||
|
||||
func (cm *Manager) close() {
|
||||
cm.cancel()
|
||||
|
||||
cm.writeLock.Lock()
|
||||
|
||||
75
rootless-compose.example.yml
Normal file
75
rootless-compose.example.yml
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
services:
|
||||
socket-proxy:
|
||||
container_name: socket-proxy
|
||||
image: ghcr.io/yusing/socket-proxy:latest
|
||||
environment:
|
||||
- ALLOW_START=1
|
||||
- ALLOW_STOP=1
|
||||
- ALLOW_RESTARTS=1
|
||||
- CONTAINERS=1
|
||||
- EVENTS=1
|
||||
- INFO=1
|
||||
- PING=1
|
||||
- POST=1
|
||||
- VERSION=1
|
||||
volumes:
|
||||
- ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock
|
||||
restart: unless-stopped
|
||||
tmpfs:
|
||||
- /run
|
||||
networks:
|
||||
- godoxy
|
||||
frontend:
|
||||
image: ghcr.io/yusing/godoxy-frontend:${TAG:-latest}
|
||||
container_name: godoxy-frontend
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
read_only: true
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- all
|
||||
depends_on:
|
||||
- app
|
||||
environment:
|
||||
HOSTNAME: 0.0.0.0
|
||||
PORT: 3000
|
||||
labels:
|
||||
proxy.aliases: ${GODOXY_FRONTEND_ALIASES:-godoxy}
|
||||
proxy.#1.port: 3000
|
||||
networks:
|
||||
- godoxy
|
||||
app:
|
||||
image: yusing/godoxy:test
|
||||
container_name: godoxy-proxy
|
||||
restart: always
|
||||
env_file: .env
|
||||
depends_on:
|
||||
socket-proxy:
|
||||
condition: service_started
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
cap_drop:
|
||||
- all
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE
|
||||
environment:
|
||||
- DOCKER_HOST=tcp://${SOCKET_PROXY_LISTEN_ADDR:-127.0.0.1:2375}
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443/tcp
|
||||
- 443:443/udp # http3
|
||||
volumes:
|
||||
- ./config:/app/config
|
||||
- ./logs:/app/logs
|
||||
- ./error_pages:/app/error_pages:ro
|
||||
- ./data:/app/data
|
||||
- ./certs:/app/certs
|
||||
networks:
|
||||
- proxy
|
||||
- godoxy
|
||||
networks:
|
||||
proxy: # bridge network for all services that needs proxying
|
||||
external: true
|
||||
godoxy:
|
||||
72
rootless.env.example
Normal file
72
rootless.env.example
Normal file
@@ -0,0 +1,72 @@
|
||||
DOCKER_SOCKET=/var/run/user/1000/docker.sock
|
||||
SOCKET_PROXY_LISTEN_ADDR=socket-proxy:2375
|
||||
|
||||
# docker image tag (latest, nightly)
|
||||
TAG=latest
|
||||
|
||||
# set timezone to get correct log timestamp
|
||||
TZ=ETC/UTC
|
||||
|
||||
# Set GODOXY_API_JWT_SECURE=false to allow http
|
||||
GODOXY_API_JWT_SECURE=true
|
||||
# API JWT Configuration (common)
|
||||
# generate secret with `openssl rand -base64 32`
|
||||
GODOXY_API_JWT_SECRET=
|
||||
# the JWT token time-to-live
|
||||
# leave empty to use default (24 hours)
|
||||
# format: https://pkg.go.dev/time#Duration
|
||||
GODOXY_API_JWT_TOKEN_TTL=
|
||||
|
||||
# API/WebUI user password login credentials (optional)
|
||||
# These fields are not required for OIDC authentication
|
||||
GODOXY_API_USER=admin
|
||||
GODOXY_API_PASSWORD=password
|
||||
|
||||
# OIDC Configuration (optional)
|
||||
# Uncomment and configure these values to enable OIDC authentication.
|
||||
#
|
||||
# GODOXY_OIDC_ISSUER_URL=https://accounts.google.com
|
||||
# GODOXY_OIDC_CLIENT_ID=your-client-id
|
||||
# GODOXY_OIDC_CLIENT_SECRET=your-client-secret
|
||||
# GODOXY_OIDC_SCOPES=openid, profile, email, groups # you may also include `offline_access` if your Idp supports it (e.g. Authentik, Pocket ID)
|
||||
#
|
||||
# User definitions: Uncomment and configure these values to restrict access to specific users or groups.
|
||||
# These two fields act as a logical AND operator. For example, given the following membership:
|
||||
# user1, group1
|
||||
# user2, group1
|
||||
# user3, group2
|
||||
# user1, group2
|
||||
# You can allow access to user3 AND all users of group1 by providing:
|
||||
# # GODOXY_OIDC_ALLOWED_USERS=user3
|
||||
# # GODOXY_OIDC_ALLOWED_GROUPS=group1
|
||||
#
|
||||
# Comma-separated list of allowed users.
|
||||
# GODOXY_OIDC_ALLOWED_USERS=user1,user2
|
||||
# Optional: Comma-separated list of allowed groups.
|
||||
# GODOXY_OIDC_ALLOWED_GROUPS=group1,group2
|
||||
|
||||
# Proxy listening address
|
||||
GODOXY_HTTP_ADDR=:80
|
||||
GODOXY_HTTPS_ADDR=:443
|
||||
|
||||
# Enable HTTP3
|
||||
GODOXY_HTTP3_ENABLED=true
|
||||
|
||||
# API listening address
|
||||
GODOXY_API_ADDR=127.0.0.1:8888
|
||||
|
||||
# Metrics
|
||||
GODOXY_METRICS_DISABLE_CPU=false
|
||||
GODOXY_METRICS_DISABLE_MEMORY=false
|
||||
GODOXY_METRICS_DISABLE_DISK=false
|
||||
GODOXY_METRICS_DISABLE_NETWORK=false
|
||||
GODOXY_METRICS_DISABLE_SENSORS=false
|
||||
|
||||
# Frontend listening port
|
||||
GODOXY_FRONTEND_PORT=3000
|
||||
|
||||
# Frontend aliases (subdomains / FQDNs, e.g. godoxy, godoxy.domain.com)
|
||||
GODOXY_FRONTEND_ALIASES=godoxy
|
||||
|
||||
# Debug mode
|
||||
GODOXY_DEBUG=false
|
||||
@@ -180,7 +180,24 @@ for dir in "${REQUIRED_DIRECTORIES[@]}"; do
|
||||
mkdir_if_not_exists "$dir"
|
||||
done
|
||||
|
||||
# 2. .env file
|
||||
# 2. check if rootless docker is used, verify again with user input
|
||||
if docker info -f "{{println .SecurityOptions}}" | grep rootless >/dev/null 2>&1; then
|
||||
ask_while_empty "Rootless docker detected, is this correct? (y/n): " USE_ROOTLESS_DOCKER
|
||||
if [ "$USE_ROOTLESS_DOCKER" == "n" ]; then
|
||||
USE_ROOTLESS_DOCKER="false"
|
||||
else
|
||||
USE_ROOTLESS_DOCKER="true"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. if rootless docker is used, switch to rootless docker compose and .env
|
||||
if [ "$USE_ROOTLESS_DOCKER" == "true" ]; then
|
||||
COMPOSE_EXAMPLE_FILE_NAME="rootless-compose.example.yml"
|
||||
DOT_ENV_EXAMPLE_PATH="rootless.env.example"
|
||||
fi
|
||||
|
||||
|
||||
# 4. .env file
|
||||
fetch_file "$DOT_ENV_EXAMPLE_PATH" "$DOT_ENV_PATH"
|
||||
|
||||
# set random JWT secret
|
||||
@@ -192,13 +209,13 @@ if [ -n "$TIMEZONE" ]; then
|
||||
setenv "TZ" "$TIMEZONE"
|
||||
fi
|
||||
|
||||
# 3. docker-compose.yml
|
||||
# 5. docker-compose.yml
|
||||
fetch_file "$COMPOSE_EXAMPLE_FILE_NAME" "$COMPOSE_FILE_NAME"
|
||||
|
||||
# 4. config.yml
|
||||
# 6. config.yml
|
||||
fetch_file "$CONFIG_EXAMPLE_FILE_NAME" "$CONFIG_FILE_PATH"
|
||||
|
||||
# 5. setup authentication
|
||||
# 7. setup authentication
|
||||
|
||||
# ask for user and password
|
||||
echo "Setting up login user"
|
||||
@@ -208,7 +225,7 @@ echo "Setting up login user \"$LOGIN_USERNAME\" with password \"$LOGIN_PASSWORD\
|
||||
setenv "GODOXY_API_USER" "$LOGIN_USERNAME"
|
||||
setenv "GODOXY_API_PASSWORD" "$LOGIN_PASSWORD"
|
||||
|
||||
# 6. setup autocert
|
||||
# 8. setup autocert
|
||||
ask_while_empty "Configure autocert? (y/n): " ENABLE_AUTOCERT
|
||||
|
||||
# quit if not using autocert
|
||||
@@ -269,8 +286,34 @@ autocert:
|
||||
fi
|
||||
fi
|
||||
|
||||
# 7. set uid and gid
|
||||
setenv "GODOXY_UID" "$(id -u)"
|
||||
setenv "GODOXY_GID" "$(id -g)"
|
||||
# 9. set uid and gid
|
||||
if [ "$USE_ROOTLESS_DOCKER" == "false" ]; then
|
||||
setenv "GODOXY_UID" "$(id -u)"
|
||||
setenv "GODOXY_GID" "$(id -g)"
|
||||
else
|
||||
setenv "DOCKER_SOCKET" "/var/run/user/$(id -u)/docker.sock"
|
||||
fi
|
||||
|
||||
# 10. proxy network (rootless docker only)
|
||||
if [ "$USE_ROOTLESS_DOCKER" == "true" ]; then
|
||||
echo "Setting up proxy network"
|
||||
echo "Available networks:"
|
||||
docker network ls
|
||||
echo
|
||||
ask_while_empty "Which network to use for proxy? (default: proxy): " PROXY_NETWORK
|
||||
# check if network exists
|
||||
if ! docker network ls | grep -q "$PROXY_NETWORK"; then
|
||||
ask_while_empty "Network \"$PROXY_NETWORK\" does not exist, do you want to create it? (y/n): " CREATE_NETWORK
|
||||
if [ "$CREATE_NETWORK" == "y" ]; then
|
||||
docker network create "$PROXY_NETWORK"
|
||||
echo "Network \"$PROXY_NETWORK\" created"
|
||||
else
|
||||
echo "Error: network \"$PROXY_NETWORK\" does not exist, please create it first"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
sed -i "s|proxy: #|\"$PROXY_NETWORK\": #|" "$COMPOSE_FILE_NAME"
|
||||
sed -i "s|- proxy|- \"$PROXY_NETWORK\"|" "$COMPOSE_FILE_NAME"
|
||||
fi
|
||||
|
||||
echo "Setup finished"
|
||||
|
||||
Reference in New Issue
Block a user