forked from Ivasoft/opds-proxy
feat: environment configuration
Environment variables can now be used to configure any config property including the feeds list. This makes it easier to use in environments without access to config files like GCR. Some may prefer not to have a separate config file as well. Also added build metadata to the docker image and binaries.
This commit is contained in:
@@ -3,7 +3,7 @@ testdata_dir = "testdata"
|
|||||||
tmp_dir = "tmp"
|
tmp_dir = "tmp"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
args_bin = ["--dev"]
|
args_bin = []
|
||||||
bin = "./tmp/main"
|
bin = "./tmp/main"
|
||||||
cmd = "go build -o ./tmp/main ."
|
cmd = "go build -o ./tmp/main ."
|
||||||
delay = 1000
|
delay = 1000
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ cp kindlegen/kindlegen /usr/local/bin/kindlegen
|
|||||||
chmod +x /usr/local/bin/kindlegen
|
chmod +x /usr/local/bin/kindlegen
|
||||||
rm -rf kindlegen kindlegen_linux_2.6_i386_v2_9.tar.gz
|
rm -rf kindlegen kindlegen_linux_2.6_i386_v2_9.tar.gz
|
||||||
|
|
||||||
go install github.com/air-verse/air@latest
|
go install github.com/air-verse/air@latest
|
||||||
|
go install github.com/goreleaser/goreleaser/v2@latest
|
||||||
4
.github/workflows/action.yml
vendored
4
.github/workflows/action.yml
vendored
@@ -71,6 +71,10 @@ jobs:
|
|||||||
platforms: ${{ steps.set-platforms.outputs.DOCKER_PLATFORMS}}
|
platforms: ${{ steps.set-platforms.outputs.DOCKER_PLATFORMS}}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
build-args:
|
||||||
|
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}
|
||||||
|
REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}
|
||||||
|
BUILDTIME=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}
|
||||||
|
|
||||||
- name: Docker Hub Description
|
- name: Docker Hub Description
|
||||||
uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4.0.0
|
uses: peter-evans/dockerhub-description@e98e4d1628a5f3be2be7c231e50981aee98723ae # v4.0.0
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -22,6 +22,8 @@ By running your own OPDS Proxy you can allow eReaders to navigate and download b
|
|||||||
|
|
||||||
You can run OPDS Proxy as a Docker container or as an executable.
|
You can run OPDS Proxy as a Docker container or as an executable.
|
||||||
|
|
||||||
|
Configuration is done via YAML file (default `config.yml`), environment variables, and command flags in that order of precedence.
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
Docker images are published to [Docker Hub](https://hub.docker.com/r/evanbuss/opds-proxy) and the [GitHub Container Registry](https://github.com/evan-buss/opds-proxy/pkgs/container/opds-proxy).
|
Docker images are published to [Docker Hub](https://hub.docker.com/r/evanbuss/opds-proxy) and the [GitHub Container Registry](https://github.com/evan-buss/opds-proxy/pkgs/container/opds-proxy).
|
||||||
@@ -32,6 +34,14 @@ services:
|
|||||||
image: evanbuss/opds-proxy:latest
|
image: evanbuss/opds-proxy:latest
|
||||||
#image: ghcr.io/evan-buss/opds-proxy:latest
|
#image: ghcr.io/evan-buss/opds-proxy:latest
|
||||||
container_name: opds-proxy
|
container_name: opds-proxy
|
||||||
|
# You can also use environment variables to configure the container
|
||||||
|
# environment:
|
||||||
|
# - OPDS__PORT=5228
|
||||||
|
# - OPDS__FEEDS__0__NAME=Some Feed
|
||||||
|
# - OPDS__FEEDS__0__URL=http://some-feed.com/opds
|
||||||
|
# - OPDS__FEEDS__0__AUTH__USERNAME=user
|
||||||
|
# - OPDS__FEEDS__0__AUTH__PASSWORD=password
|
||||||
|
# - OPDS__FEEDS__0__AUTH__LOCAL_ONLY=true
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -23,7 +23,11 @@ RUN go mod verify
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o opds-proxy
|
ARG VERSION=dev
|
||||||
|
ARG REVISION=unknown
|
||||||
|
ARG BUILDTIME=unknown
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 go build -ldflags "-s -w -X main.version=${VERSION} -X main.commit=${REVISION} -X main.date=${BUILDTIME}" -o opds-proxy
|
||||||
|
|
||||||
FROM gcr.io/distroless/static
|
FROM gcr.io/distroless/static
|
||||||
|
|
||||||
|
|||||||
15
go.mod
15
go.mod
@@ -8,7 +8,16 @@ require (
|
|||||||
github.com/knadh/koanf/v2 v2.1.1
|
github.com/knadh/koanf/v2 v2.1.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require github.com/gorilla/securecookie v1.1.2
|
require (
|
||||||
|
github.com/gorilla/securecookie v1.1.2
|
||||||
|
github.com/spf13/pflag v1.0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/tidwall/gjson v1.14.2 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
@@ -16,9 +25,11 @@ require (
|
|||||||
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/knadh/koanf/maps v0.1.1 // indirect
|
github.com/knadh/koanf/maps v0.1.1 // indirect
|
||||||
github.com/knadh/koanf/providers/confmap v0.1.0
|
github.com/knadh/koanf/parsers/json v0.1.0
|
||||||
|
github.com/knadh/koanf/providers/posflag v0.1.0
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
|
github.com/tidwall/sjson v1.2.5
|
||||||
golang.org/x/sys v0.22.0 // indirect
|
golang.org/x/sys v0.22.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
16
go.sum
16
go.sum
@@ -14,12 +14,14 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX
|
|||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
|
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
|
||||||
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||||
|
github.com/knadh/koanf/parsers/json v0.1.0 h1:dzSZl5pf5bBcW0Acnu20Djleto19T0CfHcvZ14NJ6fU=
|
||||||
|
github.com/knadh/koanf/parsers/json v0.1.0/go.mod h1:ll2/MlXcZ2BfXD6YJcjVFzhG9P0TdJ207aIBKQhV2hY=
|
||||||
github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
|
github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
|
||||||
github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
|
github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
|
||||||
github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU=
|
|
||||||
github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU=
|
|
||||||
github.com/knadh/koanf/providers/file v1.0.0 h1:DtPvSQBeF+N0QLPMz0yf2bx0nFSxUcncpqQvzCxfCyk=
|
github.com/knadh/koanf/providers/file v1.0.0 h1:DtPvSQBeF+N0QLPMz0yf2bx0nFSxUcncpqQvzCxfCyk=
|
||||||
github.com/knadh/koanf/providers/file v1.0.0/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI=
|
github.com/knadh/koanf/providers/file v1.0.0/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI=
|
||||||
|
github.com/knadh/koanf/providers/posflag v0.1.0 h1:mKJlLrKPcAP7Ootf4pBZWJ6J+4wHYujwipe7Ie3qW6U=
|
||||||
|
github.com/knadh/koanf/providers/posflag v0.1.0/go.mod h1:SYg03v/t8ISBNrMBRMlojH8OsKowbkXV7giIbBVgbz0=
|
||||||
github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM=
|
github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM=
|
||||||
github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es=
|
github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es=
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
@@ -28,8 +30,18 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
|
|||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo=
|
||||||
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||||
|
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
|||||||
105
internal/envextended/provider.go
Normal file
105
internal/envextended/provider.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package envextended
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tidwall/sjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Env struct {
|
||||||
|
prefix string
|
||||||
|
delim string
|
||||||
|
cb func(key string, value string) (string, interface{})
|
||||||
|
out string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Provider(prefix, delim string, cb func(s string) string) *Env {
|
||||||
|
e := &Env{
|
||||||
|
prefix: prefix,
|
||||||
|
delim: delim,
|
||||||
|
out: "{}",
|
||||||
|
}
|
||||||
|
if cb != nil {
|
||||||
|
e.cb = func(key string, value string) (string, interface{}) {
|
||||||
|
return cb(key), value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProviderWithValue works exactly the same as Provider except the callback
|
||||||
|
// takes a (key, value) with the variable name and value and allows you
|
||||||
|
// to modify both. This is useful for cases where you may want to return
|
||||||
|
// other types like a string slice instead of just a string.
|
||||||
|
func ProviderWithValue(prefix, delim string, cb func(key string, value string) (string, interface{})) *Env {
|
||||||
|
return &Env{
|
||||||
|
prefix: prefix,
|
||||||
|
delim: delim,
|
||||||
|
cb: cb,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadBytes reads the contents of a file on disk and returns the bytes.
|
||||||
|
func (e *Env) ReadBytes() ([]byte, error) {
|
||||||
|
// Collect the environment variable keys.
|
||||||
|
var keys []string
|
||||||
|
for _, k := range os.Environ() {
|
||||||
|
if e.prefix != "" {
|
||||||
|
if strings.HasPrefix(k, e.prefix) {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range keys {
|
||||||
|
parts := strings.SplitN(k, "=", 2)
|
||||||
|
|
||||||
|
var (
|
||||||
|
key string
|
||||||
|
value interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// If there's a transformation callback,
|
||||||
|
// run it through every key/value.
|
||||||
|
if e.cb != nil {
|
||||||
|
key, value = e.cb(parts[0], parts[1])
|
||||||
|
// If the callback blanked the key, it should be omitted
|
||||||
|
if key == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
key = parts[0]
|
||||||
|
value = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := e.set(key, value); err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.out == "" {
|
||||||
|
return []byte("{}"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(e.out), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Env) set(key string, value interface{}) error {
|
||||||
|
out, err := sjson.Set(e.out, strings.Replace(key, e.delim, ".", -1), value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.out = out
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read is not supported by the file provider.
|
||||||
|
func (e *Env) Read() (map[string]interface{}, error) {
|
||||||
|
return nil, errors.New("envextended provider does not support this method")
|
||||||
|
}
|
||||||
115
main.go
115
main.go
@@ -2,24 +2,33 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"flag"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/evan-buss/opds-proxy/internal/envextended"
|
||||||
"github.com/gorilla/securecookie"
|
"github.com/gorilla/securecookie"
|
||||||
|
"github.com/knadh/koanf/parsers/json"
|
||||||
"github.com/knadh/koanf/parsers/yaml"
|
"github.com/knadh/koanf/parsers/yaml"
|
||||||
"github.com/knadh/koanf/providers/confmap"
|
|
||||||
"github.com/knadh/koanf/providers/file"
|
"github.com/knadh/koanf/providers/file"
|
||||||
|
"github.com/knadh/koanf/providers/posflag"
|
||||||
"github.com/knadh/koanf/v2"
|
"github.com/knadh/koanf/v2"
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Version information set at build time
|
||||||
|
var version = "dev"
|
||||||
|
var commit = "unknown"
|
||||||
|
var date = "unknown"
|
||||||
|
|
||||||
type ProxyConfig struct {
|
type ProxyConfig struct {
|
||||||
Port string `koanf:"port"`
|
Port string `koanf:"port"`
|
||||||
Auth AuthConfig `koanf:"auth"`
|
Auth AuthConfig `koanf:"auth"`
|
||||||
Feeds []FeedConfig `koanf:"feeds" `
|
Feeds []FeedConfig `koanf:"feeds" `
|
||||||
isDevMode bool
|
DebugMode bool `koanf:"debug"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthConfig struct {
|
type AuthConfig struct {
|
||||||
@@ -40,44 +49,54 @@ type FeedConfigAuth struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fs := flag.NewFlagSet("", flag.ContinueOnError)
|
var k = koanf.New(".")
|
||||||
// These aren't mapped to the config file.
|
|
||||||
configPath := fs.String("config", "config.yml", "config file to load")
|
|
||||||
generateKeys := fs.Bool("generate-keys", false, "generate cookie signing keys and exit")
|
|
||||||
isDevMode := fs.Bool("dev", false, "enable development mode")
|
|
||||||
|
|
||||||
port := fs.String("port", "8080", "port to listen on")
|
fs := flag.NewFlagSet("", flag.ContinueOnError)
|
||||||
|
fs.StringP("port", "p", "8080", "port to listen on")
|
||||||
|
fs.StringP("config", "c", "config.yml", "config file to load")
|
||||||
|
fs.Bool("generate-keys", false, "generate cookie signing keys and exit")
|
||||||
|
fs.BoolP("version", "v", false, "print version and exit")
|
||||||
|
fs.Usage = func() {
|
||||||
|
fmt.Println("Usage: opds-proxy [flags]")
|
||||||
|
fmt.Println(fs.FlagUsages())
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
if err := fs.Parse(os.Args[1:]); err != nil {
|
if err := fs.Parse(os.Args[1:]); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatalf("error parsing flags: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *generateKeys {
|
if showVersion, _ := fs.GetBool("version"); showVersion {
|
||||||
|
fmt.Println("opds-proxy")
|
||||||
|
fmt.Printf(" Version: %s\n", version)
|
||||||
|
fmt.Printf(" Commit: %s\n", commit)
|
||||||
|
fmt.Printf(" Build Date: %s\n", date)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if generate, _ := fs.GetBool("generate-keys"); generate {
|
||||||
displayKeys()
|
displayKeys()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
var k = koanf.New(".")
|
// YAML Config
|
||||||
|
configPath, _ := fs.GetString("config")
|
||||||
// Load config file from disk.
|
if err := k.Load(file.Provider(configPath), yaml.Parser()); err != nil && !os.IsNotExist(err) {
|
||||||
// Feed options must be defined here.
|
log.Fatalf("error loading config file: %v", err)
|
||||||
if err := k.Load(file.Provider(*configPath), yaml.Parser()); err != nil && !os.IsNotExist(err) {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selectively add command line options to the config. Overriding the config file.
|
// Environment Variables Config
|
||||||
if err := k.Load(confmap.Provider(map[string]interface{}{
|
if err := k.Load(envextended.ProviderWithValue("OPDS", ".", envCallback), json.Parser()); err != nil {
|
||||||
"port": *port,
|
log.Fatalf("error loading environment variables: %v", err)
|
||||||
}, "."), nil); err != nil {
|
}
|
||||||
log.Fatal(err)
|
|
||||||
|
// CLI Flags Config
|
||||||
|
if err := k.Load(posflag.Provider(fs, ".", k), nil); err != nil {
|
||||||
|
log.Fatalf("error loading CLI flags: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
config := ProxyConfig{}
|
config := ProxyConfig{}
|
||||||
k.Unmarshal("", &config)
|
k.Unmarshal("", &config)
|
||||||
|
|
||||||
if len(config.Feeds) == 0 {
|
|
||||||
log.Fatal("No feeds defined in config")
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Auth.HashKey == "" || config.Auth.BlockKey == "" {
|
if config.Auth.HashKey == "" || config.Auth.BlockKey == "" {
|
||||||
log.Println("Generating new cookie signing credentials")
|
log.Println("Generating new cookie signing credentials")
|
||||||
hashKey, blockKey := displayKeys()
|
hashKey, blockKey := displayKeys()
|
||||||
@@ -86,16 +105,17 @@ func main() {
|
|||||||
config.Auth.BlockKey = blockKey
|
config.Auth.BlockKey = blockKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should only be set by the command line flag,
|
if err := config.Validate(); err != nil {
|
||||||
// so we don't use koanf to set this.
|
log.Fatalf("invalid configuration: %v", err)
|
||||||
config.isDevMode = *isDevMode
|
}
|
||||||
|
|
||||||
server, err := NewServer(&config)
|
server, err := NewServer(&config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatalf("error creating server: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = server.Serve(); err != nil && err != http.ErrServerClosed {
|
if err = server.Serve(); err != nil && err != http.ErrServerClosed {
|
||||||
log.Fatal(err)
|
log.Fatalf("error serving: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,3 +130,36 @@ func displayKeys() (string, string) {
|
|||||||
|
|
||||||
return hashKey, blockKey
|
return hashKey, blockKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func envCallback(key string, value string) (string, interface{}) {
|
||||||
|
key = strings.TrimPrefix(key, "OPDS__")
|
||||||
|
key = strings.ReplaceAll(key, "__", ".")
|
||||||
|
key = strings.ToLower(key)
|
||||||
|
return key, value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProxyConfig) Validate() error {
|
||||||
|
if c.Port == "" {
|
||||||
|
return errors.New("port is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Auth.HashKey == "" || c.Auth.BlockKey == "" {
|
||||||
|
return errors.New("auth.hash_key and auth.block_key are required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.Feeds) == 0 {
|
||||||
|
return errors.New("at least one feed must be defined")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, feed := range c.Feeds {
|
||||||
|
if feed.Name == "" {
|
||||||
|
return errors.New("feed.name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if feed.Url == "" {
|
||||||
|
return errors.New("feed.url is required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func NewServer(config *ProxyConfig) (*Server, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !config.isDevMode {
|
if !config.DebugMode {
|
||||||
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
|
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user