0
0
mirror of https://github.com/vx3r/wg-gen-web.git synced 2024-12-18 00:13:23 +00:00

logo, send email

This commit is contained in:
vx3r 2020-02-03 16:19:24 +09:00
parent c9fb3c2e29
commit dcb0769edb
18 changed files with 372 additions and 22 deletions

8
.env
View File

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

View File

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

[![pipeline status](https://gitlab.127-0-0-1.fr/vx3r/wg-gen-web/badges/master/pipeline.svg)](https://gitlab.127-0-0-1.fr/vx3r/wg-gen-web/commits/master)
[![Go Report Card](https://goreportcard.com/badge/github.com/vx3r/wg-gen-web)](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
![Screenshot](Wg-Gen-Web.png)
![Screenshot](wg-gen-web_screenshot.png)

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

View File

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

2
go.mod
View File

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

4
go.sum
View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

BIN
ui/public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

View File

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

View File

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


Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

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

Before

Width:  |  Height:  |  Size: 539 B

View File

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

View File

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


BIN
wg-gen-web_cover.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
wg-gen-web_screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB