added new file button in config editor, dockerfile fix

This commit is contained in:
yusing 2024-03-29 01:24:47 +00:00
parent 6bc4c1c49a
commit 9b34dc994d
28 changed files with 329 additions and 185 deletions

2
.gitignore vendored
View file

@ -6,5 +6,3 @@ bin/go-proxy.bak
logs/ logs/
log/ log/
config-editor/

12
.vscode/settings.json vendored
View file

@ -1,14 +1,16 @@
{ {
"go.inferGopath": false, "go.inferGopath": false,
"yaml.schemas": { "yaml.schemas": {
"https://github.com/yusing/go-proxy/raw/main/schema/config.schema.json": [ // "https://github.com/yusing/go-proxy/raw/main/schema/config.schema.json": [
"config.example.yml", // "config.example.yml",
"config.yml", // "config.yml"
"file:///config/workspace/go-proxy/config.example.yml" // ],
],
"https://github.com/yusing/go-proxy/raw/main/schema/providers.schema.json": [ "https://github.com/yusing/go-proxy/raw/main/schema/providers.schema.json": [
"providers.example.yml", "providers.example.yml",
"*.providers.yml" "*.providers.yml"
],
"file:///config/workspace/go-proxy/schema/config.schema.json": [
"file:///config/workspace/go-proxy/config.example.yml"
] ]
} }
} }

View file

@ -2,13 +2,13 @@ FROM alpine:latest
LABEL maintainer="yusing@6uo.me" LABEL maintainer="yusing@6uo.me"
RUN apk add --no-cache bash tzdata RUN apk add --no-cache tzdata
RUN mkdir /app RUN mkdir /app
COPY bin/go-proxy entrypoint.sh /app/ COPY bin/go-proxy /app/
COPY templates/ /app/templates COPY templates/ /app/templates
COPY config.example.yml /app/config/config.yml COPY schema/ /app/schema
RUN chmod +x /app/go-proxy /app/entrypoint.sh RUN chmod +x /app/go-proxy
ENV DOCKER_HOST unix:///var/run/docker.sock ENV DOCKER_HOST unix:///var/run/docker.sock
ENV GOPROXY_DEBUG 0 ENV GOPROXY_DEBUG 0
ENV GOPROXY_REDIRECT_HTTP 1 ENV GOPROXY_REDIRECT_HTTP 1
@ -19,4 +19,4 @@ EXPOSE 443
EXPOSE 8443 EXPOSE 8443
WORKDIR /app WORKDIR /app
ENTRYPOINT /app/entrypoint.sh CMD ["/app/go-proxy"]

View file

