feat(publicip): PUBLICIP_ENABLED replaces PUBLICIP_PERIOD
- No point periodically fetch the public IP address. Could not find anything mentioning why this was added. - Simplification of the publicip loop code - `PUBLICIP_ENABLED` (on, off) can be set to enable or not public ip data fetching on VPN connection - `PUBLICIP_PERIOD=0` still works to indicate to disable public ip fetching - `PUBLICIP_PERIOD` != 0 means to enable public ip fetching - Warnings logged when using `PUBLICIP_PERIOD`
This commit is contained in:
@@ -204,7 +204,7 @@ ENV VPN_SERVICE_PROVIDER=pia \
|
|||||||
UPDATER_VPN_SERVICE_PROVIDERS= \
|
UPDATER_VPN_SERVICE_PROVIDERS= \
|
||||||
# Public IP
|
# Public IP
|
||||||
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
PUBLICIP_FILE="/tmp/gluetun/ip" \
|
||||||
PUBLICIP_PERIOD=12h \
|
PUBLICIP_ENABLED=on \
|
||||||
PUBLICIP_API=ipinfo \
|
PUBLICIP_API=ipinfo \
|
||||||
PUBLICIP_API_TOKEN= \
|
PUBLICIP_API_TOKEN= \
|
||||||
# Storage
|
# Storage
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ var (
|
|||||||
ErrPortForwardingEnabled = errors.New("port forwarding cannot be enabled")
|
ErrPortForwardingEnabled = errors.New("port forwarding cannot be enabled")
|
||||||
ErrPortForwardingUserEmpty = errors.New("port forwarding username is empty")
|
ErrPortForwardingUserEmpty = errors.New("port forwarding username is empty")
|
||||||
ErrPortForwardingPasswordEmpty = errors.New("port forwarding password is empty")
|
ErrPortForwardingPasswordEmpty = errors.New("port forwarding password is empty")
|
||||||
ErrPublicIPPeriodTooShort = errors.New("public IP address check period is too short")
|
|
||||||
ErrRegionNotValid = errors.New("the region specified is not valid")
|
ErrRegionNotValid = errors.New("the region specified is not valid")
|
||||||
ErrServerAddressNotValid = errors.New("server listening address is not valid")
|
ErrServerAddressNotValid = errors.New("server listening address is not valid")
|
||||||
ErrSystemPGIDNotValid = errors.New("process group id is not valid")
|
ErrSystemPGIDNotValid = errors.New("process group id is not valid")
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package settings
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/publicip/api"
|
"github.com/qdm12/gluetun/internal/publicip/api"
|
||||||
"github.com/qdm12/gosettings"
|
"github.com/qdm12/gosettings"
|
||||||
@@ -13,11 +12,9 @@ import (
|
|||||||
|
|
||||||
// PublicIP contains settings for port forwarding.
|
// PublicIP contains settings for port forwarding.
|
||||||
type PublicIP struct {
|
type PublicIP struct {
|
||||||
// Period is the period to get the public IP address.
|
// Enabled is set to true to fetch the public ip address
|
||||||
// It can be set to 0 to disable periodic checking.
|
// information on VPN connection. It defaults to true.
|
||||||
// It cannot be nil for the internal state.
|
Enabled *bool
|
||||||
// TODO change to value and add enabled field
|
|
||||||
Period *time.Duration
|
|
||||||
// IPFilepath is the public IP address status file path
|
// IPFilepath is the public IP address status file path
|
||||||
// to use. It can be the empty string to indicate not
|
// to use. It can be the empty string to indicate not
|
||||||
// to write to a file. It cannot be nil for the
|
// to write to a file. It cannot be nil for the
|
||||||
@@ -48,12 +45,6 @@ func (p PublicIP) UpdateWith(partialUpdate PublicIP) (updatedSettings PublicIP,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p PublicIP) validate() (err error) {
|
func (p PublicIP) validate() (err error) {
|
||||||
const minPeriod = 5 * time.Second
|
|
||||||
if *p.Period < minPeriod {
|
|
||||||
return fmt.Errorf("%w: %s must be at least %s",
|
|
||||||
ErrPublicIPPeriodTooShort, p.Period, minPeriod)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *p.IPFilepath != "" { // optional
|
if *p.IPFilepath != "" { // optional
|
||||||
_, err := filepath.Abs(*p.IPFilepath)
|
_, err := filepath.Abs(*p.IPFilepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -71,7 +62,7 @@ func (p PublicIP) validate() (err error) {
|
|||||||
|
|
||||||
func (p *PublicIP) copy() (copied PublicIP) {
|
func (p *PublicIP) copy() (copied PublicIP) {
|
||||||
return PublicIP{
|
return PublicIP{
|
||||||
Period: gosettings.CopyPointer(p.Period),
|
Enabled: gosettings.CopyPointer(p.Enabled),
|
||||||
IPFilepath: gosettings.CopyPointer(p.IPFilepath),
|
IPFilepath: gosettings.CopyPointer(p.IPFilepath),
|
||||||
API: p.API,
|
API: p.API,
|
||||||
APIToken: gosettings.CopyPointer(p.APIToken),
|
APIToken: gosettings.CopyPointer(p.APIToken),
|
||||||
@@ -79,15 +70,14 @@ func (p *PublicIP) copy() (copied PublicIP) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PublicIP) overrideWith(other PublicIP) {
|
func (p *PublicIP) overrideWith(other PublicIP) {
|
||||||
p.Period = gosettings.OverrideWithPointer(p.Period, other.Period)
|
p.Enabled = gosettings.OverrideWithPointer(p.Enabled, other.Enabled)
|
||||||
p.IPFilepath = gosettings.OverrideWithPointer(p.IPFilepath, other.IPFilepath)
|
p.IPFilepath = gosettings.OverrideWithPointer(p.IPFilepath, other.IPFilepath)
|
||||||
p.API = gosettings.OverrideWithComparable(p.API, other.API)
|
p.API = gosettings.OverrideWithComparable(p.API, other.API)
|
||||||
p.APIToken = gosettings.OverrideWithPointer(p.APIToken, other.APIToken)
|
p.APIToken = gosettings.OverrideWithPointer(p.APIToken, other.APIToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PublicIP) setDefaults() {
|
func (p *PublicIP) setDefaults() {
|
||||||
const defaultPeriod = 12 * time.Hour
|
p.Enabled = gosettings.DefaultPointer(p.Enabled, true)
|
||||||
p.Period = gosettings.DefaultPointer(p.Period, defaultPeriod)
|
|
||||||
p.IPFilepath = gosettings.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip")
|
p.IPFilepath = gosettings.DefaultPointer(p.IPFilepath, "/tmp/gluetun/ip")
|
||||||
p.API = gosettings.DefaultComparable(p.API, "ipinfo")
|
p.API = gosettings.DefaultComparable(p.API, "ipinfo")
|
||||||
p.APIToken = gosettings.DefaultPointer(p.APIToken, "")
|
p.APIToken = gosettings.DefaultPointer(p.APIToken, "")
|
||||||
@@ -98,19 +88,12 @@ func (p PublicIP) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p PublicIP) toLinesNode() (node *gotree.Node) {
|
func (p PublicIP) toLinesNode() (node *gotree.Node) {
|
||||||
|
if !*p.Enabled {
|
||||||
|
return gotree.New("Public IP settings: disabled")
|
||||||
|
}
|
||||||
|
|
||||||
node = gotree.New("Public IP settings:")
|
node = gotree.New("Public IP settings:")
|
||||||
|
|
||||||
if *p.Period == 0 {
|
|
||||||
node.Appendf("Enabled: no")
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
updatePeriod := "disabled"
|
|
||||||
if *p.Period > 0 {
|
|
||||||
updatePeriod = "every " + p.Period.String()
|
|
||||||
}
|
|
||||||
node.Appendf("Fetching: %s", updatePeriod)
|
|
||||||
|
|
||||||
if *p.IPFilepath != "" {
|
if *p.IPFilepath != "" {
|
||||||
node.Appendf("IP file path: %s", *p.IPFilepath)
|
node.Appendf("IP file path: %s", *p.IPFilepath)
|
||||||
}
|
}
|
||||||
@@ -124,8 +107,8 @@ func (p PublicIP) toLinesNode() (node *gotree.Node) {
|
|||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PublicIP) read(r *reader.Reader) (err error) {
|
func (p *PublicIP) read(r *reader.Reader, warner Warner) (err error) {
|
||||||
p.Period, err = r.DurationPtr("PUBLICIP_PERIOD")
|
p.Enabled, err = readPublicIPEnabled(r, warner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -136,3 +119,23 @@ func (p *PublicIP) read(r *reader.Reader) (err error) {
|
|||||||
p.APIToken = r.Get("PUBLICIP_API_TOKEN")
|
p.APIToken = r.Get("PUBLICIP_API_TOKEN")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readPublicIPEnabled(r *reader.Reader, warner Warner) (
|
||||||
|
enabled *bool, err error) {
|
||||||
|
periodPtr, err := r.DurationPtr("PUBLICIP_PERIOD") // Retro-compatibility
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if periodPtr == nil {
|
||||||
|
return r.BoolPtr("PUBLICIP_ENABLED")
|
||||||
|
}
|
||||||
|
|
||||||
|
if *periodPtr == 0 {
|
||||||
|
warner.Warn("please replace PUBLICIP_PERIOD=0 with PUBLICIP_ENABLED=no")
|
||||||
|
return ptrTo(false), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
warner.Warn("PUBLICIP_PERIOD is no longer used. " +
|
||||||
|
"It is assumed from its non-zero value you want PUBLICIP_ENABLED=yes. " +
|
||||||
|
"Please migrate to use PUBLICIP_ENABLED only in the future.")
|
||||||
|
return ptrTo(true), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -197,7 +197,9 @@ func (s *Settings) Read(r *reader.Reader, warner Warner) (err error) {
|
|||||||
"health": s.Health.Read,
|
"health": s.Health.Read,
|
||||||
"http proxy": s.HTTPProxy.read,
|
"http proxy": s.HTTPProxy.read,
|
||||||
"log": s.Log.read,
|
"log": s.Log.read,
|
||||||
"public ip": s.PublicIP.read,
|
"public ip": func(r *reader.Reader) error {
|
||||||
|
return s.PublicIP.read(r, warner)
|
||||||
|
},
|
||||||
"shadowsocks": s.Shadowsocks.read,
|
"shadowsocks": s.Shadowsocks.read,
|
||||||
"storage": s.Storage.read,
|
"storage": s.Storage.read,
|
||||||
"system": s.System.read,
|
"system": s.System.read,
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ func Test_Settings_String(t *testing.T) {
|
|||||||
| ├── Process UID: 1000
|
| ├── Process UID: 1000
|
||||||
| └── Process GID: 1000
|
| └── Process GID: 1000
|
||||||
├── Public IP settings:
|
├── Public IP settings:
|
||||||
| ├── Fetching: every 12h0m0s
|
|
||||||
| ├── IP file path: /tmp/gluetun/ip
|
| ├── IP file path: /tmp/gluetun/ip
|
||||||
| └── Public IP data API: ipinfo
|
| └── Public IP data API: ipinfo
|
||||||
└── Version settings:
|
└── Version settings:
|
||||||
|
|||||||
@@ -79,9 +79,12 @@ func (s *Service) Start(ctx context.Context) (runError <-chan error, err error)
|
|||||||
keepPortDoneCh := make(chan struct{})
|
keepPortDoneCh := make(chan struct{})
|
||||||
s.keepPortDoneCh = keepPortDoneCh
|
s.keepPortDoneCh = keepPortDoneCh
|
||||||
|
|
||||||
|
readyCh := make(chan struct{})
|
||||||
go func(ctx context.Context, portForwarder PortForwarder,
|
go func(ctx context.Context, portForwarder PortForwarder,
|
||||||
obj utils.PortForwardObjects, runError chan<- error, doneCh chan<- struct{}) {
|
obj utils.PortForwardObjects, readyCh chan<- struct{},
|
||||||
|
runError chan<- error, doneCh chan<- struct{}) {
|
||||||
defer close(doneCh)
|
defer close(doneCh)
|
||||||
|
close(readyCh)
|
||||||
err = portForwarder.KeepPortForward(ctx, obj)
|
err = portForwarder.KeepPortForward(ctx, obj)
|
||||||
crashed := ctx.Err() == nil
|
crashed := ctx.Err() == nil
|
||||||
if !crashed { // stopped by Stop call
|
if !crashed { // stopped by Stop call
|
||||||
@@ -91,7 +94,8 @@ func (s *Service) Start(ctx context.Context) (runError <-chan error, err error)
|
|||||||
defer s.startStopMutex.Unlock()
|
defer s.startStopMutex.Unlock()
|
||||||
_ = s.cleanup()
|
_ = s.cleanup()
|
||||||
runError <- err
|
runError <- err
|
||||||
}(keepPortCtx, s.settings.PortForwarder, obj, runErrorCh, keepPortDoneCh)
|
}(keepPortCtx, s.settings.PortForwarder, obj, readyCh, runErrorCh, keepPortDoneCh)
|
||||||
|
<-readyCh
|
||||||
|
|
||||||
return runErrorCh, nil
|
return runErrorCh, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,14 +76,8 @@ func (l *Loop) run(runCtx context.Context, runDone chan<- struct{},
|
|||||||
updateTrigger <-chan settings.PublicIP, updatedResult chan<- error) {
|
updateTrigger <-chan settings.PublicIP, updatedResult chan<- error) {
|
||||||
defer close(runDone)
|
defer close(runDone)
|
||||||
|
|
||||||
timer := time.NewTimer(time.Hour)
|
|
||||||
defer timer.Stop()
|
|
||||||
_ = timer.Stop()
|
|
||||||
timerIsReadyToReset := true
|
|
||||||
lastFetch := time.Unix(0, 0)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
singleRunCtx := runCtx
|
var singleRunCtx context.Context
|
||||||
var singleRunResult chan<- error
|
var singleRunResult chan<- error
|
||||||
select {
|
select {
|
||||||
case <-runCtx.Done():
|
case <-runCtx.Done():
|
||||||
@@ -91,26 +85,17 @@ func (l *Loop) run(runCtx context.Context, runDone chan<- struct{},
|
|||||||
case singleRunCtx = <-runTrigger:
|
case singleRunCtx = <-runTrigger:
|
||||||
// Note singleRunCtx is canceled if runCtx is canceled.
|
// Note singleRunCtx is canceled if runCtx is canceled.
|
||||||
singleRunResult = runResult
|
singleRunResult = runResult
|
||||||
case <-timer.C:
|
|
||||||
timerIsReadyToReset = true
|
|
||||||
case partialUpdate := <-updateTrigger:
|
case partialUpdate := <-updateTrigger:
|
||||||
var err error
|
var err error
|
||||||
timerIsReadyToReset, err = l.update(partialUpdate, lastFetch, timer, timerIsReadyToReset)
|
err = l.update(partialUpdate)
|
||||||
updatedResult <- err
|
updatedResult <- err
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
lastFetch = l.timeNow()
|
|
||||||
timerIsReadyToReset = l.updateTimer(*l.settings.Period, lastFetch, timer, timerIsReadyToReset)
|
|
||||||
|
|
||||||
result, err := l.fetcher.FetchInfo(singleRunCtx, netip.Addr{})
|
result, err := l.fetcher.FetchInfo(singleRunCtx, netip.Addr{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("fetching information: %w", err)
|
err = fmt.Errorf("fetching information: %w", err)
|
||||||
if singleRunResult != nil {
|
|
||||||
singleRunResult <- err
|
singleRunResult <- err
|
||||||
} else {
|
|
||||||
l.logger.Error(err.Error())
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,11 +113,7 @@ func (l *Loop) run(runCtx context.Context, runDone chan<- struct{},
|
|||||||
err = fmt.Errorf("persisting public ip address: %w", err)
|
err = fmt.Errorf("persisting public ip address: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if singleRunResult != nil {
|
|
||||||
singleRunResult <- err
|
singleRunResult <- err
|
||||||
} else if err != nil {
|
|
||||||
l.logger.Error(err.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,25 +3,16 @@ package publicip
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/configuration/settings"
|
"github.com/qdm12/gluetun/internal/configuration/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *Loop) update(partialUpdate settings.PublicIP,
|
func (l *Loop) update(partialUpdate settings.PublicIP) (err error) {
|
||||||
lastTick time.Time, timer *time.Timer, timerIsReadyToReset bool) (
|
|
||||||
newTimerIsReadyToReset bool, err error) {
|
|
||||||
newTimerIsReadyToReset = timerIsReadyToReset
|
|
||||||
// No need to lock the mutex since it can only be written
|
// No need to lock the mutex since it can only be written
|
||||||
// in the code below in this goroutine.
|
// in the code below in this goroutine.
|
||||||
updatedSettings, err := l.settings.UpdateWith(partialUpdate)
|
updatedSettings, err := l.settings.UpdateWith(partialUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newTimerIsReadyToReset, err
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
if *l.settings.Period != *updatedSettings.Period {
|
|
||||||
newTimerIsReadyToReset = l.updateTimer(*updatedSettings.Period, lastTick,
|
|
||||||
timer, timerIsReadyToReset)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if *l.settings.IPFilepath != *updatedSettings.IPFilepath {
|
if *l.settings.IPFilepath != *updatedSettings.IPFilepath {
|
||||||
@@ -30,17 +21,17 @@ func (l *Loop) update(partialUpdate settings.PublicIP,
|
|||||||
err = persistPublicIP(*updatedSettings.IPFilepath,
|
err = persistPublicIP(*updatedSettings.IPFilepath,
|
||||||
l.ipData.IP.String(), l.puid, l.pgid)
|
l.ipData.IP.String(), l.puid, l.pgid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newTimerIsReadyToReset, fmt.Errorf("persisting ip data: %w", err)
|
return fmt.Errorf("persisting ip data: %w", err)
|
||||||
}
|
}
|
||||||
case *updatedSettings.IPFilepath == "":
|
case *updatedSettings.IPFilepath == "":
|
||||||
err = os.Remove(*l.settings.IPFilepath)
|
err = os.Remove(*l.settings.IPFilepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newTimerIsReadyToReset, fmt.Errorf("removing ip data file path: %w", err)
|
return fmt.Errorf("removing ip data file path: %w", err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
err = os.Rename(*l.settings.IPFilepath, *updatedSettings.IPFilepath)
|
err = os.Rename(*l.settings.IPFilepath, *updatedSettings.IPFilepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newTimerIsReadyToReset, fmt.Errorf("renaming ip data file path: %w", err)
|
return fmt.Errorf("renaming ip data file path: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,34 +40,5 @@ func (l *Loop) update(partialUpdate settings.PublicIP,
|
|||||||
l.settings = updatedSettings
|
l.settings = updatedSettings
|
||||||
l.settingsMutex.Unlock()
|
l.settingsMutex.Unlock()
|
||||||
|
|
||||||
return newTimerIsReadyToReset, nil
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Loop) updateTimer(period time.Duration, lastFetch time.Time,
|
|
||||||
timer *time.Timer, timerIsReadyToReset bool) (newTimerIsReadyToReset bool) {
|
|
||||||
disableTimer := period == 0
|
|
||||||
if disableTimer {
|
|
||||||
if !timer.Stop() {
|
|
||||||
<-timer.C
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if !timerIsReadyToReset {
|
|
||||||
if !timer.Stop() {
|
|
||||||
<-timer.C
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var waited time.Duration
|
|
||||||
if lastFetch.UnixNano() > 0 {
|
|
||||||
waited = l.timeNow().Sub(lastFetch)
|
|
||||||
}
|
|
||||||
leftToWait := period - waited
|
|
||||||
if leftToWait <= 0 {
|
|
||||||
leftToWait = time.Nanosecond
|
|
||||||
}
|
|
||||||
|
|
||||||
timer.Reset(leftToWait)
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user