mirror of
https://github.com/yusing/godoxy.git
synced 2026-01-11 22:30:47 +01:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03bf425a38 | ||
|
|
5fafa619ee | ||
|
|
bebf99ed6c | ||
|
|
8483263d01 | ||
|
|
351bf84559 | ||
|
|
cbe23d2ed1 | ||
|
|
6e45f3683c | ||
|
|
581894c05b | ||
|
|
2657b1f726 | ||
|
|
3505e8ff7e | ||
|
|
2314e39291 | ||
|
|
bd19f443d4 | ||
|
|
ce433f0c51 | ||
|
|
47877e5119 | ||
|
|
486122f3d8 | ||
|
|
a0be1f11d3 | ||
|
|
662190e09e | ||
|
|
ce1e5da72e | ||
|
|
eb7e744a75 | ||
|
|
ac26baf97f | ||
|
|
5a8c11de16 | ||
|
|
a8ecafcd09 | ||
|
|
af37d1f29e | ||
|
|
8cfd24e6bd | ||
|
|
7bf5784016 | ||
|
|
25930a1a73 | ||
|
|
f20a1ff523 | ||
|
|
ba51796a64 | ||
|
|
c445d50221 | ||
|
|
73dfc17a82 | ||
|
|
fdab026a3b | ||
|
|
c789c69c86 | ||
|
|
2b298aa7fa | ||
|
|
d20e4d435a | ||
|
|
15d9436d52 | ||
|
|
ca98b31458 | ||
|
|
77f957c7a8 | ||
|
|
51493c9fdd | ||
|
|
9b34dc994d |
14
.github/workflows/docker-image.yml
vendored
Normal file
14
.github/workflows/docker-image.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: Docker Image CI
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
jobs:
|
||||
build_and_push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Build and Push Container to ghcr.io
|
||||
uses: GlueOps/github-actions-build-push-containers@v0.3.7
|
||||
with:
|
||||
tags: latest,${{ github.ref_name }}
|
||||
30
.github/workflows/go.yml
vendored
Normal file
30
.github/workflows/go.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# This workflow will build a golang project
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
|
||||
|
||||
name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.22.1"
|
||||
|
||||
- name: Build
|
||||
run: make build
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: bin/go-proxy
|
||||
#- name: Test
|
||||
# run: go test -v ./...
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,10 +1,9 @@
|
||||
compose.yml
|
||||
|
||||
config/**
|
||||
|
||||
bin/go-proxy.bak
|
||||
config/
|
||||
certs/
|
||||
bin/
|
||||
templates/codemirror/
|
||||
|
||||
logs/
|
||||
log/
|
||||
|
||||
config-editor/
|
||||
log/
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "templates/codemirror"]
|
||||
path = templates/codemirror
|
||||
url = https://github.com/codemirror/codemirror5.git
|
||||
|
||||
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
@@ -1,14 +1,16 @@
|
||||
{
|
||||
"go.inferGopath": false,
|
||||
"yaml.schemas": {
|
||||
"https://github.com/yusing/go-proxy/raw/main/schema/config.schema.json": [
|
||||
"config.example.yml",
|
||||
"config.yml",
|
||||
"file:///config/workspace/go-proxy/config.example.yml"
|
||||
],
|
||||
// "https://github.com/yusing/go-proxy/raw/main/schema/config.schema.json": [
|
||||
// "config.example.yml",
|
||||
// "config.yml"
|
||||
// ],
|
||||
"https://github.com/yusing/go-proxy/raw/main/schema/providers.schema.json": [
|
||||
"providers.example.yml",
|
||||
"*.providers.yml"
|
||||
],
|
||||
"file:///config/workspace/go-proxy/schema/config.schema.json": [
|
||||
"file:///config/workspace/go-proxy/config.example.yml"
|
||||
]
|
||||
}
|
||||
}
|
||||
31
Dockerfile
31
Dockerfile
@@ -1,17 +1,34 @@
|
||||
FROM alpine:latest AS codemirror
|
||||
RUN apk add --no-cache unzip wget make
|
||||
COPY Makefile .
|
||||
RUN make setup-codemirror
|
||||
|
||||
FROM golang:1.22.1-alpine as builder
|
||||
COPY src/ /src
|
||||
COPY go.mod go.sum /src/go-proxy
|
||||
WORKDIR /src/go-proxy
|
||||
RUN --mount=type=cache,target="/go/pkg/mod" \
|
||||
go mod download
|
||||
|
||||
ENV GOCACHE=/root/.cache/go-build
|
||||
RUN --mount=type=cache,target="/go/pkg/mod" \
|
||||
--mount=type=cache,target="/root/.cache/go-build" \
|
||||
CGO_ENABLED=0 GOOS=linux go build -pgo=auto -o go-proxy
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
LABEL maintainer="yusing@6uo.me"
|
||||
|
||||
RUN apk add --no-cache bash tzdata
|
||||
RUN mkdir /app
|
||||
COPY bin/go-proxy entrypoint.sh /app/
|
||||
RUN apk add --no-cache tzdata
|
||||
RUN mkdir -p /app/templates
|
||||
COPY --from=codemirror templates/codemirror/ /app/templates/codemirror
|
||||
COPY templates/ /app/templates
|
||||
COPY config.example.yml /app/config/config.yml
|
||||
COPY schema/ /app/schema
|
||||
COPY --from=builder /src/go-proxy /app/
|
||||
|
||||
RUN chmod +x /app/go-proxy /app/entrypoint.sh
|
||||
RUN chmod +x /app/go-proxy
|
||||
ENV DOCKER_HOST unix:///var/run/docker.sock
|
||||
ENV GOPROXY_DEBUG 0
|
||||
ENV GOPROXY_REDIRECT_HTTP 1
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 8080
|
||||
@@ -19,4 +36,4 @@ EXPOSE 443
|
||||
EXPOSE 8443
|
||||
|
||||
WORKDIR /app
|
||||
ENTRYPOINT /app/entrypoint.sh
|
||||
CMD ["/app/go-proxy"]
|
||||
24
Makefile
24
Makefile
@@ -2,22 +2,28 @@
|
||||
|
||||
all: build quick-restart logs
|
||||
|
||||
setup:
|
||||
mkdir -p config certs
|
||||
[ -f config/config.yml ] || cp config.example.yml config/config.yml
|
||||
[ -f config/providers.yml ] || touch config/providers.yml
|
||||
|
||||
setup-codemirror:
|
||||
wget https://codemirror.net/5/codemirror.zip
|
||||
unzip codemirror.zip
|
||||
rm codemirror.zip
|
||||
mkdir -p templates
|
||||
mv codemirror-* templates/codemirror
|
||||
|
||||
build:
|
||||
mkdir -p bin
|
||||
CGO_ENABLED=0 GOOS=linux go build -pgo=auto -o bin/go-proxy src/go-proxy/*.go
|
||||
|
||||
up:
|
||||
docker compose up -d --build go-proxy
|
||||
|
||||
quick-restart: # quick restart without restarting the container
|
||||
docker cp bin/go-proxy go-proxy:/app/go-proxy
|
||||
docker cp templates/* go-proxy:/app/templates
|
||||
docker cp entrypoint.sh go-proxy:/app/entrypoint.sh
|
||||
docker exec -d go-proxy bash /app/entrypoint.sh restart
|
||||
docker compose up -d --build app
|
||||
|
||||
restart:
|
||||
docker kill go-proxy
|
||||
docker compose up -d go-proxy
|
||||
docker compose up -d app
|
||||
|
||||
logs:
|
||||
tail -f log/go-proxy.log
|
||||
@@ -30,6 +36,6 @@ udp-server:
|
||||
-p 9999:9999/udp \
|
||||
--label proxy.test-udp.scheme=udp \
|
||||
--label proxy.test-udp.port=20003:9999 \
|
||||
--network data_default \
|
||||
--network host \
|
||||
--name test-udp \
|
||||
$$(docker build -q -f udp-test-server.Dockerfile .)
|
||||
|
||||
160
README.md
160
README.md
@@ -10,8 +10,8 @@ In the examples domain `x.y.z` is used, replace them with your domain
|
||||
- [Table of content](#table-of-content)
|
||||
- [Key Points](#key-points)
|
||||
- [How to use](#how-to-use)
|
||||
- [Binary](#binary)
|
||||
- [Docker](#docker)
|
||||
- [Command-line args](#command-line-args)
|
||||
- [Commands](#commands)
|
||||
- [Use JSON Schema in VSCode](#use-json-schema-in-vscode)
|
||||
- [Configuration](#configuration)
|
||||
- [Labels (docker)](#labels-docker)
|
||||
@@ -37,78 +37,50 @@ In the examples domain `x.y.z` is used, replace them with your domain
|
||||
- Fast (See [benchmarks](#benchmarks))
|
||||
- Auto certificate obtaining and renewal (See [Config File](#config-file) and [Supported DNS Challenge Providers](#supported-dns-challenge-providers))
|
||||
- Auto detect reverse proxies from docker
|
||||
- Auto hot-reload on container `start` / `die` / `stop` or config file changes
|
||||
- Custom proxy entries with `config.yml` and additional provider files
|
||||
- Subdomain matching + Path matching **(domain name doesn't matter)**
|
||||
- HTTP(s) proxy + TCP/UDP Proxy
|
||||
- HTTP(s) proxy + TCP/UDP Proxy (UDP is _experimental_)
|
||||
- HTTP(s) round robin load balance support (same subdomain and path across different hosts)
|
||||
- Auto hot-reload on container `start` / `die` / `stop` or config file changes
|
||||
- Simple panel to see all reverse proxies and health available on port [panel_port_http] (http) and port [panel_port_https] (https)
|
||||
- Web UI on port 8080 (http) and port 8443 (https)
|
||||
|
||||

|
||||
- Config editor to edit config and provider files with validation
|
||||
- a simple panel to see all reverse proxies and health
|
||||
|
||||
**Validate and save file with Ctrl+S**
|
||||

|
||||
|
||||

|
||||
- a config editor to edit config and provider files with validation
|
||||
|
||||
**Validate and save file with Ctrl+S**
|
||||
|
||||

|
||||
|
||||
## How to use
|
||||
|
||||
1. Download and extract the latest release (or clone the repository if you want to try out experimental features)
|
||||
1. Setup DNS Records to your machine's IP address
|
||||
|
||||
2. Copy `config.example.yml` to `config/config.yml` and modify the content to fit your needs
|
||||
- A Record: `*.y.z` -> `10.0.10.1`
|
||||
- AAAA Record: `*.y.z` -> `::ffff:a00:a01`
|
||||
|
||||
3. (Optional) write your own `config/providers.yml` from `providers.example.yml`
|
||||
2. Start `go-proxy` (see [Binary](docs/binary.md) or [docker](docs/docker.md))
|
||||
|
||||
4. See [Binary](#binary) or [docker](#docker)
|
||||
3. Start editing config files
|
||||
- with text editor (i.e. Visual Studio Code)
|
||||
- or with web config editor by navigate to `ip:8080`
|
||||
|
||||
### Binary
|
||||
## Command-line args
|
||||
|
||||
1. (Optional) enabled HTTPS
|
||||
`go-proxy [command]`
|
||||
|
||||
- Use autocert feature by completing `autocert` in `config.yml`
|
||||
### Commands
|
||||
|
||||
- Use existing certificate
|
||||
- empty: start proxy server
|
||||
- validate: validate config and exit
|
||||
- reload: trigger a force reload of config
|
||||
|
||||
Prepare your wildcard (`*.y.z`) SSL cert in `certs/`
|
||||
Examples:
|
||||
|
||||
- cert / chain / fullchain: `certs/cert.crt`
|
||||
- private key: `certs/priv.key`
|
||||
|
||||
2. run the binary `bin/go-proxy`
|
||||
|
||||
3. enjoy
|
||||
|
||||
### Docker
|
||||
|
||||
1. Copy content from [compose.example.yml](compose.example.yml) and create your own `compose.yml`
|
||||
|
||||
2. Add networks to make sure it is in the same network with other containers, or make sure `proxy.<alias>.host` is reachable
|
||||
|
||||
3. (Optional) enable HTTPS
|
||||
|
||||
- Use autocert feature by completing `autocert` section in `config/config.yml` and mount `certs/` to `/app/certs` in order to store obtained certs
|
||||
|
||||
- Use existing certificate by mount your wildcard (`*.y.z`) SSL cert
|
||||
|
||||
- cert / chain / fullchain -> `/app/certs/cert.crt`
|
||||
- private key -> `/app/certs/priv.key`
|
||||
|
||||
4. Start `go-proxy` with `docker compose up -d` or `make up`.
|
||||
|
||||
5. (Optional) If you are using ufw with vpn that drop all inbound traffic except vpn, run below to allow docker containers to connect to `go-proxy`
|
||||
|
||||
In case the network of your container is in subnet `172.16.0.0/16` (bridge),
|
||||
and vpn network is under `100.64.0.0/10` (i.e. tailscale)
|
||||
|
||||
`sudo ufw allow from 172.16.0.0/16 to 100.64.0.0/10`
|
||||
|
||||
You can also list CIDRs of all docker bridge networks by:
|
||||
|
||||
`docker network inspect $(docker network ls | awk '$3 == "bridge" { print $1}') | jq -r '.[] | .Name + " " + .IPAM.Config[0].Subnet' -`
|
||||
|
||||
6. start your docker app, and visit <container_name>.y.z
|
||||
|
||||
7. check the logs with `docker compose logs` or `make logs` to see if there is any error, check panel at [panel port] for active proxies
|
||||
- Binary: `go-proxy reload`
|
||||
- Docker: `docker exec -it go-proxy /app/go-proxy reload`
|
||||
|
||||
## Use JSON Schema in VSCode
|
||||
|
||||
@@ -116,22 +88,22 @@ Modify `.vscode/settings.json` to fit your needs
|
||||
|
||||
```json
|
||||
{
|
||||
"yaml.schemas": {
|
||||
"https://github.com/yusing/go-proxy/raw/main/schema/config.schema.json": [
|
||||
"config.example.yml",
|
||||
"config.yml"
|
||||
],
|
||||
"https://github.com/yusing/go-proxy/raw/main/schema/providers.schema.json": [
|
||||
"providers.example.yml",
|
||||
"*.providers.yml",
|
||||
]
|
||||
}
|
||||
"yaml.schemas": {
|
||||
"https://github.com/yusing/go-proxy/raw/main/schema/config.schema.json": [
|
||||
"config.example.yml",
|
||||
"config.yml"
|
||||
],
|
||||
"https://github.com/yusing/go-proxy/raw/main/schema/providers.schema.json": [
|
||||
"providers.example.yml",
|
||||
"*.providers.yml"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
With container name, no label needs to be added *(most of the time)*.
|
||||
With container name, no label needs to be added _(most of the time)_.
|
||||
|
||||
### Labels (docker)
|
||||
|
||||
@@ -143,7 +115,7 @@ See [compose.example.yml](compose.example.yml) for more
|
||||
|
||||
- `proxy.*.<field>`: wildcard label for all aliases
|
||||
|
||||
Below labels has a **`proxy.<alias>`** prefix (i.e. `proxy.nginx.scheme: http`)
|
||||
Below labels has a **`proxy.<alias>.`** prefix (i.e. `proxy.nginx.scheme: http`)
|
||||
|
||||
- `scheme`: proxy protocol
|
||||
- default: `http`
|
||||
@@ -179,7 +151,6 @@ Below labels has a **`proxy.<alias>`** prefix (i.e. `proxy.nginx.scheme: http`)
|
||||
### Environment variables
|
||||
|
||||
- `GOPROXY_DEBUG`: set to `1` or `true` to enable debug behaviors (i.e. output, etc.)
|
||||
- `GOPROXY_REDIRECT_HTTP`: set to `0` or `false` to disable http to https redirect (only when certs are located)
|
||||
|
||||
### Config File
|
||||
|
||||
@@ -204,7 +175,7 @@ See [config.example.yml](config.example.yml) for more
|
||||
|
||||
values:
|
||||
|
||||
- `FROM_ENV`: value from environment
|
||||
- `FROM_ENV`: value from environment (`DOCKER_HOST`)
|
||||
- full url to docker host (i.e. `tcp://host:2375`)
|
||||
|
||||
- `file`: load reverse proxies from provider file
|
||||
@@ -225,48 +196,12 @@ See [providers.example.yml](providers.example.yml) for examples
|
||||
|
||||
Follow [this guide](https://cloudkul.com/blog/automcatic-renew-and-generate-ssl-on-your-website-using-lego-client/) to create a new token with `Zone.DNS` read and edit permissions
|
||||
|
||||
To add more provider support (**CloudDNS** as an example):
|
||||
|
||||
1. Fork this repo, modify [autocert.go](src/go-proxy/autocert.go#L305)
|
||||
|
||||
```go
|
||||
var providersGenMap = map[string]ProviderGenerator{
|
||||
"cloudflare": providerGenerator(cloudflare.NewDefaultConfig, cloudflare.NewDNSProviderConfig),
|
||||
// add here, i.e.
|
||||
"clouddns": providerGenerator(clouddns.NewDefaultConfig, clouddns.NewDNSProviderConfig),
|
||||
}
|
||||
```
|
||||
|
||||
2. Go to [https://go-acme.github.io/lego/dns/clouddns](https://go-acme.github.io/lego/dns/clouddns/) and check for required config
|
||||
|
||||
3. Build `go-proxy` with `make build`
|
||||
|
||||
4. Set required config in `config.yml` `autocert` -> `options` section
|
||||
|
||||
```shell
|
||||
# From https://go-acme.github.io/lego/dns/clouddns/
|
||||
CLOUDDNS_CLIENT_ID=bLsdFAks23429841238feb177a572aX \
|
||||
CLOUDDNS_EMAIL=you@example.com \
|
||||
CLOUDDNS_PASSWORD=b9841238feb177a84330f \
|
||||
lego --email you@example.com --dns clouddns --domains my.example.org run
|
||||
```
|
||||
|
||||
Should turn into:
|
||||
|
||||
```yaml
|
||||
autocert:
|
||||
...
|
||||
options:
|
||||
client_id: bLsdFAks23429841238feb177a572aX
|
||||
email: you@example.com
|
||||
password: b9841238feb177a84330f
|
||||
```
|
||||
|
||||
5. Run and test if it works
|
||||
6. Commit and create pull request
|
||||
To add more provider support, see [this](docs/add_dns_provider.md)
|
||||
|
||||
## Examples
|
||||
|
||||
See [docker.md](docs/docker.md#docker-compose-example) for complete examples
|
||||
|
||||
### Single port configuration example
|
||||
|
||||
```yaml
|
||||
@@ -323,7 +258,7 @@ go-proxy:
|
||||
ports:
|
||||
- 80:80
|
||||
...
|
||||
- 20000:20000/tcp
|
||||
- <your desired port>:20000/tcp
|
||||
# or 20000-20010:20000-20010/tcp to declare large range at once
|
||||
|
||||
# access app-db via <*>.y.z:20000
|
||||
@@ -454,7 +389,7 @@ Local benchmark (client running wrk and `go-proxy` server are under same proxmox
|
||||
|
||||
## Known issues
|
||||
|
||||
None
|
||||
UDP proxy does not work for PalWorld Dedicated Server
|
||||
|
||||
## Memory usage
|
||||
|
||||
@@ -471,6 +406,3 @@ It takes ~15 MB for 50 proxy entries
|
||||
4. build binary with `make build`
|
||||
|
||||
5. start your container with `make up` (docker) or `bin/go-proxy` (binary)
|
||||
|
||||
[panel_port_http]: 8080
|
||||
[panel_port_https]: 8443
|
||||
|
||||
BIN
bin/go-proxy
BIN
bin/go-proxy
Binary file not shown.
@@ -1,41 +1,40 @@
|
||||
version: '3'
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
image: ghcr.io/yusing/go-proxy:latest
|
||||
container_name: go-proxy
|
||||
restart: always
|
||||
networks: # ^also add here
|
||||
- default
|
||||
# environment:
|
||||
# - GOPROXY_DEBUG=1 # (optional, enable only for debug)
|
||||
# - GOPROXY_REDIRECT_HTTP=0 # (optional, uncomment to disable http redirect (http -> https))
|
||||
ports:
|
||||
- 80:80 # http
|
||||
# - 443:443 # optional, https
|
||||
- 80:80 # http proxy
|
||||
- 8080:8080 # http panel
|
||||
# - 443:443 # optional, https proxy
|
||||
# - 8443:8443 # optional, https panel
|
||||
|
||||
# optional, if you declared any tcp/udp proxy, set a range you want to use
|
||||
# - 20000:20100/tcp
|
||||
# - 20000:20100/udp
|
||||
volumes:
|
||||
- ./config:/app/config
|
||||
|
||||
# if local docker provider is used
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
|
||||
# use existing certificate
|
||||
# - /path/to/cert.pem:/app/certs/cert.crt:ro
|
||||
# - /path/to/privkey.pem:/app/certs/priv.key:ro
|
||||
|
||||
# use autocert feature
|
||||
# store autocert obtained cert
|
||||
# - ./certs:/app/certs
|
||||
|
||||
# workaround for "lookup: no such host"
|
||||
# dns:
|
||||
# - 127.0.0.1
|
||||
|
||||
# if local docker provider is used (by default)
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
|
||||
# to use custom config and providers
|
||||
# - ./config:/app/config
|
||||
dns:
|
||||
- 127.0.0.1 # workaround for "lookup: no such host"
|
||||
extra_hosts:
|
||||
# required if you use local docker provider and have containers in `host` network_mode
|
||||
- host.docker.internal:host-gateway
|
||||
# if you have container running in "host" network mode
|
||||
# extra_hosts:
|
||||
# - host.docker.internal:host-gateway
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
# uncomment to use autocert
|
||||
autocert: # (optional, if you need autocert feature)
|
||||
email: "user@domain.com" # (required) email for acme certificate
|
||||
domains: # (required)
|
||||
- "*.y.z" # domain for acme certificate, use wild card to allow all subdomains
|
||||
provider: cloudflare # (required) dns challenge provider (string)
|
||||
options: # provider specific options
|
||||
auth_token: "YOUR_ZONE_API_TOKEN"
|
||||
# Autocert (uncomment to enable)
|
||||
# autocert: # (optional, if you need autocert feature)
|
||||
# email: "user@domain.com" # (required) email for acme certificate
|
||||
# domains: # (required)
|
||||
# - "*.y.z" # domain for acme certificate, use wild card to allow all subdomains
|
||||
# provider: cloudflare # (required) dns challenge provider (string)
|
||||
# options: # provider specific options
|
||||
# auth_token: "YOUR_ZONE_API_TOKEN"
|
||||
providers:
|
||||
local:
|
||||
kind: docker
|
||||
# for value format, see https://docs.docker.com/reference/cli/dockerd/
|
||||
# i.e. FROM_ENV, ssh://user@10.0.1.1:22, tcp://10.0.2.1:2375
|
||||
value: FROM_ENV
|
||||
# remote1:
|
||||
# kind: docker
|
||||
# value: ssh://user@10.0.1.1
|
||||
# remote2:
|
||||
# kind: docker
|
||||
# value: tcp://10.0.1.1:2375
|
||||
# provider1:
|
||||
# kind: file
|
||||
# value: provider1.yml
|
||||
# provider2:
|
||||
# kind: file
|
||||
# value: provider2.yml
|
||||
providers:
|
||||
kind: file
|
||||
value: providers.yml
|
||||
|
||||
# Fixed options (optional, non hot-reloadable)
|
||||
# timeout_shutdown: 5
|
||||
# redirect_to_https: false
|
||||
41
docs/add_dns_provider.md
Normal file
41
docs/add_dns_provider.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Adding provider support
|
||||
|
||||
## **CloudDNS** as an example
|
||||
|
||||
1. Fork this repo, modify [autocert.go](../src/go-proxy/autocert.go#L305)
|
||||
|
||||
```go
|
||||
var providersGenMap = map[string]ProviderGenerator{
|
||||
"cloudflare": providerGenerator(cloudflare.NewDefaultConfig, cloudflare.NewDNSProviderConfig),
|
||||
// add here, i.e.
|
||||
"clouddns": providerGenerator(clouddns.NewDefaultConfig, clouddns.NewDNSProviderConfig),
|
||||
}
|
||||
```
|
||||
|
||||
2. Go to [https://go-acme.github.io/lego/dns/clouddns](https://go-acme.github.io/lego/dns/clouddns/) and check for required config
|
||||
|
||||
3. Build `go-proxy` with `make build`
|
||||
|
||||
4. Set required config in `config.yml` `autocert` -> `options` section
|
||||
|
||||
```shell
|
||||
# From https://go-acme.github.io/lego/dns/clouddns/
|
||||
CLOUDDNS_CLIENT_ID=bLsdFAks23429841238feb177a572aX \
|
||||
CLOUDDNS_EMAIL=you@example.com \
|
||||
CLOUDDNS_PASSWORD=b9841238feb177a84330f \
|
||||
lego --email you@example.com --dns clouddns --domains my.example.org run
|
||||
```
|
||||
|
||||
Should turn into:
|
||||
|
||||
```yaml
|
||||
autocert:
|
||||
...
|
||||
options:
|
||||
client_id: bLsdFAks23429841238feb177a572aX
|
||||
email: you@example.com
|
||||
password: b9841238feb177a84330f
|
||||
```
|
||||
|
||||
5. Run and test if it works
|
||||
6. Commit and create pull request
|
||||
59
docs/binary.md
Normal file
59
docs/binary.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Getting started with `go-proxy` (binary)
|
||||
|
||||
## Setup
|
||||
|
||||
1. Install `bash`, `make` and `wget` if not already
|
||||
|
||||
2. Run setup script
|
||||
|
||||
To specitfy a version _(optional)_
|
||||
|
||||
```shell
|
||||
export VERSION=latest # will be resolved into real version number
|
||||
export VERSION=<version>
|
||||
```
|
||||
|
||||
If you don't need web config editor
|
||||
|
||||
```shell
|
||||
export SETUP_CODEMIRROR=0
|
||||
```
|
||||
|
||||
Setup
|
||||
|
||||
```shell
|
||||
wget -qO- https://6uo.me/go-proxy-setup-binary | sudo bash
|
||||
```
|
||||
|
||||
What it does:
|
||||
|
||||
- Download source file and binary into /opt/go-proxy/$VERSION
|
||||
- Setup `config.yml` and `providers.yml`
|
||||
- Setup `template/codemirror` which is a dependency for web config editor
|
||||
- Create a systemd service (if available) in `/etc/systemd/system/go-proxy.service`
|
||||
- Enable and start `go-proxy` service
|
||||
|
||||
3. Start editing config files in `http://<ip>:8080`
|
||||
|
||||
4. Check logs / status with `systemctl status go-proxy`
|
||||
|
||||
## Setup (alternative method)
|
||||
|
||||
1. Download the latest release and extract somewhere
|
||||
|
||||
2. Run `make setup` and _(optional) `make setup-codemirror`_
|
||||
|
||||
3. Enable HTTPS _(optional)_
|
||||
|
||||
- To use autocert feature
|
||||
|
||||
complete `autocert` in `config/config.yml`
|
||||
|
||||
- To use existing certificate
|
||||
|
||||
Prepare your wildcard (`*.y.z`) SSL cert in `certs/`
|
||||
|
||||
- cert / chain / fullchain: `certs/cert.crt`
|
||||
- private key: `certs/priv.key`
|
||||
|
||||
4. Run the binary `bin/go-proxy`
|
||||
124
docs/docker.md
Normal file
124
docs/docker.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# Getting started with `go-proxy` docker container
|
||||
|
||||
## Setup
|
||||
|
||||
1. Install `wget` if not already
|
||||
|
||||
2. Run setup script
|
||||
|
||||
`bash <(wget -qO- https://6uo.me/go-proxy-setup-docker)`
|
||||
|
||||
What it does:
|
||||
|
||||
- Create required directories
|
||||
- Setup `config.yml` and `compose.yml`
|
||||
|
||||
3. Verify folder structure and then `cd go-proxy`
|
||||
|
||||
```plain
|
||||
go-proxy
|
||||
├── certs
|
||||
├── compose.yml
|
||||
└── config
|
||||
├── config.yml
|
||||
└── providers.yml
|
||||
```
|
||||
|
||||
4. Enable HTTPs _(optional)_
|
||||
- To use autocert feature
|
||||
- completing `autocert` section in `config/config.yml`
|
||||
- mount `certs/` to `/app/certs` to store obtained certs
|
||||
|
||||
- To use existing certificate
|
||||
|
||||
mount your wildcard (`*.y.z`) SSL cert
|
||||
|
||||
- cert / chain / fullchain -> `/app/certs/cert.crt`
|
||||
- private key -> `/app/certs/priv.key`
|
||||
|
||||
5. Modify `compose.yml` fit your needs
|
||||
|
||||
Add networks to make sure it is in the same network with other containers, or make sure `proxy.<alias>.host` is reachable
|
||||
|
||||
6. Run `docker compose up -d` to start the container
|
||||
|
||||
7. Start editing config files in `http://<ip>:8080`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Firewall issues
|
||||
|
||||
If you are using `ufw` with vpn that drop all inbound traffic except vpn, run below:
|
||||
|
||||
`sudo ufw allow from 172.16.0.0/16 to 100.64.0.0/10`
|
||||
|
||||
Explaination:
|
||||
|
||||
Docker network is usually `172.16.0.0/16`
|
||||
|
||||
Tailscale is used as an example, `100.64.0.0/10` will be the CIDR
|
||||
|
||||
You can also list CIDRs of all docker bridge networks by:
|
||||
|
||||
`docker network inspect $(docker network ls | awk '$3 == "bridge" { print $1}') | jq -r '.[] | .Name + " " + .IPAM.Config[0].Subnet' -`
|
||||
|
||||
## Docker compose example
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
adg-work:
|
||||
adg-conf:
|
||||
mc-data:
|
||||
services:
|
||||
adg:
|
||||
image: adguard/adguardhome
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- proxy.aliases=adg,adg-dns,adg-setup
|
||||
- proxy.adg.port=80
|
||||
- proxy.adg-setup.port=3000
|
||||
- proxy.adg-dns.scheme=udp
|
||||
- proxy.adg-dns.port=20000:dns
|
||||
volumes:
|
||||
- adg-work:/opt/adguardhome/work
|
||||
- adg-conf:/opt/adguardhome/conf
|
||||
mc:
|
||||
image: itzg/minecraft-server
|
||||
tty: true
|
||||
stdin_open: true
|
||||
container_name: mc
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- proxy.mc.scheme=tcp
|
||||
- proxy.mc.port=20001:25565
|
||||
environment:
|
||||
EULA: "TRUE"
|
||||
volumes:
|
||||
- mc-data:/data
|
||||
go-proxy:
|
||||
image: ghcr.io/yusing/go-proxy
|
||||
container_name: go-proxy
|
||||
restart: always
|
||||
ports:
|
||||
- 80:80 # http
|
||||
- 443:443 # optional, https
|
||||
- 8080:8080 # http panel
|
||||
- 8443:8443 # optional, https panel
|
||||
|
||||
- 53:20000/udp # adguardhome
|
||||
- 25565:20001/tcp # minecraft
|
||||
volumes:
|
||||
- ./config:/app/config
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
labels:
|
||||
- proxy.aliases=gp
|
||||
- proxy.panel.port=8080
|
||||
```
|
||||
|
||||
### Services URLs
|
||||
|
||||
- `gp.yourdomain.com`: go-proxy web panel
|
||||
- `adg-setup.yourdomain.com`: adguard setup (first time running)
|
||||
- `adg.yourdomain.com`: adguard dashboard
|
||||
- `yourdomain.com:53`: adguard dns
|
||||
- `yourdomain.com:25565`: minecraft server
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
if [ "$1" == "restart" ]; then
|
||||
echo "restarting"
|
||||
killall go-proxy
|
||||
fi
|
||||
if [ "$GOPROXY_DEBUG" == "1" ]; then
|
||||
/app/go-proxy 2> log/go-proxy.log &
|
||||
tail -f /dev/null
|
||||
else
|
||||
/app/go-proxy
|
||||
fi
|
||||
9
go.mod
9
go.mod
@@ -14,6 +14,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.92.0 // indirect
|
||||
@@ -27,7 +28,9 @@ require (
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
||||
github.com/miekg/dns v1.1.58 // indirect
|
||||
@@ -39,15 +42,21 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.1.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
|
||||
google.golang.org/grpc v1.61.1 // indirect
|
||||
google.golang.org/protobuf v1.32.0 // indirect
|
||||
gotest.tools/v3 v3.5.1 // indirect
|
||||
)
|
||||
|
||||
29
go.sum
29
go.sum
@@ -1,17 +1,9 @@
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cloudflare/cloudflare-go v0.86.0 h1:jEKN5VHNYNYtfDL2lUFLTRo+nOVNPFxpXTstVx0rqHI=
|
||||
github.com/cloudflare/cloudflare-go v0.86.0/go.mod h1:wYW/5UP02TUfBToa/yKbQHV+r6h1NnJ1Je7XjuGM4Jw=
|
||||
github.com/cloudflare/cloudflare-go v0.91.0 h1:L7IR+86qrZuEMSjGFg4cwRwtHqC8uCPmMUkP7BD4CPw=
|
||||
github.com/cloudflare/cloudflare-go v0.91.0/go.mod h1:nUqvBUUDRxNzsDSQjbqUNWHEIYAoUlgRmcAzMKlFdKs=
|
||||
github.com/cloudflare/cloudflare-go v0.92.0 h1:ltJvGvqZ4G6Fm2hHOYZ5RWpJQcrM0oDrsjjZydZhFJQ=
|
||||
github.com/cloudflare/cloudflare-go v0.92.0/go.mod h1:nUqvBUUDRxNzsDSQjbqUNWHEIYAoUlgRmcAzMKlFdKs=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
@@ -19,8 +11,6 @@ github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
||||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
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/docker/cli v26.0.0+incompatible h1:90BKrx1a1HKYpSnnBFR6AgDq/FqkHxwlUyzJVPxD30I=
|
||||
@@ -50,9 +40,11 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
@@ -68,7 +60,6 @@ github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -89,7 +80,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -98,15 +88,13 @@ github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XF
|
||||
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||
github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis=
|
||||
github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
@@ -132,8 +120,6 @@ golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
@@ -147,11 +133,10 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
@@ -165,8 +150,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -180,6 +163,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU=
|
||||
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
|
||||
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -117,7 +117,17 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"timeout_shutdown": {
|
||||
"title": "Shutdown timeout (in seconds)",
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"redirect_to_https": {
|
||||
"title": "Redirect to HTTPS",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
"additionalProperties": false,
|
||||
"required": ["providers"]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"title": "go-proxy providers file",
|
||||
"anyOf": [
|
||||
{
|
||||
"type":"object"
|
||||
},
|
||||
{
|
||||
"type":"null"
|
||||
}
|
||||
],
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9_-]+$": {
|
||||
"title": "Proxy entry",
|
||||
|
||||
114
setup-binary.sh
Normal file
114
setup-binary.sh
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/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="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"
|
||||
14
setup-docker.sh
Normal file
14
setup-docker.sh
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
if [ -z "$BRANCH" ]; then
|
||||
BRANCH="main"
|
||||
fi
|
||||
BASE_URL="https://github.com/yusing/go-proxy/raw/${BRANCH}"
|
||||
mkdir -p go-proxy
|
||||
cd go-proxy
|
||||
mkdir -p config
|
||||
mkdir -p certs
|
||||
[ -f compose.yml ] || wget -cO - ${BASE_URL}/compose.example.yml > compose.yml
|
||||
[ -f config/config.yml ] || wget -cO - ${BASE_URL}/config.example.yml > config/config.yml
|
||||
[ -f config/providers.yml ] || touch config/providers.yml
|
||||
38
src/go-proxy/args.go
Normal file
38
src/go-proxy/args.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Args struct {
|
||||
Command string
|
||||
}
|
||||
|
||||
const (
|
||||
CommandStart = ""
|
||||
CommandValidate = "validate"
|
||||
CommandReload = "reload"
|
||||
)
|
||||
|
||||
var ValidCommands = []string{CommandStart, CommandValidate, CommandReload}
|
||||
|
||||
func getArgs() Args {
|
||||
var args Args
|
||||
flag.Parse()
|
||||
args.Command = flag.Arg(0)
|
||||
if err := validateArgs(args.Command, ValidCommands); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func validateArgs[T comparable](arg T, validArgs []T) error {
|
||||
for _, v := range validArgs {
|
||||
if arg == v {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return NewNestedError("invalid argument").Subjectf("%v", arg)
|
||||
}
|
||||
@@ -21,9 +21,9 @@ import (
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
)
|
||||
|
||||
type ProviderOptions = map[string]string
|
||||
type ProviderGenerator = func(ProviderOptions) (challenge.Provider, error)
|
||||
type CertExpiries = map[string]time.Time
|
||||
type ProviderOptions map[string]string
|
||||
type ProviderGenerator func(ProviderOptions) (challenge.Provider, error)
|
||||
type CertExpiries map[string]time.Time
|
||||
|
||||
type AutoCertConfig struct {
|
||||
Email string `json:"email"`
|
||||
|
||||
@@ -3,12 +3,14 @@ package main
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// commented out if unused
|
||||
type Config interface {
|
||||
Value() configModel
|
||||
// Load() error
|
||||
MustLoad()
|
||||
GetAutoCertProvider() (AutoCertProvider, error)
|
||||
@@ -21,7 +23,9 @@ type Config interface {
|
||||
}
|
||||
|
||||
func NewConfig(path string) Config {
|
||||
cfg := &config{reader: &FileReader{Path: path}}
|
||||
cfg := &config{
|
||||
reader: &FileReader{Path: path},
|
||||
}
|
||||
cfg.watcher = NewFileWatcher(
|
||||
path,
|
||||
cfg.MustReload, // OnChange
|
||||
@@ -35,6 +39,10 @@ func ValidateConfig(data []byte) error {
|
||||
return cfg.Load()
|
||||
}
|
||||
|
||||
func (cfg *config) Value() configModel {
|
||||
return *cfg.m
|
||||
}
|
||||
|
||||
func (cfg *config) Load(reader ...Reader) error {
|
||||
cfg.mutex.Lock()
|
||||
defer cfg.mutex.Unlock()
|
||||
@@ -48,7 +56,7 @@ func (cfg *config) Load(reader ...Reader) error {
|
||||
return NewNestedError("unable to read config file").With(err)
|
||||
}
|
||||
|
||||
model := &configModel{}
|
||||
model := defaultConfig()
|
||||
if err := yaml.Unmarshal(data, model); err != nil {
|
||||
return NewNestedError("unable to parse config file").With(err)
|
||||
}
|
||||
@@ -170,12 +178,21 @@ func (cfg *config) StopWatching() {
|
||||
}
|
||||
|
||||
type configModel struct {
|
||||
Providers map[string]*Provider `yaml:",flow" json:"providers"`
|
||||
AutoCert AutoCertConfig `yaml:",flow" json:"autocert"`
|
||||
Providers map[string]*Provider `yaml:",flow" json:"providers"`
|
||||
AutoCert AutoCertConfig `yaml:",flow" json:"autocert"`
|
||||
TimeoutShutdown time.Duration `yaml:"timeout_shutdown" json:"timeout_shutdown"`
|
||||
RedirectToHTTPS bool `yaml:"redirect_to_https" json:"redirect_to_https"`
|
||||
}
|
||||
|
||||
func defaultConfig() *configModel {
|
||||
return &configModel{
|
||||
TimeoutShutdown: 3 * time.Second,
|
||||
RedirectToHTTPS: false,
|
||||
}
|
||||
}
|
||||
|
||||
type config struct {
|
||||
m *configModel
|
||||
m *configModel
|
||||
|
||||
reader Reader
|
||||
watcher Watcher
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ImageNamePortMap = map[string]string{
|
||||
ImageNamePortMapTCP = map[string]string{
|
||||
"postgres": "5432",
|
||||
"mysql": "3306",
|
||||
"mariadb": "3306",
|
||||
@@ -22,7 +22,7 @@ var (
|
||||
"rabbitmq": "5672",
|
||||
"mongo": "27017",
|
||||
}
|
||||
ExtraNamePortMap = map[string]string{
|
||||
ExtraNamePortMapTCP = map[string]string{
|
||||
"dns": "53",
|
||||
"ssh": "22",
|
||||
"ftp": "21",
|
||||
@@ -30,18 +30,44 @@ var (
|
||||
"pop3": "110",
|
||||
"imap": "143",
|
||||
}
|
||||
NamePortMap = func() map[string]string {
|
||||
NamePortMapTCP = func() map[string]string {
|
||||
m := make(map[string]string)
|
||||
for k, v := range ImageNamePortMap {
|
||||
for k, v := range ImageNamePortMapTCP {
|
||||
m[k] = v
|
||||
}
|
||||
for k, v := range ExtraNamePortMap {
|
||||
for k, v := range ExtraNamePortMapTCP {
|
||||
m[k] = v
|
||||
}
|
||||
return m
|
||||
}()
|
||||
)
|
||||
|
||||
var ImageNamePortMapHTTP = map[string]uint16{
|
||||
"nginx": 80,
|
||||
"httpd": 80,
|
||||
"adguardhome": 3000,
|
||||
"gogs": 3000,
|
||||
"gitea": 3000,
|
||||
"portainer": 9000,
|
||||
"portainer-ce": 9000,
|
||||
"home-assistant": 8123,
|
||||
"homebridge": 8581,
|
||||
"uptime-kuma": 3001,
|
||||
"changedetection.io": 3000,
|
||||
"prometheus": 9090,
|
||||
"grafana": 3000,
|
||||
"dockge": 5001,
|
||||
"nginx-proxy-manager": 81,
|
||||
}
|
||||
|
||||
var wellKnownHTTPPorts = map[uint16]bool{
|
||||
80: true,
|
||||
8000: true,
|
||||
8008: true,
|
||||
8080: true,
|
||||
3000: true,
|
||||
}
|
||||
|
||||
var (
|
||||
StreamSchemes = []string{StreamType_TCP, StreamType_UDP} // TODO: support "tcp:udp", "udp:tcp"
|
||||
HTTPSchemes = []string{"http", "https"}
|
||||
@@ -92,6 +118,7 @@ var (
|
||||
Timeout: 5 * time.Second,
|
||||
KeepAlive: 5 * time.Second,
|
||||
}).DialContext,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -149,4 +176,7 @@ var logLevel = func() logrus.Level {
|
||||
return logrus.GetLevel()
|
||||
}()
|
||||
|
||||
var redirectToHTTPS = os.Getenv("GOPROXY_REDIRECT_HTTP") != "0" && os.Getenv("GOPROXY_REDIRECT_HTTP") != "false"
|
||||
var isRunningAsService = func() bool {
|
||||
v := os.Getenv("IS_SYSTEMD")
|
||||
return v == "1"
|
||||
}()
|
||||
@@ -23,7 +23,7 @@ func (p *Provider) setConfigField(c *ProxyConfig, label string, value string, pr
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provider) getContainerProxyConfigs(container types.Container, clientIP string) ProxyConfigSlice {
|
||||
func (p *Provider) getContainerProxyConfigs(container *types.Container, clientIP string) ProxyConfigSlice {
|
||||
var aliases []string
|
||||
|
||||
cfgs := make(ProxyConfigSlice, 0)
|
||||
@@ -61,10 +61,10 @@ func (p *Provider) getContainerProxyConfigs(container types.Container, clientIP
|
||||
}
|
||||
}
|
||||
if config.Port == "" {
|
||||
config.Port = fmt.Sprintf("%d", selectPort(container))
|
||||
config.Port = fmt.Sprintf("%d", selectPort(container, isRemote))
|
||||
}
|
||||
if config.Port == "0" {
|
||||
l.Debugf("no ports exposed, ignored")
|
||||
l.Infof("no ports exposed, ignored")
|
||||
continue
|
||||
}
|
||||
if config.Scheme == "" {
|
||||
@@ -74,10 +74,8 @@ func (p *Provider) getContainerProxyConfigs(container types.Container, clientIP
|
||||
case strings.HasPrefix(container.Image, "sha256:"):
|
||||
config.Scheme = "http"
|
||||
default:
|
||||
imageSplit := strings.Split(container.Image, "/")
|
||||
imageSplit = strings.Split(imageSplit[len(imageSplit)-1], ":")
|
||||
imageName := imageSplit[0]
|
||||
_, isKnownImage := ImageNamePortMap[imageName]
|
||||
imageName := getImageName(container)
|
||||
_, isKnownImage := ImageNamePortMapTCP[imageName]
|
||||
if isKnownImage {
|
||||
config.Scheme = "tcp"
|
||||
} else {
|
||||
@@ -182,25 +180,46 @@ func (p *Provider) getDockerProxyConfigs() (ProxyConfigSlice, error) {
|
||||
cfgs := make(ProxyConfigSlice, 0)
|
||||
|
||||
for _, container := range containerSlice {
|
||||
cfgs = append(cfgs, p.getContainerProxyConfigs(container, clientIP)...)
|
||||
cfgs = append(cfgs, p.getContainerProxyConfigs(&container, clientIP)...)
|
||||
}
|
||||
|
||||
return cfgs, nil
|
||||
}
|
||||
|
||||
// var dockerUrlRegex = regexp.MustCompile(`^(?P<scheme>\w+)://(?P<host>[^:]+)(?P<port>:\d+)?(?P<path>/.*)?$`)
|
||||
func getImageName(c *types.Container) string {
|
||||
imageSplit := strings.Split(c.Image, "/")
|
||||
imageSplit = strings.Split(imageSplit[len(imageSplit)-1], ":")
|
||||
return imageSplit[0]
|
||||
}
|
||||
|
||||
func getPublicPort(p types.Port) uint16 { return p.PublicPort }
|
||||
func getPrivatePort(p types.Port) uint16 { return p.PrivatePort }
|
||||
|
||||
func selectPort(c types.Container) uint16 {
|
||||
if c.HostConfig.NetworkMode == "host" {
|
||||
return selectPortInternal(c, getPrivatePort)
|
||||
func selectPort(c *types.Container, isRemote bool) uint16 {
|
||||
if isRemote || c.HostConfig.NetworkMode == "host" {
|
||||
return selectPortInternal(c, getPublicPort)
|
||||
}
|
||||
return selectPortInternal(c, getPublicPort)
|
||||
return selectPortInternal(c, getPrivatePort)
|
||||
}
|
||||
|
||||
func selectPortInternal(c types.Container, getPort func(types.Port) uint16) uint16 {
|
||||
func selectPortInternal(c *types.Container, getPort func(types.Port) uint16) uint16 {
|
||||
imageName := getImageName(c)
|
||||
// if is known image -> use known port
|
||||
if port, isKnown := ImageNamePortMapHTTP[imageName]; isKnown {
|
||||
for _, p := range c.Ports {
|
||||
if p.PrivatePort == port {
|
||||
return getPort(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
// if it has known http port -> use it
|
||||
for _, p := range c.Ports {
|
||||
if isWellKnownHTTPPort(p.PrivatePort) {
|
||||
return getPort(p)
|
||||
}
|
||||
}
|
||||
// if it has any port -> use it
|
||||
for _, p := range c.Ports {
|
||||
if port := getPort(p); port != 0 {
|
||||
return port
|
||||
@@ -208,3 +227,8 @@ func selectPortInternal(c types.Container, getPort func(types.Port) uint16) uint
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func isWellKnownHTTPPort(port uint16) bool {
|
||||
_, ok := wellKnownHTTPPorts[port]
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package main
|
||||
|
||||
import "os"
|
||||
|
||||
type Reader interface {
|
||||
Read() ([]byte, error)
|
||||
}
|
||||
|
||||
type FileReader struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func (r *FileReader) Read() ([]byte, error) {
|
||||
return os.ReadFile(r.Path)
|
||||
}
|
||||
|
||||
type ByteReader struct {
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (r *ByteReader) Read() ([]byte, error) {
|
||||
return r.Data, nil
|
||||
}
|
||||
@@ -44,8 +44,8 @@ func NewHTTPRoute(config *ProxyConfig) (*HTTPRoute, error) {
|
||||
PathMode: config.PathMode,
|
||||
l: hrlog.WithFields(logrus.Fields{
|
||||
"alias": config.Alias,
|
||||
"path": config.Path,
|
||||
"path_mode": config.PathMode,
|
||||
// "path": config.Path,
|
||||
// "path_mode": config.PathMode,
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -157,6 +157,6 @@ func (config *ProxyConfig) pathSubModResp(r *http.Response) error {
|
||||
}
|
||||
|
||||
// alias -> (path -> routes)
|
||||
type HTTPRoutes = SafeMap[string, pathPoolMap]
|
||||
type HTTPRoutes SafeMap[string, pathPoolMap]
|
||||
|
||||
var httpRoutes HTTPRoutes = NewSafeMapOf[HTTPRoutes](newPathPoolMap)
|
||||
|
||||
@@ -2,13 +2,37 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type Reader interface {
|
||||
Read() ([]byte, error)
|
||||
}
|
||||
|
||||
type FileReader struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func (r *FileReader) Read() ([]byte, error) {
|
||||
return os.ReadFile(r.Path)
|
||||
}
|
||||
|
||||
type ByteReader struct {
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (r *ByteReader) Read() ([]byte, error) {
|
||||
return r.Data, nil
|
||||
}
|
||||
|
||||
type ReadCloser struct {
|
||||
ctx context.Context
|
||||
r io.ReadCloser
|
||||
ctx context.Context
|
||||
r io.ReadCloser
|
||||
closed atomic.Bool
|
||||
}
|
||||
|
||||
func (r *ReadCloser) Read(p []byte) (int, error) {
|
||||
@@ -21,13 +45,16 @@ func (r *ReadCloser) Read(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
func (r *ReadCloser) Close() error {
|
||||
if r.closed.Load() {
|
||||
return nil
|
||||
}
|
||||
r.closed.Store(true)
|
||||
return r.r.Close()
|
||||
}
|
||||
|
||||
type Pipe struct {
|
||||
r ReadCloser
|
||||
w io.WriteCloser
|
||||
wg sync.WaitGroup
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
@@ -35,32 +62,24 @@ type Pipe struct {
|
||||
func NewPipe(ctx context.Context, r io.ReadCloser, w io.WriteCloser) *Pipe {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return &Pipe{
|
||||
r: ReadCloser{ctx, r},
|
||||
r: ReadCloser{ctx: ctx, r: r},
|
||||
w: w,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pipe) Start() {
|
||||
p.wg.Add(1)
|
||||
go func() {
|
||||
Copy(p.ctx, p.w, &p.r)
|
||||
p.wg.Done()
|
||||
}()
|
||||
func (p *Pipe) Start() error {
|
||||
return Copy(p.ctx, p.w, &p.r)
|
||||
}
|
||||
|
||||
func (p *Pipe) Stop() {
|
||||
func (p *Pipe) Stop() error {
|
||||
p.cancel()
|
||||
p.wg.Wait()
|
||||
return errors.Join(fmt.Errorf("read: %w", p.r.Close()), fmt.Errorf("write: %w", p.w.Close()))
|
||||
}
|
||||
|
||||
func (p *Pipe) Close() (error, error) {
|
||||
return p.r.Close(), p.w.Close()
|
||||
}
|
||||
|
||||
func (p *Pipe) Wait() {
|
||||
p.wg.Wait()
|
||||
func (p *Pipe) Write(b []byte) (int, error) {
|
||||
return p.w.Write(b)
|
||||
}
|
||||
|
||||
type BidirectionalPipe struct {
|
||||
@@ -75,26 +94,34 @@ func NewBidirectionalPipe(ctx context.Context, rw1 io.ReadWriteCloser, rw2 io.Re
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BidirectionalPipe) Start() {
|
||||
p.pSrcDst.Start()
|
||||
p.pDstSrc.Start()
|
||||
func NewBidirectionalPipeIntermediate(ctx context.Context, listener io.ReadCloser, client io.ReadWriteCloser, target io.ReadWriteCloser) *BidirectionalPipe {
|
||||
return &BidirectionalPipe{
|
||||
pSrcDst: *NewPipe(ctx, listener, client),
|
||||
pDstSrc: *NewPipe(ctx, client, target),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BidirectionalPipe) Stop() {
|
||||
p.pSrcDst.Stop()
|
||||
p.pDstSrc.Stop()
|
||||
func (p *BidirectionalPipe) Start() error {
|
||||
errCh := make(chan error, 2)
|
||||
go func() {
|
||||
errCh <- p.pSrcDst.Start()
|
||||
}()
|
||||
go func() {
|
||||
errCh <- p.pDstSrc.Start()
|
||||
}()
|
||||
for err := range errCh {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *BidirectionalPipe) Close() (error, error) {
|
||||
return p.pSrcDst.Close()
|
||||
}
|
||||
|
||||
func (p *BidirectionalPipe) Wait() {
|
||||
p.pSrcDst.Wait()
|
||||
p.pDstSrc.Wait()
|
||||
func (p *BidirectionalPipe) Stop() error {
|
||||
return errors.Join(p.pSrcDst.Stop(), p.pDstSrc.Stop())
|
||||
}
|
||||
|
||||
func Copy(ctx context.Context, dst io.WriteCloser, src io.ReadCloser) error {
|
||||
_, err := io.Copy(dst, &ReadCloser{ctx, src})
|
||||
_, err := io.Copy(dst, &ReadCloser{ctx: ctx, r: src})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -16,21 +17,36 @@ var cfg Config
|
||||
func main() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
var verifyOnly bool
|
||||
flag.BoolVar(&verifyOnly, "verify", false, "verify config without starting server")
|
||||
flag.Parse()
|
||||
args := getArgs()
|
||||
|
||||
logrus.SetFormatter(&logrus.TextFormatter{
|
||||
ForceColors: true,
|
||||
DisableColors: false,
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "01-02 15:04:05",
|
||||
})
|
||||
if isRunningAsService {
|
||||
logrus.SetFormatter(&logrus.TextFormatter{
|
||||
DisableColors: true,
|
||||
DisableTimestamp: true,
|
||||
DisableSorting: true,
|
||||
})
|
||||
} else {
|
||||
logrus.SetFormatter(&logrus.TextFormatter{
|
||||
ForceColors: true,
|
||||
DisableColors: false,
|
||||
DisableSorting: true,
|
||||
FullTimestamp: true,
|
||||
TimestampFormat: "01-02 15:04:05",
|
||||
})
|
||||
}
|
||||
|
||||
if args.Command == CommandReload {
|
||||
err := utils.reloadServer()
|
||||
if err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
cfg = NewConfig(configPath)
|
||||
cfg.MustLoad()
|
||||
|
||||
if verifyOnly {
|
||||
if args.Command == CommandValidate {
|
||||
logrus.Printf("config OK")
|
||||
return
|
||||
}
|
||||
@@ -63,7 +79,7 @@ func main() {
|
||||
HTTPAddr: ":80",
|
||||
HTTPSAddr: ":443",
|
||||
Handler: http.HandlerFunc(proxyHandler),
|
||||
RedirectToHTTPS: redirectToHTTPS,
|
||||
RedirectToHTTPS: cfg.Value().RedirectToHTTPS,
|
||||
})
|
||||
panelServer = NewServer(ServerOptions{
|
||||
Name: "panel",
|
||||
@@ -71,7 +87,7 @@ func main() {
|
||||
HTTPAddr: ":8080",
|
||||
HTTPSAddr: ":8443",
|
||||
Handler: panelHandler,
|
||||
RedirectToHTTPS: redirectToHTTPS,
|
||||
RedirectToHTTPS: cfg.Value().RedirectToHTTPS,
|
||||
})
|
||||
|
||||
proxyServer.Start()
|
||||
@@ -88,10 +104,32 @@ func main() {
|
||||
signal.Notify(sig, syscall.SIGHUP)
|
||||
|
||||
<-sig
|
||||
// cfg.StopWatching()
|
||||
StopFSWatcher()
|
||||
StopDockerWatcher()
|
||||
cfg.StopProviders()
|
||||
panelServer.Stop()
|
||||
proxyServer.Stop()
|
||||
logrus.Info("shutting down")
|
||||
done := make(chan struct{}, 1)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(3)
|
||||
|
||||
go func() {
|
||||
StopFSWatcher()
|
||||
StopDockerWatcher()
|
||||
cfg.StopProviders()
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
panelServer.Stop()
|
||||
proxyServer.Stop()
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
logrus.Info("shutdown complete")
|
||||
case <-time.After(cfg.Value().TimeoutShutdown * time.Second):
|
||||
logrus.Info("timeout waiting for shutdown")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -68,7 +69,7 @@ func panelCheckTargetHealth(w http.ResponseWriter, r *http.Request) {
|
||||
func panelConfigEditor(w http.ResponseWriter, r *http.Request) {
|
||||
cfgFiles := make([]string, 0)
|
||||
cfgFiles = append(cfgFiles, path.Base(configPath))
|
||||
for _, p := range cfg.(*config).m.Providers {
|
||||
for _, p := range cfg.Value().Providers {
|
||||
if p.Kind != ProviderKind_File {
|
||||
continue
|
||||
}
|
||||
@@ -99,12 +100,20 @@ func panelConfigUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
panelHandleErr(w, r, err)
|
||||
return
|
||||
}
|
||||
err = os.WriteFile(path.Join(configBasePath, p), content, 0644)
|
||||
p = path.Join(configBasePath, p)
|
||||
_, err = os.Stat(p)
|
||||
exists := !errors.Is(err, os.ErrNotExist)
|
||||
err = os.WriteFile(p, content, 0644)
|
||||
if err != nil {
|
||||
panelHandleErr(w, r, NewNestedError("unable to write config file").With(err))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if !exists {
|
||||
w.Write([]byte(fmt.Sprintf("Config file %s created, remember to add it to config.yml!", p)))
|
||||
return
|
||||
}
|
||||
w.Write([]byte(fmt.Sprintf("Config file %s updated", p)))
|
||||
}
|
||||
|
||||
func panelServeFile(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -141,4 +150,4 @@ func panelHandleErr(w http.ResponseWriter, r *http.Request, err error, code ...i
|
||||
return
|
||||
}
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ type ProxyConfig struct {
|
||||
provider *Provider
|
||||
}
|
||||
|
||||
type ProxyConfigMap = map[string]ProxyConfig
|
||||
type ProxyConfigSlice = []ProxyConfig
|
||||
type ProxyConfigMap map[string]ProxyConfig
|
||||
type ProxyConfigSlice []ProxyConfig
|
||||
|
||||
func NewProxyConfig(provider *Provider) ProxyConfig {
|
||||
return ProxyConfig{
|
||||
|
||||
@@ -15,7 +15,6 @@ func NewRoute(cfg *ProxyConfig) (Route, error) {
|
||||
if err != nil {
|
||||
return nil, NewNestedErrorFrom(err).Subject(cfg.Alias)
|
||||
}
|
||||
streamRoutes.Set(id, route)
|
||||
return route, nil
|
||||
} else {
|
||||
httpRoutes.Ensure(cfg.Alias)
|
||||
@@ -47,6 +46,6 @@ func isStreamScheme(s string) bool {
|
||||
}
|
||||
|
||||
// id -> target
|
||||
type StreamRoutes = SafeMap[string, StreamRoute]
|
||||
type StreamRoutes SafeMap[string, StreamRoute]
|
||||
|
||||
var streamRoutes StreamRoutes = NewSafeMapOf[StreamRoutes]()
|
||||
|
||||
@@ -31,11 +31,11 @@ type ServerOptions struct {
|
||||
}
|
||||
|
||||
type LogrusWrapper struct {
|
||||
l *logrus.Entry
|
||||
*logrus.Entry
|
||||
}
|
||||
|
||||
func (l LogrusWrapper) Write(b []byte) (int, error) {
|
||||
return l.l.Logger.WriterLevel(logrus.ErrorLevel).Write(b)
|
||||
return l.Logger.WriterLevel(logrus.ErrorLevel).Write(b)
|
||||
}
|
||||
|
||||
func NewServer(opt ServerOptions) *Server {
|
||||
|
||||
@@ -45,10 +45,8 @@ type StreamRouteBase struct {
|
||||
|
||||
func newStreamRouteBase(config *ProxyConfig) (*StreamRouteBase, error) {
|
||||
var streamType string = StreamType_TCP
|
||||
var srcPort string
|
||||
var dstPort string
|
||||
var srcScheme string
|
||||
var dstScheme string
|
||||
var srcPort, dstPort string
|
||||
var srcScheme, dstScheme string
|
||||
|
||||
portSplit := strings.Split(config.Port, ":")
|
||||
if len(portSplit) != 2 {
|
||||
@@ -60,7 +58,7 @@ func newStreamRouteBase(config *ProxyConfig) (*StreamRouteBase, error) {
|
||||
dstPort = portSplit[1]
|
||||
}
|
||||
|
||||
if port, hasName := NamePortMap[dstPort]; hasName {
|
||||
if port, hasName := NamePortMapTCP[dstPort]; hasName {
|
||||
dstPort = port
|
||||
}
|
||||
|
||||
@@ -85,6 +83,10 @@ func newStreamRouteBase(config *ProxyConfig) (*StreamRouteBase, error) {
|
||||
dstScheme = config.Scheme
|
||||
}
|
||||
|
||||
if srcScheme != dstScheme {
|
||||
return nil, NewNestedError("unsupported").Subjectf("%v -> %v", srcScheme, dstScheme)
|
||||
}
|
||||
|
||||
return &StreamRouteBase{
|
||||
Alias: config.Alias,
|
||||
Type: streamType,
|
||||
@@ -101,21 +103,26 @@ func newStreamRouteBase(config *ProxyConfig) (*StreamRouteBase, error) {
|
||||
started: false,
|
||||
l: srlog.WithFields(logrus.Fields{
|
||||
"alias": config.Alias,
|
||||
"src": fmt.Sprintf("%s://:%d", srcScheme, srcPortInt),
|
||||
"dst": fmt.Sprintf("%s://%s:%d", dstScheme, config.Host, dstPortInt),
|
||||
// "src": fmt.Sprintf("%s://:%d", srcScheme, srcPortInt),
|
||||
// "dst": fmt.Sprintf("%s://%s:%d", dstScheme, config.Host, dstPortInt),
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewStreamRoute(config *ProxyConfig) (StreamRoute, error) {
|
||||
base, err := newStreamRouteBase(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch config.Scheme {
|
||||
case StreamType_TCP:
|
||||
return NewTCPRoute(config)
|
||||
base.StreamImpl = NewTCPRoute(base)
|
||||
case StreamType_UDP:
|
||||
return NewUDPRoute(config)
|
||||
base.StreamImpl = NewUDPRoute(base)
|
||||
default:
|
||||
return nil, NewNestedError("invalid stream type").Subject(config.Scheme)
|
||||
}
|
||||
return base, nil
|
||||
}
|
||||
|
||||
func (route *StreamRouteBase) ListeningUrl() string {
|
||||
@@ -136,6 +143,7 @@ func (route *StreamRouteBase) Start() {
|
||||
route.l.Errorf("failed to setup: %v", err)
|
||||
return
|
||||
}
|
||||
streamRoutes.Set(route.id, route)
|
||||
route.started = true
|
||||
route.wg.Add(2)
|
||||
go route.grAcceptConnections()
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -14,21 +15,16 @@ type Pipes []*BidirectionalPipe
|
||||
type TCPRoute struct {
|
||||
*StreamRouteBase
|
||||
listener net.Listener
|
||||
pipe Pipes
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewTCPRoute(config *ProxyConfig) (StreamRoute, error) {
|
||||
base, err := newStreamRouteBase(config)
|
||||
if err != nil {
|
||||
return nil, NewNestedErrorFrom(err).Subject(config.Alias)
|
||||
}
|
||||
if base.TargetScheme != StreamType_TCP {
|
||||
return nil, NewNestedError("unsupported").Subjectf("tcp -> %s", base.TargetScheme)
|
||||
}
|
||||
base.StreamImpl = &TCPRoute{
|
||||
func NewTCPRoute(base *StreamRouteBase) StreamImpl {
|
||||
return &TCPRoute{
|
||||
StreamRouteBase: base,
|
||||
listener: nil,
|
||||
pipe: make(Pipes, 0),
|
||||
}
|
||||
return base, nil
|
||||
}
|
||||
|
||||
func (route *TCPRoute) Setup() error {
|
||||
@@ -44,11 +40,10 @@ func (route *TCPRoute) Accept() (interface{}, error) {
|
||||
return route.listener.Accept()
|
||||
}
|
||||
|
||||
func (route *TCPRoute) HandleConnection(c interface{}) error {
|
||||
func (route *TCPRoute) Handle(c interface{}) error {
|
||||
clientConn := c.(net.Conn)
|
||||
|
||||
defer clientConn.Close()
|
||||
defer route.wg.Done()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), tcpDialTimeout)
|
||||
defer cancel()
|
||||
@@ -66,11 +61,12 @@ func (route *TCPRoute) HandleConnection(c interface{}) error {
|
||||
<-route.stopCh
|
||||
pipeCancel()
|
||||
}()
|
||||
|
||||
route.mu.Lock()
|
||||
pipe := NewBidirectionalPipe(pipeCtx, clientConn, serverConn)
|
||||
pipe.Start()
|
||||
pipe.Wait()
|
||||
pipe.Close()
|
||||
return nil
|
||||
route.pipe = append(route.pipe, pipe)
|
||||
route.mu.Unlock()
|
||||
return pipe.Start()
|
||||
}
|
||||
|
||||
func (route *TCPRoute) CloseListeners() {
|
||||
@@ -79,4 +75,9 @@ func (route *TCPRoute) CloseListeners() {
|
||||
}
|
||||
route.listener.Close()
|
||||
route.listener = nil
|
||||
for _, pipe := range route.pipe {
|
||||
if err := pipe.Stop(); err != nil {
|
||||
route.l.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type UDPRoute struct {
|
||||
*StreamRouteBase
|
||||
|
||||
connMap map[net.Addr]net.Conn
|
||||
connMap UDPConnMap
|
||||
connMapMutex sync.Mutex
|
||||
|
||||
listeningConn *net.UDPConn
|
||||
targetConn *net.UDPConn
|
||||
targetAddr *net.UDPAddr
|
||||
}
|
||||
|
||||
type UDPConn struct {
|
||||
remoteAddr net.Addr
|
||||
buffer []byte
|
||||
bytesReceived []byte
|
||||
nReceived int
|
||||
src *net.UDPConn
|
||||
dst *net.UDPConn
|
||||
*BidirectionalPipe
|
||||
}
|
||||
|
||||
func NewUDPRoute(config *ProxyConfig) (StreamRoute, error) {
|
||||
base, err := newStreamRouteBase(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
type UDPConnMap map[net.Addr]*UDPConn
|
||||
|
||||
if base.TargetScheme != StreamType_UDP {
|
||||
return nil, NewNestedError("unsupported").Subjectf("udp->%s", base.TargetScheme)
|
||||
}
|
||||
|
||||
base.StreamImpl = &UDPRoute{
|
||||
func NewUDPRoute(base *StreamRouteBase) StreamImpl {
|
||||
return &UDPRoute{
|
||||
StreamRouteBase: base,
|
||||
connMap: make(map[net.Addr]net.Conn),
|
||||
connMap: make(UDPConnMap),
|
||||
}
|
||||
return base, nil
|
||||
}
|
||||
|
||||
func (route *UDPRoute) Setup() error {
|
||||
source, err := net.ListenPacket(route.ListeningScheme, fmt.Sprintf(":%v", route.ListeningPort))
|
||||
laddr, err := net.ResolveUDPAddr(route.ListeningScheme, fmt.Sprintf(":%v", route.ListeningPort))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
target, err := net.Dial(route.TargetScheme, fmt.Sprintf("%s:%v", route.TargetHost, route.TargetPort))
|
||||
source, err := net.ListenUDP(route.ListeningScheme, laddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
raddr, err := net.ResolveUDPAddr(route.TargetScheme, fmt.Sprintf("%s:%v", route.TargetHost, route.TargetPort))
|
||||
if err != nil {
|
||||
source.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
route.listeningConn = source.(*net.UDPConn)
|
||||
route.targetConn = target.(*net.UDPConn)
|
||||
route.listeningConn = source
|
||||
route.targetAddr = raddr
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -74,71 +67,39 @@ func (route *UDPRoute) Accept() (interface{}, error) {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
|
||||
conn := &UDPConn{
|
||||
remoteAddr: srcAddr,
|
||||
buffer: buffer,
|
||||
bytesReceived: buffer[:nRead],
|
||||
nReceived: nRead,
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
conn, ok := route.connMap[srcAddr]
|
||||
|
||||
func (route *UDPRoute) HandleConnection(c interface{}) error {
|
||||
var err error
|
||||
|
||||
conn := c.(*UDPConn)
|
||||
srcConn, ok := route.connMap[conn.remoteAddr]
|
||||
if !ok {
|
||||
route.connMapMutex.Lock()
|
||||
srcConn, err = net.DialUDP("udp", nil, conn.remoteAddr.(*net.UDPAddr))
|
||||
srcConn, err := net.DialUDP("udp", nil, srcAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
route.connMap[conn.remoteAddr] = srcConn
|
||||
dstConn, err := net.DialUDP("udp", nil, route.targetAddr)
|
||||
if err != nil {
|
||||
srcConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
pipeCtx, pipeCancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
<-route.stopCh
|
||||
pipeCancel()
|
||||
}()
|
||||
conn = &UDPConn{
|
||||
srcConn,
|
||||
dstConn,
|
||||
NewBidirectionalPipe(pipeCtx, sourceRWCloser{in, dstConn}, sourceRWCloser{in, srcConn}),
|
||||
}
|
||||
route.connMap[srcAddr] = conn
|
||||
route.connMapMutex.Unlock()
|
||||
}
|
||||
|
||||
var forwarder func(*UDPConn, net.Conn) error
|
||||
_, err = conn.dst.Write(buffer[:nRead])
|
||||
return conn, err
|
||||
}
|
||||
|
||||
if logLevel == logrus.DebugLevel {
|
||||
forwarder = route.forwardReceivedDebug
|
||||
} else {
|
||||
forwarder = route.forwardReceivedReal
|
||||
}
|
||||
|
||||
// initiate connection to target
|
||||
err = forwarder(conn, route.targetConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-route.stopCh:
|
||||
return nil
|
||||
default:
|
||||
// receive from target
|
||||
conn, err = route.readFrom(route.targetConn, conn.buffer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// forward to source
|
||||
err = forwarder(conn, srcConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// read from source
|
||||
conn, err = route.readFrom(srcConn, conn.buffer)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// forward to target
|
||||
err = forwarder(conn, route.targetConn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
func (route *UDPRoute) Handle(c interface{}) error {
|
||||
return c.(*UDPConn).Start()
|
||||
}
|
||||
|
||||
func (route *UDPRoute) CloseListeners() {
|
||||
@@ -146,50 +107,28 @@ func (route *UDPRoute) CloseListeners() {
|
||||
route.listeningConn.Close()
|
||||
route.listeningConn = nil
|
||||
}
|
||||
if route.targetConn != nil {
|
||||
route.targetConn.Close()
|
||||
route.targetConn = nil
|
||||
}
|
||||
for _, conn := range route.connMap {
|
||||
conn.(*net.UDPConn).Close() // TODO: change on non udp target
|
||||
if err := conn.dst.Close(); err != nil {
|
||||
route.l.Error(err)
|
||||
}
|
||||
}
|
||||
route.connMap = make(map[net.Addr]net.Conn)
|
||||
route.connMap = make(UDPConnMap)
|
||||
}
|
||||
|
||||
func (route *UDPRoute) readFrom(src net.Conn, buffer []byte) (*UDPConn, error) {
|
||||
nRead, err := src.Read(buffer)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nRead == 0 {
|
||||
return nil, io.ErrShortBuffer
|
||||
}
|
||||
|
||||
return &UDPConn{
|
||||
remoteAddr: src.RemoteAddr(),
|
||||
buffer: buffer,
|
||||
bytesReceived: buffer[:nRead],
|
||||
nReceived: nRead,
|
||||
}, nil
|
||||
type sourceRWCloser struct {
|
||||
server *net.UDPConn
|
||||
target *net.UDPConn
|
||||
}
|
||||
|
||||
func (route *UDPRoute) forwardReceivedReal(receivedConn *UDPConn, dest net.Conn) error {
|
||||
nWritten, err := dest.Write(receivedConn.bytesReceived)
|
||||
|
||||
if nWritten != receivedConn.nReceived {
|
||||
err = io.ErrShortWrite
|
||||
}
|
||||
|
||||
return err
|
||||
func (w sourceRWCloser) Read(p []byte) (int, error) {
|
||||
n, _, err := w.target.ReadFrom(p)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (route *UDPRoute) forwardReceivedDebug(receivedConn *UDPConn, dest net.Conn) error {
|
||||
route.l.WithField("size", receivedConn.nReceived).Debugf(
|
||||
"forwarding from %s to %s",
|
||||
receivedConn.remoteAddr.String(),
|
||||
dest.RemoteAddr().String(),
|
||||
)
|
||||
return route.forwardReceivedReal(receivedConn, dest)
|
||||
func (w sourceRWCloser) Write(p []byte) (int, error) {
|
||||
return w.server.WriteToUDP(p, w.target.RemoteAddr().(*net.UDPAddr)) // TODO: support non udp
|
||||
}
|
||||
|
||||
func (w sourceRWCloser) Close() error {
|
||||
return w.target.Close()
|
||||
}
|
||||
|
||||
@@ -94,6 +94,18 @@ func (*Utils) healthCheckStream(scheme, host string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*Utils) reloadServer() error {
|
||||
resp, err := healthCheckHttpClient.Post("http://localhost:8080/reload", "", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return NewNestedError("server reload failed").Subjectf("%d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*Utils) snakeToPascal(s string) string {
|
||||
toHyphenCamel := http.CanonicalHeaderKey(strings.ReplaceAll(s, "_", "-"))
|
||||
return strings.ReplaceAll(toHyphenCamel, "-", "")
|
||||
|
||||
@@ -89,7 +89,7 @@ func (w *fileWatcher) Stop() {
|
||||
fileWatchMap.Delete(w.path)
|
||||
err := fsWatcher.Remove(w.path)
|
||||
if err != nil {
|
||||
w.l.WithField("action", "stop").Error(err)
|
||||
w.l.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Submodule templates/codemirror deleted from 0c8456c3bc
@@ -18,6 +18,9 @@
|
||||
<a class="unselectable">{{$cfgFile}}</a>
|
||||
</li>
|
||||
{{- end}}
|
||||
<li id="new-file">
|
||||
<a class="unselectable">+</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="config-editor"></div>
|
||||
|
||||
@@ -11,25 +11,43 @@ let editor = CodeMirror(editorElement, {
|
||||
tabSize: 2
|
||||
});
|
||||
|
||||
function loadFile(fileName) {
|
||||
if (fileName === undefined) {
|
||||
function setCurrentFile(filename) {
|
||||
let old_nav_item = document.getElementById(`file-${currentFile}`);
|
||||
if (old_nav_item !== null) {
|
||||
old_nav_item.classList.remove("active");
|
||||
}
|
||||
currentFile = filename;
|
||||
document.title = `${currentFile} - Config Editor`;
|
||||
let new_nav_item = document.getElementById(`file-${currentFile}`);
|
||||
if (new_nav_item === null) {
|
||||
new_file_btn = document.getElementById("new-file");
|
||||
file_list = document.getElementById("file-list");
|
||||
new_nav_item = document.createElement("li");
|
||||
new_nav_item.id = `file-${currentFile}`;
|
||||
new_nav_item.innerHTML = `<a class="unselectable">${currentFile}</a>`;
|
||||
file_list.insertBefore(new_nav_item, new_file_btn);
|
||||
}
|
||||
new_nav_item.classList.add("active");
|
||||
}
|
||||
|
||||
function loadFile(filename) {
|
||||
if (filename === undefined) {
|
||||
return;
|
||||
}
|
||||
if (filename === '+') {
|
||||
newFile();
|
||||
return;
|
||||
}
|
||||
let req = new XMLHttpRequest();
|
||||
req.open("GET", `/config/${fileName}`, true);
|
||||
req.open("GET", `/config/${filename}`, true);
|
||||
req.onreadystatechange = function () {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200) {
|
||||
let old_nav_item = document.getElementById(`file-${currentFile}`);
|
||||
old_nav_item.classList.remove("active");
|
||||
editor.setValue(req.responseText);
|
||||
currentFile = fileName;
|
||||
let new_nav_item = document.getElementById(`file-${currentFile}`);
|
||||
new_nav_item.classList.add("active");
|
||||
document.title = `${currentFile} - Config Editor`;
|
||||
setCurrentFile(filename);
|
||||
console.log(`loaded ${currentFile}`);
|
||||
} else {
|
||||
let msg = `Failed to load ${fileName}: ` + req.responseText;
|
||||
let msg = `Failed to load ${filename}: ` + req.responseText;
|
||||
alert(msg);
|
||||
console.log(msg);
|
||||
}
|
||||
@@ -46,14 +64,35 @@ function saveFile(filename, content) {
|
||||
req.onreadystatechange = function () {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200) {
|
||||
alert("Saved " + filename);
|
||||
alert(req.responseText);
|
||||
} else {
|
||||
alert("Error: " + req.responseText);
|
||||
alert("Error:\n" + req.responseText);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function newFile() {
|
||||
let filename = prompt("Enter filename:");
|
||||
if (filename === undefined || filename === "") {
|
||||
alert("File name cannot be empty");
|
||||
return;
|
||||
}
|
||||
if (!filename.endsWith(".yml") && !filename.endsWith(".yaml")) {
|
||||
alert("File name must end with .yml or .yaml");
|
||||
return;
|
||||
}
|
||||
let files = document.getElementById("file-list").children;
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (files[i].id === `file-${filename}`) {
|
||||
alert("File already exists");
|
||||
return;
|
||||
}
|
||||
}
|
||||
editor.setValue("");
|
||||
setCurrentFile(filename);
|
||||
}
|
||||
|
||||
editor.setSize("100wh", "100vh");
|
||||
editor.setOption("extraKeys", {
|
||||
Tab: function (cm) {
|
||||
|
||||
@@ -36,6 +36,10 @@ body {
|
||||
padding-right: 4em;
|
||||
display: block;
|
||||
}
|
||||
#new-file {
|
||||
color: #f8f8f2 !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
.active {
|
||||
font-weight: bold;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
|
||||
1
version.txt
Normal file
1
version.txt
Normal file
@@ -0,0 +1 @@
|
||||
0.4.5
|
||||
Reference in New Issue
Block a user