mirror of
https://github.com/UnnoTed/wireguird
synced 2026-07-25 17:24:46 -04:00
new settings for country flags, reconnect, passwordless polkit policy
This commit is contained in:
parent
71c936a3b6
commit
1c61ed51c8
@ -34,13 +34,11 @@ sudo dpkg -i ./wireguird_amd64.deb
|
|||||||
|
|
||||||
## Compile
|
## Compile
|
||||||
|
|
||||||
deb dependencies: `wireguard-tools libgtk-3-dev libayatana-appindicator3-dev golang-go resolvconf`
|
deb dependencies: `sudo apt install wireguard-tools libgtk-3-dev libayatana-appindicator3-dev golang-go resolvconf`
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/UnnoTed/wireguird
|
git clone https://github.com/UnnoTed/wireguird
|
||||||
cd wireguird
|
cd wireguird
|
||||||
chmod +x ./*.sh
|
go install github.com/goreleaser/goreleaser/v2@latest
|
||||||
./deps.sh
|
goreleaser release --snapshot --clean
|
||||||
./package.sh
|
|
||||||
./install.sh
|
|
||||||
```
|
```
|
||||||
|
|||||||
@ -1,3 +1,12 @@
|
|||||||
|
wireguird (1.2) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* Add new setting to show country flags on tunnel names
|
||||||
|
* Change distribution packaging
|
||||||
|
* Update checks run once per run
|
||||||
|
* Add new setting to auto-reconnect on start
|
||||||
|
|
||||||
|
-- unknown <unknown@unknown> Sat, 26 Apr 2026 15:50:00 +0100
|
||||||
|
|
||||||
wireguird (1.1) UNRELEASED; urgency=medium
|
wireguird (1.1) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
* Add ability to import multiple config files at once (zip archive)
|
* Add ability to import multiple config files at once (zip archive)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
Package: wireguird
|
Package: wireguird
|
||||||
Source: wireguird
|
Source: wireguird
|
||||||
Version: 1.1
|
Version: 1.2
|
||||||
Section: utils
|
Section: utils
|
||||||
Priority: extra
|
Priority: extra
|
||||||
Architecture: amd64
|
Architecture: amd64
|
||||||
|
|||||||
2
fileb0x.toml
Executable file → Normal file
2
fileb0x.toml
Executable file → Normal file
@ -116,7 +116,7 @@ debug = false
|
|||||||
# type: array of objects
|
# type: array of objects
|
||||||
[[custom]]
|
[[custom]]
|
||||||
# type: array of strings
|
# type: array of strings
|
||||||
files = ["*.glade"]
|
files = ["*.glade", "./Icon/", "./flags/"]
|
||||||
|
|
||||||
# base is the path that will be removed from all files' path
|
# base is the path that will be removed from all files' path
|
||||||
# type: string
|
# type: string
|
||||||
|
|||||||
133
gui/countries/countries.go
Normal file
133
gui/countries/countries.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package countries
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/oschwald/maxminddb-golang"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
mmdbURL = "https://github.com/iplocate/ip-address-databases/raw/refs/heads/main/ip-to-country/ip-to-country.mmdb"
|
||||||
|
mmdbFile = "./countries.mmdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
initErr error
|
||||||
|
once sync.Once // OpenDatabase runs once
|
||||||
|
db *maxminddb.Reader
|
||||||
|
|
||||||
|
ready = make(chan struct{})
|
||||||
|
dnsCache = map[string]string{}
|
||||||
|
dnsCacheMu sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
type IPRecord struct {
|
||||||
|
CountryCode string `maxminddb:"country_code"`
|
||||||
|
Country struct {
|
||||||
|
IsoCode string `maxminddb:"iso_code"`
|
||||||
|
} `maxminddb:"country"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func OpenDatabase() error {
|
||||||
|
once.Do(func() {
|
||||||
|
defer close(ready)
|
||||||
|
log.Debug().Msg("initializing country database")
|
||||||
|
|
||||||
|
// downloads the db when doesnt exist or is older than a week
|
||||||
|
if s, err := os.Stat(mmdbFile); os.IsNotExist(err) || s.ModTime().Before(time.Now().Add(-7*24*time.Hour)) {
|
||||||
|
if err := downloadDatabase(); err != nil {
|
||||||
|
initErr = fmt.Errorf("failed to download country database: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
db, err = maxminddb.Open(mmdbFile)
|
||||||
|
if err != nil {
|
||||||
|
initErr = fmt.Errorf("failed to open country database: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Msg("country database opened successfully")
|
||||||
|
})
|
||||||
|
|
||||||
|
return initErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadDatabase() error {
|
||||||
|
resp, err := http.Get(mmdbURL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to download mmdb: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := os.Create(mmdbFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create local file: %w", err)
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(out, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write data to disk: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Find(ipStr string) (string, error) {
|
||||||
|
<-ready
|
||||||
|
|
||||||
|
if initErr != nil {
|
||||||
|
return "", fmt.Errorf("database initialization failed: %w", initErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := dnsCache[ipStr]; ok {
|
||||||
|
log.Debug().Str("ip", ipStr).Str("country", val).Msg("country found in cache")
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(ipStr)
|
||||||
|
if ip == nil {
|
||||||
|
return "", fmt.Errorf("invalid IP address format: %s", ipStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
var record IPRecord
|
||||||
|
err := db.Lookup(ip, &record)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error during database lookup: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.CountryCode != "" {
|
||||||
|
dnsCacheMu.Lock()
|
||||||
|
dnsCache[ipStr] = record.CountryCode
|
||||||
|
dnsCacheMu.Unlock()
|
||||||
|
return record.CountryCode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if record.Country.IsoCode != "" {
|
||||||
|
dnsCacheMu.Lock()
|
||||||
|
dnsCache[ipStr] = record.Country.IsoCode
|
||||||
|
dnsCacheMu.Unlock()
|
||||||
|
return record.Country.IsoCode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CloseDatabase() {
|
||||||
|
if db != nil {
|
||||||
|
db.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
63
gui/endpoints/endpoints.go
Normal file
63
gui/endpoints/endpoints.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package endpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Unknown = "Unknown"
|
||||||
|
IPV4 = "IPV4"
|
||||||
|
IPV6 = "IPV6"
|
||||||
|
FQDN = "FQDN"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Categorize(endpoint string) string {
|
||||||
|
// no empty or urls
|
||||||
|
if endpoint == "" || strings.Contains(endpoint, "://") {
|
||||||
|
return Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
host, _, err := net.SplitHostPort(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
// when no port is present, use the it as the host.
|
||||||
|
host = endpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPV6 with brackets
|
||||||
|
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
|
||||||
|
inner := host[1 : len(host)-1]
|
||||||
|
|
||||||
|
ip := net.ParseIP(inner)
|
||||||
|
if ip != nil && ip.To4() == nil {
|
||||||
|
return IPV6
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid [IPV6]
|
||||||
|
return Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip := net.ParseIP(host); ip != nil {
|
||||||
|
if ip.To4() != nil {
|
||||||
|
return IPV4
|
||||||
|
}
|
||||||
|
|
||||||
|
return IPV6
|
||||||
|
}
|
||||||
|
|
||||||
|
// FQDNs have no web url chars like ?, #, @...
|
||||||
|
if host == "" || strings.ContainsAny(host, " /?#@:[]") {
|
||||||
|
return Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// FQDNs must be printable and contain no spaces
|
||||||
|
// printable means that strange chars like chinese should work
|
||||||
|
for _, r := range host {
|
||||||
|
if !unicode.IsPrint(r) || unicode.IsSpace(r) {
|
||||||
|
return Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FQDN
|
||||||
|
}
|
||||||
41
gui/endpoints/endpoints_test.go
Normal file
41
gui/endpoints/endpoints_test.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package endpoints
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCategorization(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
endpoint string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"Empty string", "", Unknown},
|
||||||
|
{"Just a colon", ":", Unknown},
|
||||||
|
{"Empty brackets", "[]", Unknown},
|
||||||
|
{"Empty brackets with port", "[]:51820", Unknown},
|
||||||
|
|
||||||
|
{"IPV4 with port", "192.168.1.1:51820", IPV4},
|
||||||
|
{"IPV4 without port", "10.0.0.1", IPV4},
|
||||||
|
|
||||||
|
{"IPV6 with brackets & port", "[2001:db8::1]:51820", IPV6},
|
||||||
|
{"IPV6 with brackets, no port", "[2001:db8::1]", IPV6},
|
||||||
|
{"IPV6 no brackets, no port", "2001:db8::1", IPV6},
|
||||||
|
|
||||||
|
{"Valid FQDN with port", "vpn.example.com:51820", FQDN},
|
||||||
|
{"Valid FQDN without port", "wg.my-domain.net", FQDN},
|
||||||
|
|
||||||
|
{"FQDN with spaces", "my vpn.com:51820", Unknown},
|
||||||
|
{"URL with scheme", "https://vpn.example.com", Unknown},
|
||||||
|
{"Malformed IP in brackets", "[192.168.1.1]", Unknown},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := Categorize(tt.endpoint)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("\nCategorize(%q)\n got: %s\n want: %s", tt.endpoint, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -27,6 +27,7 @@ func main() {
|
|||||||
"Switch",
|
"Switch",
|
||||||
"Window",
|
"Window",
|
||||||
"Entry",
|
"Entry",
|
||||||
|
"Image",
|
||||||
"Label",
|
"Label",
|
||||||
"Stack",
|
"Stack",
|
||||||
"Box",
|
"Box",
|
||||||
|
|||||||
@ -246,6 +246,20 @@ func Entry(name string) (*gtk.Entry, error) {
|
|||||||
return entry1, nil
|
return entry1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Image(name string) (*gtk.Image, error) {
|
||||||
|
obj, err := Builder.GetObject(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
image1, ok := obj.(*gtk.Image)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("cant get *gtk.Image: " + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return image1, nil
|
||||||
|
}
|
||||||
|
|
||||||
func Label(name string) (*gtk.Label, error) {
|
func Label(name string) (*gtk.Label, error) {
|
||||||
obj, err := Builder.GetObject(name)
|
obj, err := Builder.GetObject(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
87
gui/gui.go
Executable file → Normal file
87
gui/gui.go
Executable file → Normal file
@ -7,7 +7,10 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/UnnoTed/go-appindicator"
|
"github.com/UnnoTed/go-appindicator"
|
||||||
|
"github.com/UnnoTed/wireguird/gui/countries"
|
||||||
"github.com/UnnoTed/wireguird/gui/get"
|
"github.com/UnnoTed/wireguird/gui/get"
|
||||||
|
"github.com/UnnoTed/wireguird/static"
|
||||||
|
"github.com/gotk3/gotk3/gdk"
|
||||||
"github.com/gotk3/gotk3/glib"
|
"github.com/gotk3/gotk3/glib"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
@ -15,14 +18,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = "1.1.0"
|
Version = "1.2.0"
|
||||||
Repo = "https://github.com/UnnoTed/wireguird"
|
Repo = "https://github.com/UnnoTed/wireguird"
|
||||||
TunnelsPath = "/etc/wireguard/"
|
TunnelsPath = "/etc/wireguard/"
|
||||||
IconPath = "/opt/wireguird/Icon/"
|
IconPath = "./Icon/"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
settingsWindow *gtk.Window
|
settingsWindow *gtk.Window
|
||||||
|
updateChecked bool
|
||||||
editorWindow *gtk.Window
|
editorWindow *gtk.Window
|
||||||
application *gtk.Application
|
application *gtk.Application
|
||||||
indicator *appindicator.Indicator
|
indicator *appindicator.Indicator
|
||||||
@ -99,7 +103,27 @@ func Create(app *gtk.Application, b *gtk.Builder, w *gtk.ApplicationWindow, ind
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if Settings.CountryFlags {
|
||||||
|
go func() {
|
||||||
|
if err := countries.OpenDatabase(); err != nil {
|
||||||
|
log.Error().Err(err).Msg("error getting country database")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
window.HideOnDelete()
|
window.HideOnDelete()
|
||||||
|
|
||||||
|
icons := map[string]string{
|
||||||
|
"icon_delete": "Icon/delete.png",
|
||||||
|
"icon_tunnel": "Icon/add_tunnel.png",
|
||||||
|
"icon_zip": "Icon/zip.png",
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, path := range icons {
|
||||||
|
if err := loadGlobalEmbeddedImage(name, path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,6 +191,12 @@ func createSettings(show bool) (*gtk.Window, error) {
|
|||||||
|
|
||||||
func updateCheck() error {
|
func updateCheck() error {
|
||||||
log.Info().Msg("Checking for updates")
|
log.Info().Msg("Checking for updates")
|
||||||
|
|
||||||
|
// once per run
|
||||||
|
if updateChecked {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := http.Get("https://api.github.com/repos/UnnoTed/wireguird/releases")
|
resp, err := http.Get("https://api.github.com/repos/UnnoTed/wireguird/releases")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -197,5 +227,58 @@ func updateCheck() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateChecked = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadEmbeddedImage(path string) (*gtk.Image, error) {
|
||||||
|
data, err := static.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
loader, err := gdk.PixbufLoaderNew()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.Write(data)
|
||||||
|
loader.Close()
|
||||||
|
|
||||||
|
pixbuf, err := loader.GetPixbuf()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Str("path", path).Msg("loaded embedded image")
|
||||||
|
return gtk.ImageNewFromPixbuf(pixbuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadGlobalEmbeddedImage(id string, path string) error {
|
||||||
|
data, err := static.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
loader, err := gdk.PixbufLoaderNew()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.Write(data)
|
||||||
|
loader.Close()
|
||||||
|
|
||||||
|
pixbuf, err := loader.GetPixbuf()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err := get.Image(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
img.SetFromPixbuf(pixbuf)
|
||||||
|
log.Debug().Str("path", path).Msg("loaded global embedded image")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
451
gui/tunnels.go
Executable file → Normal file
451
gui/tunnels.go
Executable file → Normal file
@ -2,9 +2,12 @@ package gui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -17,6 +20,8 @@ import (
|
|||||||
"github.com/ungerik/go-dry"
|
"github.com/ungerik/go-dry"
|
||||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||||
|
|
||||||
|
"github.com/UnnoTed/wireguird/gui/countries"
|
||||||
|
"github.com/UnnoTed/wireguird/gui/endpoints"
|
||||||
"github.com/UnnoTed/wireguird/gui/get"
|
"github.com/UnnoTed/wireguird/gui/get"
|
||||||
"github.com/UnnoTed/wireguird/settings"
|
"github.com/UnnoTed/wireguird/settings"
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
@ -56,16 +61,20 @@ type Tunnels struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Settings struct {
|
Settings struct {
|
||||||
MultipleTunnels *gtk.CheckButton
|
MultipleTunnels *gtk.CheckButton
|
||||||
StartOnTray *gtk.CheckButton
|
StartOnTray *gtk.CheckButton
|
||||||
CheckUpdates *gtk.CheckButton
|
CheckUpdates *gtk.CheckButton
|
||||||
|
CountryFlags *gtk.CheckButton
|
||||||
|
RememberConnectedServers *gtk.CheckButton
|
||||||
|
Passwordless *gtk.CheckButton
|
||||||
}
|
}
|
||||||
|
|
||||||
ButtonChangeState *gtk.Button
|
ButtonChangeState *gtk.Button
|
||||||
icons map[string]*gtk.Image
|
icons map[string]*gtk.Image
|
||||||
ticker *time.Ticker
|
ticker *time.Ticker
|
||||||
|
|
||||||
lastSelected string
|
lastSelected string
|
||||||
|
reconnectedRememberedServers bool
|
||||||
|
|
||||||
grayIcon *gtk.Image
|
grayIcon *gtk.Image
|
||||||
greenIcon *gtk.Image
|
greenIcon *gtk.Image
|
||||||
@ -76,12 +85,12 @@ func (t *Tunnels) Create() error {
|
|||||||
t.ticker = time.NewTicker(1 * time.Second)
|
t.ticker = time.NewTicker(1 * time.Second)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
t.grayIcon, err = gtk.ImageNewFromFile(IconPath + "not_connected.png")
|
t.grayIcon, err = loadEmbeddedImage(IconPath + "not_connected.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.greenIcon, err = gtk.ImageNewFromFile(IconPath + "connected.png")
|
t.greenIcon, err = loadEmbeddedImage(IconPath + "connected.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -207,122 +216,7 @@ func (t *Tunnels) Create() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.ButtonChangeState.Connect("clicked", func() {
|
t.ButtonChangeState.Connect("clicked", func() {
|
||||||
err := func() error {
|
if err = t.Connect(tl.GetSelectedRow(), ""); err != nil {
|
||||||
list, err := wgc.Devices()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
activeNames := t.ActiveDeviceName()
|
|
||||||
row := tl.GetSelectedRow()
|
|
||||||
// row not found for config
|
|
||||||
if row == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// conf name
|
|
||||||
name, err := row.GetName()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/UnnoTed/wireguird/issues/11#issuecomment-1332047191
|
|
||||||
if len(name) >= 16 {
|
|
||||||
ShowError(window, errors.New("Tunnel's file name is too long ("+strconv.Itoa(len(name))+"), max length: 15"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// disconnect from given tunnel
|
|
||||||
dc := func(d *wgtypes.Device) error {
|
|
||||||
glib.IdleAdd(func() {
|
|
||||||
t.icons[d.Name].SetFromPixbuf(t.grayIcon.GetPixbuf())
|
|
||||||
})
|
|
||||||
|
|
||||||
c := exec.Command("wg-quick", "down", d.Name)
|
|
||||||
output, err := c.Output()
|
|
||||||
if err != nil {
|
|
||||||
es := string(err.(*exec.ExitError).Stderr)
|
|
||||||
log.Error().Err(err).Str("output", string(output)).Str("error", es).Msg("wg-quick down error")
|
|
||||||
|
|
||||||
oerr := err.Error() + "\nwg-quick's output:\n" + es
|
|
||||||
wlog("ERROR", oerr)
|
|
||||||
return errors.New(oerr)
|
|
||||||
}
|
|
||||||
|
|
||||||
indicator.SetIcon("wireguard_off")
|
|
||||||
return wlog("INFO", "Disconnected from "+d.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// disconnects from all tunnels before connecting to a new one
|
|
||||||
// when the multipleTunnels option is disabled
|
|
||||||
if Settings.MultipleTunnels {
|
|
||||||
log.Info().Str("name", name).Msg("NAME")
|
|
||||||
d, err := wgc.Device(name)
|
|
||||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
|
||||||
if err := dc(d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
for _, d := range list {
|
|
||||||
if err := dc(d); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dont connect to the new one as this is a disconnect action
|
|
||||||
if dry.StringListContains(activeNames, name) {
|
|
||||||
t.UpdateRow(row)
|
|
||||||
|
|
||||||
glib.IdleAdd(func() {
|
|
||||||
if len(activeNames) == 1 {
|
|
||||||
header.SetSubtitle("Not connected!")
|
|
||||||
} else {
|
|
||||||
activeNames := t.ActiveDeviceName()
|
|
||||||
header.SetSubtitle("Connected to " + strings.Join(activeNames, ", "))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// connect to a tunnel
|
|
||||||
c := exec.Command("wg-quick", "up", name)
|
|
||||||
output, err := c.Output()
|
|
||||||
if err != nil {
|
|
||||||
es := string(err.(*exec.ExitError).Stderr)
|
|
||||||
log.Error().Err(err).Str("output", string(output)).Str("error", es).Msg("wg-quick up error")
|
|
||||||
|
|
||||||
oerr := err.Error() + "\nwg-quick's output:\n" + es
|
|
||||||
wlog("ERROR", oerr)
|
|
||||||
return errors.New(oerr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update header label with tunnel names
|
|
||||||
glib.IdleAdd(func() {
|
|
||||||
activeNames := t.ActiveDeviceName()
|
|
||||||
header.SetSubtitle("Connected to " + strings.Join(activeNames, ", "))
|
|
||||||
})
|
|
||||||
|
|
||||||
// set icon to connected for the tunnel's row
|
|
||||||
glib.IdleAdd(func() {
|
|
||||||
t.icons[name].SetFromPixbuf(t.greenIcon.GetPixbuf())
|
|
||||||
t.UpdateRow(row)
|
|
||||||
indicator.SetIcon("wg_connected")
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := wlog("INFO", "Connected to "+name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ShowError(window, err)
|
ShowError(window, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -793,6 +687,8 @@ func (t *Tunnels) Create() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
btnSettingsSave.Connect("clicked", func() {
|
btnSettingsSave.Connect("clicked", func() {
|
||||||
|
prevCountryFlags := Settings.CountryFlags
|
||||||
|
|
||||||
err := func() error {
|
err := func() error {
|
||||||
t.ToSettings()
|
t.ToSettings()
|
||||||
Settings.Save()
|
Settings.Save()
|
||||||
@ -804,6 +700,24 @@ func (t *Tunnels) Create() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
ShowError(window, err, "settings save error")
|
ShowError(window, err, "settings save error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only download country database when previously disabled and now enabled
|
||||||
|
if !prevCountryFlags && Settings.CountryFlags {
|
||||||
|
go func() {
|
||||||
|
err := countries.OpenDatabase()
|
||||||
|
if err != nil {
|
||||||
|
ShowError(window, err, "download country database error")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.SetPasswordless(); err != nil {
|
||||||
|
ShowError(window, err, "set passwordless error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.ScanTunnels(); err != nil {
|
||||||
|
ShowError(window, err, "scan tunnels error")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// button: settings cancel
|
// button: settings cancel
|
||||||
@ -908,6 +822,18 @@ func (t *Tunnels) Create() error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if Settings.RememberConnectedServers && Settings.ConnectedServers != nil && len(Settings.ConnectedServers) > 0 {
|
||||||
|
for tunnel := range Settings.ConnectedServers {
|
||||||
|
if err := t.Connect(nil, tunnel); err != nil {
|
||||||
|
log.Error().Err(err).Str("tunnel", tunnel).Msg("error connecting to tunnel")
|
||||||
|
wlog("ERROR", "error connecting to tunnel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.reconnectedRememberedServers = true
|
||||||
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -915,6 +841,9 @@ func (t *Tunnels) ToSettings() {
|
|||||||
Settings.MultipleTunnels = t.Settings.MultipleTunnels.GetActive()
|
Settings.MultipleTunnels = t.Settings.MultipleTunnels.GetActive()
|
||||||
Settings.StartOnTray = t.Settings.StartOnTray.GetActive()
|
Settings.StartOnTray = t.Settings.StartOnTray.GetActive()
|
||||||
Settings.CheckUpdates = t.Settings.CheckUpdates.GetActive()
|
Settings.CheckUpdates = t.Settings.CheckUpdates.GetActive()
|
||||||
|
Settings.CountryFlags = t.Settings.CountryFlags.GetActive()
|
||||||
|
Settings.RememberConnectedServers = t.Settings.RememberConnectedServers.GetActive()
|
||||||
|
Settings.Passwordless = t.Settings.Passwordless.GetActive()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tunnels) FromSettings() error {
|
func (t *Tunnels) FromSettings() error {
|
||||||
@ -941,6 +870,27 @@ func (t *Tunnels) FromSettings() error {
|
|||||||
}
|
}
|
||||||
t.Settings.CheckUpdates.SetActive(Settings.CheckUpdates)
|
t.Settings.CheckUpdates.SetActive(Settings.CheckUpdates)
|
||||||
|
|
||||||
|
// checkbox: country flags
|
||||||
|
t.Settings.CountryFlags, err = get.CheckButton("settings_country_flags")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.Settings.CountryFlags.SetActive(Settings.CountryFlags)
|
||||||
|
|
||||||
|
// checkbox: remember connected servers
|
||||||
|
t.Settings.RememberConnectedServers, err = get.CheckButton("settings_remember_connected")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.Settings.RememberConnectedServers.SetActive(Settings.RememberConnectedServers)
|
||||||
|
|
||||||
|
// checkbox: passwordless
|
||||||
|
t.Settings.Passwordless, err = get.CheckButton("settings_polkit_policy")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
t.Settings.Passwordless.SetActive(Settings.Passwordless)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1028,13 +978,17 @@ func (t *Tunnels) ScanTunnels() error {
|
|||||||
// showError(err)
|
// showError(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
configByName := map[string]string{}
|
||||||
|
|
||||||
for _, fileName := range list {
|
for _, fileName := range list {
|
||||||
if !strings.HasSuffix(fileName, ".conf") {
|
if !strings.HasSuffix(fileName, ".conf") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
configList = append(configList, strings.TrimSuffix(fileName, ".conf"))
|
name := strings.TrimSuffix(fileName, ".conf")
|
||||||
|
configByName[name] = TunnelsPath + fileName
|
||||||
|
|
||||||
|
configList = append(configList, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
tl, err := get.ListBox("tunnel_list")
|
tl, err := get.ListBox("tunnel_list")
|
||||||
@ -1056,7 +1010,11 @@ func (t *Tunnels) ScanTunnels() error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
activeNames := t.ActiveDeviceName()
|
activeNames := t.ActiveDeviceName()
|
||||||
header.SetSubtitle("Connected to " + strings.Join(activeNames, ", "))
|
if len(activeNames) > 0 {
|
||||||
|
header.SetSubtitle("Connected to " + strings.Join(activeNames, ", "))
|
||||||
|
} else {
|
||||||
|
header.SetSubtitle("No active tunnels")
|
||||||
|
}
|
||||||
|
|
||||||
lasti := len(configList) - 1
|
lasti := len(configList) - 1
|
||||||
for i, name := range configList {
|
for i, name := range configList {
|
||||||
@ -1097,6 +1055,82 @@ func (t *Tunnels) ScanTunnels() error {
|
|||||||
img.SetVExpand(false)
|
img.SetVExpand(false)
|
||||||
img.SetHExpand(false)
|
img.SetHExpand(false)
|
||||||
|
|
||||||
|
var country *gtk.Image
|
||||||
|
if Settings.CountryFlags {
|
||||||
|
cfg, err := ini.Load(configByName[name])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
peersec := cfg.Section("Peer")
|
||||||
|
|
||||||
|
nothing, err := loadEmbeddedImage("./flags/IDK.png")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
country = nothing
|
||||||
|
country.SetVAlign(gtk.ALIGN_CENTER)
|
||||||
|
country.SetHAlign(gtk.ALIGN_START)
|
||||||
|
country.SetSizeRequest(10, 10)
|
||||||
|
country.SetVExpand(false)
|
||||||
|
country.SetHExpand(false)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := func() error {
|
||||||
|
endpoint := strings.Split(peersec.Key("Endpoint").String(), ":")[0]
|
||||||
|
log.Debug().Str("endpoint", endpoint).Msg("checking country flag for tunnel")
|
||||||
|
|
||||||
|
ip := ""
|
||||||
|
switch endpoints.Categorize(endpoint) {
|
||||||
|
case endpoints.IPV4, endpoints.IPV6:
|
||||||
|
ip = endpoint
|
||||||
|
case endpoints.FQDN:
|
||||||
|
ips, err := net.LookupIP(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("endpoint", endpoint).Msg("dns lookup error")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ips) > 0 {
|
||||||
|
ip = ips[0].String()
|
||||||
|
} else {
|
||||||
|
return errors.New("endpoint \"" + endpoint + "\" coudln't find ip from dns")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("endpoint \"" + endpoint + "\" is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Str("endpoint", endpoint).Str("ip", ip).Msg("found ip for tunnel endpoint")
|
||||||
|
|
||||||
|
countryCode, err := countries.Find(ip)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("find country error")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Str("country", countryCode).Str("ip", ip).Msg("found country for tunnel")
|
||||||
|
|
||||||
|
flag, err := loadEmbeddedImage("./flags/" + countryCode + ".png")
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("country", countryCode).Str("ip", ip).Msg("error loading embedded image")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevents crash when deleting servers
|
||||||
|
glib.IdleAdd(func() {
|
||||||
|
if country != nil && country.GetPixbuf() != nil {
|
||||||
|
country.SetFromPixbuf(flag.GetPixbuf())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("country flag error")
|
||||||
|
wlog("ERROR", "country flag error: "+err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
label, err := gtk.LabelNew(name)
|
label, err := gtk.LabelNew(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -1114,6 +1148,11 @@ func (t *Tunnels) ScanTunnels() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
box.Add(img)
|
box.Add(img)
|
||||||
|
|
||||||
|
if Settings.CountryFlags {
|
||||||
|
box.Add(country)
|
||||||
|
}
|
||||||
|
|
||||||
box.Add(label)
|
box.Add(label)
|
||||||
|
|
||||||
row.SetName(name)
|
row.SetName(name)
|
||||||
@ -1171,6 +1210,170 @@ func (t *Tunnels) ActiveDeviceName() []string {
|
|||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tunnels) Connect(row *gtk.ListBoxRow, name string) error {
|
||||||
|
list, err := wgc.Devices()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
activeNames := t.ActiveDeviceName()
|
||||||
|
if row != nil && name == "" {
|
||||||
|
// conf name
|
||||||
|
name, err = row.GetName()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/UnnoTed/wireguird/issues/11#issuecomment-1332047191
|
||||||
|
if len(name) >= 16 {
|
||||||
|
ShowError(window, errors.New("Tunnel's file name is too long ("+strconv.Itoa(len(name))+"), max length: 15"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// disconnect from given tunnel
|
||||||
|
dc := func(d *wgtypes.Device) error {
|
||||||
|
glib.IdleAdd(func() {
|
||||||
|
t.icons[d.Name].SetFromPixbuf(t.grayIcon.GetPixbuf())
|
||||||
|
})
|
||||||
|
|
||||||
|
c := exec.Command("wg-quick", "down", d.Name)
|
||||||
|
output, err := c.Output()
|
||||||
|
if err != nil {
|
||||||
|
es := string(err.(*exec.ExitError).Stderr)
|
||||||
|
log.Error().Err(err).Str("output", string(output)).Str("error", es).Msg("wg-quick down error")
|
||||||
|
|
||||||
|
oerr := err.Error() + "\nwg-quick's output:\n" + es
|
||||||
|
wlog("ERROR", oerr)
|
||||||
|
return errors.New(oerr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if Settings.RememberConnectedServers && Settings.ConnectedServers != nil && len(Settings.ConnectedServers) > 0 && t.reconnectedRememberedServers {
|
||||||
|
delete(Settings.ConnectedServers, d.Name)
|
||||||
|
if err := Settings.Save(); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error saving settings on disconnect")
|
||||||
|
wlog("INFO", "Error saving settings on disconnect: "+err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
indicator.SetIcon("wireguard_off")
|
||||||
|
return wlog("INFO", "Disconnected from "+d.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// disconnects from all tunnels before connecting to a new one
|
||||||
|
// when the multipleTunnels option is disabled
|
||||||
|
if Settings.MultipleTunnels {
|
||||||
|
log.Info().Str("name", name).Msg("NAME")
|
||||||
|
d, err := wgc.Device(name)
|
||||||
|
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
if err := dc(d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
for _, d := range list {
|
||||||
|
if err := dc(d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dont connect to the new one as this is a disconnect action
|
||||||
|
if dry.StringListContains(activeNames, name) {
|
||||||
|
t.UpdateRow(row)
|
||||||
|
|
||||||
|
glib.IdleAdd(func() {
|
||||||
|
if len(activeNames) == 1 {
|
||||||
|
header.SetSubtitle("Not connected!")
|
||||||
|
} else {
|
||||||
|
activeNames := t.ActiveDeviceName()
|
||||||
|
header.SetSubtitle("Connected to " + strings.Join(activeNames, ", "))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect to a tunnel
|
||||||
|
c := exec.Command("wg-quick", "up", name)
|
||||||
|
output, err := c.Output()
|
||||||
|
if err != nil {
|
||||||
|
es := string(err.(*exec.ExitError).Stderr)
|
||||||
|
log.Error().Err(err).Str("output", string(output)).Str("error", es).Msg("wg-quick up error")
|
||||||
|
|
||||||
|
oerr := err.Error() + "\nwg-quick's output:\n" + es
|
||||||
|
wlog("ERROR", oerr)
|
||||||
|
return errors.New(oerr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if Settings.RememberConnectedServers {
|
||||||
|
// using time because order may be important
|
||||||
|
if Settings.ConnectedServers == nil {
|
||||||
|
Settings.ConnectedServers = map[string]int64{}
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings.ConnectedServers[name] = time.Now().Unix()
|
||||||
|
if err := Settings.Save(); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Error saving settings on connect")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update header label with tunnel names
|
||||||
|
glib.IdleAdd(func() {
|
||||||
|
activeNames := t.ActiveDeviceName()
|
||||||
|
header.SetSubtitle("Connected to " + strings.Join(activeNames, ", "))
|
||||||
|
})
|
||||||
|
|
||||||
|
// set icon to connected for the tunnel's row
|
||||||
|
glib.IdleAdd(func() {
|
||||||
|
t.icons[name].SetFromPixbuf(t.greenIcon.GetPixbuf())
|
||||||
|
t.UpdateRow(row)
|
||||||
|
indicator.SetIcon("wg_connected")
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := wlog("INFO", "Connected to "+name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tunnels) SetPasswordless() error {
|
||||||
|
// not installed
|
||||||
|
if !dry.FileExists("/usr/share/polkit-1/actions/wireguird.policy") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := ioutil.ReadFile("/usr/share/polkit-1/actions/wireguird.policy")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
changed := false
|
||||||
|
if Settings.Passwordless && bytes.Contains(content, []byte("<allow_any>auth_admin</allow_any>")) {
|
||||||
|
content = bytes.Replace(content, []byte("<allow_any>auth_admin</allow_any>"), []byte("<allow_any>yes</allow_any>"), 1)
|
||||||
|
content = bytes.Replace(content, []byte("<allow_inactive>auth_admin</allow_inactive>"), []byte("<allow_inactive>yes</allow_inactive>"), 1)
|
||||||
|
content = bytes.Replace(content, []byte("<allow_active>auth_admin_keep</allow_active>"), []byte("<allow_active>yes</allow_active>"), 1)
|
||||||
|
changed = true
|
||||||
|
} else if !Settings.Passwordless && bytes.Contains(content, []byte("<allow_any>yes</allow_any>")) {
|
||||||
|
content = bytes.Replace(content, []byte("<allow_any>yes</allow_any>"), []byte("<allow_any>auth_admin</allow_any>"), 1)
|
||||||
|
content = bytes.Replace(content, []byte("<allow_inactive>yes</allow_inactive>"), []byte("<allow_inactive>auth_admin</allow_inactive>"), 1)
|
||||||
|
content = bytes.Replace(content, []byte("<allow_active>yes</allow_active>"), []byte("<allow_active>auth_admin_keep</allow_active>"), 1)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
if err := ioutil.WriteFile("/usr/share/polkit-1/actions/wireguird.policy", content, 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func wlog(t string, text string) error {
|
func wlog(t string, text string) error {
|
||||||
wlogs, err := get.ListBox("wireguard_logs")
|
wlogs, err := get.ListBox("wireguard_logs")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
42
main.go
Executable file → Normal file
42
main.go
Executable file → Normal file
@ -3,11 +3,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/UnnoTed/go-appindicator"
|
"github.com/UnnoTed/go-appindicator"
|
||||||
"github.com/UnnoTed/horizontal"
|
"github.com/UnnoTed/horizontal"
|
||||||
"github.com/UnnoTed/wireguird/gui"
|
"github.com/UnnoTed/wireguird/gui"
|
||||||
|
"github.com/UnnoTed/wireguird/gui/countries"
|
||||||
"github.com/UnnoTed/wireguird/static"
|
"github.com/UnnoTed/wireguird/static"
|
||||||
"github.com/gotk3/gotk3/gdk"
|
"github.com/gotk3/gotk3/gdk"
|
||||||
"github.com/gotk3/gotk3/glib"
|
"github.com/gotk3/gotk3/glib"
|
||||||
@ -15,9 +18,31 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var iconTmpDir = os.TempDir()
|
||||||
var win *gtk.ApplicationWindow
|
var win *gtk.ApplicationWindow
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
if os.Geteuid() != 0 {
|
||||||
|
fmt.Println("\n\n\n\n")
|
||||||
|
fmt.Println("===========================================================================")
|
||||||
|
fmt.Println("===========================================================================")
|
||||||
|
fmt.Println("===========================================================================")
|
||||||
|
fmt.Println("=== ===")
|
||||||
|
fmt.Println("=== ===")
|
||||||
|
fmt.Println("=== Wireguird must be ran as root to access /etc/wireguard and wg-quick ===")
|
||||||
|
fmt.Println("=== Use pkexec to run as root: ===")
|
||||||
|
fmt.Println("=== pkexec wireguird ===")
|
||||||
|
fmt.Println("=== ===")
|
||||||
|
fmt.Println("=== ===")
|
||||||
|
fmt.Println("===========================================================================")
|
||||||
|
fmt.Println("===========================================================================")
|
||||||
|
fmt.Println("===========================================================================")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.Remove(iconTmpDir)
|
||||||
|
defer countries.CloseDatabase()
|
||||||
|
|
||||||
log.Logger = log.Output(horizontal.ConsoleWriter{Out: os.Stderr})
|
log.Logger = log.Output(horizontal.ConsoleWriter{Out: os.Stderr})
|
||||||
log.Info().Uint("major", gtk.GetMajorVersion()).Uint("minor", gtk.GetMinorVersion()).Uint("micro", gtk.GetMicroVersion()).Msg("GTK Version")
|
log.Info().Uint("major", gtk.GetMajorVersion()).Uint("minor", gtk.GetMinorVersion()).Uint("micro", gtk.GetMicroVersion()).Msg("GTK Version")
|
||||||
|
|
||||||
@ -57,8 +82,23 @@ func createTray(application *gtk.Application) (*appindicator.Indicator, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
icons := []string{"wireguard_off", "wg_connected"}
|
||||||
|
for _, icon := range icons {
|
||||||
|
iconBytes, err := static.ReadFile("./Icon/" + icon + ".png")
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("icon", icon).Msg("Error reading embedded icon")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iconPath := filepath.Join(iconTmpDir, icon+".png")
|
||||||
|
if err := os.WriteFile(iconPath, iconBytes, 0644); err != nil {
|
||||||
|
log.Error().Err(err).Str("icon", icon).Msg("Failed to write temp icon")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
indicator := appindicator.New(application.GetApplicationID(), "wireguard_off", appindicator.CategoryApplicationStatus)
|
indicator := appindicator.New(application.GetApplicationID(), "wireguard_off", appindicator.CategoryApplicationStatus)
|
||||||
indicator.SetIconThemePath("/opt/wireguird/Icon")
|
indicator.SetIconThemePath(iconTmpDir)
|
||||||
indicator.SetTitle("Wireguird")
|
indicator.SetTitle("Wireguird")
|
||||||
// indicator.SetLabel("Wireguird", "")
|
// indicator.SetLabel("Wireguird", "")
|
||||||
indicator.SetStatus(appindicator.StatusActive)
|
indicator.SetStatus(appindicator.StatusActive)
|
||||||
|
|||||||
@ -12,19 +12,16 @@ import (
|
|||||||
const FilePath = "./wireguird.settings"
|
const FilePath = "./wireguird.settings"
|
||||||
|
|
||||||
type Settings struct {
|
type Settings struct {
|
||||||
MultipleTunnels bool
|
MultipleTunnels bool
|
||||||
StartOnTray bool
|
StartOnTray bool
|
||||||
CheckUpdates bool
|
CheckUpdates bool
|
||||||
Debug bool
|
Debug bool
|
||||||
|
RememberConnectedServers bool
|
||||||
|
ConnectedServers map[string]int64
|
||||||
|
CountryFlags bool
|
||||||
|
Passwordless bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
multipleTunnels *bool
|
|
||||||
checkUpdates *bool
|
|
||||||
debug *bool
|
|
||||||
tray *bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *Settings) Init() error {
|
func (s *Settings) Init() error {
|
||||||
log.Debug().Msg("Settings init")
|
log.Debug().Msg("Settings init")
|
||||||
|
|
||||||
|
|||||||
59
wireguird.glade
Executable file → Normal file
59
wireguird.glade
Executable file → Normal file
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- Generated with glade 3.40.0 -->
|
<!-- Generated with glade 3.38.2 -->
|
||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk+" version="3.22"/>
|
<requires lib="gtk+" version="3.22"/>
|
||||||
<object class="GtkWindow" id="editor_window">
|
<object class="GtkWindow" id="editor_window">
|
||||||
@ -156,21 +156,18 @@
|
|||||||
<object class="GtkImage" id="icon_delete">
|
<object class="GtkImage" id="icon_delete">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="pixbuf">/opt/wireguird/Icon/delete.png</property>
|
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkImage" id="icon_tunnel">
|
<object class="GtkImage" id="icon_tunnel">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="pixbuf">/opt/wireguird/Icon/add_tunnel.png</property>
|
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkImage" id="icon_zip">
|
<object class="GtkImage" id="icon_zip">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="pixbuf">/opt/wireguird/Icon/zip.png</property>
|
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkApplicationWindow" id="main_window">
|
<object class="GtkApplicationWindow" id="main_window">
|
||||||
<property name="width-request">650</property>
|
<property name="width-request">720</property>
|
||||||
<property name="height-request">470</property>
|
<property name="height-request">480</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="window-position">center</property>
|
<property name="window-position">center</property>
|
||||||
<property name="show-menubar">False</property>
|
<property name="show-menubar">False</property>
|
||||||
@ -519,7 +516,7 @@
|
|||||||
<object class="GtkLabel">
|
<object class="GtkLabel">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="label" translatable="yes">Interface:</property>
|
<property name="label" translatable="yes"> Interface</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
@ -715,7 +712,7 @@
|
|||||||
<object class="GtkLabel">
|
<object class="GtkLabel">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="label" translatable="yes">Peer</property>
|
<property name="label" translatable="yes"> Peer</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
@ -892,7 +889,7 @@
|
|||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkCheckButton" id="settings_start_tray">
|
<object class="GtkCheckButton" id="settings_start_tray">
|
||||||
<property name="label" translatable="yes">Start in system tray</property>
|
<property name="label" translatable="yes">Launch minimized to system tray</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">True</property>
|
<property name="can-focus">True</property>
|
||||||
<property name="receives-default">False</property>
|
<property name="receives-default">False</property>
|
||||||
@ -918,6 +915,20 @@
|
|||||||
<property name="position">2</property>
|
<property name="position">2</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="settings_remember_connected">
|
||||||
|
<property name="label" translatable="yes">Auto-reconnect to previous servers on launch</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">False</property>
|
||||||
|
<property name="draw-indicator">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
@ -965,7 +976,35 @@
|
|||||||
<property name="expand">True</property>
|
<property name="expand">True</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="pack-type">end</property>
|
<property name="pack-type">end</property>
|
||||||
<property name="position">3</property>
|
<property name="position">4</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="settings_country_flags">
|
||||||
|
<property name="label" translatable="yes">Country flags (local db 16MB, updated weekly via github.com/iplocate/ip-address-databases)</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">False</property>
|
||||||
|
<property name="draw-indicator">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">4</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="settings_polkit_policy">
|
||||||
|
<property name="label" translatable="yes">Enable passwordless authentication (less secure)</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">False</property>
|
||||||
|
<property name="draw-indicator">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">5</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user