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.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 \
|
||||
VERSION_INFORMATION=on \
|
||||
PROTOCOL=udp \
|
||||
OPENVPN_VERBOSITY=1 \
|
||||
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 |
|
||||
| --- | --- | --- | --- |
|
||||
| `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
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
@@ -25,6 +26,7 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/shadowsocks"
|
||||
"github.com/qdm12/gluetun/internal/storage"
|
||||
"github.com/qdm12/gluetun/internal/tinyproxy"
|
||||
versionpkg "github.com/qdm12/gluetun/internal/version"
|
||||
"github.com/qdm12/golibs/command"
|
||||
"github.com/qdm12/golibs/files"
|
||||
"github.com/qdm12/golibs/logging"
|
||||
@@ -220,6 +222,18 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
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() {
|
||||
var restartTickerContext context.Context
|
||||
var restartTickerCancel context.CancelFunc = func() {}
|
||||
@@ -232,7 +246,7 @@ func _main(background context.Context, args []string) int { //nolint:gocognit,go
|
||||
restartTickerCancel()
|
||||
restartTickerContext, restartTickerCancel = context.WithCancel(ctx)
|
||||
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,
|
||||
portForward, restartUnbound, restartPublicIP func(),
|
||||
portForward, restartUnbound, restartPublicIP, versionInformation func(),
|
||||
) {
|
||||
restartUnbound()
|
||||
restartPublicIP()
|
||||
@@ -354,4 +368,5 @@ func onConnected(allSettings settings.Settings, logger logging.Logger, routingCo
|
||||
logger.Info("Gateway VPN IP address: %s", vpnGatewayIP)
|
||||
}
|
||||
}
|
||||
versionInformation()
|
||||
}
|
||||
|
||||
@@ -108,6 +108,8 @@ type Reader interface {
|
||||
|
||||
// Public IP getters
|
||||
GetPublicIPPeriod() (period time.Duration, err error)
|
||||
|
||||
GetVersionInformation() (enabled bool, err error)
|
||||
}
|
||||
|
||||
type reader struct {
|
||||
@@ -138,3 +140,7 @@ func (r *reader) GetVPNSP() (vpnServiceProvider models.VPNProvider, err error) {
|
||||
}
|
||||
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 {
|
||||
const (
|
||||
enabled = "enabled"
|
||||
disabled = "disabled"
|
||||
)
|
||||
if !d.Enabled {
|
||||
return fmt.Sprintf("DNS over TLS disabled, using plaintext DNS %s", d.PlaintextAddress)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@ import (
|
||||
"github.com/qdm12/gluetun/internal/params"
|
||||
)
|
||||
|
||||
const (
|
||||
enabled = "enabled"
|
||||
disabled = "disabled"
|
||||
)
|
||||
|
||||
// Settings contains all settings for the program to run
|
||||
type Settings struct {
|
||||
VPNSP models.VPNProvider
|
||||
@@ -18,9 +23,14 @@ type Settings struct {
|
||||
TinyProxy TinyProxy
|
||||
ShadowSocks ShadowSocks
|
||||
PublicIPPeriod time.Duration
|
||||
VersionInformation bool
|
||||
}
|
||||
|
||||
func (s *Settings) String() string {
|
||||
versionInformation := disabled
|
||||
if s.VersionInformation {
|
||||
versionInformation = enabled
|
||||
}
|
||||
return strings.Join([]string{
|
||||
"Settings summary below:",
|
||||
s.OpenVPN.String(),
|
||||
@@ -30,6 +40,7 @@ func (s *Settings) String() string {
|
||||
s.TinyProxy.String(),
|
||||
s.ShadowSocks.String(),
|
||||
"Public IP check period: " + s.PublicIPPeriod.String(),
|
||||
"Version information: " + versionInformation,
|
||||
"", // new line at the end
|
||||
}, "\n")
|
||||
}
|
||||
@@ -69,5 +80,9 @@ func GetAllSettings(paramsReader params.Reader) (settings Settings, err error) {
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
settings.VersionInformation, err = paramsReader.GetVersionInformation()
|
||||
if err != nil {
|
||||
return settings, err
|
||||
}
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ func (s *ShadowSocks) String() string {
|
||||
if !s.Enabled {
|
||||
return "ShadowSocks settings: disabled"
|
||||
}
|
||||
log := "disabled"
|
||||
log := disabled
|
||||
if s.Log {
|
||||
log = "enabled"
|
||||
log = enabled
|
||||
}
|
||||
settingsList := []string{
|
||||
"ShadowSocks settings:",
|
||||
|
||||
@@ -21,9 +21,9 @@ func (t *TinyProxy) String() string {
|
||||
if !t.Enabled {
|
||||
return "TinyProxy settings: disabled"
|
||||
}
|
||||
auth := "disabled"
|
||||
auth := disabled
|
||||
if t.User != "" {
|
||||
auth = "enabled"
|
||||
auth = enabled
|
||||
}
|
||||
settingsList := []string{
|
||||
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