mirror of
https://github.com/yusing/godoxy.git
synced 2025-07-13 01:14:04 +02:00
added new file button in config editor, dockerfile fix
This commit is contained in:
parent
6bc4c1c49a
commit
9b34dc994d
28 changed files with 329 additions and 185 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -6,5 +6,3 @@ bin/go-proxy.bak
|
||||||
|
|
||||||
logs/
|
logs/
|
||||||
log/
|
log/
|
||||||
|
|
||||||
config-editor/
|
|
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
|
@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
10
Dockerfile
10
Dockerfile
|
@ -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"]
|
11
Makefile
11
Makefile
|
@ -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
105
README.md
|
@ -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)
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
BIN
bin/go-proxy
BIN
bin/go-proxy
Binary file not shown.
|
@ -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:
|
||||||
|
|
|
@ -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
|
|
|
@ -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
24
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 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=
|
||||||
|
|
|
@ -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#",
|
"$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
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 = ""
|
||||||
|
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)
|
||||||
|
}
|
|
@ -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"`
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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"
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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]()
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, "-", "")
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Reference in a new issue