Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4a4e441c1 | ||
|
|
e8526141be | ||
|
|
9abb630692 | ||
|
|
9b92ece5a1 | ||
|
|
87a3e54044 | ||
|
|
76b730e2a6 | ||
|
|
51af8d1ab0 | ||
|
|
002ffacd35 | ||
|
|
404cee9371 | ||
|
|
f89e7aa8dc | ||
|
|
a0312ec916 | ||
|
|
83cf59b93e | ||
|
|
ad5de13c25 |
51
README.md
51
README.md
@@ -1,7 +1,7 @@
|
||||
# Gluetun VPN client
|
||||
|
||||
*Lightweight swiss-knife-like VPN client to tunnel to Private Internet Access,
|
||||
Mullvad, Windscribe, Surfshark Cyberghost and NordVPN VPN servers, using Go, OpenVPN,
|
||||
Mullvad, Windscribe, Surfshark Cyberghost, VyprVPN and NordVPN VPN servers, using Go, OpenVPN,
|
||||
iptables, DNS over TLS, ShadowSocks and Tinyproxy*
|
||||
|
||||
**ANNOUNCEMENT**: *[Video of the Git history of Gluetun](https://youtu.be/khipOYJtGJ0)*
|
||||
@@ -35,8 +35,7 @@ iptables, DNS over TLS, ShadowSocks and Tinyproxy*
|
||||
## Features
|
||||
|
||||
- Based on Alpine 3.12 for a small Docker image of 52MB
|
||||
- Supports **Private Internet Access**, **Mullvad**, **Windscribe**,
|
||||
**Surfshark**, **Cyberghost** and **NordVPN** servers
|
||||
- Supports **Private Internet Access**, **Mullvad**, **Windscribe**, **Surfshark**, **Cyberghost**, **Vyprvpn** and **NordVPN** servers
|
||||
- DNS over TLS baked in with service provider(s) of your choice
|
||||
- DNS fine blocking of malicious/ads/surveillance hostnames and IP addresses, with live update every 24 hours
|
||||
- Choose the vpn network protocol, `udp` or `tcp`
|
||||
@@ -47,33 +46,15 @@ iptables, DNS over TLS, ShadowSocks and Tinyproxy*
|
||||
- [Connect LAN devices to it](https://github.com/qdm12/private-internet-access-docker#connect-to-it)
|
||||
- Compatible with amd64, i686 (32 bit), **ARM** 64 bit, ARM 32 bit v6 and v7 🎆
|
||||
|
||||
### Private Internet Access
|
||||
### VPN provider specifics
|
||||
|
||||
- Pick the [region](https://www.privateinternetaccess.com/pages/network/)
|
||||
- Pick the level of encryption
|
||||
- Enable port forwarding
|
||||
|
||||
### Mullvad
|
||||
|
||||
- Pick the [country, city and ISP](https://mullvad.net/en/servers/#openvpn)
|
||||
- Pick the port to use (i.e. `53` (udp) or `80` (tcp))
|
||||
|
||||
### Windscribe
|
||||
|
||||
- Pick the [region](https://windscribe.com/status)
|
||||
- Pick the port to use
|
||||
|
||||
### Surfshark
|
||||
|
||||
- Pick the [region](https://github.com/qdm12/private-internet-access-docker/wiki/Surfshark) or a multi hop region name
|
||||
|
||||
### Cyberghost
|
||||
|
||||
- Pick the [region](https://github.com/qdm12/private-internet-access-docker/wiki/Cyberghost)
|
||||
|
||||
### Vyprvpn
|
||||
|
||||
- Pick the [region](https://www.vyprvpn.com/server-locations)
|
||||
- **Private Internet Access**: pick the [region](https://www.privateinternetaccess.com/pages/network/), the level of encryption and enable port forwarding
|
||||
- **Mullvad**: Pick the [country, city and ISP](https://mullvad.net/en/servers/#openvpn) and optionally a custom port to use (i.e. `53` (udp) or `80` (tcp))
|
||||
- **Windscribe**: Pick the [region](https://windscribe.com/status), and optionally a custom port to use
|
||||
- **Surfshark**: Pick the [region](https://github.com/qdm12/private-internet-access-docker/wiki/Surfshark) or a multi hop region name
|
||||
- **Cyberghost**: Pick the [region](https://github.com/qdm12/private-internet-access-docker/wiki/Cyberghost) and server group.
|
||||
- **VyprVPN**: Pick the [region](https://www.vyprvpn.com/server-locations), port forwarding works by default
|
||||
- **NordVPN**: Pick the region and optionally the server number
|
||||
|
||||
### Extra niche features
|
||||
|
||||
@@ -212,14 +193,13 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet-
|
||||
|
||||
And use the line produced as the value for the environment variable `CLIENT_KEY`.
|
||||
|
||||
- NordVPN
|
||||
- Vyprvpn
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| 🏁 `USER` | | | Your username |
|
||||
| 🏁 `PASSWORD` | | | Your password |
|
||||
| `REGION` | `Austria` | One of the [VyprVPN regions](https://www.vyprvpn.com/server-locations) | VPN server region |
|
||||
| `SERVER_NUMBER` | | Server integer number | Optional server number. For example `251` for `Italy #251` |
|
||||
|
||||
- NordVPN
|
||||
|
||||
@@ -227,7 +207,8 @@ Want more testing? ▶ [see the Wiki](https://github.com/qdm12/private-internet-
|
||||
| --- | --- | --- | --- |
|
||||
| 🏁 `USER` | | | Your username |
|
||||
| 🏁 `PASSWORD` | | | Your password |
|
||||
| 🏁 `REGION` | `Austria` (wrong) | One of the NordVPN server name, i.e. `Cyprus #12` | VPN server name |
|
||||
| 🏁 `REGION` | `Austria` (wrong) | One of the NordVPN server country, i.e. `Switzerland` | VPN server country |
|
||||
| `SERVER_NUMBER` | | Server integer number | Optional server number. For example `251` for `Italy #251` |
|
||||
|
||||
### DNS over TLS
|
||||
|
||||
@@ -289,6 +270,12 @@ That one is important if you want to connect to the container from your LAN for
|
||||
| `UID` | `1000` | | User ID to run as non root and for ownership of files written |
|
||||
| `GID` | `1000` | | Group ID to run as non root and for ownership of files written |
|
||||
|
||||
### Other
|
||||
|
||||
| Variable | Default | Choices | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `PUBLICIP_PERIOD` | `12h` | Valid duration | Period to check for public IP address. Set to `0` to disable. |
|
||||
|
||||
## Connect to it
|
||||
|
||||
There are various ways to achieve this, depending on your use case.
|
||||
|
||||
@@ -137,10 +137,12 @@ func _main(background context.Context, args []string) int {
|
||||
// wait for restartUnbound
|
||||
go unboundLooper.Run(ctx, wg)
|
||||
|
||||
publicIPLooper := publicip.NewLooper(client, logger, fileManager, allSettings.System.IPStatusFilepath, uid, gid)
|
||||
publicIPLooper := publicip.NewLooper(client, logger, fileManager, allSettings.System.IPStatusFilepath, allSettings.PublicIPPeriod, uid, gid)
|
||||
restartPublicIP := publicIPLooper.Restart
|
||||
setPublicIPPeriod := publicIPLooper.SetPeriod
|
||||
go publicIPLooper.Run(ctx)
|
||||
go publicIPLooper.RunRestartTicker(ctx)
|
||||
setPublicIPPeriod(allSettings.PublicIPPeriod) // call after RunRestartTicker
|
||||
|
||||
tinyproxyLooper := tinyproxy.NewLooper(tinyProxyConf, firewallConf, allSettings.TinyProxy, logger, streamMerger, uid, gid)
|
||||
restartTinyproxy := tinyproxyLooper.Restart
|
||||
|
||||
@@ -16,16 +16,24 @@ type Looper interface {
|
||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||
RunRestartTicker(ctx context.Context)
|
||||
Restart()
|
||||
Start()
|
||||
Stop()
|
||||
GetSettings() (settings settings.DNS)
|
||||
SetSettings(settings settings.DNS)
|
||||
}
|
||||
|
||||
type looper struct {
|
||||
conf Configurator
|
||||
settings settings.DNS
|
||||
logger logging.Logger
|
||||
streamMerger command.StreamMerger
|
||||
uid int
|
||||
gid int
|
||||
restart chan struct{}
|
||||
conf Configurator
|
||||
settings settings.DNS
|
||||
settingsMutex sync.RWMutex
|
||||
logger logging.Logger
|
||||
streamMerger command.StreamMerger
|
||||
uid int
|
||||
gid int
|
||||
restart chan struct{}
|
||||
start chan struct{}
|
||||
stop chan struct{}
|
||||
updateTicker chan struct{}
|
||||
}
|
||||
|
||||
func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger,
|
||||
@@ -38,10 +46,44 @@ func NewLooper(conf Configurator, settings settings.DNS, logger logging.Logger,
|
||||
gid: gid,
|
||||
streamMerger: streamMerger,
|
||||
restart: make(chan struct{}),
|
||||
start: make(chan struct{}),
|
||||
stop: make(chan struct{}),
|
||||
updateTicker: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) Restart() { l.restart <- struct{}{} }
|
||||
func (l *looper) Start() { l.start <- struct{}{} }
|
||||
func (l *looper) Stop() { l.stop <- struct{}{} }
|
||||
|
||||
func (l *looper) GetSettings() (settings settings.DNS) {
|
||||
l.settingsMutex.RLock()
|
||||
defer l.settingsMutex.RUnlock()
|
||||
return l.settings
|
||||
}
|
||||
|
||||
func (l *looper) SetSettings(settings settings.DNS) {
|
||||
l.settingsMutex.Lock()
|
||||
defer l.settingsMutex.Unlock()
|
||||
updatePeriodDiffers := l.settings.UpdatePeriod != settings.UpdatePeriod
|
||||
l.settings = settings
|
||||
l.settingsMutex.Unlock()
|
||||
if updatePeriodDiffers {
|
||||
l.updateTicker <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) isEnabled() bool {
|
||||
l.settingsMutex.RLock()
|
||||
defer l.settingsMutex.RUnlock()
|
||||
return l.settings.Enabled
|
||||
}
|
||||
|
||||
func (l *looper) setEnabled(enabled bool) {
|
||||
l.settingsMutex.Lock()
|
||||
defer l.settingsMutex.Unlock()
|
||||
l.settings.Enabled = enabled
|
||||
}
|
||||
|
||||
func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||
l.logger.Warn(err)
|
||||
@@ -55,10 +97,18 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
wg.Add(1)
|
||||
defer wg.Done()
|
||||
l.fallbackToUnencryptedDNS()
|
||||
select {
|
||||
case <-l.restart:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
waitForStart := true
|
||||
for waitForStart {
|
||||
select {
|
||||
case <-l.stop:
|
||||
l.logger.Info("not started yet")
|
||||
case <-l.restart:
|
||||
waitForStart = false
|
||||
case <-l.start:
|
||||
waitForStart = false
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
defer l.logger.Warn("loop exited")
|
||||
|
||||
@@ -66,17 +116,25 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
var unboundCancel context.CancelFunc = func() {}
|
||||
var waitError chan error
|
||||
triggeredRestart := false
|
||||
l.setEnabled(true)
|
||||
for ctx.Err() == nil {
|
||||
if !l.settings.Enabled {
|
||||
// wait for another restart signal to recheck if it is enabled
|
||||
for !l.isEnabled() {
|
||||
// wait for a signal to re-enable
|
||||
select {
|
||||
case <-l.stop:
|
||||
l.logger.Info("already disabled")
|
||||
case <-l.restart:
|
||||
l.setEnabled(true)
|
||||
case <-l.start:
|
||||
l.setEnabled(true)
|
||||
case <-ctx.Done():
|
||||
unboundCancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
settings := l.GetSettings()
|
||||
|
||||
// Setup
|
||||
if err := l.conf.DownloadRootHints(l.uid, l.gid); err != nil {
|
||||
l.logAndWait(ctx, err)
|
||||
@@ -86,7 +144,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
if err := l.conf.MakeUnboundConf(l.settings, l.uid, l.gid); err != nil {
|
||||
if err := l.conf.MakeUnboundConf(settings, l.uid, l.gid); err != nil {
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
}
|
||||
@@ -98,7 +156,7 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
close(waitError)
|
||||
}
|
||||
unboundCtx, unboundCancel = context.WithCancel(context.Background())
|
||||
stream, waitFn, err := l.conf.Start(unboundCtx, l.settings.VerbosityDetailsLevel)
|
||||
stream, waitFn, err := l.conf.Start(unboundCtx, settings.VerbosityDetailsLevel)
|
||||
if err != nil {
|
||||
unboundCancel()
|
||||
l.fallbackToUnencryptedDNS()
|
||||
@@ -108,8 +166,8 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
|
||||
// Started successfully
|
||||
go l.streamMerger.Merge(unboundCtx, stream, command.MergeName("unbound"))
|
||||
l.conf.UseDNSInternally(net.IP{127, 0, 0, 1}) // use Unbound
|
||||
if err := l.conf.UseDNSSystemWide(net.IP{127, 0, 0, 1}, l.settings.KeepNameserver); err != nil { // use Unbound
|
||||
l.conf.UseDNSInternally(net.IP{127, 0, 0, 1}) // use Unbound
|
||||
if err := l.conf.UseDNSSystemWide(net.IP{127, 0, 0, 1}, settings.KeepNameserver); err != nil { // use Unbound
|
||||
l.logger.Error(err)
|
||||
}
|
||||
if err := l.conf.WaitForUnbound(); err != nil {
|
||||
@@ -124,48 +182,63 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
waitError <- err
|
||||
}()
|
||||
|
||||
// Wait for one of the three cases below
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
l.logger.Warn("context canceled: exiting loop")
|
||||
unboundCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
return
|
||||
case <-l.restart: // triggered restart
|
||||
l.logger.Info("restarting")
|
||||
// unboundCancel occurs next loop run when the setup is complete
|
||||
triggeredRestart = true
|
||||
case err := <-waitError: // unexpected error
|
||||
close(waitError)
|
||||
unboundCancel()
|
||||
l.fallbackToUnencryptedDNS()
|
||||
l.logAndWait(ctx, err)
|
||||
stayHere := true
|
||||
for stayHere {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
l.logger.Warn("context canceled: exiting loop")
|
||||
unboundCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
return
|
||||
case <-l.restart: // triggered restart
|
||||
l.logger.Info("restarting")
|
||||
// unboundCancel occurs next loop run when the setup is complete
|
||||
triggeredRestart = true
|
||||
stayHere = false
|
||||
case <-l.start:
|
||||
l.logger.Info("already started")
|
||||
case <-l.stop:
|
||||
l.logger.Info("stopping")
|
||||
unboundCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
l.setEnabled(false)
|
||||
stayHere = false
|
||||
case err := <-waitError: // unexpected error
|
||||
close(waitError)
|
||||
unboundCancel()
|
||||
l.fallbackToUnencryptedDNS()
|
||||
l.logAndWait(ctx, err)
|
||||
stayHere = false
|
||||
}
|
||||
}
|
||||
}
|
||||
unboundCancel()
|
||||
}
|
||||
|
||||
func (l *looper) fallbackToUnencryptedDNS() {
|
||||
settings := l.GetSettings()
|
||||
|
||||
// Try with user provided plaintext ip address
|
||||
targetIP := l.settings.PlaintextAddress
|
||||
targetIP := settings.PlaintextAddress
|
||||
if targetIP != nil {
|
||||
l.logger.Info("falling back on plaintext DNS at address %s", targetIP)
|
||||
l.conf.UseDNSInternally(targetIP)
|
||||
if err := l.conf.UseDNSSystemWide(targetIP, l.settings.KeepNameserver); err != nil {
|
||||
if err := l.conf.UseDNSSystemWide(targetIP, settings.KeepNameserver); err != nil {
|
||||
l.logger.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Try with any IPv4 address from the providers chosen
|
||||
for _, provider := range l.settings.Providers {
|
||||
for _, provider := range settings.Providers {
|
||||
data := constants.DNSProviderMapping()[provider]
|
||||
for _, targetIP = range data.IPs {
|
||||
if targetIP.To4() != nil {
|
||||
l.logger.Info("falling back on plaintext DNS at address %s", targetIP)
|
||||
l.conf.UseDNSInternally(targetIP)
|
||||
if err := l.conf.UseDNSSystemWide(targetIP, l.settings.KeepNameserver); err != nil {
|
||||
if err := l.conf.UseDNSSystemWide(targetIP, settings.KeepNameserver); err != nil {
|
||||
l.logger.Error(err)
|
||||
}
|
||||
return
|
||||
@@ -174,14 +247,17 @@ func (l *looper) fallbackToUnencryptedDNS() {
|
||||
}
|
||||
|
||||
// No IPv4 address found
|
||||
l.logger.Error("no ipv4 DNS address found for providers %s", l.settings.Providers)
|
||||
l.logger.Error("no ipv4 DNS address found for providers %s", settings.Providers)
|
||||
}
|
||||
|
||||
func (l *looper) RunRestartTicker(ctx context.Context) {
|
||||
if l.settings.UpdatePeriod == 0 {
|
||||
return
|
||||
ticker := time.NewTicker(time.Hour)
|
||||
settings := l.GetSettings()
|
||||
if settings.UpdatePeriod > 0 {
|
||||
ticker = time.NewTicker(settings.UpdatePeriod)
|
||||
} else {
|
||||
ticker.Stop()
|
||||
}
|
||||
ticker := time.NewTicker(l.settings.UpdatePeriod)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -189,6 +265,10 @@ func (l *looper) RunRestartTicker(ctx context.Context) {
|
||||
return
|
||||
case <-ticker.C:
|
||||
l.restart <- struct{}{}
|
||||
case <-l.updateTicker:
|
||||
ticker.Stop()
|
||||
period := l.GetSettings().UpdatePeriod
|
||||
ticker = time.NewTicker(period)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
"github.com/qdm12/private-internet-access-docker/internal/constants"
|
||||
)
|
||||
@@ -26,8 +27,35 @@ var regularExpressions = struct { //nolint:gochecknoglobals
|
||||
func PostProcessLine(s string) (filtered string, level logging.Level) {
|
||||
switch {
|
||||
case strings.HasPrefix(s, "openvpn: "):
|
||||
filtered = constants.ColorOpenvpn().Sprintf(s)
|
||||
return filtered, logging.InfoLevel
|
||||
for _, ignored := range []string{
|
||||
"openvpn: WARNING: you are using user/group/chroot/setcon without persist-tun -- this may cause restarts to fail",
|
||||
"openvpn: NOTE: UID/GID downgrade will be delayed because of --client, --pull, or --up-delay",
|
||||
} {
|
||||
if s == ignored {
|
||||
return "", ""
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(s, "openvpn: NOTE: "):
|
||||
filtered = strings.TrimPrefix(s, "openvpn: NOTE: ")
|
||||
filtered = "openvpn: " + filtered
|
||||
level = logging.InfoLevel
|
||||
case strings.HasPrefix(s, "openvpn: WARNING: "):
|
||||
filtered = strings.TrimPrefix(s, "openvpn: WARNING: ")
|
||||
filtered = "openvpn: " + filtered
|
||||
level = logging.WarnLevel
|
||||
case strings.HasPrefix(s, "openvpn: Options error: "):
|
||||
filtered = strings.TrimPrefix(s, "openvpn: Options error: ")
|
||||
filtered = "openvpn: " + filtered
|
||||
level = logging.ErrorLevel
|
||||
case s == "openvpn: Initialization Sequence Completed":
|
||||
return color.HiGreenString(s), logging.InfoLevel
|
||||
default:
|
||||
filtered = s
|
||||
level = logging.InfoLevel
|
||||
}
|
||||
filtered = constants.ColorOpenvpn().Sprintf(filtered)
|
||||
return filtered, level
|
||||
case strings.HasPrefix(s, "unbound: "):
|
||||
prefix := regularExpressions.unboundPrefix.FindString(s)
|
||||
filtered = s[len(prefix):]
|
||||
|
||||
@@ -80,6 +80,30 @@ func Test_PostProcessLine(t *testing.T) {
|
||||
"tinyproxy: BLABLA Jul 12 23:07:25 [32]: Reloading config file",
|
||||
"tinyproxy: Reloading config file",
|
||||
logging.ErrorLevel},
|
||||
"openvpn unknown": {
|
||||
"openvpn: message",
|
||||
"openvpn: message",
|
||||
logging.InfoLevel},
|
||||
"openvpn note": {
|
||||
"openvpn: NOTE: message",
|
||||
"openvpn: message",
|
||||
logging.InfoLevel},
|
||||
"openvpn warning": {
|
||||
"openvpn: WARNING: message",
|
||||
"openvpn: message",
|
||||
logging.WarnLevel},
|
||||
"openvpn options error": {
|
||||
"openvpn: Options error: message",
|
||||
"openvpn: message",
|
||||
logging.ErrorLevel},
|
||||
"openvpn ignored message": {
|
||||
"openvpn: NOTE: UID/GID downgrade will be delayed because of --client, --pull, or --up-delay",
|
||||
"",
|
||||
""},
|
||||
"openvpn success": {
|
||||
"openvpn: Initialization Sequence Completed",
|
||||
"openvpn: Initialization Sequence Completed",
|
||||
logging.InfoLevel},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
tc := tc
|
||||
|
||||
@@ -21,12 +21,15 @@ type Looper interface {
|
||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||
Restart()
|
||||
PortForward()
|
||||
GetSettings() (settings settings.OpenVPN)
|
||||
SetSettings(settings settings.OpenVPN)
|
||||
}
|
||||
|
||||
type looper struct {
|
||||
// Variable parameters
|
||||
provider models.VPNProvider
|
||||
settings settings.OpenVPN
|
||||
provider models.VPNProvider
|
||||
settings settings.OpenVPN
|
||||
settingsMutex sync.RWMutex
|
||||
// Fixed parameters
|
||||
uid int
|
||||
gid int
|
||||
@@ -69,6 +72,18 @@ func NewLooper(provider models.VPNProvider, settings settings.OpenVPN,
|
||||
func (l *looper) Restart() { l.restart <- struct{}{} }
|
||||
func (l *looper) PortForward() { l.portForwardSignals <- struct{}{} }
|
||||
|
||||
func (l *looper) GetSettings() (settings settings.OpenVPN) {
|
||||
l.settingsMutex.RLock()
|
||||
defer l.settingsMutex.RUnlock()
|
||||
return l.settings
|
||||
}
|
||||
|
||||
func (l *looper) SetSettings(settings settings.OpenVPN) {
|
||||
l.settingsMutex.Lock()
|
||||
defer l.settingsMutex.Unlock()
|
||||
l.settings = settings
|
||||
}
|
||||
|
||||
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
wg.Add(1)
|
||||
defer wg.Done()
|
||||
@@ -80,23 +95,24 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
defer l.logger.Warn("loop exited")
|
||||
|
||||
for ctx.Err() == nil {
|
||||
settings := l.GetSettings()
|
||||
providerConf := provider.New(l.provider)
|
||||
connections, err := providerConf.GetOpenVPNConnections(l.settings.Provider.ServerSelection)
|
||||
connections, err := providerConf.GetOpenVPNConnections(settings.Provider.ServerSelection)
|
||||
l.fatalOnError(err)
|
||||
lines := providerConf.BuildConf(
|
||||
connections,
|
||||
l.settings.Verbosity,
|
||||
settings.Verbosity,
|
||||
l.uid,
|
||||
l.gid,
|
||||
l.settings.Root,
|
||||
l.settings.Cipher,
|
||||
l.settings.Auth,
|
||||
l.settings.Provider.ExtraConfigOptions,
|
||||
settings.Root,
|
||||
settings.Cipher,
|
||||
settings.Auth,
|
||||
settings.Provider.ExtraConfigOptions,
|
||||
)
|
||||
err = l.fileManager.WriteLinesToFile(string(constants.OpenVPNConf), lines, files.Ownership(l.uid, l.gid), files.Permissions(0400))
|
||||
l.fatalOnError(err)
|
||||
|
||||
err = l.conf.WriteAuthFile(l.settings.User, l.settings.Password, l.uid, l.gid)
|
||||
err = l.conf.WriteAuthFile(settings.User, settings.Password, l.uid, l.gid)
|
||||
l.fatalOnError(err)
|
||||
|
||||
if err := l.fw.SetVPNConnections(ctx, connections); err != nil {
|
||||
@@ -158,7 +174,8 @@ func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||
}
|
||||
|
||||
func (l *looper) portForward(ctx context.Context, providerConf provider.Provider, client network.Client) {
|
||||
if !l.settings.Provider.PortForwarding.Enabled {
|
||||
settings := l.GetSettings()
|
||||
if !settings.Provider.PortForwarding.Enabled {
|
||||
return
|
||||
}
|
||||
var port uint16
|
||||
@@ -175,7 +192,7 @@ func (l *looper) portForward(ctx context.Context, providerConf provider.Provider
|
||||
l.logger.Info("port forwarded is %d", port)
|
||||
}
|
||||
|
||||
filepath := l.settings.Provider.PortForwarding.Filepath
|
||||
filepath := settings.Provider.PortForwarding.Filepath
|
||||
l.logger.Info("writing forwarded port to %s", filepath)
|
||||
err = l.fileManager.WriteLinesToFile(
|
||||
string(filepath), []string{fmt.Sprintf("%d", port)},
|
||||
|
||||
@@ -99,6 +99,9 @@ type Reader interface {
|
||||
GetTinyProxyUser() (user string, err error)
|
||||
GetTinyProxyPassword() (password string, err error)
|
||||
|
||||
// Public IP getters
|
||||
GetPublicIPPeriod() (period time.Duration, err error)
|
||||
|
||||
// Version getters
|
||||
GetVersion() string
|
||||
GetBuildDate() string
|
||||
|
||||
17
internal/params/publicip.go
Normal file
17
internal/params/publicip.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package params
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
libparams "github.com/qdm12/golibs/params"
|
||||
)
|
||||
|
||||
// GetPublicIPPeriod obtains the period to fetch the IP address periodically.
|
||||
// Set to 0 to disable
|
||||
func (r *reader) GetPublicIPPeriod() (period time.Duration, err error) {
|
||||
s, err := r.envParams.GetEnv("PUBLICIP_PERIOD", libparams.Default("12h"))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return time.ParseDuration(s)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package publicip
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/golibs/files"
|
||||
@@ -14,9 +15,14 @@ type Looper interface {
|
||||
Run(ctx context.Context)
|
||||
RunRestartTicker(ctx context.Context)
|
||||
Restart()
|
||||
Stop()
|
||||
GetPeriod() (period time.Duration)
|
||||
SetPeriod(period time.Duration)
|
||||
}
|
||||
|
||||
type looper struct {
|
||||
period time.Duration
|
||||
periodMutex sync.RWMutex
|
||||
getter IPGetter
|
||||
logger logging.Logger
|
||||
fileManager files.FileManager
|
||||
@@ -24,11 +30,14 @@ type looper struct {
|
||||
uid int
|
||||
gid int
|
||||
restart chan struct{}
|
||||
stop chan struct{}
|
||||
updateTicker chan struct{}
|
||||
}
|
||||
|
||||
func NewLooper(client network.Client, logger logging.Logger, fileManager files.FileManager,
|
||||
ipStatusFilepath models.Filepath, uid, gid int) Looper {
|
||||
ipStatusFilepath models.Filepath, period time.Duration, uid, gid int) Looper {
|
||||
return &looper{
|
||||
period: period,
|
||||
getter: NewIPGetter(client),
|
||||
logger: logger.WithPrefix("ip getter: "),
|
||||
fileManager: fileManager,
|
||||
@@ -36,10 +45,26 @@ func NewLooper(client network.Client, logger logging.Logger, fileManager files.F
|
||||
uid: uid,
|
||||
gid: gid,
|
||||
restart: make(chan struct{}),
|
||||
stop: make(chan struct{}),
|
||||
updateTicker: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) Restart() { l.restart <- struct{}{} }
|
||||
func (l *looper) Stop() { l.stop <- struct{}{} }
|
||||
|
||||
func (l *looper) GetPeriod() (period time.Duration) {
|
||||
l.periodMutex.RLock()
|
||||
defer l.periodMutex.RUnlock()
|
||||
return l.period
|
||||
}
|
||||
|
||||
func (l *looper) SetPeriod(period time.Duration) {
|
||||
l.periodMutex.Lock()
|
||||
l.period = period
|
||||
l.periodMutex.Unlock()
|
||||
l.updateTicker <- struct{}{}
|
||||
}
|
||||
|
||||
func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||
l.logger.Error(err)
|
||||
@@ -57,7 +82,23 @@ func (l *looper) Run(ctx context.Context) {
|
||||
}
|
||||
defer l.logger.Warn("loop exited")
|
||||
|
||||
enabled := true
|
||||
|
||||
for ctx.Err() == nil {
|
||||
for !enabled {
|
||||
// wait for a signal to re-enable
|
||||
select {
|
||||
case <-l.stop:
|
||||
l.logger.Info("already disabled")
|
||||
case <-l.restart:
|
||||
enabled = true
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Enabled and has a period set
|
||||
|
||||
ip, err := l.getter.Get()
|
||||
if err != nil {
|
||||
l.logAndWait(ctx, err)
|
||||
@@ -75,8 +116,9 @@ func (l *looper) Run(ctx context.Context) {
|
||||
}
|
||||
select {
|
||||
case <-l.restart: // triggered restart
|
||||
case <-l.stop:
|
||||
enabled = false
|
||||
case <-ctx.Done():
|
||||
l.logger.Warn("context canceled: exiting loop")
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -84,6 +126,12 @@ func (l *looper) Run(ctx context.Context) {
|
||||
|
||||
func (l *looper) RunRestartTicker(ctx context.Context) {
|
||||
ticker := time.NewTicker(time.Hour)
|
||||
period := l.GetPeriod()
|
||||
if period > 0 {
|
||||
ticker = time.NewTicker(period)
|
||||
} else {
|
||||
ticker.Stop()
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -91,6 +139,9 @@ func (l *looper) RunRestartTicker(ctx context.Context) {
|
||||
return
|
||||
case <-ticker.C:
|
||||
l.restart <- struct{}{}
|
||||
case <-l.updateTicker:
|
||||
ticker.Stop()
|
||||
ticker = time.NewTicker(l.GetPeriod())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package settings
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/qdm12/private-internet-access-docker/internal/models"
|
||||
"github.com/qdm12/private-internet-access-docker/internal/params"
|
||||
@@ -9,13 +10,14 @@ import (
|
||||
|
||||
// Settings contains all settings for the program to run
|
||||
type Settings struct {
|
||||
VPNSP models.VPNProvider
|
||||
OpenVPN OpenVPN
|
||||
System System
|
||||
DNS DNS
|
||||
Firewall Firewall
|
||||
TinyProxy TinyProxy
|
||||
ShadowSocks ShadowSocks
|
||||
VPNSP models.VPNProvider
|
||||
OpenVPN OpenVPN
|
||||
System System
|
||||
DNS DNS
|
||||
Firewall Firewall
|
||||
TinyProxy TinyProxy
|
||||
ShadowSocks ShadowSocks
|
||||
PublicIPPeriod time.Duration
|
||||
}
|
||||
|
||||
func (s *Settings) String() string {
|
||||
@@ -27,6 +29,7 @@ func (s *Settings) String() string {
|
||||
s.Firewall.String(),
|
||||
s.TinyProxy.String(),
|
||||
s.ShadowSocks.String(),
|
||||
"Public IP check period: " + s.PublicIPPeriod.String(),
|
||||
"", // new line at the end
|
||||
}, "\n")
|
||||
}
|
||||
@@ -62,5 +65,9 @@ func GetAllSettings(paramsReader params.Reader) (settings Settings, err error) {
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
settings.PublicIPPeriod, err = paramsReader.GetPublicIPPeriod()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
@@ -14,18 +14,25 @@ import (
|
||||
type Looper interface {
|
||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||
Restart()
|
||||
Start()
|
||||
Stop()
|
||||
GetSettings() (settings settings.ShadowSocks)
|
||||
SetSettings(settings settings.ShadowSocks)
|
||||
}
|
||||
|
||||
type looper struct {
|
||||
conf Configurator
|
||||
firewallConf firewall.Configurator
|
||||
settings settings.ShadowSocks
|
||||
dnsSettings settings.DNS
|
||||
logger logging.Logger
|
||||
streamMerger command.StreamMerger
|
||||
uid int
|
||||
gid int
|
||||
restart chan struct{}
|
||||
conf Configurator
|
||||
firewallConf firewall.Configurator
|
||||
settings settings.ShadowSocks
|
||||
settingsMutex sync.RWMutex
|
||||
dnsSettings settings.DNS // TODO
|
||||
logger logging.Logger
|
||||
streamMerger command.StreamMerger
|
||||
uid int
|
||||
gid int
|
||||
restart chan struct{}
|
||||
start chan struct{}
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||
@@ -48,34 +55,81 @@ func NewLooper(conf Configurator, firewallConf firewall.Configurator, settings s
|
||||
uid: uid,
|
||||
gid: gid,
|
||||
restart: make(chan struct{}),
|
||||
start: make(chan struct{}),
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) Restart() { l.restart <- struct{}{} }
|
||||
func (l *looper) Start() { l.start <- struct{}{} }
|
||||
func (l *looper) Stop() { l.stop <- struct{}{} }
|
||||
|
||||
func (l *looper) GetSettings() (settings settings.ShadowSocks) {
|
||||
l.settingsMutex.RLock()
|
||||
defer l.settingsMutex.RUnlock()
|
||||
return l.settings
|
||||
}
|
||||
|
||||
func (l *looper) SetSettings(settings settings.ShadowSocks) {
|
||||
l.settingsMutex.Lock()
|
||||
defer l.settingsMutex.Unlock()
|
||||
l.settings = settings
|
||||
}
|
||||
|
||||
func (l *looper) isEnabled() bool {
|
||||
l.settingsMutex.RLock()
|
||||
defer l.settingsMutex.RUnlock()
|
||||
return l.settings.Enabled
|
||||
}
|
||||
|
||||
func (l *looper) setEnabled(enabled bool) {
|
||||
l.settingsMutex.Lock()
|
||||
defer l.settingsMutex.Unlock()
|
||||
l.settings.Enabled = enabled
|
||||
}
|
||||
|
||||
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
wg.Add(1)
|
||||
defer wg.Done()
|
||||
select {
|
||||
case <-l.restart:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
waitForStart := true
|
||||
for waitForStart {
|
||||
select {
|
||||
case <-l.stop:
|
||||
l.logger.Info("not started yet")
|
||||
case <-l.start:
|
||||
waitForStart = false
|
||||
case <-l.restart:
|
||||
waitForStart = false
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
defer l.logger.Warn("loop exited")
|
||||
|
||||
l.setEnabled(true)
|
||||
|
||||
var previousPort uint16
|
||||
for ctx.Err() == nil {
|
||||
for !l.isEnabled() {
|
||||
// wait for a signal to re-enable
|
||||
select {
|
||||
case <-l.stop:
|
||||
l.logger.Info("already disabled")
|
||||
case <-l.restart:
|
||||
l.setEnabled(true)
|
||||
case <-l.start:
|
||||
l.setEnabled(true)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
nameserver := l.dnsSettings.PlaintextAddress.String()
|
||||
if l.dnsSettings.Enabled {
|
||||
nameserver = "127.0.0.1"
|
||||
}
|
||||
err := l.conf.MakeConf(
|
||||
l.settings.Port,
|
||||
l.settings.Password,
|
||||
l.settings.Method,
|
||||
nameserver,
|
||||
l.uid,
|
||||
l.gid)
|
||||
settings := l.GetSettings()
|
||||
err := l.conf.MakeConf(settings.Port, settings.Password, settings.Method, nameserver, l.uid, l.gid)
|
||||
if err != nil {
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
@@ -87,14 +141,14 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err := l.firewallConf.SetAllowedPort(ctx, l.settings.Port); err != nil {
|
||||
if err := l.firewallConf.SetAllowedPort(ctx, settings.Port); err != nil {
|
||||
l.logger.Error(err)
|
||||
continue
|
||||
}
|
||||
previousPort = l.settings.Port
|
||||
previousPort = settings.Port
|
||||
|
||||
shadowsocksCtx, shadowsocksCancel := context.WithCancel(context.Background())
|
||||
stdout, stderr, waitFn, err := l.conf.Start(shadowsocksCtx, "0.0.0.0", l.settings.Port, l.settings.Password, l.settings.Log)
|
||||
stdout, stderr, waitFn, err := l.conf.Start(shadowsocksCtx, "0.0.0.0", settings.Port, settings.Password, settings.Log)
|
||||
if err != nil {
|
||||
shadowsocksCancel()
|
||||
l.logAndWait(ctx, err)
|
||||
@@ -107,22 +161,37 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
err := waitFn() // blocking
|
||||
waitError <- err
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
l.logger.Warn("context canceled: exiting loop")
|
||||
shadowsocksCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
return
|
||||
case <-l.restart: // triggered restart
|
||||
l.logger.Info("restarting")
|
||||
shadowsocksCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
case err := <-waitError: // unexpected error
|
||||
shadowsocksCancel()
|
||||
close(waitError)
|
||||
l.logAndWait(ctx, err)
|
||||
|
||||
stayHere := true
|
||||
for stayHere {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
l.logger.Warn("context canceled: exiting loop")
|
||||
shadowsocksCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
return
|
||||
case <-l.restart: // triggered restart
|
||||
l.logger.Info("restarting")
|
||||
shadowsocksCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
stayHere = false
|
||||
case <-l.start:
|
||||
l.logger.Info("already started")
|
||||
case <-l.stop:
|
||||
l.logger.Info("stopping")
|
||||
shadowsocksCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
l.setEnabled(false)
|
||||
stayHere = false
|
||||
case err := <-waitError: // unexpected error
|
||||
shadowsocksCancel()
|
||||
close(waitError)
|
||||
l.logAndWait(ctx, err)
|
||||
}
|
||||
}
|
||||
shadowsocksCancel() // repetition for linter only
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,17 +14,24 @@ import (
|
||||
type Looper interface {
|
||||
Run(ctx context.Context, wg *sync.WaitGroup)
|
||||
Restart()
|
||||
Start()
|
||||
Stop()
|
||||
GetSettings() (settings settings.TinyProxy)
|
||||
SetSettings(settings settings.TinyProxy)
|
||||
}
|
||||
|
||||
type looper struct {
|
||||
conf Configurator
|
||||
firewallConf firewall.Configurator
|
||||
settings settings.TinyProxy
|
||||
logger logging.Logger
|
||||
streamMerger command.StreamMerger
|
||||
uid int
|
||||
gid int
|
||||
restart chan struct{}
|
||||
conf Configurator
|
||||
firewallConf firewall.Configurator
|
||||
settings settings.TinyProxy
|
||||
settingsMutex sync.RWMutex
|
||||
logger logging.Logger
|
||||
streamMerger command.StreamMerger
|
||||
uid int
|
||||
gid int
|
||||
restart chan struct{}
|
||||
start chan struct{}
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
func (l *looper) logAndWait(ctx context.Context, err error) {
|
||||
@@ -46,30 +53,75 @@ func NewLooper(conf Configurator, firewallConf firewall.Configurator, settings s
|
||||
uid: uid,
|
||||
gid: gid,
|
||||
restart: make(chan struct{}),
|
||||
start: make(chan struct{}),
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *looper) GetSettings() (settings settings.TinyProxy) {
|
||||
l.settingsMutex.RLock()
|
||||
defer l.settingsMutex.RUnlock()
|
||||
return l.settings
|
||||
}
|
||||
|
||||
func (l *looper) SetSettings(settings settings.TinyProxy) {
|
||||
l.settingsMutex.Lock()
|
||||
defer l.settingsMutex.Unlock()
|
||||
l.settings = settings
|
||||
}
|
||||
|
||||
func (l *looper) isEnabled() bool {
|
||||
l.settingsMutex.RLock()
|
||||
defer l.settingsMutex.RUnlock()
|
||||
return l.settings.Enabled
|
||||
}
|
||||
|
||||
func (l *looper) setEnabled(enabled bool) {
|
||||
l.settingsMutex.Lock()
|
||||
defer l.settingsMutex.Unlock()
|
||||
l.settings.Enabled = enabled
|
||||
}
|
||||
|
||||
func (l *looper) Restart() { l.restart <- struct{}{} }
|
||||
func (l *looper) Start() { l.start <- struct{}{} }
|
||||
func (l *looper) Stop() { l.stop <- struct{}{} }
|
||||
|
||||
func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
wg.Add(1)
|
||||
defer wg.Done()
|
||||
select {
|
||||
case <-l.restart:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
waitForStart := true
|
||||
for waitForStart {
|
||||
select {
|
||||
case <-l.stop:
|
||||
l.logger.Info("not started yet")
|
||||
case <-l.start:
|
||||
waitForStart = false
|
||||
case <-l.restart:
|
||||
waitForStart = false
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
defer l.logger.Warn("loop exited")
|
||||
|
||||
var previousPort uint16
|
||||
for ctx.Err() == nil {
|
||||
err := l.conf.MakeConf(
|
||||
l.settings.LogLevel,
|
||||
l.settings.Port,
|
||||
l.settings.User,
|
||||
l.settings.Password,
|
||||
l.uid,
|
||||
l.gid)
|
||||
for !l.isEnabled() {
|
||||
// wait for a signal to re-enable
|
||||
select {
|
||||
case <-l.stop:
|
||||
l.logger.Info("already disabled")
|
||||
case <-l.restart:
|
||||
l.setEnabled(true)
|
||||
case <-l.start:
|
||||
l.setEnabled(true)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
settings := l.GetSettings()
|
||||
err := l.conf.MakeConf(settings.LogLevel, settings.Port, settings.User, settings.Password, l.uid, l.gid)
|
||||
if err != nil {
|
||||
l.logAndWait(ctx, err)
|
||||
continue
|
||||
@@ -81,11 +133,11 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err := l.firewallConf.SetAllowedPort(ctx, l.settings.Port); err != nil {
|
||||
if err := l.firewallConf.SetAllowedPort(ctx, settings.Port); err != nil {
|
||||
l.logger.Error(err)
|
||||
continue
|
||||
}
|
||||
previousPort = l.settings.Port
|
||||
previousPort = settings.Port
|
||||
|
||||
tinyproxyCtx, tinyproxyCancel := context.WithCancel(context.Background())
|
||||
stream, waitFn, err := l.conf.Start(tinyproxyCtx)
|
||||
@@ -100,22 +152,36 @@ func (l *looper) Run(ctx context.Context, wg *sync.WaitGroup) {
|
||||
err := waitFn() // blocking
|
||||
waitError <- err
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
l.logger.Warn("context canceled: exiting loop")
|
||||
tinyproxyCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
return
|
||||
case <-l.restart: // triggered restart
|
||||
l.logger.Info("restarting")
|
||||
tinyproxyCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
case err := <-waitError: // unexpected error
|
||||
tinyproxyCancel()
|
||||
close(waitError)
|
||||
l.logAndWait(ctx, err)
|
||||
stayHere := true
|
||||
for stayHere {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
l.logger.Warn("context canceled: exiting loop")
|
||||
tinyproxyCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
return
|
||||
case <-l.restart: // triggered restart
|
||||
l.logger.Info("restarting")
|
||||
tinyproxyCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
stayHere = false
|
||||
case <-l.start:
|
||||
l.logger.Info("already started")
|
||||
case <-l.stop:
|
||||
l.logger.Info("stopping")
|
||||
tinyproxyCancel()
|
||||
<-waitError
|
||||
close(waitError)
|
||||
l.setEnabled(false)
|
||||
stayHere = false
|
||||
case err := <-waitError: // unexpected error
|
||||
tinyproxyCancel()
|
||||
close(waitError)
|
||||
l.logAndWait(ctx, err)
|
||||
}
|
||||
}
|
||||
tinyproxyCancel() // repetition for linter only
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user