mirror of
https://github.com/yusing/godoxy.git
synced 2025-07-05 22:24:02 +02:00
v0.5: (BREAKING) simplified config format, improved error output, updated proxy entry default value for 'port'
This commit is contained in:
parent
23e7d06081
commit
719693deb7
21 changed files with 197 additions and 302 deletions
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.22.6-alpine as builder
|
FROM golang:1.22.6-alpine AS builder
|
||||||
COPY src /src
|
COPY src /src
|
||||||
ENV GOCACHE=/root/.cache/go-build
|
ENV GOCACHE=/root/.cache/go-build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
14
README.md
14
README.md
|
@ -80,12 +80,14 @@ autocert:
|
||||||
- ...
|
- ...
|
||||||
# reverse proxy providers configuration
|
# reverse proxy providers configuration
|
||||||
providers:
|
providers:
|
||||||
entry_1:
|
include:
|
||||||
kind: docker
|
- providers.yml
|
||||||
value: # `FROM_ENV` or full url to docker host
|
- other_file_1.yml
|
||||||
entry_2:
|
- ...
|
||||||
kind: file
|
docker:
|
||||||
value: # relative path of file to `config/`
|
local: $DOCKER_HOST
|
||||||
|
remote-1: tcp://10.0.2.1:2375
|
||||||
|
remote-2: ssh://root:1234@10.0.2.2
|
||||||
```
|
```
|
||||||
|
|
||||||
[🔼Back to top](#table-of-content)
|
[🔼Back to top](#table-of-content)
|
||||||
|
|
|
@ -11,22 +11,26 @@
|
||||||
# provider: cloudflare
|
# provider: cloudflare
|
||||||
# email: # ACME Email
|
# email: # ACME Email
|
||||||
# domains: # a list of domains for cert registration
|
# domains: # a list of domains for cert registration
|
||||||
# -
|
# - x.y.z
|
||||||
# options:
|
# options:
|
||||||
# - auth_token: # your zone API token
|
# - auth_token: c1234565789-abcdefghijklmnopqrst # your zone API token
|
||||||
|
|
||||||
# 3. other providers, check readme for more
|
# 3. other providers, check readme for more
|
||||||
|
|
||||||
providers:
|
providers:
|
||||||
local:
|
include:
|
||||||
kind: docker
|
- providers.yml # config/providers.yml
|
||||||
|
# add some more below if you want
|
||||||
|
# - file1.yml # config/file_1.yml
|
||||||
|
# - file2.yml
|
||||||
|
docker:
|
||||||
# for value format, see https://docs.docker.com/reference/cli/dockerd/
|
# for value format, see https://docs.docker.com/reference/cli/dockerd/
|
||||||
# i.e. ssh://user@10.0.1.1:22, tcp://10.0.2.1:2375
|
# $DOCKER_HOST implies unix:///var/run/docker.sock by default
|
||||||
# use FROM_ENV if you have binded docker socket to /var/run/docker.sock
|
local: $DOCKER_HOST
|
||||||
value: FROM_ENV
|
# add more docker providers if needed
|
||||||
providers:
|
# remote-1: tcp://10.0.2.1:2375
|
||||||
kind: file
|
# remote-2: ssh://root:1234@10.0.2.2
|
||||||
value: providers.yml
|
|
||||||
# Fixed options (optional, non hot-reloadable)
|
# Fixed options (optional, non hot-reloadable)
|
||||||
|
|
||||||
# timeout_shutdown: 5
|
# timeout_shutdown: 5
|
||||||
|
|
|
@ -34,12 +34,7 @@
|
||||||
"provider": {
|
"provider": {
|
||||||
"title": "DNS Challenge Provider",
|
"title": "DNS Challenge Provider",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": ["local", "cloudflare", "clouddns", "duckdns"]
|
||||||
"local",
|
|
||||||
"cloudflare",
|
|
||||||
"clouddns",
|
|
||||||
"duckdns"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"title": "Provider specific options",
|
"title": "Provider specific options",
|
||||||
|
@ -57,12 +52,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"then": {
|
"then": {
|
||||||
"required": [
|
"required": ["email", "domains", "provider", "options"]
|
||||||
"email",
|
|
||||||
"domains",
|
|
||||||
"provider",
|
|
||||||
"options"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -76,9 +66,7 @@
|
||||||
"then": {
|
"then": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"options": {
|
"options": {
|
||||||
"required": [
|
"required": ["auth_token"],
|
||||||
"auth_token"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"auth_token": {
|
"auth_token": {
|
||||||
|
@ -101,11 +89,7 @@
|
||||||
"then": {
|
"then": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"options": {
|
"options": {
|
||||||
"required": [
|
"required": ["client_id", "email", "password"],
|
||||||
"client_id",
|
|
||||||
"email",
|
|
||||||
"password"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"client_id": {
|
"client_id": {
|
||||||
|
@ -136,9 +120,7 @@
|
||||||
"then": {
|
"then": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"options": {
|
"options": {
|
||||||
"required": [
|
"required": ["token"],
|
||||||
"token"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"token": {
|
"token": {
|
||||||
|
@ -155,73 +137,54 @@
|
||||||
"providers": {
|
"providers": {
|
||||||
"title": "Proxy providers configuration",
|
"title": "Proxy providers configuration",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"patternProperties": {
|
"additionalProperties": false,
|
||||||
"^[a-zA-Z0-9_-]+$": {
|
"properties": {
|
||||||
"description": "Proxy provider",
|
"include": {
|
||||||
|
"title": "Proxy providers configuration files",
|
||||||
|
"description": "relative path to 'config'",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[a-zA-Z0-9_-]+\\.(yml|yaml)$",
|
||||||
|
"patternErrorMessage": "Invalid file name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"docker": {
|
||||||
|
"title": "Docker provider configuration",
|
||||||
|
"description": "docker clients (name: address)",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"patternProperties": {
|
||||||
"kind": {
|
"^[a-zA-Z0-9-_]+$": {
|
||||||
"description": "Proxy provider kind",
|
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"examples": [
|
||||||
"docker",
|
"unix:///var/run/docker.sock",
|
||||||
"file"
|
"tcp://127.0.0.1:2375",
|
||||||
|
"ssh://user@host:port"
|
||||||
|
],
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"const": "$DOCKER_HOST",
|
||||||
|
"description": "Use DOCKER_HOST environment variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "^unix://.+$",
|
||||||
|
"description": "A Unix socket for local Docker communication."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "^ssh://.+$",
|
||||||
|
"description": "An SSH connection to a remote Docker host."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "^fd://.+$",
|
||||||
|
"description": "A file descriptor for Docker communication."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "^tcp://.+$",
|
||||||
|
"description": "A TCP connection to a remote Docker host."
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
"value": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"required": [
|
|
||||||
"kind",
|
|
||||||
"value"
|
|
||||||
],
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"if": {
|
|
||||||
"properties": {
|
|
||||||
"kind": {
|
|
||||||
"const": "docker"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"then": {
|
|
||||||
"if": {
|
|
||||||
"properties": {
|
|
||||||
"value": {
|
|
||||||
"const": "FROM_ENV"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"then": {
|
|
||||||
"properties": {
|
|
||||||
"value": {
|
|
||||||
"description": "use docker client from environment"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"else": {
|
|
||||||
"properties": {
|
|
||||||
"value": {
|
|
||||||
"description": "docker client URL",
|
|
||||||
"examples": [
|
|
||||||
"unix:///var/run/docker.sock",
|
|
||||||
"tcp://127.0.0.1:2375",
|
|
||||||
"ssh://user@host:port"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"else": {
|
|
||||||
"properties": {
|
|
||||||
"value": {
|
|
||||||
"description": "file path"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -236,7 +199,5 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": [
|
"required": ["providers"]
|
||||||
"providers"
|
}
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"host": {
|
"host": {
|
||||||
"anyOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "ipv4",
|
"format": "ipv4",
|
||||||
|
@ -69,9 +69,7 @@
|
||||||
"set_headers": {},
|
"set_headers": {},
|
||||||
"hide_headers": {}
|
"hide_headers": {}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["host"],
|
||||||
"host"
|
|
||||||
],
|
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
|
@ -80,10 +78,7 @@
|
||||||
{
|
{
|
||||||
"properties": {
|
"properties": {
|
||||||
"scheme": {
|
"scheme": {
|
||||||
"enum": [
|
"enum": ["http", "https"]
|
||||||
"http",
|
|
||||||
"https"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -171,9 +166,7 @@
|
||||||
"not": true
|
"not": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["port"]
|
||||||
"port"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -198,4 +191,4 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
|
|
114
setup-binary.sh
114
setup-binary.sh
|
@ -1,114 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
REPO_URL=https://github.com/yusing/go-proxy
|
|
||||||
BIN_URL="${REPO_URL}/releases/download/${VERSION}/go-proxy"
|
|
||||||
SRC_URL="${REPO_URL}/archive/refs/tags/${VERSION}.tar.gz"
|
|
||||||
APP_ROOT="/opt/go-proxy/${VERSION}"
|
|
||||||
LOG_FILE="/tmp/go-proxy-setup.log"
|
|
||||||
|
|
||||||
if [ -z "$VERSION" ] || [ "$VERSION" = "latest" ]; then
|
|
||||||
VERSION_URL="${REPO_URL}/raw/main/version.txt"
|
|
||||||
VERSION=$(wget -qO- "$VERSION_URL")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -d "$APP_ROOT" ]; then
|
|
||||||
echo "$APP_ROOT already exists"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# check if wget exists
|
|
||||||
if ! [ -x "$(command -v wget)" ]; then
|
|
||||||
echo "wget is not installed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# check if make exists
|
|
||||||
if ! [ -x "$(command -v make)" ]; then
|
|
||||||
echo "make is not installed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
dl_source() {
|
|
||||||
cd /tmp
|
|
||||||
echo "Downloading go-proxy source ${VERSION}"
|
|
||||||
wget -c "${SRC_URL}" -O go-proxy.tar.gz &> $LOG_FILE
|
|
||||||
if [ $? -gt 0 ]; then
|
|
||||||
echo "Source download failed, check your internet connection and version number"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "Done"
|
|
||||||
echo "Extracting go-proxy source ${VERSION}"
|
|
||||||
tar xzf go-proxy.tar.gz &> $LOG_FILE
|
|
||||||
if [ $? -gt 0 ]; then
|
|
||||||
echo "failed to untar go-proxy.tar.gz"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
rm go-proxy.tar.gz
|
|
||||||
mkdir -p "$(dirname "${APP_ROOT}")"
|
|
||||||
mv "go-proxy-${VERSION}" "$APP_ROOT"
|
|
||||||
cd "$APP_ROOT"
|
|
||||||
echo "Done"
|
|
||||||
}
|
|
||||||
dl_binary() {
|
|
||||||
mkdir -p bin
|
|
||||||
echo "Downloading go-proxy binary ${VERSION}"
|
|
||||||
wget -c "${BIN_URL}" -O bin/go-proxy &> $LOG_FILE
|
|
||||||
if [ $? -gt 0 ]; then
|
|
||||||
echo "Binary download failed, check your internet connection and version number"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
chmod +x bin/go-proxy
|
|
||||||
echo "Done"
|
|
||||||
}
|
|
||||||
setup() {
|
|
||||||
make setup &> $LOG_FILE
|
|
||||||
if [ $? -gt 0 ]; then
|
|
||||||
echo "make setup failed"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
# SETUP_CODEMIRROR = 1
|
|
||||||
if [ "$SETUP_CODEMIRROR" != "0" ]; then
|
|
||||||
make setup-codemirror &> $LOG_FILE || echo "make setup-codemirror failed, ignored"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
dl_source
|
|
||||||
dl_binary
|
|
||||||
setup
|
|
||||||
|
|
||||||
# setup systemd
|
|
||||||
|
|
||||||
# check if systemctl exists
|
|
||||||
if ! command -v systemctl is-system-running > /dev/null 2>&1; then
|
|
||||||
echo "systemctl not found, skipping systemd setup"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
systemctl_failed() {
|
|
||||||
echo "Failed to enable and start go-proxy"
|
|
||||||
systemctl status go-proxy
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
echo "Setting up systemd service"
|
|
||||||
cat <<EOF > /etc/systemd/system/go-proxy.service
|
|
||||||
[Unit]
|
|
||||||
Description=go-proxy reverse proxy
|
|
||||||
After=network-online.target
|
|
||||||
Wants=network-online.target systemd-networkd-wait-online.service
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
ExecStart=${APP_ROOT}/bin/go-proxy
|
|
||||||
WorkingDirectory=${APP_ROOT}
|
|
||||||
Environment="GOPROXY_IS_SYSTEMD=1"
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=1s
|
|
||||||
KillMode=process
|
|
||||||
KillSignal=SIGINT
|
|
||||||
TimeoutStartSec=5s
|
|
||||||
TimeoutStopSec=5s
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
systemctl daemon-reload &>$LOG_FILE || systemctl_failed
|
|
||||||
systemctl enable --now go-proxy &>$LOG_FILE || systemctl_failed
|
|
||||||
echo "Done"
|
|
||||||
echo "Setup complete"
|
|
|
@ -35,7 +35,7 @@ const (
|
||||||
ProvidersSchemaPath = SchemaBasePath + "providers.schema.json"
|
ProvidersSchemaPath = SchemaBasePath + "providers.schema.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
const DockerHostFromEnv = "FROM_ENV"
|
const DockerHostFromEnv = "$DOCKER_HOST"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ProxyHTTPPort = ":80"
|
ProxyHTTPPort = ":80"
|
||||||
|
|
|
@ -134,16 +134,9 @@ func (cfg *Config) Statistics() map[string]interface{} {
|
||||||
panic("bug: should not reach here")
|
panic("bug: should not reach here")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
stats["type"] = p.GetType()
|
||||||
stats["num_streams"] = nStreams
|
stats["num_streams"] = nStreams
|
||||||
stats["num_reverse_proxies"] = nRPs
|
stats["num_reverse_proxies"] = nRPs
|
||||||
switch p.ProviderImpl.(type) {
|
|
||||||
case *PR.DockerProvider:
|
|
||||||
stats["type"] = "docker"
|
|
||||||
case *PR.FileProvider:
|
|
||||||
stats["type"] = "file"
|
|
||||||
default:
|
|
||||||
panic("bug: should not reach here")
|
|
||||||
}
|
|
||||||
providerStats[p.GetName()] = stats
|
providerStats[p.GetName()] = stats
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -202,7 +195,7 @@ func (cfg *Config) load() E.NestedError {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !common.NoSchemaValidation {
|
if !common.NoSchemaValidation {
|
||||||
if err := Validate(data); err.IsNotNil() {
|
if err = Validate(data); err.IsNotNil() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,13 +213,19 @@ func (cfg *Config) load() E.NestedError {
|
||||||
|
|
||||||
cfg.l.Debug("starting providers")
|
cfg.l.Debug("starting providers")
|
||||||
cfg.proxyProviders = F.NewMap[string, *PR.Provider]()
|
cfg.proxyProviders = F.NewMap[string, *PR.Provider]()
|
||||||
for name, pm := range model.Providers {
|
for _, filename := range model.Providers.Files {
|
||||||
p := PR.NewProvider(name, pm)
|
p := PR.NewFileProvider(filename)
|
||||||
cfg.proxyProviders.Set(name, p)
|
cfg.proxyProviders.Set(p.GetName(), p)
|
||||||
if err := p.StartAllRoutes(); err.IsNotNil() {
|
|
||||||
warnings.Add(E.Failure("start routes").Subjectf("provider %s", name).With(err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
for name, dockerHost := range model.Providers.Docker {
|
||||||
|
p := PR.NewDockerProvider(name, dockerHost)
|
||||||
|
cfg.proxyProviders.Set(p.GetName(), p)
|
||||||
|
}
|
||||||
|
cfg.proxyProviders.EachKV(func(name string, p *PR.Provider) {
|
||||||
|
if err := p.StartAllRoutes(); err.IsNotNil() {
|
||||||
|
warnings.Add(E.Failure("start routes").Subject(p).With(err))
|
||||||
|
}
|
||||||
|
})
|
||||||
cfg.l.Debug("started providers")
|
cfg.l.Debug("started providers")
|
||||||
|
|
||||||
cfg.value = model
|
cfg.value = model
|
||||||
|
@ -244,7 +243,7 @@ func (cfg *Config) controlProviders(action string, do func(*PR.Provider) E.Neste
|
||||||
|
|
||||||
cfg.proxyProviders.EachKVParallel(func(name string, p *PR.Provider) {
|
cfg.proxyProviders.EachKVParallel(func(name string, p *PR.Provider) {
|
||||||
if err := do(p); err.IsNotNil() {
|
if err := do(p); err.IsNotNil() {
|
||||||
errors.Add(E.From(err).Subjectf("provider %s", name))
|
errors.Add(E.From(err).Subject(p))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ type (
|
||||||
// Caller then should handle the nested error,
|
// Caller then should handle the nested error,
|
||||||
// and continue with the valid values.
|
// and continue with the valid values.
|
||||||
NestedError struct {
|
NestedError struct {
|
||||||
subject any
|
subject string
|
||||||
err error // can be nil
|
err error // can be nil
|
||||||
extras []NestedError
|
extras []NestedError
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,14 @@ func (ne NestedError) Extraf(format string, args ...any) NestedError {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ne NestedError) Subject(s any) NestedError {
|
func (ne NestedError) Subject(s any) NestedError {
|
||||||
ne.subject = s
|
switch ss := s.(type) {
|
||||||
|
case string:
|
||||||
|
ne.subject = ss
|
||||||
|
case fmt.Stringer:
|
||||||
|
ne.subject = ss.String()
|
||||||
|
default:
|
||||||
|
ne.subject = fmt.Sprint(s)
|
||||||
|
}
|
||||||
return ne
|
return ne
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +114,8 @@ func (ne NestedError) Subjectf(format string, args ...any) NestedError {
|
||||||
if strings.Contains(format, "%w") {
|
if strings.Contains(format, "%w") {
|
||||||
panic("Subjectf format should not contain %w")
|
panic("Subjectf format should not contain %w")
|
||||||
}
|
}
|
||||||
return ne.Subject(fmt.Sprintf(format, args...))
|
ne.subject = fmt.Sprintf(format, args...)
|
||||||
|
return ne
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ne NestedError) IsNil() bool {
|
func (ne NestedError) IsNil() bool {
|
||||||
|
@ -134,7 +142,7 @@ func (ne *NestedError) writeToSB(sb *strings.Builder, level int, prefix string)
|
||||||
if ne.err != nil {
|
if ne.err != nil {
|
||||||
sb.WriteString(ne.err.Error())
|
sb.WriteString(ne.err.Error())
|
||||||
}
|
}
|
||||||
if ne.subject != nil {
|
if ne.subject != "" {
|
||||||
if ne.err != nil {
|
if ne.err != nil {
|
||||||
sb.WriteString(fmt.Sprintf(" for %q", ne.subject))
|
sb.WriteString(fmt.Sprintf(" for %q", ne.subject))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3,8 +3,8 @@ module github.com/yusing/go-proxy
|
||||||
go 1.22
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/docker/cli v27.1.1+incompatible
|
github.com/docker/cli v27.1.2+incompatible
|
||||||
github.com/docker/docker v27.1.1+incompatible
|
github.com/docker/docker v27.1.2+incompatible
|
||||||
github.com/fsnotify/fsnotify v1.7.0
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
github.com/go-acme/lego/v4 v4.17.4
|
github.com/go-acme/lego/v4 v4.17.4
|
||||||
github.com/santhosh-tekuri/jsonschema v1.2.4
|
github.com/santhosh-tekuri/jsonschema v1.2.4
|
||||||
|
|
|
@ -13,10 +13,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE=
|
github.com/docker/cli v27.1.2+incompatible h1:nYviRv5Y+YAKx3dFrTvS1ErkyVVunKOhoweCTE1BsnI=
|
||||||
github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v27.1.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
|
github.com/docker/docker v27.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY=
|
||||||
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
|
|
|
@ -40,4 +40,10 @@ func (e *ProxyEntry) SetDefaults() {
|
||||||
if e.Path == "" {
|
if e.Path == "" {
|
||||||
e.Path = "/"
|
e.Path = "/"
|
||||||
}
|
}
|
||||||
|
switch e.Scheme {
|
||||||
|
case "http":
|
||||||
|
e.Port = "80"
|
||||||
|
case "https":
|
||||||
|
e.Port = "443"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
package model
|
|
||||||
|
|
||||||
type (
|
|
||||||
ProxyProvider struct {
|
|
||||||
Kind string `json:"kind"` // docker, file
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
ProxyProviders = map[string]ProxyProvider
|
|
||||||
)
|
|
6
src/models/proxy_providers.go
Normal file
6
src/models/proxy_providers.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
type ProxyProviders struct {
|
||||||
|
Files []string `yaml:"include" json:"include"` // docker, file
|
||||||
|
Docker map[string]string `yaml:"docker" json:"docker"`
|
||||||
|
}
|
|
@ -11,7 +11,7 @@ type Port int
|
||||||
func NewPort(v string) (Port, E.NestedError) {
|
func NewPort(v string) (Port, E.NestedError) {
|
||||||
p, err := strconv.Atoi(v)
|
p, err := strconv.Atoi(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrPort, E.From(err)
|
return ErrPort, E.Invalid("port number", v).With(err)
|
||||||
}
|
}
|
||||||
return NewPortInt(p)
|
return NewPortInt(p)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ type DockerProvider struct {
|
||||||
dockerHost string
|
dockerHost string
|
||||||
}
|
}
|
||||||
|
|
||||||
func DockerProviderImpl(model *M.ProxyProvider) ProviderImpl {
|
func DockerProviderImpl(dockerHost string) ProviderImpl {
|
||||||
return &DockerProvider{dockerHost: model.Value}
|
return &DockerProvider{dockerHost: dockerHost}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetProxyEntries returns proxy entries from a docker client.
|
// GetProxyEntries returns proxy entries from a docker client.
|
||||||
|
@ -32,15 +32,16 @@ func DockerProviderImpl(model *M.ProxyProvider) ProviderImpl {
|
||||||
// - p: A pointer to the DockerProvider struct.
|
// - p: A pointer to the DockerProvider struct.
|
||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// - P.EntryModelSlice: A slice of EntryModel structs representing the proxy entries.
|
// - P.EntryModelSlice: (non-nil) A slice of EntryModel structs representing the proxy entries.
|
||||||
// - error: An error object if there was an error retrieving the docker client information or parsing the labels.
|
// - error: An error object if there was an error retrieving the docker client information or parsing the labels.
|
||||||
func (p DockerProvider) GetProxyEntries() (M.ProxyEntries, E.NestedError) {
|
func (p DockerProvider) GetProxyEntries() (M.ProxyEntries, E.NestedError) {
|
||||||
|
entries := M.NewProxyEntries()
|
||||||
|
|
||||||
info, err := D.GetClientInfo(p.dockerHost)
|
info, err := D.GetClientInfo(p.dockerHost)
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
return nil, E.From(err)
|
return entries, E.From(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries := M.NewProxyEntries()
|
|
||||||
errors := E.NewBuilder("errors when parse docker labels for %q", p.dockerHost)
|
errors := E.NewBuilder("errors when parse docker labels for %q", p.dockerHost)
|
||||||
|
|
||||||
for _, container := range info.Containers {
|
for _, container := range info.Containers {
|
||||||
|
|
|
@ -16,10 +16,10 @@ type FileProvider struct {
|
||||||
path string
|
path string
|
||||||
}
|
}
|
||||||
|
|
||||||
func FileProviderImpl(m *M.ProxyProvider) ProviderImpl {
|
func FileProviderImpl(filename string) ProviderImpl {
|
||||||
return &FileProvider{
|
return &FileProvider{
|
||||||
fileName: m.Value,
|
fileName: filename,
|
||||||
path: path.Join(common.ConfigBasePath, m.Value),
|
path: path.Join(common.ConfigBasePath, filename),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,13 +27,17 @@ func Validate(data []byte) E.NestedError {
|
||||||
return U.ValidateYaml(U.GetSchema(common.ProvidersSchemaPath), data)
|
return U.ValidateYaml(U.GetSchema(common.ProvidersSchemaPath), data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *FileProvider) String() string {
|
||||||
|
return p.fileName
|
||||||
|
}
|
||||||
|
|
||||||
func (p *FileProvider) GetProxyEntries() (M.ProxyEntries, E.NestedError) {
|
func (p *FileProvider) GetProxyEntries() (M.ProxyEntries, E.NestedError) {
|
||||||
entries := M.NewProxyEntries()
|
entries := M.NewProxyEntries()
|
||||||
data, err := E.Check(os.ReadFile(p.path))
|
data, err := E.Check(os.ReadFile(p.path))
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
return entries, E.Failure("read file").Subject(p.fileName).With(err)
|
return entries, E.Failure("read file").Subject(p).With(err)
|
||||||
}
|
}
|
||||||
ne := E.Failure("validation").Subject(p.fileName)
|
ne := E.Failure("validation").Subject(p)
|
||||||
if !common.NoSchemaValidation {
|
if !common.NoSchemaValidation {
|
||||||
if err = Validate(data); err.IsNotNil() {
|
if err = Validate(data); err.IsNotNil() {
|
||||||
return entries, ne.With(err)
|
return entries, ne.With(err)
|
||||||
|
|
|
@ -2,9 +2,10 @@ package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/yusing/go-proxy/common"
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/error"
|
||||||
M "github.com/yusing/go-proxy/models"
|
M "github.com/yusing/go-proxy/models"
|
||||||
R "github.com/yusing/go-proxy/route"
|
R "github.com/yusing/go-proxy/route"
|
||||||
|
@ -20,6 +21,7 @@ type Provider struct {
|
||||||
ProviderImpl
|
ProviderImpl
|
||||||
|
|
||||||
name string
|
name string
|
||||||
|
t ProviderType
|
||||||
routes *R.Routes
|
routes *R.Routes
|
||||||
reloadReqCh chan struct{}
|
reloadReqCh chan struct{}
|
||||||
|
|
||||||
|
@ -30,27 +32,49 @@ type Provider struct {
|
||||||
l *logrus.Entry
|
l *logrus.Entry
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProvider(name string, model M.ProxyProvider) (p *Provider) {
|
type ProviderType string
|
||||||
p = &Provider{
|
|
||||||
|
const (
|
||||||
|
ProviderTypeDocker ProviderType = "docker"
|
||||||
|
ProviderTypeFile ProviderType = "file"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newProvider(name string, t ProviderType) *Provider {
|
||||||
|
return &Provider{
|
||||||
name: name,
|
name: name,
|
||||||
|
t: t,
|
||||||
routes: R.NewRoutes(),
|
routes: R.NewRoutes(),
|
||||||
reloadReqCh: make(chan struct{}, 1),
|
reloadReqCh: make(chan struct{}, 1),
|
||||||
l: logrus.WithField("provider", name),
|
l: logrus.WithField("provider", name),
|
||||||
}
|
}
|
||||||
switch model.Kind {
|
}
|
||||||
case common.ProviderKind_Docker:
|
func NewFileProvider(filename string) *Provider {
|
||||||
p.ProviderImpl = DockerProviderImpl(&model)
|
name := path.Base(filename)
|
||||||
case common.ProviderKind_File:
|
p := newProvider(name, ProviderTypeFile)
|
||||||
p.ProviderImpl = FileProviderImpl(&model)
|
p.ProviderImpl = FileProviderImpl(filename)
|
||||||
}
|
|
||||||
p.watcher = p.NewWatcher()
|
p.watcher = p.NewWatcher()
|
||||||
return
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDockerProvider(name string, dockerHost string) *Provider {
|
||||||
|
p := newProvider(name, ProviderTypeDocker)
|
||||||
|
p.ProviderImpl = DockerProviderImpl(dockerHost)
|
||||||
|
p.watcher = p.NewWatcher()
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) GetName() string {
|
func (p *Provider) GetName() string {
|
||||||
return p.name
|
return p.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) GetType() ProviderType {
|
||||||
|
return p.t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Provider) String() string {
|
||||||
|
return fmt.Sprintf("%s (%s provider)", p.name, p.t)
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provider) StartAllRoutes() E.NestedError {
|
func (p *Provider) StartAllRoutes() E.NestedError {
|
||||||
err := p.loadRoutes()
|
err := p.loadRoutes()
|
||||||
|
|
||||||
|
@ -58,23 +82,24 @@ func (p *Provider) StartAllRoutes() E.NestedError {
|
||||||
p.watcherCtx, p.watcherCancel = context.WithCancel(context.Background())
|
p.watcherCtx, p.watcherCancel = context.WithCancel(context.Background())
|
||||||
go p.watchEvents()
|
go p.watchEvents()
|
||||||
|
|
||||||
if err.IsNotNil() {
|
errors := E.NewBuilder("errors in routes")
|
||||||
return err
|
|
||||||
}
|
|
||||||
errors := E.NewBuilder("errors starting routes for provider %q", p.name)
|
|
||||||
nStarted := 0
|
nStarted := 0
|
||||||
|
nFailed := 0
|
||||||
|
|
||||||
|
if err.IsNotNil() {
|
||||||
|
errors.Add(err)
|
||||||
|
}
|
||||||
|
|
||||||
p.routes.EachKVParallel(func(alias string, r R.Route) {
|
p.routes.EachKVParallel(func(alias string, r R.Route) {
|
||||||
if err := r.Start(); err.IsNotNil() {
|
if err := r.Start(); err.IsNotNil() {
|
||||||
errors.Add(err.Subject(alias))
|
errors.Add(err.Subject(r))
|
||||||
|
nFailed++
|
||||||
} else {
|
} else {
|
||||||
nStarted++
|
nStarted++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if err := errors.Build(); err.IsNotNil() {
|
p.l.Infof("%d routes started, %d failed", nStarted, nFailed)
|
||||||
return err
|
return errors.Build()
|
||||||
}
|
|
||||||
p.l.Infof("%d routes started", nStarted)
|
|
||||||
return E.Nil()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Provider) StopAllRoutes() E.NestedError {
|
func (p *Provider) StopAllRoutes() E.NestedError {
|
||||||
|
@ -87,7 +112,7 @@ func (p *Provider) StopAllRoutes() E.NestedError {
|
||||||
nStopped := 0
|
nStopped := 0
|
||||||
p.routes.EachKVParallel(func(alias string, r R.Route) {
|
p.routes.EachKVParallel(func(alias string, r R.Route) {
|
||||||
if err := r.Stop(); err.IsNotNil() {
|
if err := r.Stop(); err.IsNotNil() {
|
||||||
errors.Add(err.Subject(alias))
|
errors.Add(err.Subject(r))
|
||||||
} else {
|
} else {
|
||||||
nStopped++
|
nStopped++
|
||||||
}
|
}
|
||||||
|
@ -146,7 +171,7 @@ func (p *Provider) loadRoutes() E.NestedError {
|
||||||
entries, err := p.GetProxyEntries()
|
entries, err := p.GetProxyEntries()
|
||||||
|
|
||||||
if err.IsNotNil() {
|
if err.IsNotNil() {
|
||||||
p.l.Warn(err.Subjectf("provider %s", p.name))
|
p.l.Warn(err.Subject(p))
|
||||||
}
|
}
|
||||||
p.routes = R.NewRoutes()
|
p.routes = R.NewRoutes()
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,10 @@ func NewHTTPRoute(entry *P.Entry) (*HTTPRoute, E.NestedError) {
|
||||||
return r, E.Nil()
|
return r, E.Nil()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *HTTPRoute) String() string {
|
||||||
|
return fmt.Sprintf("%s (reverse proxy)", r.Alias)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *HTTPRoute) Start() E.NestedError {
|
func (r *HTTPRoute) Start() E.NestedError {
|
||||||
httpRoutes.Set(r.Alias.String(), r)
|
httpRoutes.Set(r.Alias.String(), r)
|
||||||
return E.Nil()
|
return E.Nil()
|
||||||
|
|
|
@ -11,6 +11,7 @@ type (
|
||||||
Route interface {
|
Route interface {
|
||||||
Start() E.NestedError
|
Start() E.NestedError
|
||||||
Stop() E.NestedError
|
Stop() E.NestedError
|
||||||
|
String() string
|
||||||
}
|
}
|
||||||
Routes = F.Map[string, Route]
|
Routes = F.Map[string, Route]
|
||||||
)
|
)
|
||||||
|
|
|
@ -49,6 +49,10 @@ func NewStreamRoute(entry *P.StreamEntry) (*StreamRoute, E.NestedError) {
|
||||||
return base, E.Nil()
|
return base, E.Nil()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *StreamRoute) String() string {
|
||||||
|
return fmt.Sprintf("%s (%v stream)", r.Alias, r.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *StreamRoute) Start() E.NestedError {
|
func (r *StreamRoute) Start() E.NestedError {
|
||||||
if r.started.Load() {
|
if r.started.Load() {
|
||||||
return E.Invalid("state", "already started")
|
return E.Invalid("state", "already started")
|
||||||
|
|
Loading…
Add table
Reference in a new issue