diff --git a/.env b/.env index 0533cb795b3339a06660a5a15ee11f6fa653c946..9a8241785f6c65304c0c1153eb06bc08ad589324 100644 --- a/.env +++ b/.env @@ -1,6 +1,12 @@ SERVER=0.0.0.0 PORT=8080 -GIN_MODE=debug +GIN_MODE=release WG_CONF_DIR=./wireguard WG_INTERFACE_NAME=wg0.conf + +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_USERNAME=account@gmail.com +SMTP_PASSWORD="*************" +SMTP_FROM="Wg Gen Web <account@gmail.com>" diff --git a/README.md b/README.md index 9a563ec04c1f22aaa2833e1e4e8f5c4b2b3c042a..74b424f5764bbddbdc1b7b6ea2eb32d7b8ac12f4 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Wg Gen Web -Simple Web based configuration generator for [WireGuard](https://wireguard.com). +<h1 align="center"><img height="420" src="./wg-gen-web_cover.png" alt="Simple Web based configuration generator for WireGuard"></h1> ---- +Simple Web based configuration generator for [WireGuard](https://wireguard.com). [](https://gitlab.127-0-0-1.fr/vx3r/wg-gen-web/commits/master) [](https://goreportcard.com/report/github.com/vx3r/wg-gen-web) @@ -30,10 +30,12 @@ The goal is to run Wg Gen Web in a container and WireGuard on host system. * Generation of `wg0.conf` after any modification * Dockerized * Pretty cool look - + ## Running +### Docker + The easiest way to run Wg Gen Web is using the container image ``` docker run --rm -it -v /tmp/wireguard:/data -p 8080:8080 -e "WG_CONF_DIR=/data" vx3r/wg-gen-web:latest @@ -51,6 +53,11 @@ services: environment: - WG_CONF_DIR=/data - WG_INTERFACE_NAME=wg0.conf + - SMTP_HOST=smtp.gmail.com + - SMTP_PORT=587 + - SMTP_USERNAME=account@gmail.com + - SMTP_PASSWORD="*************" + - SMTP_FROM="Wg Gen Web <account@gmail.com>" volumes: - /etc/wireguard:/data ``` @@ -58,7 +65,18 @@ Please note that mapping ```/etc/wireguard``` to ```/data``` inside the docker, If needed, please make sure to backup your files from ```/etc/wireguard```. A workaround would be to change the ```WG_INTERFACE_NAME``` to something different, as it will create a new interface (```wg-auto.conf``` for example), note that if you do so, you will have to adapt your daemon accordingly. -### Automatically apply changes using ```systemd``` + +### Directly without docker + +Fill free to download latest artefacts from my GitLab server: +* [Backend](https://gitlab.127-0-0-1.fr/vx3r/wg-gen-web/-/jobs/artifacts/master/download?job=build-front) +* [Frontend](https://gitlab.127-0-0-1.fr/vx3r/wg-gen-web/-/jobs/artifacts/master/download?job=build-back) + +Put everything in one directory, create `.env` file with all configurations and run the backend. + +## Automatically apply changes to WireGuard + +### Using ```systemd``` Using `systemd.path` monitor for directory changes see [systemd doc](https://www.freedesktop.org/software/systemd/man/systemd.path.html) ``` # /etc/systemd/system/wg-gen-web.path @@ -87,7 +105,7 @@ WantedBy=multi-user.target ``` Which will restart WireGuard service -### Automatically apply changes using ```inotifywait``` +### Using ```inotifywait``` For any other init system, create a daemon running this script ``` #!/bin/sh @@ -111,8 +129,8 @@ Feel free to modify this file in order to use your existing keys ## TODO * Multi-user support behind [Authelia](https://github.com/authelia/authelia) (suggestions / thoughts are welcome) - * Send configs by email to client - + * ~~Send configs by email to client~~ + ## License * Do What the Fuck You Want to Public License. [LICENSE-WTFPL](LICENSE-WTFPL) or http://www.wtfpl.net \ No newline at end of file diff --git a/Wg-Gen-Web.png b/Wg-Gen-Web.png deleted file mode 100644 index e3382b90b3dafa4eef375188928e4096e285bd43..0000000000000000000000000000000000000000 Binary files a/Wg-Gen-Web.png and /dev/null differ diff --git a/api/api.go b/api/api.go index 9978adb9c96ca4670ef24101bd2a3df44076a510..3aa4d6ce262a533b493d3a8176b4195ccb95351c 100644 --- a/api/api.go +++ b/api/api.go @@ -20,6 +20,7 @@ func ApplyRoutes(r *gin.Engine) { client.DELETE("/:id", deleteClient) client.GET("", readClients) client.GET("/:id/config", configClient) + client.GET("/:id/email", emailClient) } server := r.Group("/api/v1.0/server") @@ -149,6 +150,21 @@ func configClient(c *gin.Context) { return } +func emailClient(c *gin.Context) { + id := c.Param("id") + + err := repository.EmailClient(id) + if err != nil { + log.WithFields(log.Fields{ + "err": err, + }).Error("failed to send email to client") + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + c.JSON(http.StatusOK, gin.H{}) +} + func readServer(c *gin.Context) { client, err := repository.ReadServer() if err != nil { diff --git a/go.mod b/go.mod index 5ab8ea803c57cb281b29bbcf2c14527ee46f03a8..1bf44d93d523d9c1cbdbea59b0b2356487ee5abf 100644 --- a/go.mod +++ b/go.mod @@ -12,4 +12,6 @@ require ( github.com/sirupsen/logrus v1.4.2 github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200114203027-fcfc50b29cbb + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df ) diff --git a/go.sum b/go.sum index 682d0478a07bb117b7fd282c10150384567df594..554bbe951cd8bbfba4cc4e55d0591ab9e6e0697e 100644 --- a/go.sum +++ b/go.sum @@ -98,11 +98,15 @@ golang.zx2c4.com/wireguard v0.0.20191012/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49 golang.zx2c4.com/wireguard v0.0.20200121 h1:vcswa5Q6f+sylDfjqyrVNNrjsFUUbPsgAQTBCAg/Qf8= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200114203027-fcfc50b29cbb h1:EZFZIHfDUPApqlA2wgF5LBAXKIKAxNckrehUTPYYAHc= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200114203027-fcfc50b29cbb/go.mod h1:vpFXH8L2bfaEJ/8I7DZ0CVOHsVydo6KeW9Iqh3qMa4g= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= +gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go index f6011686931aaef61b9853464b83624279cd3ddb..9127ee2f229c1644157898fb0b196307d0b5e87c 100644 --- a/main.go +++ b/main.go @@ -40,7 +40,10 @@ func main() { } } - if os.Getenv("GIN_MODE") == "release" { + if os.Getenv("GIN_MODE") == "debug" { + // set gin release mode + gin.SetMode(gin.DebugMode) + } else { // set gin release mode gin.SetMode(gin.ReleaseMode) // disable console color diff --git a/repository/repository.go b/repository/repository.go index 193abecbe4d6b4186120b192a52916954ce7d847..f52cd1f4e87ad97116464308251670a388d303be 100644 --- a/repository/repository.go +++ b/repository/repository.go @@ -5,13 +5,16 @@ import ( "errors" uuid "github.com/satori/go.uuid" log "github.com/sirupsen/logrus" + "github.com/skip2/go-qrcode" "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/model" "gitlab.127-0-0-1.fr/vx3r/wg-gen-web/util" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "gopkg.in/gomail.v2" "io/ioutil" "os" "path/filepath" "sort" + "strconv" "strings" "time" ) @@ -157,6 +160,82 @@ func DeleteClient(id string) error { return generateWgConfig() } +// SendEmail to client +func EmailClient(id string) error { + client, err := ReadClient(id) + if err != nil { + return err + } + + configData, err := ReadClientConfig(id) + if err != nil { + return err + } + + // conf as .conf file + tmpfileCfg, err := ioutil.TempFile("", "wireguard-vpn-*.conf") + if err != nil { + return err + } + if _, err := tmpfileCfg.Write(configData); err != nil { + return err + } + if err := tmpfileCfg.Close(); err != nil { + return err + } + defer os.Remove(tmpfileCfg.Name()) // clean up + + // conf as png image + png, err := qrcode.Encode(string(configData), qrcode.Medium, 280) + if err != nil { + return err + } + tmpfilePng, err := ioutil.TempFile("", "qrcode-*.png") + if err != nil { + return err + } + if _, err := tmpfilePng.Write(png); err != nil { + return err + } + if err := tmpfilePng.Close(); err != nil { + return err + } + defer os.Remove(tmpfilePng.Name()) // clean up + + // get email body + emailBody, err := util.DumpEmail(client, filepath.Base(tmpfilePng.Name())) + if err != nil { + return err + } + + // port to int + port, err := strconv.Atoi(os.Getenv("SMTP_PORT")) + if err != nil { + return err + } + + d := gomail.NewDialer(os.Getenv("SMTP_HOST"), port, os.Getenv("SMTP_USERNAME"), os.Getenv("SMTP_PASSWORD")) + s, err := d.Dial() + if err != nil { + return err + } + m := gomail.NewMessage() + + m.SetHeader("From", os.Getenv("SMTP_FROM")) + m.SetAddressHeader("To", client.Email, client.Name) + m.SetHeader("Subject", "WireGuard VPN Configuration") + m.SetBody("text/html", emailBody.String()) + m.Attach(tmpfileCfg.Name()) + m.Embed(tmpfilePng.Name()) + + err = gomail.Send(s, m) + if err != nil { + return err + } + + return nil +} + // ReadClients all clients func ReadClients() ([]*model.Client, error) { clients := make([]*model.Client, 0) diff --git a/ui/public/favicon.ico b/ui/public/favicon.ico deleted file mode 100644 index df36fcfb72584e00488330b560ebcf34a41c64c2..0000000000000000000000000000000000000000 Binary files a/ui/public/favicon.ico and /dev/null differ diff --git a/ui/public/favicon.png b/ui/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..31b8281ebc553fab586f408bbf93a28efe61602c Binary files /dev/null and b/ui/public/favicon.png differ diff --git a/ui/public/index.html b/ui/public/index.html index b1e2b42fe24ecb8139c5c6741bb97f1e3fa128b5..cffd76337e274620b8b1d6b88ac8d56fb45fe081 100644 --- a/ui/public/index.html +++ b/ui/public/index.html @@ -4,7 +4,7 @@ <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> - <link rel="icon" href="<%= BASE_URL %>favicon.ico"> + <link rel="icon" href="<%= BASE_URL %>favicon.png"> <title>ui</title> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"> diff --git a/ui/src/App.vue b/ui/src/App.vue index 2ed1d11e0bd119f555de53133acbfba78116338e..753e268f9cad1bb20fcd7621653f77e762635298 100644 --- a/ui/src/App.vue +++ b/ui/src/App.vue @@ -2,6 +2,7 @@ <v-app id="inspire"> <v-app-bar app> + <img class="mr-3" :src="require('./assets/logo.png')" height="50"/> <v-toolbar-title>Wg Gen Web</v-toolbar-title> </v-app-bar> diff --git a/ui/src/assets/logo.png b/ui/src/assets/logo.png index f3d2503fc2a44b5053b0837ebea6e87a2d339a43..67dc5359b0b1454a2345ee0e48546947e7f6bc96 100644 Binary files a/ui/src/assets/logo.png and b/ui/src/assets/logo.png differ diff --git a/ui/src/assets/logo.svg b/ui/src/assets/logo.svg deleted file mode 100644 index 145b6d13089c81fcb16f68ad8f976e389dcd77e3..0000000000000000000000000000000000000000 --- a/ui/src/assets/logo.svg +++ /dev/null @@ -1 +0,0 @@ -<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg> diff --git a/ui/src/views/Home.vue b/ui/src/views/Home.vue index b5b611f66bc82b03db368ce5db0b6499995385db..b2f9424260dccc6ea7a85f957bd56382b025a855 100644 --- a/ui/src/views/Home.vue +++ b/ui/src/views/Home.vue @@ -13,15 +13,15 @@ <v-text-field v-model="server.name" :rules="[ - v => !!v || 'Name is required', + v => !!v || 'Friendly name is required', ]" - label="Friendly server name" + label="Friendly name" required /> <v-text-field type="number" v-model="server.persistentKeepalive" - label="Persistent keepalive for clients" + label="Persistent keepalive" :rules="[ v => !!v || 'Persistent keepalive is required', ]" @@ -29,9 +29,9 @@ /> <v-text-field v-model="server.endpoint" - label="Endpoint for clients to connect to" + label="Public endpoint for clients to connect to" :rules="[ - v => !!v || 'Endpoint is required', + v => !!v || 'Public endpoint for clients to connect to is required', ]" required /> @@ -47,12 +47,12 @@ <v-col cols="6"> <v-text-field v-model="server.publicKey" - label="Server public key" + label="Public key" disabled /> <v-text-field v-model="server.presharedKey" - label="Preshared Key key" + label="Preshared key" disabled /> <v-text-field @@ -69,7 +69,7 @@ :rules="[ v => !!v || 'Listen port is required', ]" - label="Server listen port" + label="Listen port" required /> </v-col> @@ -78,10 +78,12 @@ <v-card-actions> <v-spacer/> <v-btn + class="ma-2" color="warning" @click="updateServer" > Update server configuration + <v-icon right dark>mdi-update</v-icon> </v-btn> </v-card-actions> </v-card> @@ -100,6 +102,7 @@ @click.stop="dialogAddClient = true" > Add new client + <v-icon right dark>mdi-account-multiple-plus-outline</v-icon> </v-btn> </v-list-item> <v-row> @@ -146,8 +149,8 @@ text :href="getUrlToConfig(client.id, false)" > - Download configuration - <v-icon right dark>mdi-cloud-download</v-icon> + Download + <v-icon right dark>mdi-cloud-download-outline</v-icon> </v-btn> <v-btn text @@ -163,6 +166,13 @@ Delete <v-icon right dark>mdi-trash-can-outline</v-icon> </v-btn> + <v-btn + text + @click="sendEmailClient(client.id)" + > + Send email + <v-icon right dark>mdi-email-send-outline</v-icon> + </v-btn> <v-spacer/> <v-tooltip right> <template v-slot:activator="{ on }"> @@ -174,7 +184,7 @@ v-on:change="updateClient(client)" /> </template> - <span>Enable or disable this client</span> + <span> {{client.enable ? 'Disable' : 'Enable'}} this client</span> </v-tooltip> </v-card-actions> @@ -407,6 +417,14 @@ }); } }, + sendEmailClient(id) { + this.$get(`/client/${id}/email`).then((res) => { + this.notify('success', "Email successfully sent"); + this.getData() + }).catch((e) => { + this.notify('error', e.response.status + ' ' + e.response.statusText); + }); + }, getUrlToConfig(id, qrcode){ let base = "/api/v1.0"; if (process.env.NODE_ENV === "development"){ diff --git a/util/tpl.go b/util/tpl.go index a92c6b40d3d53158538b1c85cfc034fd1ed24253..87338399952fda1b6bd8f0a1d27e275384ab2cdb 100644 --- a/util/tpl.go +++ b/util/tpl.go @@ -8,6 +8,192 @@ import ( ) var ( + emailTpl = ` +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office"> +<head> + <!--[if gte mso 9]> + <xml> + <o:OfficeDocumentSettings> + <o:AllowPNG/> + <o:PixelsPerInch>96</o:PixelsPerInch> + </o:OfficeDocumentSettings> + </xml> + <![endif]--> + <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" /> + <meta http-equiv="X-UA-Compatible" content="IE=edge" /> + <meta name="format-detection" content="date=no" /> + <meta name="format-detection" content="address=no" /> + <meta name="format-detection" content="telephone=no" /> + <meta name="x-apple-disable-message-reformatting" /> + <!--[if !mso]><!--> + <link href="https://fonts.googleapis.com/css?family=Muli:400,400i,700,700i" rel="stylesheet" /> + <!--<![endif]--> + <title>Email Template</title> + <!--[if gte mso 9]> + <style type="text/css" media="all"> + sup { font-size: 100% !important; } + </style> + <![endif]--> + <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> + + <style type="text/css" media="screen"> + /* Linked Styles */ + body { padding:0 !important; margin:0 !important; display:block !important; min-width:100% !important; width:100% !important; background:#001736; -webkit-text-size-adjust:none } + a { color:#66c7ff; text-decoration:none } + p { padding:0 !important; margin:0 !important } + img { -ms-interpolation-mode: bicubic; /* Allow smoother rendering of resized image in Internet Explorer */ } + .mcnPreviewText { display: none !important; } + + + /* Mobile styles */ + @media only screen and (max-device-width: 480px), only screen and (max-width: 480px) { + .mobile-shell { width: 100% !important; min-width: 100% !important; } + .bg { background-size: 100% auto !important; -webkit-background-size: 100% auto !important; } + + .text-header, + .m-center { text-align: center !important; } + + .center { margin: 0 auto !important; } + .container { padding: 20px 10px !important } + + .td { width: 100% !important; min-width: 100% !important; } + + .m-br-15 { height: 15px !important; } + .p30-15 { padding: 30px 15px !important; } + + .m-td, + .m-hide { display: none !important; width: 0 !important; height: 0 !important; font-size: 0 !important; line-height: 0 !important; min-height: 0 !important; } + + .m-block { display: block !important; } + + .fluid-img img { width: 100% !important; max-width: 100% !important; height: auto !important; } + + .column, + .column-top, + .column-empty, + .column-empty2, + .column-dir-top { float: left !important; width: 100% !important; display: block !important; } + + .column-empty { padding-bottom: 10px !important; } + .column-empty2 { padding-bottom: 30px !important; } + + .content-spacing { width: 15px !important; } + } + </style> +</head> +<body class="body" style="padding:0 !important; margin:0 !important; display:block !important; min-width:100% !important; width:100% !important; background:#001736; -webkit-text-size-adjust:none;"> +<table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#001736"> + <tr> + <td align="center" valign="top"> + <table width="650" border="0" cellspacing="0" cellpadding="0" class="mobile-shell"> + <tr> + <td class="td container" style="width:650px; min-width:650px; font-size:0pt; line-height:0pt; margin:0; font-weight:normal; padding:55px 0px;"> + + <!-- Article / Image On The Left - Copy On The Right --> + <table width="100%" border="0" cellspacing="0" cellpadding="0"> + <tr> + <td style="padding-bottom: 10px;"> + <table width="100%" border="0" cellspacing="0" cellpadding="0"> + <tr> + <td class="tbrr p30-15" style="padding: 60px 30px; border-radius:26px 26px 0px 0px;" bgcolor="#12325c"> + <table width="100%" border="0" cellspacing="0" cellpadding="0"> + <tr> + <th class="column-top" width="280" style="font-size:0pt; line-height:0pt; padding:0; margin:0; font-weight:normal; vertical-align:top;"> + <table width="100%" border="0" cellspacing="0" cellpadding="0"> + <tr> + <td class="fluid-img" style="font-size:0pt; line-height:0pt; text-align:left;"><img src="cid:{{.QrcodePngName}}" width="280" height="210" border="0" alt="" /></td> + </tr> + </table> + </th> + <th class="column-empty2" width="30" style="font-size:0pt; line-height:0pt; padding:0; margin:0; font-weight:normal; vertical-align:top;"></th> + <th class="column-top" width="280" style="font-size:0pt; line-height:0pt; padding:0; margin:0; font-weight:normal; vertical-align:top;"> + <table width="100%" border="0" cellspacing="0" cellpadding="0"> + <tr> + <td class="h4 pb20" style="color:#ffffff; font-family:'Muli', Arial,sans-serif; font-size:20px; line-height:28px; text-align:left; padding-bottom:20px;">Hello</td> + </tr> + <tr> + <td class="text pb20" style="color:#ffffff; font-family:Arial,sans-serif; font-size:14px; line-height:26px; text-align:left; padding-bottom:20px;">You probably requested VPN configuration. Here is <strong>{{.Client.Name}}</strong> configuration created <strong>{{.Client.Created.Format "Jan 02, 2006 15:04:05 UTC"}}</strong>. Scan the Qrcode or open attached configuration file in VPN client.</td> + </tr> + </table> + </th> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + <!-- END Article / Image On The Left - Copy On The Right --> + + <!-- Two Columns / Articles --> + <table width="100%" border="0" cellspacing="0" cellpadding="0"> + <tr> + <td style="padding-bottom: 10px;"> + <table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#0e264b"> + <tr> + <td> + <table width="100%" border="0" cellspacing="0" cellpadding="0"> + <tr> + <td class="p30-15" style="padding: 50px 30px;"> + <table width="100%" border="0" cellspacing="0" cellpadding="0"> + <tr> + <td class="h3 pb20" style="color:#ffffff; font-family:'Muli', Arial,sans-serif; font-size:25px; line-height:32px; text-align:left; padding-bottom:20px;">About WireGuard</td> + </tr> + <tr> + <td class="text pb20" style="color:#ffffff; font-family:Arial,sans-serif; font-size:14px; line-height:26px; text-align:left; padding-bottom:20px;">WireGuard is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be faster, simpler, leaner, and more useful than IPsec, while avoiding the massive headache. It intends to be considerably more performant than OpenVPN.</td> + </tr> + <!-- Button --> + <tr> + <td align="left"> + <table border="0" cellspacing="0" cellpadding="0"> + <tr> + <td class="blue-button text-button" style="background:#66c7ff; color:#c1cddc; font-family:'Muli', Arial,sans-serif; font-size:14px; line-height:18px; padding:12px 30px; text-align:center; border-radius:0px 22px 22px 22px; font-weight:bold;"><a href="https://www.wireguard.com/install" target="_blank" class="link-white" style="color:#ffffff; text-decoration:none;"><span class="link-white" style="color:#ffffff; text-decoration:none;">Download WireGuard VPN Client</span></a></td> + </tr> + </table> + </td> + </tr> + <!-- END Button --> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + </td> + </tr> + </table> + <!-- END Two Columns / Articles --> + + <!-- Footer --> + <table width="100%" border="0" cellspacing="0" cellpadding="0"> + <tr> + <td class="p30-15 bbrr" style="padding: 50px 30px; border-radius:0px 0px 26px 26px;" bgcolor="#0e264b"> + <table width="100%" border="0" cellspacing="0" cellpadding="0"> + <tr> + <td class="text-footer1 pb10" style="color:#c1cddc; font-family:'Muli', Arial,sans-serif; font-size:16px; line-height:20px; text-align:center; padding-bottom:10px;">Wg Gen Web - Simple Web based configuration generator for WireGuard</td> + </tr> + <tr> + <td class="text-footer2" style="color:#8297b3; font-family:'Muli', Arial,sans-serif; font-size:12px; line-height:26px; text-align:center;"><a href="https://github.com/vx3r/wg-gen-web" target="_blank" class="link" style="color:#66c7ff; text-decoration:none;"><span class="link" style="color:#66c7ff; text-decoration:none;">More info on Github</span></a></td> + </tr> + </table> + </td> + </tr> + </table> + <!-- END Footer --> + </td> + </tr> + </table> + </td> + </tr> +</table> +</body> +</html> +` + clientTpl = ` [Interface] Address = {{.Client.Address}} @@ -78,6 +264,24 @@ func DumpServerWg(clients []*model.Client, server *model.Server) (bytes.Buffer, }) } +// DumpEmail dump server wg config with go template +func DumpEmail(client *model.Client, qrcodePngName string) (bytes.Buffer, error) { + var tplBuff bytes.Buffer + + t, err := template.New("email").Parse(emailTpl) + if err != nil { + return tplBuff, err + } + + return dump(t, struct { + Client *model.Client + QrcodePngName string + }{ + Client: client, + QrcodePngName: qrcodePngName, + }) +} + func dump(tpl *template.Template, data interface{}) (bytes.Buffer, error) { var tplBuff bytes.Buffer diff --git a/wg-gen-web_cover.png b/wg-gen-web_cover.png new file mode 100644 index 0000000000000000000000000000000000000000..d1e66cc82012efbc730f85d4c3995c5b56b34d79 Binary files /dev/null and b/wg-gen-web_cover.png differ diff --git a/wg-gen-web_screenshot.png b/wg-gen-web_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..667c7a150072ba838bf4eba4c5810ee389dd7391 Binary files /dev/null and b/wg-gen-web_screenshot.png differ