This commit is contained in:
Quentin McGaw
2020-08-30 14:48:57 +00:00
parent aac5274eab
commit 7c102c0028
11 changed files with 262 additions and 18 deletions

View File

@@ -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 \

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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"))
}

View File

@@ -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)
}

View File

@@ -8,19 +8,29 @@ 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
OpenVPN OpenVPN
System System
DNS DNS
Firewall Firewall
TinyProxy TinyProxy
ShadowSocks ShadowSocks
PublicIPPeriod time.Duration
VPNSP models.VPNProvider
OpenVPN OpenVPN
System System
DNS DNS
Firewall Firewall
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
}

View File

@@ -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:",

View File

@@ -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),

View 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
}

View 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)
}

View 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)
})
}
}