This commit is contained in:
Ioannis Dressos 2023-10-30 18:40:24 +00:00 committed by GitHub
commit fa905d5f22
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 250 additions and 129 deletions

View file

@ -1,4 +1,4 @@
![](https://github.com/ngoduykhanh/wireguard-ui/workflows/wireguard-ui%20build%20release/badge.svg) ![](https://github.com/idressos/wireguard-ui/workflows/wireguard-ui%20build%20release/badge.svg)
# wireguard-ui # wireguard-ui
@ -53,7 +53,11 @@ docker-compose up
| `WGUI_TABLE` | The default WireGuard table value settings | `auto` | | `WGUI_TABLE` | The default WireGuard table value settings | `auto` |
| `WGUI_CONFIG_FILE_PATH` | The default WireGuard config file path used in global settings | `/etc/wireguard/wg0.conf` | | `WGUI_CONFIG_FILE_PATH` | The default WireGuard config file path used in global settings | `/etc/wireguard/wg0.conf` |
| `WGUI_LOG_LEVEL` | The default log level. Possible values: `DEBUG`, `INFO`, `WARN`, `ERROR`, `OFF` | `INFO` | | `WGUI_LOG_LEVEL` | The default log level. Possible values: `DEBUG`, `INFO`, `WARN`, `ERROR`, `OFF` | `INFO` |
| `WG_CONF_TEMPLATE` | The custom `wg.conf` config file template. Please refer to our [default template](https://github.com/ngoduykhanh/wireguard-ui/blob/master/templates/wg.conf) | N/A | | `WGUI_BRAND_TEXT` | The brand text of the web application | `WireGuard UI` |
| `WGUI_ACCENT_COLOR` | The color of the interface sidebar | `#343a40` |
| `WGUI_LOGO_FILE_PATH` | The file path of the website logo | Embedded WireGuard logo |
| `WGUI_PAGE_TITLE_PREFIX` | The HTML title prefix for all pages | N/A |
| `WG_CONF_TEMPLATE` | The custom `wg.conf` config file template. Please refer to our [default template](https://github.com/idressos/wireguard-ui/blob/master/templates/wg.conf) | N/A |
| `EMAIL_FROM_ADDRESS` | The sender email address | N/A | | `EMAIL_FROM_ADDRESS` | The sender email address | N/A |
| `EMAIL_FROM_NAME` | The sender name | `WireGuard UI` | | `EMAIL_FROM_NAME` | The sender name | `WireGuard UI` |
| `SENDGRID_API_KEY` | The SendGrid api key | N/A | | `SENDGRID_API_KEY` | The SendGrid api key | N/A |
@ -63,6 +67,7 @@ docker-compose up
| `SMTP_PASSWORD` | The SMTP user password | N/A | | `SMTP_PASSWORD` | The SMTP user password | N/A |
| `SMTP_AUTH_TYPE` | The SMTP authentication type. Possible values: `PLAIN`, `LOGIN`, `NONE` | `NONE` | | `SMTP_AUTH_TYPE` | The SMTP authentication type. Possible values: `PLAIN`, `LOGIN`, `NONE` | `NONE` |
| `SMTP_ENCRYPTION` | the encryption method. Possible values: `NONE`, `SSL`, `SSLTLS`, `TLS`, `STARTTLS` | `STARTTLS` | | `SMTP_ENCRYPTION` | the encryption method. Possible values: `NONE`, `SSL`, `SSLTLS`, `TLS`, `STARTTLS` | `STARTTLS` |
| `HELLO_HOSTNAME` | Hostname to use for the hello message. smtp-relay.gmail.com needs this set to anything but `localhost` | `localhost` |
### Defaults for server configuration ### Defaults for server configuration
@ -181,9 +186,9 @@ rc-update add wgui default
### Using Docker ### Using Docker
Set `WGUI_MANAGE_RESTART=true` to manage Wireguard interface restarts. Set `WGUI_MANAGE_RESTART=true` to manage WireGuard interface restarts.
Using `WGUI_MANAGE_START=true` can also replace the function of `wg-quick@wg0` service, to start Wireguard at boot, by Using `WGUI_MANAGE_START=true` can also replace the function of `wg-quick@wg0` service, to start WireGuard at boot, by
running the container with `restart: unless-stopped`. These settings can also pick up changes to Wireguard Config File running the container with `restart: unless-stopped`. These settings can also pick up changes to WireGuard Config File
Path, after restarting the container. Please make sure you have `--cap-add=NET_ADMIN` in your container config to make Path, after restarting the container. Please make sure you have `--cap-add=NET_ADMIN` in your container config to make
this this
feature work. feature work.
@ -224,7 +229,7 @@ go build -o wireguard-ui
## License ## License
MIT. See [LICENSE](https://github.com/ngoduykhanh/wireguard-ui/blob/master/LICENSE). MIT. See [LICENSE](https://github.com/idressos/wireguard-ui/blob/master/LICENSE).
## Support ## Support

BIN
custom/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View file

@ -13,6 +13,7 @@ type SmtpMail struct {
port int port int
username string username string
password string password string
helloHostName string
authType mail.AuthType authType mail.AuthType
encryption mail.Encryption encryption mail.Encryption
noTLSCheck bool noTLSCheck bool
@ -46,8 +47,8 @@ func encryptionType(encryptionType string) mail.Encryption {
} }
} }
func NewSmtpMail(hostname string, port int, username string, password string, noTLSCheck bool, auth string, fromName, from string, encryption string) *SmtpMail { func NewSmtpMail(hostname string, port int, username string, password string, helloHostName string, noTLSCheck bool, auth string, fromName, from string, encryption string) *SmtpMail {
ans := SmtpMail{hostname: hostname, port: port, username: username, password: password, noTLSCheck: noTLSCheck, fromName: fromName, from: from, authType: authType(auth), encryption: encryptionType(encryption)} ans := SmtpMail{hostname: hostname, port: port, username: username, password: password, helloHostName: helloHostName, noTLSCheck: noTLSCheck, fromName: fromName, from: from, authType: authType(auth), encryption: encryptionType(encryption)}
return &ans return &ans
} }
@ -66,6 +67,7 @@ func (o *SmtpMail) Send(toName string, to string, subject string, content string
server.Authentication = o.authType server.Authentication = o.authType
server.Username = o.username server.Username = o.username
server.Password = o.password server.Password = o.password
server.Helo = o.helloHostName
server.Encryption = o.encryption server.Encryption = o.encryption
server.KeepAlive = false server.KeepAlive = false
server.ConnectTimeout = 10 * time.Second server.ConnectTimeout = 10 * time.Second

View file

@ -2,7 +2,7 @@
### Kernel Module ### Kernel Module
Depending on if the Wireguard kernel module is available on your system you have more or less choices which example to use. Depending on if the WireGuard kernel module is available on your system you have more or less choices which example to use.
You can check if the kernel modules are available via the following command: You can check if the kernel modules are available via the following command:
```shell ```shell
@ -21,10 +21,10 @@ For security reasons it's highly recommended to change them before the first sta
## Examples ## Examples
- **[system](system.yml)** - **[system](system.yml)**
If you have Wireguard already installed on your system and only want to run the UI in docker this might fit the most. If you have WireGuard already installed on your system and only want to run the UI in docker this might fit the most.
- **[linuxserver](linuxserver.yml)** - **[linuxserver](linuxserver.yml)**
If you have the Wireguard kernel modules installed (included in the mainline kernel since version 5.6) but want it running inside of docker, this might fit the most. If you have the WireGuard kernel modules installed (included in the mainline kernel since version 5.6) but want it running inside of docker, this might fit the most.
- **[boringtun](boringtun.yml)** - **[boringtun](boringtun.yml)**
If Wireguard kernel modules are not available, you can switch to an userspace implementation like [boringtun](https://github.com/cloudflare/boringtun). If WireGuard kernel modules are not available, you can switch to an userspace implementation like [boringtun](https://github.com/cloudflare/boringtun).

View file

@ -2,6 +2,7 @@ package handler
import ( import (
"crypto/subtle" "crypto/subtle"
"path/filepath"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -42,6 +43,15 @@ func Favicon() echo.HandlerFunc {
} }
} }
func Logo() echo.HandlerFunc {
return func(c echo.Context) error {
if logo, ok := os.LookupEnv(util.LogoFilePathEnvVar); ok {
return c.File(logo)
}
return c.Redirect(http.StatusFound, util.BasePath+"/static/custom/img/logo.png")
}
}
// LoginPage handler // LoginPage handler
func LoginPage() echo.HandlerFunc { func LoginPage() echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
@ -337,7 +347,7 @@ func WireGuardClients(db store.IStore) echo.HandlerFunc {
} }
} }
// GetClients handler return a JSON list of Wireguard client data // GetClients handler return a JSON list of WireGuard client data
func GetClients(db store.IStore) echo.HandlerFunc { func GetClients(db store.IStore) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
@ -352,7 +362,7 @@ func GetClients(db store.IStore) echo.HandlerFunc {
} }
} }
// GetClient handler returns a JSON object of Wireguard client data // GetClient handler returns a JSON object of WireGuard client data
func GetClient(db store.IStore) echo.HandlerFunc { func GetClient(db store.IStore) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
@ -409,12 +419,12 @@ func NewClient(db store.IStore) echo.HandlerFunc {
guid := xid.New() guid := xid.New()
client.ID = guid.String() client.ID = guid.String()
// gen Wireguard key pair // gen WireGuard key pair
if client.PublicKey == "" { if client.PublicKey == "" {
key, err := wgtypes.GeneratePrivateKey() key, err := wgtypes.GeneratePrivateKey()
if err != nil { if err != nil {
log.Error("Cannot generate wireguard key pair: ", err) log.Error("Cannot generate wireguard key pair: ", err)
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate Wireguard key pair"}) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate WireGuard key pair"})
} }
client.PrivateKey = key.String() client.PrivateKey = key.String()
client.PublicKey = key.PublicKey().String() client.PublicKey = key.PublicKey().String()
@ -422,7 +432,7 @@ func NewClient(db store.IStore) echo.HandlerFunc {
_, err := wgtypes.ParseKey(client.PublicKey) _, err := wgtypes.ParseKey(client.PublicKey)
if err != nil { if err != nil {
log.Error("Cannot verify wireguard public key: ", err) log.Error("Cannot verify wireguard public key: ", err)
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify Wireguard public key"}) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify WireGuard public key"})
} }
// check for duplicates // check for duplicates
clients, err := db.GetClients(false) clients, err := db.GetClients(false)
@ -444,7 +454,7 @@ func NewClient(db store.IStore) echo.HandlerFunc {
if err != nil { if err != nil {
log.Error("Cannot generated preshared key: ", err) log.Error("Cannot generated preshared key: ", err)
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{ return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{
false, "Cannot generate Wireguard preshared key", false, "Cannot generate WireGuard preshared key",
}) })
} }
client.PresharedKey = presharedKey.String() client.PresharedKey = presharedKey.String()
@ -455,7 +465,7 @@ func NewClient(db store.IStore) echo.HandlerFunc {
_, err := wgtypes.ParseKey(client.PresharedKey) _, err := wgtypes.ParseKey(client.PresharedKey)
if err != nil { if err != nil {
log.Error("Cannot verify wireguard preshared key: ", err) log.Error("Cannot verify wireguard preshared key: ", err)
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify Wireguard preshared key"}) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify WireGuard preshared key"})
} }
} }
client.CreatedAt = time.Now().UTC() client.CreatedAt = time.Now().UTC()
@ -567,12 +577,12 @@ func UpdateClient(db store.IStore) echo.HandlerFunc {
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Extra Allowed IPs must be in CIDR format"}) return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Extra Allowed IPs must be in CIDR format"})
} }
// update Wireguard Client PublicKey // update WireGuard Client PublicKey
if client.PublicKey != _client.PublicKey && _client.PublicKey != "" { if client.PublicKey != _client.PublicKey && _client.PublicKey != "" {
_, err := wgtypes.ParseKey(_client.PublicKey) _, err := wgtypes.ParseKey(_client.PublicKey)
if err != nil { if err != nil {
log.Error("Cannot verify provided Wireguard public key: ", err) log.Error("Cannot verify provided WireGuard public key: ", err)
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify provided Wireguard public key"}) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify provided WireGuard public key"})
} }
// check for duplicates // check for duplicates
clients, err := db.GetClients(false) clients, err := db.GetClients(false)
@ -587,7 +597,7 @@ func UpdateClient(db store.IStore) echo.HandlerFunc {
} }
} }
// When replacing any PublicKey, discard any locally stored Wireguard Client PrivateKey // When replacing any PublicKey, discard any locally stored WireGuard Client PrivateKey
// Client PubKey no longer corresponds to locally stored PrivKey. // Client PubKey no longer corresponds to locally stored PrivKey.
// QR code (needs PrivateKey) for this client is no longer possible now. // QR code (needs PrivateKey) for this client is no longer possible now.
@ -597,12 +607,12 @@ func UpdateClient(db store.IStore) echo.HandlerFunc {
} }
// update Wireguard Client PresharedKey // update WireGuard Client PresharedKey
if client.PresharedKey != _client.PresharedKey && _client.PresharedKey != "" { if client.PresharedKey != _client.PresharedKey && _client.PresharedKey != "" {
_, err := wgtypes.ParseKey(_client.PresharedKey) _, err := wgtypes.ParseKey(_client.PresharedKey)
if err != nil { if err != nil {
log.Error("Cannot verify provided Wireguard preshared key: ", err) log.Error("Cannot verify provided WireGuard preshared key: ", err)
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify provided Wireguard preshared key"}) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify provided WireGuard preshared key"})
} }
} }
@ -614,6 +624,7 @@ func UpdateClient(db store.IStore) echo.HandlerFunc {
client.AllocatedIPs = _client.AllocatedIPs client.AllocatedIPs = _client.AllocatedIPs
client.AllowedIPs = _client.AllowedIPs client.AllowedIPs = _client.AllowedIPs
client.ExtraAllowedIPs = _client.ExtraAllowedIPs client.ExtraAllowedIPs = _client.ExtraAllowedIPs
client.Endpoint = _client.Endpoint
client.PublicKey = _client.PublicKey client.PublicKey = _client.PublicKey
client.PresharedKey = _client.PresharedKey client.PresharedKey = _client.PresharedKey
client.UpdatedAt = time.Now().UTC() client.UpdatedAt = time.Now().UTC()
@ -689,7 +700,7 @@ func DownloadClient(db store.IStore) echo.HandlerFunc {
// set response header for downloading // set response header for downloading
c.Response().Header().Set(echo.HeaderContentDisposition, fmt.Sprintf("attachment; filename=%s.conf", clientData.Client.Name)) c.Response().Header().Set(echo.HeaderContentDisposition, fmt.Sprintf("attachment; filename=%s.conf", clientData.Client.Name))
return c.Stream(http.StatusOK, "text/plain", reader) return c.Stream(http.StatusOK, "text/conf", reader)
} }
} }
@ -759,11 +770,11 @@ func WireGuardServerInterfaces(db store.IStore) echo.HandlerFunc {
func WireGuardServerKeyPair(db store.IStore) echo.HandlerFunc { func WireGuardServerKeyPair(db store.IStore) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
// gen Wireguard key pair // gen WireGuard key pair
key, err := wgtypes.GeneratePrivateKey() key, err := wgtypes.GeneratePrivateKey()
if err != nil { if err != nil {
log.Error("Cannot generate wireguard key pair: ", err) log.Error("Cannot generate wireguard key pair: ", err)
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate Wireguard key pair"}) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate WireGuard key pair"})
} }
var serverKeyPair model.ServerKeypair var serverKeyPair model.ServerKeypair
@ -772,7 +783,7 @@ func WireGuardServerKeyPair(db store.IStore) echo.HandlerFunc {
serverKeyPair.UpdatedAt = time.Now().UTC() serverKeyPair.UpdatedAt = time.Now().UTC()
if err := db.SaveServerKeyPair(serverKeyPair); err != nil { if err := db.SaveServerKeyPair(serverKeyPair); err != nil {
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate Wireguard key pair"}) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate WireGuard key pair"})
} }
log.Infof("Updated wireguard server interfaces settings: %v", serverKeyPair) log.Infof("Updated wireguard server interfaces settings: %v", serverKeyPair)
@ -796,6 +807,16 @@ func GlobalSettings(db store.IStore) echo.HandlerFunc {
} }
} }
func extractDeviceNameFromConfigPath(db store.IStore) string {
settings, err := db.GetGlobalSettings()
if err != nil {
log.Error("Cannot get global settings: ", err)
}
base := filepath.Base(settings.ConfigFilePath)
return strings.TrimSuffix(base, filepath.Ext(base))
}
// Status handler // Status handler
func Status(db store.IStore) echo.HandlerFunc { func Status(db store.IStore) echo.HandlerFunc {
type PeerVM struct { type PeerVM struct {
@ -826,6 +847,8 @@ func Status(db store.IStore) echo.HandlerFunc {
}) })
} }
deviceName := extractDeviceNameFromConfigPath(db)
devices, err := wgClient.Devices() devices, err := wgClient.Devices()
if err != nil { if err != nil {
return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{ return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{
@ -854,35 +877,37 @@ func Status(db store.IStore) echo.HandlerFunc {
conv := map[bool]int{true: 1, false: 0} conv := map[bool]int{true: 1, false: 0}
for i := range devices { for i := range devices {
devVm := DeviceVM{Name: devices[i].Name} if devices[i].Name == deviceName {
for j := range devices[i].Peers { devVm := DeviceVM{Name: devices[i].Name}
var allocatedIPs string for j := range devices[i].Peers {
for _, ip := range devices[i].Peers[j].AllowedIPs { var allocatedIPs string
if len(allocatedIPs) > 0 { for _, ip := range devices[i].Peers[j].AllowedIPs {
allocatedIPs += "</br>" if len(allocatedIPs) > 0 {
allocatedIPs += "</br>"
}
allocatedIPs += ip.String()
} }
allocatedIPs += ip.String() pVm := PeerVM{
} PublicKey: devices[i].Peers[j].PublicKey.String(),
pVm := PeerVM{ ReceivedBytes: devices[i].Peers[j].ReceiveBytes,
PublicKey: devices[i].Peers[j].PublicKey.String(), TransmitBytes: devices[i].Peers[j].TransmitBytes,
ReceivedBytes: devices[i].Peers[j].ReceiveBytes, LastHandshakeTime: devices[i].Peers[j].LastHandshakeTime,
TransmitBytes: devices[i].Peers[j].TransmitBytes, LastHandshakeRel: time.Since(devices[i].Peers[j].LastHandshakeTime),
LastHandshakeTime: devices[i].Peers[j].LastHandshakeTime, AllocatedIP: allocatedIPs,
LastHandshakeRel: time.Since(devices[i].Peers[j].LastHandshakeTime), Endpoint: devices[i].Peers[j].Endpoint.String(),
AllocatedIP: allocatedIPs, }
Endpoint: devices[i].Peers[j].Endpoint.String(), pVm.Connected = pVm.LastHandshakeRel.Minutes() < 3.
}
pVm.Connected = pVm.LastHandshakeRel.Minutes() < 3.
if _client, ok := m[pVm.PublicKey]; ok { if _client, ok := m[pVm.PublicKey]; ok {
pVm.Name = _client.Name pVm.Name = _client.Name
pVm.Email = _client.Email pVm.Email = _client.Email
}
devVm.Peers = append(devVm.Peers, pVm)
} }
devVm.Peers = append(devVm.Peers, pVm) sort.SliceStable(devVm.Peers, func(i, j int) bool { return devVm.Peers[i].Name < devVm.Peers[j].Name })
sort.SliceStable(devVm.Peers, func(i, j int) bool { return conv[devVm.Peers[i].Connected] > conv[devVm.Peers[j].Connected] })
devicesVm = append(devicesVm, devVm)
} }
sort.SliceStable(devVm.Peers, func(i, j int) bool { return devVm.Peers[i].Name < devVm.Peers[j].Name })
sort.SliceStable(devVm.Peers, func(i, j int) bool { return conv[devVm.Peers[i].Connected] > conv[devVm.Peers[j].Connected] })
devicesVm = append(devicesVm, devVm)
} }
} }
@ -911,7 +936,7 @@ func GlobalSettingSubmit(db store.IStore) echo.HandlerFunc {
// write config to the database // write config to the database
if err := db.SaveGlobalSettings(globalSettings); err != nil { if err := db.SaveGlobalSettings(globalSettings); err != nil {
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate Wireguard key pair"}) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate WireGuard key pair"})
} }
log.Infof("Updated global settings: %v", globalSettings) log.Infof("Updated global settings: %v", globalSettings)
@ -985,7 +1010,7 @@ func SuggestIPAllocation(db store.IStore) echo.HandlerFunc {
} }
} }
// ApplyServerConfig handler to write config file and restart Wireguard server // ApplyServerConfig handler to write config file and restart WireGuard server
func ApplyServerConfig(db store.IStore, tmplDir fs.FS) echo.HandlerFunc { func ApplyServerConfig(db store.IStore, tmplDir fs.FS) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {

34
main.go
View file

@ -30,6 +30,7 @@ var (
flagBindAddress string = "0.0.0.0:5000" flagBindAddress string = "0.0.0.0:5000"
flagSmtpHostname string = "127.0.0.1" flagSmtpHostname string = "127.0.0.1"
flagSmtpPort int = 25 flagSmtpPort int = 25
flagHelloHostName string = "localhost"
flagSmtpUsername string flagSmtpUsername string
flagSmtpPassword string flagSmtpPassword string
flagSmtpAuthType string = "NONE" flagSmtpAuthType string = "NONE"
@ -41,14 +42,18 @@ var (
flagSessionSecret string = util.RandomString(32) flagSessionSecret string = util.RandomString(32)
flagWgConfTemplate string flagWgConfTemplate string
flagBasePath string flagBasePath string
flagBrandText string = "WireGuard UI"
flagAccentColor string = "#343a40"
flagPageTitlePrefix string
) )
const ( const (
defaultEmailSubject = "Your wireguard configuration" defaultEmailSubject = "Your VPN configuration"
defaultEmailContent = `Hi,</br>
<p>In this email you can find your personal configuration for our wireguard server.</p>
<p>Best</p> defaultEmailContent = `
<p>Greetings,</p>
<p>Please find attached your personal configuration for our VPN server.<br>You may find instructions on how to install the WireGuard VPN client <a href="https://www.wireguard.com/install/">here</a>.</p>
<p>Best regards.</p>
` `
) )
@ -69,6 +74,7 @@ func init() {
flag.StringVar(&flagBindAddress, "bind-address", util.LookupEnvOrString("BIND_ADDRESS", flagBindAddress), "Address:Port to which the app will be bound.") flag.StringVar(&flagBindAddress, "bind-address", util.LookupEnvOrString("BIND_ADDRESS", flagBindAddress), "Address:Port to which the app will be bound.")
flag.StringVar(&flagSmtpHostname, "smtp-hostname", util.LookupEnvOrString("SMTP_HOSTNAME", flagSmtpHostname), "SMTP Hostname") flag.StringVar(&flagSmtpHostname, "smtp-hostname", util.LookupEnvOrString("SMTP_HOSTNAME", flagSmtpHostname), "SMTP Hostname")
flag.IntVar(&flagSmtpPort, "smtp-port", util.LookupEnvOrInt("SMTP_PORT", flagSmtpPort), "SMTP Port") flag.IntVar(&flagSmtpPort, "smtp-port", util.LookupEnvOrInt("SMTP_PORT", flagSmtpPort), "SMTP Port")
flag.StringVar(&flagHelloHostName, "hello-hostname", util.LookupEnvOrString("HELLO_HOSTNAME", flagHelloHostName), "Hello HostName")
flag.StringVar(&flagSmtpUsername, "smtp-username", util.LookupEnvOrString("SMTP_USERNAME", flagSmtpUsername), "SMTP Username") flag.StringVar(&flagSmtpUsername, "smtp-username", util.LookupEnvOrString("SMTP_USERNAME", flagSmtpUsername), "SMTP Username")
flag.StringVar(&flagSmtpPassword, "smtp-password", util.LookupEnvOrString("SMTP_PASSWORD", flagSmtpPassword), "SMTP Password") flag.StringVar(&flagSmtpPassword, "smtp-password", util.LookupEnvOrString("SMTP_PASSWORD", flagSmtpPassword), "SMTP Password")
flag.BoolVar(&flagSmtpNoTLSCheck, "smtp-no-tls-check", util.LookupEnvOrBool("SMTP_NO_TLS_CHECK", flagSmtpNoTLSCheck), "Disable TLS verification for SMTP. This is potentially dangerous.") flag.BoolVar(&flagSmtpNoTLSCheck, "smtp-no-tls-check", util.LookupEnvOrBool("SMTP_NO_TLS_CHECK", flagSmtpNoTLSCheck), "Disable TLS verification for SMTP. This is potentially dangerous.")
@ -80,6 +86,10 @@ func init() {
flag.StringVar(&flagSessionSecret, "session-secret", util.LookupEnvOrString("SESSION_SECRET", flagSessionSecret), "The key used to encrypt session cookies.") flag.StringVar(&flagSessionSecret, "session-secret", util.LookupEnvOrString("SESSION_SECRET", flagSessionSecret), "The key used to encrypt session cookies.")
flag.StringVar(&flagWgConfTemplate, "wg-conf-template", util.LookupEnvOrString("WG_CONF_TEMPLATE", flagWgConfTemplate), "Path to custom wg.conf template.") flag.StringVar(&flagWgConfTemplate, "wg-conf-template", util.LookupEnvOrString("WG_CONF_TEMPLATE", flagWgConfTemplate), "Path to custom wg.conf template.")
flag.StringVar(&flagBasePath, "base-path", util.LookupEnvOrString("BASE_PATH", flagBasePath), "The base path of the URL") flag.StringVar(&flagBasePath, "base-path", util.LookupEnvOrString("BASE_PATH", flagBasePath), "The base path of the URL")
flag.StringVar(&flagBrandText, "brand-text", util.LookupEnvOrString("WGUI_BRAND_TEXT", flagBrandText), "The UI brand text or name")
flag.StringVar(&flagAccentColor, "accent-color", util.LookupEnvOrString("WGUI_ACCENT_COLOR", flagAccentColor), "The UI accent color")
flag.StringVar(&flagPageTitlePrefix, "page-title-prefix", util.LookupEnvOrString("WGUI_PAGE_TITLE_PREFIX", flagPageTitlePrefix), "The prefix of the page title")
flag.Parse() flag.Parse()
// update runtime config // update runtime config
@ -87,6 +97,7 @@ func init() {
util.BindAddress = flagBindAddress util.BindAddress = flagBindAddress
util.SmtpHostname = flagSmtpHostname util.SmtpHostname = flagSmtpHostname
util.SmtpPort = flagSmtpPort util.SmtpPort = flagSmtpPort
util.HelloHostName = flagHelloHostName
util.SmtpUsername = flagSmtpUsername util.SmtpUsername = flagSmtpUsername
util.SmtpPassword = flagSmtpPassword util.SmtpPassword = flagSmtpPassword
util.SmtpAuthType = flagSmtpAuthType util.SmtpAuthType = flagSmtpAuthType
@ -98,16 +109,19 @@ func init() {
util.SessionSecret = []byte(flagSessionSecret) util.SessionSecret = []byte(flagSessionSecret)
util.WgConfTemplate = flagWgConfTemplate util.WgConfTemplate = flagWgConfTemplate
util.BasePath = util.ParseBasePath(flagBasePath) util.BasePath = util.ParseBasePath(flagBasePath)
util.BrandText = flagBrandText
util.AccentColor = flagAccentColor
util.PageTitlePrefix = flagPageTitlePrefix
// print only if log level is INFO or lower // print only if log level is INFO or lower
if lvl, _ := util.ParseLogLevel(util.LookupEnvOrString(util.LogLevel, "INFO")); lvl <= log.INFO { if lvl, _ := util.ParseLogLevel(util.LookupEnvOrString(util.LogLevel, "INFO")); lvl <= log.INFO {
// print app information // print app information
fmt.Println("Wireguard UI") fmt.Println("WireGuard UI")
fmt.Println("App Version\t:", appVersion) fmt.Println("App Version\t:", appVersion)
fmt.Println("Git Commit\t:", gitCommit) fmt.Println("Git Commit\t:", gitCommit)
fmt.Println("Git Ref\t\t:", gitRef) fmt.Println("Git Ref\t\t:", gitRef)
fmt.Println("Build Time\t:", buildTime) fmt.Println("Build Time\t:", buildTime)
fmt.Println("Git Repo\t:", "https://github.com/ngoduykhanh/wireguard-ui") fmt.Println("Git Repo\t:", "https://github.com/idressos/wireguard-ui")
fmt.Println("Authentication\t:", !util.DisableLogin) fmt.Println("Authentication\t:", !util.DisableLogin)
fmt.Println("Bind address\t:", util.BindAddress) fmt.Println("Bind address\t:", util.BindAddress)
//fmt.Println("Sendgrid key\t:", util.SendgridApiKey) //fmt.Println("Sendgrid key\t:", util.SendgridApiKey)
@ -133,6 +147,9 @@ func main() {
extraData["gitCommit"] = gitCommit extraData["gitCommit"] = gitCommit
extraData["basePath"] = util.BasePath extraData["basePath"] = util.BasePath
extraData["loginDisabled"] = flagDisableLogin extraData["loginDisabled"] = flagDisableLogin
extraData["brandText"] = flagBrandText;
extraData["accentColor"] = flagAccentColor;
extraData["pageTitlePrefix"] = flagPageTitlePrefix;
// strip the "templates/" prefix from the embedded directory so files can be read by their direct name (e.g. // strip the "templates/" prefix from the embedded directory so files can be read by their direct name (e.g.
// "base.html" instead of "templates/base.html") // "base.html" instead of "templates/base.html")
@ -163,13 +180,14 @@ func main() {
if util.SendgridApiKey != "" { if util.SendgridApiKey != "" {
sendmail = emailer.NewSendgridApiMail(util.SendgridApiKey, util.EmailFromName, util.EmailFrom) sendmail = emailer.NewSendgridApiMail(util.SendgridApiKey, util.EmailFromName, util.EmailFrom)
} else { } else {
sendmail = emailer.NewSmtpMail(util.SmtpHostname, util.SmtpPort, util.SmtpUsername, util.SmtpPassword, util.SmtpNoTLSCheck, util.SmtpAuthType, util.EmailFromName, util.EmailFrom, util.SmtpEncryption) sendmail = emailer.NewSmtpMail(util.SmtpHostname, util.SmtpPort, util.SmtpUsername, util.SmtpPassword, util.HelloHostName, util.SmtpNoTLSCheck, util.SmtpAuthType, util.EmailFromName, util.EmailFrom, util.SmtpEncryption)
} }
app.GET(util.BasePath+"/test-hash", handler.GetHashesChanges(db), handler.ValidSession) app.GET(util.BasePath+"/test-hash", handler.GetHashesChanges(db), handler.ValidSession)
app.GET(util.BasePath+"/about", handler.AboutPage()) app.GET(util.BasePath+"/about", handler.AboutPage())
app.GET(util.BasePath+"/_health", handler.Health()) app.GET(util.BasePath+"/_health", handler.Health())
app.GET(util.BasePath+"/favicon", handler.Favicon()) app.GET(util.BasePath+"/favicon", handler.Favicon())
app.GET(util.BasePath+"/logo", handler.Logo())
app.POST(util.BasePath+"/new-client", handler.NewClient(db), handler.ValidSession, handler.ContentTypeJson) app.POST(util.BasePath+"/new-client", handler.NewClient(db), handler.ValidSession, handler.ContentTypeJson)
app.POST(util.BasePath+"/update-client", handler.UpdateClient(db), handler.ValidSession, handler.ContentTypeJson) app.POST(util.BasePath+"/update-client", handler.UpdateClient(db), handler.ValidSession, handler.ContentTypeJson)
app.POST(util.BasePath+"/email-client", handler.EmailClient(db, sendmail, defaultEmailSubject, defaultEmailContent), handler.ValidSession, handler.ContentTypeJson) app.POST(util.BasePath+"/email-client", handler.EmailClient(db, sendmail, defaultEmailSubject, defaultEmailContent), handler.ValidSession, handler.ContentTypeJson)

View file

@ -15,6 +15,7 @@ type Client struct {
AllocatedIPs []string `json:"allocated_ips"` AllocatedIPs []string `json:"allocated_ips"`
AllowedIPs []string `json:"allowed_ips"` AllowedIPs []string `json:"allowed_ips"`
ExtraAllowedIPs []string `json:"extra_allowed_ips"` ExtraAllowedIPs []string `json:"extra_allowed_ips"`
Endpoint string `json:"endpoint"`
UseServerDNS bool `json:"use_server_dns"` UseServerDNS bool `json:"use_server_dns"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`

View file

@ -1,7 +1,7 @@
{ {
"name": "wireguard-ui", "name": "wireguard-ui",
"version": "1.0.0", "version": "1.0.0",
"description": "Wireguard web interface", "description": "WireGuard web interface",
"main": "index.js", "main": "index.js",
"repository": "git@github.com:ngoduykhanh/wireguard-ui.git", "repository": "git@github.com:ngoduykhanh/wireguard-ui.git",
"author": "Khanh Ngo <k@ndk.name>", "author": "Khanh Ngo <k@ndk.name>",

View file

@ -68,7 +68,10 @@ func (o *JsonDB) Init() error {
serverInterface.PostDown = util.LookupEnvOrString(util.ServerPostDownScriptEnvVar, "") serverInterface.PostDown = util.LookupEnvOrString(util.ServerPostDownScriptEnvVar, "")
serverInterface.UpdatedAt = time.Now().UTC() serverInterface.UpdatedAt = time.Now().UTC()
o.conn.Write("server", "interfaces", serverInterface) o.conn.Write("server", "interfaces", serverInterface)
os.Chmod(serverInterfacePath, 0600) err := util.ManagePerms(serverInterfacePath)
if err != nil {
return err
}
} }
// server's key pair // server's key pair
@ -83,7 +86,10 @@ func (o *JsonDB) Init() error {
serverKeyPair.PublicKey = key.PublicKey().String() serverKeyPair.PublicKey = key.PublicKey().String()
serverKeyPair.UpdatedAt = time.Now().UTC() serverKeyPair.UpdatedAt = time.Now().UTC()
o.conn.Write("server", "keypair", serverKeyPair) o.conn.Write("server", "keypair", serverKeyPair)
os.Chmod(serverKeyPairPath, 0600) err = util.ManagePerms(serverKeyPairPath)
if err != nil {
return err
}
} }
// global settings // global settings
@ -108,7 +114,10 @@ func (o *JsonDB) Init() error {
globalSetting.ConfigFilePath = util.LookupEnvOrString(util.ConfigFilePathEnvVar, util.DefaultConfigFilePath) globalSetting.ConfigFilePath = util.LookupEnvOrString(util.ConfigFilePathEnvVar, util.DefaultConfigFilePath)
globalSetting.UpdatedAt = time.Now().UTC() globalSetting.UpdatedAt = time.Now().UTC()
o.conn.Write("server", "global_settings", globalSetting) o.conn.Write("server", "global_settings", globalSetting)
os.Chmod(globalSettingPath, 0600) err := util.ManagePerms(globalSettingPath)
if err != nil {
return err
}
} }
// hashes // hashes
@ -117,7 +126,10 @@ func (o *JsonDB) Init() error {
clientServerHashes.Client = "none" clientServerHashes.Client = "none"
clientServerHashes.Server = "none" clientServerHashes.Server = "none"
o.conn.Write("server", "hashes", clientServerHashes) o.conn.Write("server", "hashes", clientServerHashes)
os.Chmod(hashesPath, 0600) err := util.ManagePerms(hashesPath)
if err != nil {
return err
}
} }
// user info // user info
@ -136,7 +148,10 @@ func (o *JsonDB) Init() error {
user.PasswordHash = hash user.PasswordHash = hash
} }
o.conn.Write("users", user.Username, user) o.conn.Write("users", user.Username, user)
os.Chmod(path.Join(path.Join(o.dbPath, "users"), user.Username+".json"), 0600) err = util.ManagePerms(path.Join(path.Join(o.dbPath, "users"), user.Username+".json"))
if err != nil {
return err
}
} }
return nil return nil
@ -182,7 +197,10 @@ func (o *JsonDB) GetUserByName(username string) (model.User, error) {
func (o *JsonDB) SaveUser(user model.User) error { func (o *JsonDB) SaveUser(user model.User) error {
userPath := path.Join(path.Join(o.dbPath, "users"), user.Username+".json") userPath := path.Join(path.Join(o.dbPath, "users"), user.Username+".json")
output := o.conn.Write("users", user.Username, user) output := o.conn.Write("users", user.Username, user)
os.Chmod(userPath, 0600) err := util.ManagePerms(userPath)
if err != nil {
return err
}
return output return output
} }
@ -295,7 +313,10 @@ func (o *JsonDB) GetClientByID(clientID string, qrCodeSettings model.QRCodeSetti
func (o *JsonDB) SaveClient(client model.Client) error { func (o *JsonDB) SaveClient(client model.Client) error {
clientPath := path.Join(path.Join(o.dbPath, "clients"), client.ID+".json") clientPath := path.Join(path.Join(o.dbPath, "clients"), client.ID+".json")
output := o.conn.Write("clients", client.ID, client) output := o.conn.Write("clients", client.ID, client)
os.Chmod(clientPath, 0600) err := util.ManagePerms(clientPath)
if err != nil {
return err
}
return output return output
} }
@ -306,21 +327,30 @@ func (o *JsonDB) DeleteClient(clientID string) error {
func (o *JsonDB) SaveServerInterface(serverInterface model.ServerInterface) error { func (o *JsonDB) SaveServerInterface(serverInterface model.ServerInterface) error {
serverInterfacePath := path.Join(path.Join(o.dbPath, "server"), "interfaces.json") serverInterfacePath := path.Join(path.Join(o.dbPath, "server"), "interfaces.json")
output := o.conn.Write("server", "interfaces", serverInterface) output := o.conn.Write("server", "interfaces", serverInterface)
os.Chmod(serverInterfacePath, 0600) err := util.ManagePerms(serverInterfacePath)
if err != nil {
return err
}
return output return output
} }
func (o *JsonDB) SaveServerKeyPair(serverKeyPair model.ServerKeypair) error { func (o *JsonDB) SaveServerKeyPair(serverKeyPair model.ServerKeypair) error {
serverKeyPairPath := path.Join(path.Join(o.dbPath, "server"), "keypair.json") serverKeyPairPath := path.Join(path.Join(o.dbPath, "server"), "keypair.json")
output := o.conn.Write("server", "keypair", serverKeyPair) output := o.conn.Write("server", "keypair", serverKeyPair)
os.Chmod(serverKeyPairPath, 0600) err := util.ManagePerms(serverKeyPairPath)
if err != nil {
return err
}
return output return output
} }
func (o *JsonDB) SaveGlobalSettings(globalSettings model.GlobalSetting) error { func (o *JsonDB) SaveGlobalSettings(globalSettings model.GlobalSetting) error {
globalSettingsPath := path.Join(path.Join(o.dbPath, "server"), "global_settings.json") globalSettingsPath := path.Join(path.Join(o.dbPath, "server"), "global_settings.json")
output := o.conn.Write("server", "global_settings", globalSettings) output := o.conn.Write("server", "global_settings", globalSettings)
os.Chmod(globalSettingsPath, 0600) err := util.ManagePerms(globalSettingsPath)
if err != nil {
return err
}
return output return output
} }
@ -336,6 +366,9 @@ func (o *JsonDB) GetHashes() (model.ClientServerHashes, error) {
func (o *JsonDB) SaveHashes(hashes model.ClientServerHashes) error { func (o *JsonDB) SaveHashes(hashes model.ClientServerHashes) error {
hashesPath := path.Join(path.Join(o.dbPath, "server"), "hashes.json") hashesPath := path.Join(path.Join(o.dbPath, "server"), "hashes.json")
output := o.conn.Write("server", "hashes", hashes) output := o.conn.Write("server", "hashes", hashes)
os.Chmod(hashesPath, 0600) err := util.ManagePerms(hashesPath)
if err != nil {
return err
}
return output return output
} }

View file

@ -3,10 +3,10 @@ package jsondb
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"path" "path"
"github.com/ngoduykhanh/wireguard-ui/model" "github.com/ngoduykhanh/wireguard-ui/model"
"github.com/ngoduykhanh/wireguard-ui/util"
) )
func (o *JsonDB) GetWakeOnLanHosts() ([]model.WakeOnLanHost, error) { func (o *JsonDB) GetWakeOnLanHosts() ([]model.WakeOnLanHost, error) {
@ -70,7 +70,11 @@ func (o *JsonDB) SaveWakeOnLanHost(host model.WakeOnLanHost) error {
wakeOnLanHostPath := path.Join(path.Join(o.dbPath, model.WakeOnLanHostCollectionName), resourceName+".json") wakeOnLanHostPath := path.Join(path.Join(o.dbPath, model.WakeOnLanHostCollectionName), resourceName+".json")
output := o.conn.Write(model.WakeOnLanHostCollectionName, resourceName, host) output := o.conn.Write(model.WakeOnLanHostCollectionName, resourceName, host)
os.Chmod(wakeOnLanHostPath, 0600) err = util.ManagePerms(wakeOnLanHostPath)
if err != nil {
return err
}
return output return output
} }

View file

@ -22,7 +22,7 @@ About
<div class="col-md-6"> <div class="col-md-6">
<div class="card card-success"> <div class="card card-success">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">About Wireguard-UI</h3> <h3 class="card-title">About WireGuard-UI</h3>
</div> </div>
<!-- /.card-header --> <!-- /.card-header -->
<div class="card-body"> <div class="card-body">
@ -63,7 +63,7 @@ About
</div> </div>
<strong>Copyright &copy; <strong>Copyright &copy;
<script>document.write(new Date().getFullYear())</script> <script>document.write(new Date().getFullYear())</script>
<a href="https://github.com/ngoduykhanh/wireguard-ui">Wireguard UI</a>. <a href="https://github.com/idressos/wireguard-ui">WireGuard UI</a>.
</strong> All rights reserved. </strong> All rights reserved.
</div> </div>
@ -83,7 +83,7 @@ About
$.ajax({ $.ajax({
cache: false, cache: false,
method: 'GET', method: 'GET',
url: 'https://api.github.com/repos/ngoduykhanh/wireguard-ui/releases/tags/' + $("#version").val(), url: 'https://api.github.com/repos/idressos/wireguard-ui/releases/tags/' + $("#version").val(),
dataType: 'json', dataType: 'json',
contentType: "application/json", contentType: "application/json",
success: function (data) { success: function (data) {
@ -99,7 +99,7 @@ About
$.ajax({ $.ajax({
cache: false, cache: false,
method: 'GET', method: 'GET',
url: 'https://api.github.com/repos/ngoduykhanh/wireguard-ui/releases/latest', url: 'https://api.github.com/repos/idressos/wireguard-ui/releases/latest',
dataType: 'json', dataType: 'json',
contentType: "application/json", contentType: "application/json",
success: function (data) { success: function (data) {
@ -121,7 +121,7 @@ About
$.ajax({ $.ajax({
cache: false, cache: false,
method: 'GET', method: 'GET',
url: 'https://api.github.com/repos/ngoduykhanh/wireguard-ui/contributors', url: 'https://api.github.com/repos/idressos/wireguard-ui/contributors',
dataType: 'json', dataType: 'json',
contentType: "application/json", contentType: "application/json",
success: function (data) { success: function (data) {

View file

@ -5,7 +5,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{{template "title" .}}</title> <title>{{.pageTitlePrefix}}{{template "title" .}}</title>
<!-- Tell the browser to be responsive to screen width --> <!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Favicon --> <!-- Favicon -->
@ -84,10 +84,10 @@
<!-- /.navbar --> <!-- /.navbar -->
<!-- Main Sidebar Container --> <!-- Main Sidebar Container -->
<aside class="main-sidebar sidebar-dark-primary elevation-4"> <aside class="main-sidebar sidebar-dark-primary elevation-4" style="background-color: {{.accentColor}};">
<!-- Brand Logo --> <!-- Brand Logo -->
<a href="{{.basePath}}" class="brand-link"> <a href="{{.basePath}}" class="brand-link">
<span class="brand-text">&nbsp; WIREGUARD UI</span> <span class="brand-text">&nbsp; {{.brandText}}</span>
</a> </a>
<!-- Sidebar --> <!-- Sidebar -->
@ -120,7 +120,7 @@
<a href="{{.basePath}}/" class="nav-link {{if eq .baseData.Active ""}}active{{end}}"> <a href="{{.basePath}}/" class="nav-link {{if eq .baseData.Active ""}}active{{end}}">
<i class="nav-icon fas fa-user-secret"></i> <i class="nav-icon fas fa-user-secret"></i>
<p> <p>
Wireguard Clients Clients
</p> </p>
</a> </a>
</li> </li>
@ -130,7 +130,7 @@
<a href="{{.basePath}}/wg-server" class="nav-link {{if eq .baseData.Active "wg-server" }}active{{end}}"> <a href="{{.basePath}}/wg-server" class="nav-link {{if eq .baseData.Active "wg-server" }}active{{end}}">
<i class="nav-icon fas fa-server"></i> <i class="nav-icon fas fa-server"></i>
<p> <p>
Wireguard Server WireGuard Server
</p> </p>
</a> </a>
</li> </li>
@ -174,6 +174,8 @@
</p> </p>
</a> </a>
</li> </li>
{{if .baseData.Admin}}
<li class="nav-header">ABOUT</li> <li class="nav-header">ABOUT</li>
<li class="nav-item"> <li class="nav-item">
<a href="{{.basePath}}/about" class="nav-link {{if eq .baseData.Active "about" }}active{{end}}"> <a href="{{.basePath}}/about" class="nav-link {{if eq .baseData.Active "about" }}active{{end}}">
@ -183,6 +185,7 @@
</p> </p>
</a> </a>
</li> </li>
{{end}}
</ul> </ul>
</nav> </nav>
<!-- /.sidebar-menu --> <!-- /.sidebar-menu -->
@ -194,7 +197,7 @@
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h4 class="modal-title">New Wireguard Client</h4> <h4 class="modal-title">New WireGuard Client</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
@ -232,6 +235,10 @@
</label> </label>
<input type="text" data-role="tagsinput" class="form-control" id="client_extra_allowed_ips" value="{{ StringsJoin .client_defaults.ExtraAllowedIps "," }}"> <input type="text" data-role="tagsinput" class="form-control" id="client_extra_allowed_ips" value="{{ StringsJoin .client_defaults.ExtraAllowedIps "," }}">
</div> </div>
<div class="form-group">
<label for="client_endpoint" class="control-label">Endpoint</label>
<input type="text" class="form-control" id="client_endpoint" name="client_endpoint">
</div>
<div class="form-group"> <div class="form-group">
<div class="icheck-primary d-inline"> <div class="icheck-primary d-inline">
<input type="checkbox" id="use_server_dns" {{ if .client_defaults.UseServerDNS }}checked{{ end }}> <input type="checkbox" id="use_server_dns" {{ if .client_defaults.UseServerDNS }}checked{{ end }}>
@ -328,7 +335,7 @@
<div class="float-right d-none d-sm-block"> <div class="float-right d-none d-sm-block">
<b>Version</b> {{ .appVersion }} <b>Version</b> {{ .appVersion }}
</div> </div>
<strong>Copyright &copy; <script>document.write(new Date().getFullYear())</script> <a href="https://github.com/ngoduykhanh/wireguard-ui">Wireguard UI</a>.</strong> All rights <strong>Copyright &copy; <script>document.write(new Date().getFullYear())</script> <a href="https://github.com/idressos/wireguard-ui">WireGuard UI</a>.</strong> All rights
reserved. reserved.
</footer> </footer>
--> -->
@ -413,6 +420,7 @@
const email = $("#client_email").val(); const email = $("#client_email").val();
const allocated_ips = $("#client_allocated_ips").val().split(","); const allocated_ips = $("#client_allocated_ips").val().split(",");
const allowed_ips = $("#client_allowed_ips").val().split(","); const allowed_ips = $("#client_allowed_ips").val().split(",");
const endpoint = $("#client_endpoint").val();
let use_server_dns = false; let use_server_dns = false;
let extra_allowed_ips = []; let extra_allowed_ips = [];
@ -434,7 +442,7 @@
const preshared_key = $("#client_preshared_key").val(); const preshared_key = $("#client_preshared_key").val();
const data = {"name": name, "email": email, "allocated_ips": allocated_ips, "allowed_ips": allowed_ips, const data = {"name": name, "email": email, "allocated_ips": allocated_ips, "allowed_ips": allowed_ips,
"extra_allowed_ips": extra_allowed_ips, "use_server_dns": use_server_dns, "enabled": enabled, "extra_allowed_ips": extra_allowed_ips, "endpoint": endpoint, "use_server_dns": use_server_dns, "enabled": enabled,
"public_key": public_key, "preshared_key": preshared_key}; "public_key": public_key, "preshared_key": preshared_key};
$.ajax({ $.ajax({

View file

@ -1,5 +1,5 @@
{{define "title"}} {{define "title"}}
Wireguard Clients Clients
{{end}} {{end}}
{{define "top_css"}} {{define "top_css"}}
@ -17,13 +17,13 @@ Wireguard Clients
{{end}} {{end}}
{{define "page_title"}} {{define "page_title"}}
Wireguard Clients Clients
{{end}} {{end}}
{{define "page_content"}} {{define "page_content"}}
<section class="content"> <section class="content">
<div class="container-fluid"> <div class="container-fluid">
<!-- <h5 class="mt-4 mb-2">Wireguard Clients</h5> --> <!-- <h5 class="mt-4 mb-2">Clients</h5> -->
<div class="row" id="client-list"> <div class="row" id="client-list">
</div> </div>
<!-- /.row --> <!-- /.row -->
@ -113,6 +113,10 @@ Wireguard Clients
<input type="text" data-role="tagsinput" class="form-control" <input type="text" data-role="tagsinput" class="form-control"
id="_client_extra_allowed_ips"> id="_client_extra_allowed_ips">
</div> </div>
<div class="form-group">
<label for="_client_endpoint" class="control-label">Endpoint</label>
<input type="text" class="form-control" id="_client_endpoint" name="client_endpoint">
</div>
<div class="form-group"> <div class="form-group">
<div class="icheck-primary d-inline"> <div class="icheck-primary d-inline">
<input type="checkbox" id="_use_server_dns"> <input type="checkbox" id="_use_server_dns">
@ -477,6 +481,8 @@ Wireguard Clients
modal.find("#_client_extra_allowed_ips").addTag(obj); modal.find("#_client_extra_allowed_ips").addTag(obj);
}); });
modal.find("#_client_endpoint").val(client.endpoint);
modal.find("#_use_server_dns").prop("checked", client.use_server_dns); modal.find("#_use_server_dns").prop("checked", client.use_server_dns);
modal.find("#_enabled").prop("checked", client.enabled); modal.find("#_enabled").prop("checked", client.enabled);
@ -564,6 +570,8 @@ Wireguard Clients
extra_allowed_ips = $("#_client_extra_allowed_ips").val().split(","); extra_allowed_ips = $("#_client_extra_allowed_ips").val().split(",");
} }
const endpoint = $("#_client_endpoint").val();
if ($("#_use_server_dns").is(':checked')){ if ($("#_use_server_dns").is(':checked')){
use_server_dns = true; use_server_dns = true;
} }
@ -575,7 +583,8 @@ Wireguard Clients
} }
const data = {"id": client_id, "name": name, "email": email, "allocated_ips": allocated_ips, const data = {"id": client_id, "name": name, "email": email, "allocated_ips": allocated_ips,
"allowed_ips": allowed_ips, "extra_allowed_ips": extra_allowed_ips, "use_server_dns": use_server_dns, "enabled": enabled, "public_key": public_key, "preshared_key": preshared_key}; "allowed_ips": allowed_ips, "extra_allowed_ips": extra_allowed_ips, "endpoint": endpoint,
"use_server_dns": use_server_dns, "enabled": enabled, "public_key": public_key, "preshared_key": preshared_key};
$.ajax({ $.ajax({
cache: false, cache: false,

View file

@ -22,7 +22,7 @@ Global Settings
<div class="col-md-6"> <div class="col-md-6">
<div class="card card-success"> <div class="card card-success">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">Wireguard Global Settings</h3> <h3 class="card-title">WireGuard Global Settings</h3>
</div> </div>
<!-- /.card-header --> <!-- /.card-header -->
<!-- form start --> <!-- form start -->
@ -68,7 +68,7 @@ Global Settings
value="{{ .globalSettings.Table }}"> value="{{ .globalSettings.Table }}">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="config_file_path">Wireguard Config File Path</label> <label for="config_file_path">WireGuard Config File Path</label>
<input type="text" class="form-control" id="config_file_path" <input type="text" class="form-control" id="config_file_path"
name="config_file_path" placeholder="E.g. /etc/wireguard/wg0.conf" name="config_file_path" placeholder="E.g. /etc/wireguard/wg0.conf"
value="{{ .globalSettings.ConfigFilePath }}"> value="{{ .globalSettings.ConfigFilePath }}">
@ -92,7 +92,7 @@ Global Settings
<div class="card-body"> <div class="card-body">
<dl> <dl>
<dt>1. Endpoint Address</dt> <dt>1. Endpoint Address</dt>
<dd>The public IP address of your Wireguard server that the client will connect to. Click on <dd>The public IP address of your WireGuard server that the client will connect to. Click on
<strong>Suggest</strong> button to auto detect the public IP address of your server.</dd> <strong>Suggest</strong> button to auto detect the public IP address of your server.</dd>
<dt>2. DNS Servers</dt> <dt>2. DNS Servers</dt>
<dd>The DNS servers will be set to client config.</dd> <dd>The DNS servers will be set to client config.</dd>
@ -110,8 +110,8 @@ Global Settings
<dd>Add a matching <code>fwmark</code> on all packets going out of a WireGuard non-default-route tunnel. Default value: <code>0xca6c</code></dd> <dd>Add a matching <code>fwmark</code> on all packets going out of a WireGuard non-default-route tunnel. Default value: <code>0xca6c</code></dd>
<dt>6. Table</dt> <dt>6. Table</dt>
<dd>Value for the <code>Table</code> setting in the wg conf file. Default value: <code>auto</code></dd> <dd>Value for the <code>Table</code> setting in the wg conf file. Default value: <code>auto</code></dd>
<dt>7. Wireguard Config File Path</dt> <dt>7. WireGuard Config File Path</dt>
<dd>The path of your Wireguard server config file. Please make sure the parent directory <dd>The path of your WireGuard server config file. Please make sure the parent directory
exists and is writable.</dd> exists and is writable.</dd>
</dl> </dl>
</div> </div>
@ -195,7 +195,7 @@ Global Settings
} }
</script> </script>
<script> <script>
// Wireguard Interface DNS server tag input // WireGuard Interface DNS server tag input
$("#dns_servers").tagsInput({ $("#dns_servers").tagsInput({
'width': '100%', 'width': '100%',
'height': '75%', 'height': '75%',

View file

@ -4,7 +4,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>WireGuard UI</title> <title>{{.brandText}}</title>
<!-- Tell the browser to be responsive to screen width --> <!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Favicon --> <!-- Favicon -->
@ -24,8 +24,8 @@
<body class="hold-transition login-page"> <body class="hold-transition login-page">
<div class="login-box"> <div class="login-box">
<div class="login-logo"> <div class="login-logo pb-3">
<a href="https://github.com/ngoduykhanh/wireguard-ui">WireGuard UI</a> <img class="img-fluid" src="{{.basePath}}/logo">
</div> </div>
<!-- /.login-logo --> <!-- /.login-logo -->
<div class="card"> <div class="card">

View file

@ -1,5 +1,5 @@
{{define "title"}} {{define "title"}}
Wireguard Server WireGuard Server
{{end}} {{end}}
{{define "top_css"}} {{define "top_css"}}
@ -10,13 +10,13 @@ Wireguard Server
{{end}} {{end}}
{{define "page_title"}} {{define "page_title"}}
Wireguard Server Settings WireGuard Server Settings
{{end}} {{end}}
{{define "page_content"}} {{define "page_content"}}
<section class="content"> <section class="content">
<div class="container-fluid"> <div class="container-fluid">
<!-- <h5 class="mt-4 mb-2">Wireguard Server</h5> --> <!-- <h5 class="mt-4 mb-2">WireGuard Server</h5> -->
<div class="row"> <div class="row">
<!-- left column --> <!-- left column -->
<div class="col-md-6"> <div class="col-md-6">
@ -39,13 +39,13 @@ Wireguard Server Settings
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="post_up">Post Up Script</label> <label for="post_up">Post Up Script</label>
<input type="text" class="form-control" id="post_up" name="post_up" <textarea class="form-control" id="post_up" name="post_up"
placeholder="Post Up Script" value="{{ .serverInterface.PostUp }}"> placeholder="Post Up Script">{{ .serverInterface.PostUp }}</textarea>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="post_down">Post Down Script</label> <label for="post_down">Post Down Script</label>
<input type="text" class="form-control" id="post_down" name="post_down" <textarea class="form-control" id="post_down" name="post_down"
placeholder="Post Down Script" value="{{ .serverInterface.PostDown }}"> placeholder="Post Down Script">{{ .serverInterface.PostDown }}</textarea>
</div> </div>
</div> </div>
<!-- /.card-body --> <!-- /.card-body -->
@ -109,7 +109,7 @@ Wireguard Server Settings
</button> </button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Are you sure to generate a new key pair for the Wireguard server?<br/> <p>Are you sure to generate a new key pair for the WireGuard server?<br/>
The existing Client's peer public key need to be updated to keep the connection working.</p> The existing Client's peer public key need to be updated to keep the connection working.</p>
</div> </div>
<div class="modal-footer justify-content-between"> <div class="modal-footer justify-content-between">
@ -142,7 +142,7 @@ Wireguard Server Settings
data: JSON.stringify(data), data: JSON.stringify(data),
success: function(data) { success: function(data) {
$("#modal_new_client").modal('hide'); $("#modal_new_client").modal('hide');
toastr.success('Updated Wireguard server interface addresses successfully'); toastr.success('Updated WireGuard server interface addresses successfully');
}, },
error: function(jqXHR, exception) { error: function(jqXHR, exception) {
const responseJson = jQuery.parseJSON(jqXHR.responseText); const responseJson = jQuery.parseJSON(jqXHR.responseText);
@ -152,7 +152,7 @@ Wireguard Server Settings
} }
</script> </script>
<script> <script>
// Wireguard Interface Addresses tag input // WireGuard Interface Addresses tag input
$("#addresses").tagsInput({ $("#addresses").tagsInput({
'width': '100%', 'width': '100%',
// 'height': '75%', // 'height': '75%',
@ -169,7 +169,7 @@ Wireguard Server Settings
$("#addresses").addTag('{{.}}'); $("#addresses").addTag('{{.}}');
{{end}} {{end}}
// Wireguard Interface Addresses form validation // WireGuard Interface Addresses form validation
$(document).ready(function () { $(document).ready(function () {
$.validator.setDefaults({ $.validator.setDefaults({
submitHandler: function () { submitHandler: function () {
@ -205,7 +205,7 @@ Wireguard Server Settings
}); });
}); });
// Wireguard Key Pair generation confirmation button // WireGuard Key Pair generation confirmation button
$(document).ready(function () { $(document).ready(function () {
$("#btn_generate_confirm").click(function () { $("#btn_generate_confirm").click(function () {
$.ajax({ $.ajax({

View file

@ -35,7 +35,6 @@ Connected Peers
{{ end}} {{ end}}
{{ range $dev := .devices }} {{ range $dev := .devices }}
<table class="table table-sm"> <table class="table table-sm">
<caption>List of connected peers for device with name {{ $dev.Name }} </caption>
<thead> <thead>
<tr> <tr>
<th scope="col">#</th> <th scope="col">#</th>

View file

@ -1,4 +1,4 @@
# This file was generated using wireguard-ui (https://github.com/ngoduykhanh/wireguard-ui) # This file was generated using wireguard-ui (https://github.com/idressos/wireguard-ui)
# Please don't modify it manually, otherwise your change might get replaced. # Please don't modify it manually, otherwise your change might get replaced.
# Address updated at: {{ .serverConfig.Interface.UpdatedAt }} # Address updated at: {{ .serverConfig.Interface.UpdatedAt }}
@ -7,10 +7,11 @@
Address = {{$first :=true}}{{range .serverConfig.Interface.Addresses }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}} Address = {{$first :=true}}{{range .serverConfig.Interface.Addresses }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}}
ListenPort = {{ .serverConfig.Interface.ListenPort }} ListenPort = {{ .serverConfig.Interface.ListenPort }}
PrivateKey = {{ .serverConfig.KeyPair.PrivateKey }} PrivateKey = {{ .serverConfig.KeyPair.PrivateKey }}
{{if .globalSettings.FirewallMark }}FwMark = {{ .globalSettings.FirewallMark }}{{end}}
{{if .globalSettings.MTU}}MTU = {{ .globalSettings.MTU }}{{end}} {{if .globalSettings.MTU}}MTU = {{ .globalSettings.MTU }}{{end}}
PostUp = {{ .serverConfig.Interface.PostUp }} {{if .serverConfig.Interface.PostUp }}PostUp = {{ .serverConfig.Interface.PostUp }}{{end}}
PostDown = {{ .serverConfig.Interface.PostDown }} {{if .serverConfig.Interface.PostDown }}PostDown = {{ .serverConfig.Interface.PostDown }}{{end}}
Table = {{ .globalSettings.Table }} {{if .globalSettings.Table}}Table = {{ .globalSettings.Table }}{{end}}
{{range .clientDataList}}{{if eq .Client.Enabled true}} {{range .clientDataList}}{{if eq .Client.Enabled true}}
# ID: {{ .Client.ID }} # ID: {{ .Client.ID }}
@ -20,6 +21,8 @@ Table = {{ .globalSettings.Table }}
# Update at: {{ .Client.UpdatedAt }} # Update at: {{ .Client.UpdatedAt }}
[Peer] [Peer]
PublicKey = {{ .Client.PublicKey }} PublicKey = {{ .Client.PublicKey }}
{{if .Client.PresharedKey }}PresharedKey = {{ .Client.PresharedKey }} {{if .Client.PresharedKey }}PresharedKey = {{ .Client.PresharedKey }}{{end}}
{{end}}AllowedIPs = {{$first :=true}}{{range .Client.AllocatedIPs }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}}{{range .Client.ExtraAllowedIPs }},{{.}}{{end}} AllowedIPs = {{$first :=true}}{{range .Client.AllocatedIPs }}{{if $first}}{{$first = false}}{{else}},{{end}}{{.}}{{end}}{{range .Client.ExtraAllowedIPs }},{{.}}{{end}}{{end}}
{{end}}{{end}} {{if $.globalSettings.PersistentKeepalive}}PersistentKeepalive = {{ $.globalSettings.PersistentKeepalive }}{{end}}
{{if .Client.Endpoint }}Endpoint = {{ .Client.Endpoint }}{{end}}
{{end}}

View file

@ -10,6 +10,7 @@ var (
SmtpPort int SmtpPort int
SmtpUsername string SmtpUsername string
SmtpPassword string SmtpPassword string
HelloHostName string
SmtpNoTLSCheck bool SmtpNoTLSCheck bool
SmtpEncryption string SmtpEncryption string
SmtpAuthType string SmtpAuthType string
@ -19,6 +20,9 @@ var (
SessionSecret []byte SessionSecret []byte
WgConfTemplate string WgConfTemplate string
BasePath string BasePath string
BrandText string
AccentColor string
PageTitlePrefix string
) )
const ( const (
@ -53,6 +57,10 @@ const (
DefaultClientExtraAllowedIpsEnvVar = "WGUI_DEFAULT_CLIENT_EXTRA_ALLOWED_IPS" DefaultClientExtraAllowedIpsEnvVar = "WGUI_DEFAULT_CLIENT_EXTRA_ALLOWED_IPS"
DefaultClientUseServerDNSEnvVar = "WGUI_DEFAULT_CLIENT_USE_SERVER_DNS" DefaultClientUseServerDNSEnvVar = "WGUI_DEFAULT_CLIENT_USE_SERVER_DNS"
DefaultClientEnableAfterCreationEnvVar = "WGUI_DEFAULT_CLIENT_ENABLE_AFTER_CREATION" DefaultClientEnableAfterCreationEnvVar = "WGUI_DEFAULT_CLIENT_ENABLE_AFTER_CREATION"
BrandTextEnvVar = "WGUI_BRAND_TEXT"
AccentColorEnvVar = "WGUI_ACCENT_COLOR"
PageTitlePrefixEnvVar = "WGUI_PAGE_TITLE_PREFIX"
LogoFilePathEnvVar = "WGUI_LOGO_FILE_PATH"
) )
func ParseBasePath(basePath string) string { func ParseBasePath(basePath string) string {

View file

@ -4,8 +4,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/ngoduykhanh/wireguard-ui/store"
"golang.org/x/mod/sumdb/dirhash"
"io" "io"
"io/fs" "io/fs"
"io/ioutil" "io/ioutil"
@ -19,6 +17,9 @@ import (
"text/template" "text/template"
"time" "time"
"github.com/ngoduykhanh/wireguard-ui/store"
"golang.org/x/mod/sumdb/dirhash"
externalip "github.com/glendc/go-external-ip" externalip "github.com/glendc/go-external-ip"
"github.com/labstack/gommon/log" "github.com/labstack/gommon/log"
"github.com/ngoduykhanh/wireguard-ui/model" "github.com/ngoduykhanh/wireguard-ui/model"
@ -382,7 +383,7 @@ func ValidateIPAllocation(serverAddresses []string, ipAllocatedList []string, ip
return true, nil return true, nil
} }
// WriteWireGuardServerConfig to write Wireguard server config. e.g. wg0.conf // WriteWireGuardServerConfig to write WireGuard server config. e.g. wg0.conf
func WriteWireGuardServerConfig(tmplDir fs.FS, serverConfig model.Server, clientDataList []model.ClientData, usersList []model.User, globalSettings model.GlobalSetting) error { func WriteWireGuardServerConfig(tmplDir fs.FS, serverConfig model.Server, clientDataList []model.ClientData, usersList []model.User, globalSettings model.GlobalSetting) error {
var tmplWireguardConf string var tmplWireguardConf string
@ -540,3 +541,8 @@ func RandomString(length int) string {
} }
return string(b) return string(b)
} }
func ManagePerms(path string) error {
err := os.Chmod(path, 0600)
return err
}