@ -2,6 +2,11 @@
all: build quick-restart logs all: build quick-restart logs
init-config:
mkdir -p config certs
[ -f config/config.yml ] || cp config.example.yml config/config.yml
[ -f config/providers.yml ] || touch config/providers.yml
build: build:
mkdir -p bin mkdir -p bin
CGO_ENABLED=0 GOOS=linux go build -pgo=auto -o bin/go-proxy src/go-proxy/*.go CGO_ENABLED=0 GOOS=linux go build -pgo=auto -o bin/go-proxy src/go-proxy/*.go
@ -9,12 +14,6 @@ build:
up: up:
docker compose up -d --build go-proxy 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
restart: restart:
docker kill go-proxy docker kill go-proxy
docker compose up -d go-proxy docker compose up -d go-proxy

105
README.md
View file

@ -12,6 +12,8 @@ In the examples domain `x.y.z` is used, replace them with your domain
- [How to use](#how-to-use) - [How to use](#how-to-use)
- [Binary](#binary) - [Binary](#binary)
- [Docker](#docker) - [Docker](#docker)
- [Command-line args](#command-line-args)
- [Commands](#commands)
- [Use JSON Schema in VSCode](#use-json-schema-in-vscode) - [Use JSON Schema in VSCode](#use-json-schema-in-vscode)
- [Configuration](#configuration) - [Configuration](#configuration)
- [Labels (docker)](#labels-docker) - [Labels (docker)](#labels-docker)
@ -37,14 +39,15 @@ In the examples domain `x.y.z` is used, replace them with your domain
- Fast (See [benchmarks](#benchmarks)) - Fast (See [benchmarks](#benchmarks))
- Auto certificate obtaining and renewal (See [Config File](#config-file) and [Supported DNS Challenge Providers](#supported-dns-challenge-providers)) - 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 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 - Custom proxy entries with `config.yml` and additional provider files
- Subdomain matching + Path matching **(domain name doesn't matter)** - 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) - 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 8080 (http) and port 8443 (https)
- Simple panel to see all reverse proxies and health available on port [panel_port_http] (http) and port [panel_port_https] (https)
![panel screenshot](screenshots/panel.png) ![panel screenshot](screenshots/panel.png)
- Config editor to edit config and provider files with validation - Config editor to edit config and provider files with validation
**Validate and save file with Ctrl+S** **Validate and save file with Ctrl+S**
@ -53,13 +56,20 @@ In the examples domain `x.y.z` is used, replace them with your domain
## How to use ## How to use
1. Download and extract the latest release (or clone the repository if you want to try out experimental features) 1. Download and extract the latest release (or clone the repository)
2. Copy `config.example.yml` to `config/config.yml` and modify the content to fit your needs 2. Call `make init-config` to init config file and provider file
3. (Optional) write your own `config/providers.yml` from `providers.example.yml` 3. Point your domain (i.e `y.z`) to your machine's IP address
4. See [Binary](#binary) or [docker](#docker) - A Record: `*.y.z` -> `10.0.10.1`
- AAAA Record: `*.y.z` -> `::ffff:a00:a01`
4. Start `go-proxy` (see [Binary](#binary) or [docker](#docker))
5. Start editing config files
- with text editor (i.e. Visual Studio Code)
- with web config editor by navigate to `ip:8080`
### Binary ### Binary
@ -110,28 +120,38 @@ In the examples domain `x.y.z` is used, replace them with your domain
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 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
## Command-line args
`go-proxy [command]`
### Commands
- empty: start proxy server
- validate: validate config and exit
- reload: force reload config and exit
## Use JSON Schema in VSCode ## Use JSON Schema in VSCode
Modify `.vscode/settings.json` to fit your needs Modify `.vscode/settings.json` to fit your needs
```json ```json
{ {
"yaml.schemas": { "yaml.schemas": {
"https://github.com/yusing/go-proxy/raw/main/schema/config.schema.json": [ "https://github.com/yusing/go-proxy/raw/main/schema/config.schema.json": [
"config.example.yml", "config.example.yml",
"config.yml" "config.yml"
], ],
"https://github.com/yusing/go-proxy/raw/main/schema/providers.schema.json": [ "https://github.com/yusing/go-proxy/raw/main/schema/providers.schema.json": [
"providers.example.yml", "providers.example.yml",
"*.providers.yml", "*.providers.yml"
] ]
} }
} }
``` ```
## Configuration ## 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) ### Labels (docker)
@ -229,13 +249,13 @@ To add more provider support (**CloudDNS** as an example):
1. Fork this repo, modify [autocert.go](src/go-proxy/autocert.go#L305) 1. Fork this repo, modify [autocert.go](src/go-proxy/autocert.go#L305)
```go ```go
var providersGenMap = map[string]ProviderGenerator{ var providersGenMap = map[string]ProviderGenerator{
"cloudflare": providerGenerator(cloudflare.NewDefaultConfig, cloudflare.NewDNSProviderConfig), "cloudflare": providerGenerator(cloudflare.NewDefaultConfig, cloudflare.NewDNSProviderConfig),
// add here, i.e. // add here, i.e.
"clouddns": providerGenerator(clouddns.NewDefaultConfig, clouddns.NewDNSProviderConfig), "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 2. Go to [https://go-acme.github.io/lego/dns/clouddns](https://go-acme.github.io/lego/dns/clouddns/) and check for required config
@ -243,24 +263,24 @@ To add more provider support (**CloudDNS** as an example):
4. Set required config in `config.yml` `autocert` -> `options` section 4. Set required config in `config.yml` `autocert` -> `options` section
```shell ```shell
# From https://go-acme.github.io/lego/dns/clouddns/ # From https://go-acme.github.io/lego/dns/clouddns/
CLOUDDNS_CLIENT_ID=bLsdFAks23429841238feb177a572aX \ CLOUDDNS_CLIENT_ID=bLsdFAks23429841238feb177a572aX \
CLOUDDNS_EMAIL=you@example.com \ CLOUDDNS_EMAIL=you@example.com \
CLOUDDNS_PASSWORD=b9841238feb177a84330f \ CLOUDDNS_PASSWORD=b9841238feb177a84330f \
lego --email you@example.com --dns clouddns --domains my.example.org run lego --email you@example.com --dns clouddns --domains my.example.org run
``` ```
Should turn into: Should turn into:
```yaml ```yaml
autocert: autocert:
... ...
options: options:
client_id: bLsdFAks23429841238feb177a572aX client_id: bLsdFAks23429841238feb177a572aX
email: you@example.com email: you@example.com
password: b9841238feb177a84330f password: b9841238feb177a84330f
``` ```
5. Run and test if it works 5. Run and test if it works
6. Commit and create pull request 6. Commit and create pull request
@ -471,6 +491,3 @@ It takes ~15 MB for 50 proxy entries
4. build binary with `make build` 4. build binary with `make build`
5. start your container with `make up` (docker) or `bin/go-proxy` (binary) 5. start your container with `make up` (docker) or `bin/go-proxy` (binary)
[panel_port_http]: 8080
[panel_port_https]: 8443

Binary file not shown.

View file

@ -19,23 +19,25 @@ services:
# - 20000:20100/tcp # - 20000:20100/tcp
# - 20000:20100/udp # - 20000:20100/udp
volumes: volumes:
- ./config:/app/config
# if local docker provider is used
# - /var/run/docker.sock:/var/run/docker.sock:ro
# use existing certificate # use existing certificate
# - /path/to/cert.pem:/app/certs/cert.crt:ro # - /path/to/cert.pem:/app/certs/cert.crt:ro
# - /path/to/privkey.pem:/app/certs/priv.key:ro # - /path/to/privkey.pem:/app/certs/priv.key:ro
# use autocert feature # store autocert obtained cert
# - ./certs:/app/certs # - ./certs:/app/certs
# if local docker provider is used (by default) # workaround for "lookup: no such host"
- /var/run/docker.sock:/var/run/docker.sock:ro # dns:
# - 127.0.0.1
# to use custom config and providers # if you have container running in "host" network mode
# - ./config:/app/config # extra_hosts:
dns: # - host.docker.internal:host-gateway
- 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
logging: logging:
driver: 'json-file' driver: 'json-file'
options: options:

View file

@ -1,25 +1,21 @@
# uncomment to use autocert # Autocert (uncomment to enable)
autocert: # (optional, if you need autocert feature) # autocert: # (optional, if you need autocert feature)
email: "user@domain.com" # (required) email for acme certificate # email: "user@domain.com" # (required) email for acme certificate
domains: # (required) # domains: # (required)
- "*.y.z" # domain for acme certificate, use wild card to allow all subdomains # - "*.y.z" # domain for acme certificate, use wild card to allow all subdomains
provider: cloudflare # (required) dns challenge provider (string) # provider: cloudflare # (required) dns challenge provider (string)
options: # provider specific options # options: # provider specific options
auth_token: "YOUR_ZONE_API_TOKEN" # auth_token: "YOUR_ZONE_API_TOKEN"
providers: providers:
local: local:
kind: docker kind: 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. FROM_ENV, ssh://user@10.0.1.1:22, tcp://10.0.2.1:2375
value: FROM_ENV value: FROM_ENV
# remote1: providers:
# kind: docker kind: file
# value: ssh://user@10.0.1.1 value: providers.yml
# remote2:
# kind: docker # Fixed options (optional, non hot-reloadable)
# value: tcp://10.0.1.1:2375 # timeout_shutdown: 5
# provider1: # redirect_to_https: false
# kind: file
# value: provider1.yml
# provider2:
# kind: file
# value: provider2.yml

View file

@ -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

24
go.sum
View file

@ -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 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 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 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= 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 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 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 h1:ltJvGvqZ4G6Fm2hHOYZ5RWpJQcrM0oDrsjjZydZhFJQ=
github.com/cloudflare/cloudflare-go v0.92.0/go.mod h1:nUqvBUUDRxNzsDSQjbqUNWHEIYAoUlgRmcAzMKlFdKs= github.com/cloudflare/cloudflare-go v0.92.0/go.mod h1:nUqvBUUDRxNzsDSQjbqUNWHEIYAoUlgRmcAzMKlFdKs=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 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.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 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.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 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 v26.0.0+incompatible h1:90BKrx1a1HKYpSnnBFR6AgDq/FqkHxwlUyzJVPxD30I= github.com/docker/cli v26.0.0+incompatible h1:90BKrx1a1HKYpSnnBFR6AgDq/FqkHxwlUyzJVPxD30I=
@ -68,7 +58,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/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/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 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 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@ -89,7 +78,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/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 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= 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 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -98,15 +86,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/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 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis=
github.com/santhosh-tekuri/jsonschema v1.2.4/go.mod h1:TEAUOeZSmIxTTuHatJzrvARHiuO9LYd+cIxzgEHCQI4= 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 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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.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.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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/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= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
@ -132,8 +118,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/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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.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 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -147,10 +131,8 @@ 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.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 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-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-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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
@ -165,8 +147,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-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-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.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 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= 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= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View file

@ -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"]
} }

View file

@ -1,7 +1,14 @@
{ {
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "go-proxy providers file", "title": "go-proxy providers file",
"anyOf": [
{
"type":"object"
},
{
"type":"null"
}
],
"patternProperties": { "patternProperties": {
"^[a-zA-Z0-9_-]+$": { "^[a-zA-Z0-9_-]+$": {
"title": "Proxy entry", "title": "Proxy entry",

38
src/go-proxy/args.go Normal file
View file

@ -0,0 +1,38 @@
package main
import (
"flag"
"github.com/sirupsen/logrus"
)
type Args struct {
Command string
}
const (
CommandStart = ""
CommandVerify = "verify"
CommandReload = "reload"
)
var ValidCommands = []string{CommandStart, CommandVerify, 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)
}

View file

@ -21,9 +21,9 @@ import (
"github.com/go-acme/lego/v4/registration" "github.com/go-acme/lego/v4/registration"
) )
type ProviderOptions = map[string]string type ProviderOptions map[string]string
type ProviderGenerator = func(ProviderOptions) (challenge.Provider, error) type ProviderGenerator func(ProviderOptions) (challenge.Provider, error)
type CertExpiries = map[string]time.Time type CertExpiries map[string]time.Time
type AutoCertConfig struct { type AutoCertConfig struct {
Email string `json:"email"` Email string `json:"email"`

View file

@ -3,12 +3,14 @@ package main
import ( import (
"os" "os"
"sync" "sync"
"time"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// commented out if unused // commented out if unused
type Config interface { type Config interface {
Value() configModel
// Load() error // Load() error
MustLoad() MustLoad()
GetAutoCertProvider() (AutoCertProvider, error) GetAutoCertProvider() (AutoCertProvider, error)
@ -21,7 +23,13 @@ type Config interface {
} }
func NewConfig(path string) Config { func NewConfig(path string) Config {
cfg := &config{reader: &FileReader{Path: path}} cfg := &config{
m: &configModel{
TimeoutShutdown: 3 * time.Second,
RedirectToHTTPS: false,
},
reader: &FileReader{Path: path},
}
cfg.watcher = NewFileWatcher( cfg.watcher = NewFileWatcher(
path, path,
cfg.MustReload, // OnChange cfg.MustReload, // OnChange
@ -35,6 +43,10 @@ func ValidateConfig(data []byte) error {
return cfg.Load() return cfg.Load()
} }
func (cfg *config) Value() configModel {
return *cfg.m
}
func (cfg *config) Load(reader ...Reader) error { func (cfg *config) Load(reader ...Reader) error {
cfg.mutex.Lock() cfg.mutex.Lock()
defer cfg.mutex.Unlock() defer cfg.mutex.Unlock()
@ -170,8 +182,10 @@ func (cfg *config) StopWatching() {
} }
type configModel struct { type configModel struct {
Providers map[string]*Provider `yaml:",flow" json:"providers"` Providers map[string]*Provider `yaml:",flow" json:"providers"`
AutoCert AutoCertConfig `yaml:",flow" json:"autocert"` 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"`
} }
type config struct { type config struct {

View file

@ -148,5 +148,3 @@ var logLevel = func() logrus.Level {
} }
return logrus.GetLevel() return logrus.GetLevel()
}() }()
var redirectToHTTPS = os.Getenv("GOPROXY_REDIRECT_HTTP") != "0" && os.Getenv("GOPROXY_REDIRECT_HTTP") != "false"

View file

@ -44,8 +44,8 @@ func NewHTTPRoute(config *ProxyConfig) (*HTTPRoute, error) {
PathMode: config.PathMode, PathMode: config.PathMode,
l: hrlog.WithFields(logrus.Fields{ l: hrlog.WithFields(logrus.Fields{
"alias": config.Alias, "alias": config.Alias,
"path": config.Path, // "path": config.Path,
"path_mode": config.PathMode, // "path_mode": config.PathMode,
}), }),
} }
@ -157,6 +157,6 @@ func (config *ProxyConfig) pathSubModResp(r *http.Response) error {
} }
// alias -> (path -> routes) // alias -> (path -> routes)
type HTTPRoutes = SafeMap[string, pathPoolMap] type HTTPRoutes SafeMap[string, pathPoolMap]
var httpRoutes HTTPRoutes = NewSafeMapOf[HTTPRoutes](newPathPoolMap) var httpRoutes HTTPRoutes = NewSafeMapOf[HTTPRoutes](newPathPoolMap)

View file

@ -1,12 +1,13 @@
package main package main
import ( import (
"flag"
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"runtime" "runtime"
"sync"
"syscall" "syscall"
"time"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -16,21 +17,27 @@ var cfg Config
func main() { func main() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
var verifyOnly bool args := getArgs()
flag.BoolVar(&verifyOnly, "verify", false, "verify config without starting server")
flag.Parse()
logrus.SetFormatter(&logrus.TextFormatter{ logrus.SetFormatter(&logrus.TextFormatter{
ForceColors: true, ForceColors: true,
DisableColors: false, DisableColors: false,
FullTimestamp: true, FullTimestamp: true,
TimestampFormat: "01-02 15:04:05", TimestampFormat: "01-02 15:04:05",
}) })
if args.Command == CommandReload {
err := utils.reloadServer()
if err != nil {
logrus.Fatal(err)
}
return
}
cfg = NewConfig(configPath) cfg = NewConfig(configPath)
cfg.MustLoad() cfg.MustLoad()
if verifyOnly { if args.Command == CommandVerify {
logrus.Printf("config OK") logrus.Printf("config OK")
return return
} }
@ -63,7 +70,7 @@ func main() {
HTTPAddr: ":80", HTTPAddr: ":80",
HTTPSAddr: ":443", HTTPSAddr: ":443",
Handler: http.HandlerFunc(proxyHandler), Handler: http.HandlerFunc(proxyHandler),
RedirectToHTTPS: redirectToHTTPS, RedirectToHTTPS: cfg.Value().RedirectToHTTPS,
}) })
panelServer = NewServer(ServerOptions{ panelServer = NewServer(ServerOptions{
Name: "panel", Name: "panel",
@ -71,7 +78,7 @@ func main() {
HTTPAddr: ":8080", HTTPAddr: ":8080",
HTTPSAddr: ":8443", HTTPSAddr: ":8443",
Handler: panelHandler, Handler: panelHandler,
RedirectToHTTPS: redirectToHTTPS, RedirectToHTTPS: cfg.Value().RedirectToHTTPS,
}) })
proxyServer.Start() proxyServer.Start()
@ -88,10 +95,32 @@ func main() {
signal.Notify(sig, syscall.SIGHUP) signal.Notify(sig, syscall.SIGHUP)
<-sig <-sig
// cfg.StopWatching() logrus.Info("shutting down")
StopFSWatcher() done := make(chan struct{}, 1)
StopDockerWatcher()
cfg.StopProviders() var wg sync.WaitGroup
panelServer.Stop() wg.Add(3)
proxyServer.Stop()
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")
}
} }

View file

@ -2,6 +2,7 @@ package main
import ( import (
"errors" "errors"
"fmt"
"html/template" "html/template"
"net/http" "net/http"
"net/url" "net/url"
@ -68,7 +69,7 @@ func panelCheckTargetHealth(w http.ResponseWriter, r *http.Request) {
func panelConfigEditor(w http.ResponseWriter, r *http.Request) { func panelConfigEditor(w http.ResponseWriter, r *http.Request) {
cfgFiles := make([]string, 0) cfgFiles := make([]string, 0)
cfgFiles = append(cfgFiles, path.Base(configPath)) cfgFiles = append(cfgFiles, path.Base(configPath))
for _, p := range cfg.(*config).m.Providers { for _, p := range cfg.Value().Providers {
if p.Kind != ProviderKind_File { if p.Kind != ProviderKind_File {
continue continue
} }
@ -99,12 +100,20 @@ func panelConfigUpdate(w http.ResponseWriter, r *http.Request) {
panelHandleErr(w, r, err) panelHandleErr(w, r, err)
return 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 { if err != nil {
panelHandleErr(w, r, NewNestedError("unable to write config file").With(err)) panelHandleErr(w, r, NewNestedError("unable to write config file").With(err))
return return
} }
w.WriteHeader(http.StatusOK) 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) { func panelServeFile(w http.ResponseWriter, r *http.Request) {

View file

@ -15,8 +15,8 @@ type ProxyConfig struct {
provider *Provider provider *Provider
} }
type ProxyConfigMap = map[string]ProxyConfig type ProxyConfigMap map[string]ProxyConfig
type ProxyConfigSlice = []ProxyConfig type ProxyConfigSlice []ProxyConfig
func NewProxyConfig(provider *Provider) ProxyConfig { func NewProxyConfig(provider *Provider) ProxyConfig {
return ProxyConfig{ return ProxyConfig{

View file

@ -47,6 +47,6 @@ func isStreamScheme(s string) bool {
} }
// id -> target // id -> target
type StreamRoutes = SafeMap[string, StreamRoute] type StreamRoutes SafeMap[string, StreamRoute]
var streamRoutes StreamRoutes = NewSafeMapOf[StreamRoutes]() var streamRoutes StreamRoutes = NewSafeMapOf[StreamRoutes]()

View file

@ -31,11 +31,11 @@ type ServerOptions struct {
} }
type LogrusWrapper struct { type LogrusWrapper struct {
l *logrus.Entry *logrus.Entry
} }
func (l LogrusWrapper) Write(b []byte) (int, error) { 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 { func NewServer(opt ServerOptions) *Server {

View file

@ -45,10 +45,8 @@ type StreamRouteBase struct {
func newStreamRouteBase(config *ProxyConfig) (*StreamRouteBase, error) { func newStreamRouteBase(config *ProxyConfig) (*StreamRouteBase, error) {
var streamType string = StreamType_TCP var streamType string = StreamType_TCP
var srcPort string var srcPort, dstPort string
var dstPort string var srcScheme, dstScheme string
var srcScheme string
var dstScheme string
portSplit := strings.Split(config.Port, ":") portSplit := strings.Split(config.Port, ":")
if len(portSplit) != 2 { if len(portSplit) != 2 {
@ -101,8 +99,8 @@ func newStreamRouteBase(config *ProxyConfig) (*StreamRouteBase, error) {
started: false, started: false,
l: srlog.WithFields(logrus.Fields{ l: srlog.WithFields(logrus.Fields{
"alias": config.Alias, "alias": config.Alias,
"src": fmt.Sprintf("%s://:%d", srcScheme, srcPortInt), // "src": fmt.Sprintf("%s://:%d", srcScheme, srcPortInt),
"dst": fmt.Sprintf("%s://%s:%d", dstScheme, config.Host, dstPortInt), // "dst": fmt.Sprintf("%s://%s:%d", dstScheme, config.Host, dstPortInt),
}), }),
}, nil }, nil
} }

View file

@ -94,6 +94,18 @@ func (*Utils) healthCheckStream(scheme, host string) error {
return nil 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 { func (*Utils) snakeToPascal(s string) string {
toHyphenCamel := http.CanonicalHeaderKey(strings.ReplaceAll(s, "_", "-")) toHyphenCamel := http.CanonicalHeaderKey(strings.ReplaceAll(s, "_", "-"))
return strings.ReplaceAll(toHyphenCamel, "-", "") return strings.ReplaceAll(toHyphenCamel, "-", "")

View file

@ -89,7 +89,7 @@ func (w *fileWatcher) Stop() {
fileWatchMap.Delete(w.path) fileWatchMap.Delete(w.path)
err := fsWatcher.Remove(w.path) err := fsWatcher.Remove(w.path)
if err != nil { if err != nil {
w.l.WithField("action", "stop").Error(err) w.l.Error(err)
} }
} }

View file

@ -18,6 +18,9 @@
<a class="unselectable">{{$cfgFile}}</a> <a class="unselectable">{{$cfgFile}}</a>
</li> </li>
{{- end}} {{- end}}
<li id="new-file">
<a class="unselectable">+</a>
</li>
</ul> </ul>
</div> </div>
<div id="config-editor"></div> <div id="config-editor"></div>

View file

@ -11,25 +11,43 @@ let editor = CodeMirror(editorElement, {
tabSize: 2 tabSize: 2
}); });
function loadFile(fileName) { function setCurrentFile(filename) {
if (fileName === undefined) { 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; return;
} }
let req = new XMLHttpRequest(); let req = new XMLHttpRequest();
req.open("GET", `/config/${fileName}`, true); req.open("GET", `/config/${filename}`, true);
req.onreadystatechange = function () { req.onreadystatechange = function () {
if (req.readyState == 4) { if (req.readyState == 4) {
if (req.status == 200) { if (req.status == 200) {
let old_nav_item = document.getElementById(`file-${currentFile}`);
old_nav_item.classList.remove("active");
editor.setValue(req.responseText); editor.setValue(req.responseText);
currentFile = fileName; setCurrentFile(filename);
let new_nav_item = document.getElementById(`file-${currentFile}`);
new_nav_item.classList.add("active");
document.title = `${currentFile} - Config Editor`;
console.log(`loaded ${currentFile}`); console.log(`loaded ${currentFile}`);
} else { } else {
let msg = `Failed to load ${fileName}: ` + req.responseText; let msg = `Failed to load ${filename}: ` + req.responseText;
alert(msg); alert(msg);
console.log(msg); console.log(msg);
} }
@ -46,14 +64,35 @@ function saveFile(filename, content) {
req.onreadystatechange = function () { req.onreadystatechange = function () {
if (req.readyState == 4) { if (req.readyState == 4) {
if (req.status == 200) { if (req.status == 200) {
alert("Saved " + filename); alert(req.responseText);
} else { } 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.setSize("100wh", "100vh");
editor.setOption("extraKeys", { editor.setOption("extraKeys", {
Tab: function (cm) { Tab: function (cm) {

View file

@ -36,6 +36,10 @@ body {
padding-right: 4em; padding-right: 4em;
display: block; display: block;
} }
#new-file {
color: #f8f8f2 !important;
font-weight: bold;
}
.active { .active {
font-weight: bold; font-weight: bold;
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);