Fix #135
This commit is contained in:
@@ -38,6 +38,7 @@ LABEL \
|
|||||||
org.opencontainers.image.title="VPN client for PIA, Mullvad, Windscribe, Surfshark and Cyberghost" \
|
org.opencontainers.image.title="VPN client for PIA, Mullvad, Windscribe, Surfshark and Cyberghost" \
|
||||||
org.opencontainers.image.description="VPN client to tunnel to PIA, Mullvad, Windscribe, Surfshark and Cyberghost servers using OpenVPN, IPtables, DNS over TLS and Alpine Linux"
|
org.opencontainers.image.description="VPN client to tunnel to PIA, Mullvad, Windscribe, Surfshark and Cyberghost servers using OpenVPN, IPtables, DNS over TLS and Alpine Linux"
|
||||||
ENV VPNSP=pia \
|
ENV VPNSP=pia \
|
||||||
|
VERSION_INFORMATION=on \
|
||||||
PROTOCOL=udp \
|
PROTOCOL=udp \
|
||||||
OPENVPN_VERBOSITY=1 \
|
OPENVPN_VERBOSITY=1 \
|
||||||
OPENVPN_ROOT=no \
|
OPENVPN_ROOT=no \
|
||||||
|
|||||||
@@ -253,6 +253,7 @@ That one is important if you want to connect to the container from your LAN for
|
|||||||
| Variable | Default | Choices | Description |
|
| Variable | Default | Choices | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `PUBLICIP_PERIOD` | `12h` | Valid duration | Period to check for public IP address. Set to `0` to disable. |
|
| `PUBLICIP_PERIOD` | `12h` | Valid duration | Period to check for public IP address. Set to `0` to disable. |
|
||||||
|
| `VERSION_INFORMATION` | `on` | `on`, `off` | Logs a message indicating if a newer version is available once the VPN is connected |
|
||||||
|
|
||||||
## Connect to it
|
## Connect to it
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -25,6 +26,7 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/shadowsocks"
|
"github.com/qdm12/gluetun/internal/shadowsocks"
|
||||||
"github.com/qdm12/gluetun/internal/storage"
|
"github.com/qdm12/gluetun/internal/storage"
|
||||||
"github.com/qdm12/gluetun/internal/tinyproxy"
|
"github.com/qdm12/gluetun/internal/tinyproxy"
|
||||||
|
versionpkg "github.com/qdm12/gluetun/internal/version"
|
||||||
"github.com/qdm12/golibs/command"
|
"github.com/qdm12/golibs/command"
|
||||||
"github.com/qdm12/golibs/files"
|
"github.com/qdm12/golibs/files"
|
||||||
"github.com/qdm12/golibs/logging"
|
"github.com/qdm12/golibs/logging"
|
||||||
@@ -220,6 +222,18 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
|||||||
restartShadowsocks()
|
restartShadowsocks()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
versionInformation := func() {
|
||||||
|
if !allSettings.VersionInformation {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client := &http.Client{Timeout: 5 * time.Second}
|
||||||
|
message, err := versionpkg.GetMessage(version, commit, client)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Info(message)
|
||||||
|
}
|
||||||
go func() {
|
go func() {
|
||||||
var restartTickerContext context.Context
|
var restartTickerContext context.Context
|
||||||
var restartTickerCancel context.CancelFunc = func() {}
|
var restartTickerCancel context.CancelFunc = func() {}
|
||||||
@@ -232,7 +246,7 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
|||||||
restartTickerCancel()
|
restartTickerCancel()
|
||||||
restartTickerContext, restartTickerCancel = context.WithCancel(ctx)
|
restartTickerContext, restartTickerCancel = context.WithCancel(ctx)
|
||||||
go unboundLooper.RunRestartTicker(restartTickerContext)
|
go unboundLooper.RunRestartTicker(restartTickerContext)
|
||||||
onConnected(allSettings, logger, routingConf, portForward, restartUnbound, restartPublicIP)
|
onConnected(allSettings, logger, routingConf, portForward, restartUnbound, restartPublicIP, versionInformation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -336,7 +350,7 @@ func collectStreamLines(ctx context.Context, streamMerger command.StreamMerger,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func onConnected(allSettings settings.Settings, logger logging.Logger, routingConf routing.Routing,
|
func onConnected(allSettings settings.Settings, logger logging.Logger, routingConf routing.Routing,
|
||||||
portForward, restartUnbound, restartPublicIP func(),
|
portForward, restartUnbound, restartPublicIP, versionInformation func(),
|
||||||
) {
|
) {
|
||||||
restartUnbound()
|
restartUnbound()
|
||||||
restartPublicIP()
|
restartPublicIP()
|
||||||
@@ -354,4 +368,5 @@ func onConnected(allSettings settings.Settings, logger logging.Logger, routingCo
|
|||||||
logger.Info("Gateway VPN IP address: %s", vpnGatewayIP)
|
logger.Info("Gateway VPN IP address: %s", vpnGatewayIP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
versionInformation()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ type Reader interface {
|
|||||||
|
|
||||||
// Public IP getters
|
// Public IP getters
|
||||||
GetPublicIPPeriod() (period time.Duration, err error)
|
GetPublicIPPeriod() (period time.Duration, err error)
|
||||||
|
|
||||||
|
GetVersionInformation() (enabled bool, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type reader struct {
|
type reader struct {
|
||||||
@@ -138,3 +140,7 @@ func (r *reader) GetVPNSP() (vpnServiceProvider models.VPNProvider, err error) {
|
|||||||
}
|
}
|
||||||
return models.VPNProvider(s), err
|
return models.VPNProvider(s), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *reader) GetVersionInformation() (enabled bool, err error) {
|
||||||
|
return r.envParams.GetOnOff("VERSION_INFORMATION", libparams.Default("on"))
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ type DNS struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DNS) String() string {
|
func (d *DNS) String() string {
|
||||||
const (
|
|
||||||
enabled = "enabled"
|
|
||||||
disabled = "disabled"
|
|
||||||
)
|
|
||||||
if !d.Enabled {
|
if !d.Enabled {
|
||||||
return fmt.Sprintf("DNS over TLS disabled, using plaintext DNS %s", d.PlaintextAddress)
|
return fmt.Sprintf("DNS over TLS disabled, using plaintext DNS %s", d.PlaintextAddress)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ import (
|
|||||||
"github.com/qdm12/gluetun/internal/params"
|
"github.com/qdm12/gluetun/internal/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
enabled = "enabled"
|
||||||
|
disabled = "disabled"
|
||||||
|
)
|
||||||
|
|
||||||
// Settings contains all settings for the program to run
|
// Settings contains all settings for the program to run
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
VPNSP models.VPNProvider
|
VPNSP models.VPNProvider
|
||||||
@@ -18,9 +23,14 @@ type Settings struct {
|
|||||||
TinyProxy TinyProxy
|
TinyProxy TinyProxy
|
||||||
ShadowSocks ShadowSocks
|
ShadowSocks ShadowSocks
|
||||||
PublicIPPeriod time.Duration
|
PublicIPPeriod time.Duration
|
||||||
|
VersionInformation bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Settings) String() string {
|
func (s *Settings) String() string {
|
||||||
|
versionInformation := disabled
|
||||||
|
if s.VersionInformation {
|
||||||
|
versionInformation = enabled
|
||||||
|
}
|
||||||
return strings.Join([]string{
|
return strings.Join([]string{
|
||||||
"Settings summary below:",
|
"Settings summary below:",
|
||||||
s.OpenVPN.String(),
|
s.OpenVPN.String(),
|
||||||
@@ -30,6 +40,7 @@ func (s *Settings) String() string {
|
|||||||
s.TinyProxy.String(),
|
s.TinyProxy.String(),
|
||||||
s.ShadowSocks.String(),
|
s.ShadowSocks.String(),
|
||||||
"Public IP check period: " + s.PublicIPPeriod.String(),
|
"Public IP check period: " + s.PublicIPPeriod.String(),
|
||||||
|
"Version information: " + versionInformation,
|
||||||
"", // new line at the end
|
"", // new line at the end
|
||||||
}, "\n")
|
}, "\n")
|
||||||
}
|
}
|
||||||
@@ -69,5 +80,9 @@ func GetAllSettings(paramsReader params.Reader) (settings Settings, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return settings, err
|
return settings, err
|
||||||
}
|
}
|
||||||
|
settings.VersionInformation, err = paramsReader.GetVersionInformation()
|
||||||
|
if err != nil {
|
||||||
|
return settings, err
|
||||||
|
}
|
||||||
return settings, nil
|
return settings, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ func (s *ShadowSocks) String() string {
|
|||||||
if !s.Enabled {
|
if !s.Enabled {
|
||||||
return "ShadowSocks settings: disabled"
|
return "ShadowSocks settings: disabled"
|
||||||
}
|
}
|
||||||
log := "disabled"
|
log := disabled
|
||||||
if s.Log {
|
if s.Log {
|
||||||
log = "enabled"
|
log = enabled
|
||||||
}
|
}
|
||||||
settingsList := []string{
|
settingsList := []string{
|
||||||
"ShadowSocks settings:",
|
"ShadowSocks settings:",
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ func (t *TinyProxy) String() string {
|
|||||||
if !t.Enabled {
|
if !t.Enabled {
|
||||||
return "TinyProxy settings: disabled"
|
return "TinyProxy settings: disabled"
|
||||||
}
|
}
|
||||||
auth := "disabled"
|
auth := disabled
|
||||||
if t.User != "" {
|
if t.User != "" {
|
||||||
auth = "enabled"
|
auth = enabled
|
||||||
}
|
}
|
||||||
settingsList := []string{
|
settingsList := []string{
|
||||||
fmt.Sprintf("Port: %d", t.Port),
|
fmt.Sprintf("Port: %d", t.Port),
|
||||||
|
|||||||
58
internal/version/github.go
Normal file
58
internal/version/github.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type githubRelease struct {
|
||||||
|
TagName string `json:"tag_name"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Prerelease bool `json:"prerelease"`
|
||||||
|
PublishedAt time.Time `json:"published_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type githubCommit struct {
|
||||||
|
Sha string `json:"sha"`
|
||||||
|
Commit struct {
|
||||||
|
Committer struct {
|
||||||
|
Date time.Time `json:"date"`
|
||||||
|
} `json:"committer"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGithubReleases(client *http.Client) (releases []githubRelease, err error) {
|
||||||
|
const url = "https://api.github.com/repos/qdm12/gluetun/releases"
|
||||||
|
response, err := client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
b, err := ioutil.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(b, &releases); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return releases, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGithubCommits(client *http.Client) (commits []githubCommit, err error) {
|
||||||
|
const url = "https://api.github.com/repos/qdm12/gluetun/commits"
|
||||||
|
response, err := client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
b, err := ioutil.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(b, &commits); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return commits, nil
|
||||||
|
}
|
||||||
88
internal/version/version.go
Normal file
88
internal/version/version.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetMessage returns a message for the user describing if there is a newer version
|
||||||
|
// available. It should only be called once the tunnel is established.
|
||||||
|
func GetMessage(version, commitShort string, client *http.Client) (message string, err error) {
|
||||||
|
if version == "latest" {
|
||||||
|
// Find # of commits between current commit and latest commit
|
||||||
|
commitsSince, err := getCommitsSince(client, commitShort)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("cannot get version information: %w", err)
|
||||||
|
} else if commitsSince == 0 {
|
||||||
|
return fmt.Sprintf("You are running on the bleeding edge of %s!", version), nil
|
||||||
|
}
|
||||||
|
commits := "commits"
|
||||||
|
if commitsSince == 1 {
|
||||||
|
commits = "commit"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("You are running %d %s behind the most recent %s", commitsSince, commits, version), nil
|
||||||
|
}
|
||||||
|
tagName, name, releaseTime, err := getLatestRelease(client)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("cannot get version information: %w", err)
|
||||||
|
}
|
||||||
|
if tagName == version {
|
||||||
|
return fmt.Sprintf("You are running the latest release %s", version), nil
|
||||||
|
}
|
||||||
|
timeSinceRelease := formatDuration(time.Since(releaseTime))
|
||||||
|
return fmt.Sprintf("There is a new release %s (%s) created %s ago",
|
||||||
|
tagName, name, timeSinceRelease),
|
||||||
|
nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatDuration(duration time.Duration) string {
|
||||||
|
switch {
|
||||||
|
case duration < time.Minute:
|
||||||
|
seconds := int(duration.Round(time.Second).Seconds())
|
||||||
|
if seconds < 2 {
|
||||||
|
return fmt.Sprintf("%d second", seconds)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d seconds", seconds)
|
||||||
|
case duration <= time.Hour:
|
||||||
|
minutes := int(duration.Round(time.Minute).Minutes())
|
||||||
|
if minutes == 1 {
|
||||||
|
return "1 minute"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d minutes", minutes)
|
||||||
|
case duration < 48*time.Hour:
|
||||||
|
hours := int(duration.Truncate(time.Hour).Hours())
|
||||||
|
return fmt.Sprintf("%d hours", hours)
|
||||||
|
default:
|
||||||
|
days := int(duration.Truncate(time.Hour).Hours() / 24)
|
||||||
|
return fmt.Sprintf("%d days", days)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLatestRelease(client *http.Client) (tagName, name string, time time.Time, err error) {
|
||||||
|
releases, err := getGithubReleases(client)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", time, err
|
||||||
|
}
|
||||||
|
for _, release := range releases {
|
||||||
|
if release.Prerelease {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return release.TagName, release.Name, release.PublishedAt, nil
|
||||||
|
}
|
||||||
|
return "", "", time, fmt.Errorf("no releases found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCommitsSince(client *http.Client, commitShort string) (n int, err error) {
|
||||||
|
commits, err := getGithubCommits(client)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
for i := range commits {
|
||||||
|
if commits[i].Sha[:7] == commitShort {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("no commit matching %q was found", commitShort)
|
||||||
|
}
|
||||||
64
internal/version/version_test.go
Normal file
64
internal/version/version_test.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_formatDuration(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
testCases := map[string]struct {
|
||||||
|
duration time.Duration
|
||||||
|
s string
|
||||||
|
}{
|
||||||
|
"zero": {
|
||||||
|
s: "0 second",
|
||||||
|
},
|
||||||
|
"one second": {
|
||||||
|
duration: time.Second,
|
||||||
|
s: "1 second",
|
||||||
|
},
|
||||||
|
"59 seconds": {
|
||||||
|
duration: 59 * time.Second,
|
||||||
|
s: "59 seconds",
|
||||||
|
},
|
||||||
|
"1 minute": {
|
||||||
|
duration: time.Minute,
|
||||||
|
s: "1 minute",
|
||||||
|
},
|
||||||
|
"2 minutes": {
|
||||||
|
duration: 2 * time.Minute,
|
||||||
|
s: "2 minutes",
|
||||||
|
},
|
||||||
|
"1 hour": {
|
||||||
|
duration: time.Hour,
|
||||||
|
s: "60 minutes",
|
||||||
|
},
|
||||||
|
"2 hours": {
|
||||||
|
duration: 2 * time.Hour,
|
||||||
|
s: "2 hours",
|
||||||
|
},
|
||||||
|
"26 hours": {
|
||||||
|
duration: 26 * time.Hour,
|
||||||
|
s: "26 hours",
|
||||||
|
},
|
||||||
|
"28 hours": {
|
||||||
|
duration: 28 * time.Hour,
|
||||||
|
s: "28 hours",
|
||||||
|
},
|
||||||
|
"55 hours": {
|
||||||
|
duration: 55 * time.Hour,
|
||||||
|
s: "2 days",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for name, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
s := formatDuration(testCase.duration)
|
||||||
|
assert.Equal(t, testCase.s, s)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user