Go HTTP control server with restart openvpn route

- Fix #147
- Dockerfile updated
- Documentation updated
- Using contexts to restart openvpn
- Code foundation for more http routes
This commit is contained in:
Quentin McGaw
2020-04-30 23:41:57 +00:00
parent 944e6a107b
commit f4cd1896c9
6 changed files with 126 additions and 6 deletions

View File

@@ -85,7 +85,7 @@ ENV VPNSP="private internet access" \
SHADOWSOCKS_PASSWORD= \
SHADOWSOCKS_METHOD=chacha20-ietf-poly1305
ENTRYPOINT /entrypoint
EXPOSE 8888/tcp 8388/tcp 8388/udp
EXPOSE 8000/tcp 8888/tcp 8388/tcp 8388/udp
HEALTHCHECK --interval=3m --timeout=3s --start-period=20s --retries=1 CMD /entrypoint healthcheck
RUN apk add -q --progress --no-cache --update openvpn ca-certificates iptables unbound tinyproxy tzdata && \
echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories && \

View File

@@ -98,6 +98,7 @@
- Change the many [environment variables](#environment-variables) available
- Use `-p 8888:8888/tcp` to access the HTTP web proxy (and put your LAN in `EXTRA_SUBNETS` environment variable, in example `192.168.1.0/24`)
- Use `-p 8388:8388/tcp -p 8388:8388/udp` to access the SOCKS5 proxy (and put your LAN in `EXTRA_SUBNETS` environment variable, in example `192.168.1.0/24`)
- Use `-p 8000:8000/tcp` to access the [HTTP control server](#HTTP-control-server) built-in
- Pass additional arguments to *openvpn* using Docker's command function (commands after the image name)
1. You can update the image with `docker pull qmcgaw/private-internet-access:latest`. There are also docker tags for older versions available:
@@ -260,6 +261,12 @@ When `PORT_FORWARDING=on`, a port will be forwarded on the PIA server side and w
It can be useful to mount this file as a volume to read it from other containers, for example to configure a torrenting client.
## HTTP control server
A built-in HTTP server listens on port `8000` to modify the state of the container. You have the following routes available:
- `http://<your-docker-host-ip>:8000/openvpn/actions/restart` restarts the openvpn process
## FAQ
Please refer to [the FAQ table of content](https://github.com/qdm12/private-internet-access-docker/blob/master/doc/faq.md#Table-of-content)

View File

@@ -27,6 +27,7 @@ import (
"github.com/qdm12/private-internet-access-docker/internal/params"
"github.com/qdm12/private-internet-access-docker/internal/pia"
"github.com/qdm12/private-internet-access-docker/internal/routing"
"github.com/qdm12/private-internet-access-docker/internal/server"
"github.com/qdm12/private-internet-access-docker/internal/settings"
"github.com/qdm12/private-internet-access-docker/internal/shadowsocks"
"github.com/qdm12/private-internet-access-docker/internal/splash"
@@ -267,13 +268,20 @@ func main() {
go streamMerger.Merge(ctx, stdout, command.MergeName("shadowsocks"), command.MergeColor(constants.ColorShadowsocks()))
go streamMerger.Merge(ctx, stderr, command.MergeName("shadowsocks error"), command.MergeColor(constants.ColorShadowsocksError()))
}
httpServer := server.New("0.0.0.0:8000", logger)
// Runs openvpn and restarts it if it does not exit cleanly
openvpnCancelSet, signalOpenvpnCancelSet := context.WithCancel(context.Background())
go func() {
waitErrors := make(chan error)
for {
stream, waitFn, err := ovpnConf.Start(ctx)
openvpnCtx, openvpnCancel := context.WithCancel(ctx)
stream, waitFn, err := ovpnConf.Start(openvpnCtx)
e.FatalOnError(err)
go streamMerger.Merge(ctx, stream, command.MergeName("openvpn"), command.MergeColor(constants.ColorOpenvpn()))
httpServer.SetOpenVPNRestart(openvpnCancel)
signalOpenvpnCancelSet()
go streamMerger.Merge(openvpnCtx, stream, command.MergeName("openvpn"), command.MergeColor(constants.ColorOpenvpn()))
waiter.Add(func() error {
err := <-waitErrors
logger.Error("openvpn: %s", err)
@@ -284,8 +292,18 @@ func main() {
} else {
break
}
openvpnCancel()
}
}()
<-openvpnCancelSet.Done()
waiter.Add(func() error {
err := httpServer.Run(ctx)
logger.Error("http server: %s", err)
return err
})
signalsCh := make(chan os.Signal, 1)
signal.Notify(signalsCh,
syscall.SIGINT,

View File

@@ -37,3 +37,4 @@ You need the following to allow communicating with the VPN servers
- If `SHADOWSOCKS=on`, allow inbound TCP 8388 and UDP 8388 from your LAN
- If `TINYPROXY=on`, allow inbound TCP 8888 from your LAN
- If you want access to the built-in HTTP control server, allow inbound TCP 8000 from your LAN

View File

@@ -8,9 +8,10 @@ services:
network_mode: bridge
init: true
ports:
- 8888:8888/tcp
- 8388:8388/tcp
- 8388:8388/udp
- 8888:8888/tcp # Tinyproxy
- 8388:8388/tcp # Shadowsocks
- 8388:8388/udp # Shadowsocks
- 8000:8000/tcp # Built-in HTTP control server
# command:
environment:
# More variables are available, see the readme table

93
internal/server/server.go Normal file
View File

@@ -0,0 +1,93 @@
package server
import (
"context"
"fmt"
"net/http"
"sync"
"time"
"github.com/qdm12/golibs/logging"
)
type Server interface {
SetOpenVPNRestart(f func())
Run(ctx context.Context) error
}
type server struct {
address string
logger logging.Logger
restartOpenvpn func()
sync.RWMutex
}
func New(address string, logger logging.Logger) Server {
return &server{
address: address,
logger: logger.WithPrefix("http server: "),
}
}
func (s *server) Run(ctx context.Context) error {
if s.restartOpenvpn == nil {
s.logger.Warn("restartOpenvpn function is not set")
}
server := http.Server{Addr: s.address, Handler: s.makeHandler()}
go func() {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
if err := server.Shutdown(shutdownCtx); err != nil {
s.logger.Error("failed shutting down: %s", err)
}
}()
s.logger.Info("listening on %s", s.address)
return server.ListenAndServe()
}
func (s *server) SetOpenVPNRestart(f func()) {
s.Lock()
defer s.Unlock()
s.restartOpenvpn = f
}
func (s *server) makeHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
s.logger.Info("HTTP %s %s", r.Method, r.RequestURI)
switch r.Method {
case http.MethodGet:
switch r.RequestURI {
case "/openvpn/actions/restart":
s.RLock()
defer s.RUnlock()
if s.restartOpenvpn == nil {
functionNotSet("restartOpenvpn", s.logger, w)
return
}
s.restartOpenvpn()
default:
routeDoesNotExist(s.logger, w, r)
}
default:
routeDoesNotExist(s.logger, w, r)
}
}
}
func routeDoesNotExist(logger logging.Logger, w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
_, err := w.Write([]byte(fmt.Sprintf("Nothing here for %s %s", r.Method, r.RequestURI)))
if err != nil {
logger.Error(err)
}
}
func functionNotSet(functionName string, logger logging.Logger, w http.ResponseWriter) {
logger.Error("function %s is not set", functionName)
w.WriteHeader(http.StatusInternalServerError)
_, err := w.Write([]byte(fmt.Sprintf("%s function is not set", functionName)))
if err != nil {
logger.Error(err)
}
}