From 9a27cc366f7bca46f3174f4b5de848b35b9779ff Mon Sep 17 00:00:00 2001 From: Khanh Ngo Date: Thu, 21 May 2020 15:51:24 +0700 Subject: [PATCH] Single binary build (#10) Single binary build Use go rice for embedding the static files and templates to the binary file --- .dockerignore | 4 +++- .gitignore | 9 +++++++-- Dockerfile | 28 +++++++++++++++------------ README.md | 47 ++++++++++++++++++++++++++++++++++++++++++--- docker-compose.yaml | 5 +++-- go.mod | 1 + go.sum | 8 ++++++++ handler/routes.go | 5 +++-- main.go | 18 ++++++++++++++--- prepare_assets.sh | 24 +++++++++++++++++++++++ router/router.go | 39 +++++++++++++++++++++++++++++++------ util/util.go | 15 ++++++++++++--- 12 files changed, 169 insertions(+), 34 deletions(-) create mode 100755 prepare_assets.sh diff --git a/.dockerignore b/.dockerignore index b5ecbf8..b9ff5d8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -19,7 +19,9 @@ docker-compose* # Others .lgtm.yml .travis.yml +.vscode # App data & bin db -wireguard-ui \ No newline at end of file +assets +wireguard-ui diff --git a/.gitignore b/.gitignore index 84cffa0..d6eba78 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,12 @@ wireguard-ui # Output of the go coverage tool, specifically when used with LiteIDE *.out -# Dependency directories (remove the comment below to include it) +# Dependency directories and files (remove the comment below to include it) vendor/ assets/ -node_modules +node_modules/ +rice-box.go + +# IDEs +.vscode +.idea diff --git a/Dockerfile b/Dockerfile index 1010554..3d73d38 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,16 +12,19 @@ WORKDIR /build # Add sources COPY . /build -# Get application dependencies and build -RUN go mod download -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o wg-ui . - # Prepare assets RUN yarn install --pure-lockfile --production && \ yarn cache clean +# Move admin-lte dist +RUN mkdir -p assets/dist/js assets/dist/css && \ + cp /build/node_modules/admin-lte/dist/js/adminlte.min.js \ + assets/dist/js/adminlte.min.js && \ + cp /build/node_modules/admin-lte/dist/css/adminlte.min.css \ + assets/dist/css/adminlte.min.css + # Move plugin assets -RUN mkdir -p /assets/plugins && \ +RUN mkdir -p assets/plugins && \ cp -r /build/node_modules/admin-lte/plugins/jquery/ \ /build/node_modules/admin-lte/plugins/fontawesome-free/ \ /build/node_modules/admin-lte/plugins/bootstrap/ \ @@ -30,8 +33,15 @@ RUN mkdir -p /assets/plugins && \ /build/node_modules/admin-lte/plugins/jquery-validation/ \ /build/node_modules/admin-lte/plugins/select2/ \ /build/node_modules/jquery-tags-input/ \ - /assets/plugins/ + assets/plugins/ +# Get go modules and build tool +RUN go mod download && \ + go get github.com/GeertJohan/go.rice/rice + +# Build +RUN rice embed-go && \ + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o wg-ui . # Release stage FROM alpine:3.11 @@ -47,12 +57,6 @@ RUN mkdir -p db # Copy binary files COPY --from=builder --chown=wgui:wgui /build/wg-ui /app -# Copy templates -COPY --from=builder --chown=wgui:wgui /build/templates /app/templates -# Copy assets -COPY --from=builder --chown=wgui:wgui /build/node_modules/admin-lte/dist/js/adminlte.min.js /app/assets/dist/js/adminlte.min.js -COPY --from=builder --chown=wgui:wgui /build/node_modules/admin-lte/dist/css/adminlte.min.css /app/assets/dist/css/adminlte.min.css -COPY --from=builder --chown=wgui:wgui /assets/plugins /app/assets/plugins RUN chmod +x wg-ui diff --git a/README.md b/README.md index 22977aa..927150f 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,24 @@ A web user interface to manage your WireGuard setup. - Retrieve configs using QR code / file ## Run WireGuard-UI -Only docker option for now, please refer to this example of [docker-compose.yml](https://github.com/ngoduykhanh/wireguard-ui/blob/master/docker-compose.yaml). -Please adjust volume mount points to work with your setup. Then run it: +Default username and password are `admin`. + +### Using docker compose + +You can take a look at this example of [docker-compose.yml](https://github.com/ngoduykhanh/wireguard-ui/blob/master/docker-compose.yaml). Please adjust volume mount points to work with your setup. Then run it like below: ``` docker-compose up ``` -Default username and password are `admin`. +### Using binary file + +Download the binary file from the release and run it with command: + +``` +./wireguard-ui +``` ## Auto restart WireGuard daemon WireGuard-UI only takes care of configuration generation. You can use systemd to watch for the changes and restart the service. Following is an example: @@ -52,6 +61,38 @@ systemctl enable wgui.{path,service} systemctl start wgui.{path,service} ``` +## Build + +### Build docker image + +Go to the project root directory and run the following command: + +``` +docker build -t wireguard-ui . +``` + +### Build binary file + +Prepare the assets directory + +``` +./prepare_assets.sh +``` + +Then you can embed resources by generating Go source code + +``` +rice embed-go +go build -o wireguard-ui +``` + +Or, append resources to executable as zip file + +``` +go build -o wireguard-ui +rice append --exec wireguard-ui +``` + ## Screenshot ![wireguard-ui](https://user-images.githubusercontent.com/6447444/80270680-76adf980-86e4-11ea-8ca1-9237f0dfa249.png) diff --git a/docker-compose.yaml b/docker-compose.yaml index 72da096..d8ba0a1 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -2,7 +2,8 @@ version: '3' services: wg: - image: ngoduykhanh/wireguard-ui:latest + #image: ngoduykhanh/wireguard-ui:latest + image: wgui:latest container_name: wgui ports: - 5000:5000 @@ -12,4 +13,4 @@ services: max-size: 50m volumes: - ./db:/app/db - - /etc/wireguard:/etc/wireguard +# - /etc/wireguard:/etc/wireguard diff --git a/go.mod b/go.mod index 8ad8494..a932024 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/ngoduykhanh/wireguard-ui go 1.14 require ( + github.com/GeertJohan/go.rice v1.0.0 github.com/glendc/go-external-ip v0.0.0-20170425150139-139229dcdddd github.com/go-playground/universal-translator v0.17.0 // indirect github.com/gorilla/sessions v1.2.0 diff --git a/go.sum b/go.sum index ec083b5..0bd16d2 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,8 @@ +github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= +github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voiMLQ= +github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= @@ -7,6 +11,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/casbin/casbin/v2 v2.0.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY= +github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= @@ -39,6 +45,7 @@ github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYb github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 h1:EFT6MH3igZK/dIVqgGbTqWVvkZ7wJ5iGN03SVtvvdd8= github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25/go.mod h1:sWkGw/wsaHtRsT9zGQ/WyJCotGWG/Anow/9hsAcBWRw= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -75,6 +82,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/handler/routes.go b/handler/routes.go index 7b0d689..274de90 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -3,6 +3,7 @@ package handler import ( "encoding/json" "fmt" + rice "github.com/GeertJohan/go.rice" "net/http" "strings" "time" @@ -429,7 +430,7 @@ func SuggestIPAllocation() echo.HandlerFunc { } // ApplyServerConfig handler to write config file and restart Wireguard server -func ApplyServerConfig() echo.HandlerFunc { +func ApplyServerConfig(tmplBox *rice.Box) echo.HandlerFunc { return func(c echo.Context) error { // access validation validSession(c) @@ -453,7 +454,7 @@ func ApplyServerConfig() echo.HandlerFunc { } // Write config file - err = util.WriteWireGuardServerConfig(server, clients, settings) + err = util.WriteWireGuardServerConfig(tmplBox, server, clients, settings) if err != nil { log.Error("Cannot apply server config: ", err) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, fmt.Sprintf("Cannot apply server config: %v", err)}) diff --git a/main.go b/main.go index bf8d911..0d63700 100644 --- a/main.go +++ b/main.go @@ -2,10 +2,12 @@ package main import ( "fmt" - + rice "github.com/GeertJohan/go.rice" + "github.com/labstack/echo/v4" "github.com/ngoduykhanh/wireguard-ui/handler" "github.com/ngoduykhanh/wireguard-ui/router" "github.com/ngoduykhanh/wireguard-ui/util" + "net/http" ) func main() { @@ -15,8 +17,14 @@ func main() { fmt.Print("Cannot init database: ", err) } + // create rice box for embedded template + tmplBox := rice.MustFindBox("templates") + + // rice file server for assets. "assets" is the folder where the files come from. + assetHandler := http.FileServer(rice.MustFindBox("assets").HTTPBox()) + // register routes - app := router.New() + app := router.New(tmplBox) app.GET("/", handler.WireGuardClients()) app.GET("/login", handler.LoginPage()) @@ -33,6 +41,10 @@ func main() { app.POST("/global-settings", handler.GlobalSettingSubmit()) app.GET("/api/machine-ips", handler.MachineIPAddresses()) app.GET("/api/suggest-client-ips", handler.SuggestIPAllocation()) - app.GET("/api/apply-wg-config", handler.ApplyServerConfig()) + app.GET("/api/apply-wg-config", handler.ApplyServerConfig(tmplBox)) + + // servers other static files + app.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", assetHandler))) + app.Logger.Fatal(app.Start("0.0.0.0:5000")) } diff --git a/prepare_assets.sh b/prepare_assets.sh new file mode 100755 index 0000000..801da8c --- /dev/null +++ b/prepare_assets.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -e + +DIR=$(dirname "$0") + +# install node modules +yarn install --pure-lockfile --production + +# Copy admin-lte dist +mkdir -p "${DIR}/assets/dist/js" "${DIR}/assets/dist/css" && \ + cp -r "${DIR}/node_modules/admin-lte/dist/js/adminlte.min.js" "${DIR}/assets/dist/js/adminlte.min.js" && \ + cp -r "${DIR}/node_modules/admin-lte/dist/css/adminlte.min.css" "${DIR}/assets/dist/css/adminlte.min.css" + +# Copy plugins +mkdir -p "${DIR}/assets/plugins" && \ + cp -r "${DIR}/node_modules/admin-lte/plugins/jquery" \ + "${DIR}/node_modules/admin-lte/plugins/fontawesome-free" \ + "${DIR}/node_modules/admin-lte/plugins/bootstrap" \ + "${DIR}/node_modules/admin-lte/plugins/icheck-bootstrap" \ + "${DIR}/node_modules/admin-lte/plugins/toastr" \ + "${DIR}/node_modules/admin-lte/plugins/jquery-validation" \ + "${DIR}/node_modules/admin-lte/plugins/select2" \ + "${DIR}/node_modules/jquery-tags-input" \ + "${DIR}/assets/plugins/" diff --git a/router/router.go b/router/router.go index de836f2..17ee70a 100644 --- a/router/router.go +++ b/router/router.go @@ -5,6 +5,7 @@ import ( "io" "text/template" + "github.com/GeertJohan/go.rice" "github.com/gorilla/sessions" "github.com/labstack/echo-contrib/session" "github.com/labstack/echo/v4" @@ -32,15 +33,42 @@ func (t *TemplateRegistry) Render(w io.Writer, name string, data interface{}, c } // New function -func New() *echo.Echo { +func New(tmplBox *rice.Box) *echo.Echo { e := echo.New() e.Use(session.Middleware(sessions.NewCookieStore([]byte("secret")))) + // read html template file to string + tmplBaseString, err := tmplBox.String("base.html") + if err != nil { + log.Fatal(err) + } + + tmplLoginString, err := tmplBox.String("login.html") + if err != nil { + log.Fatal(err) + } + + tmplClientsString, err := tmplBox.String("clients.html") + if err != nil { + log.Fatal(err) + } + + tmplServerString, err := tmplBox.String("server.html") + if err != nil { + log.Fatal(err) + } + + tmplGlobalSettingsString, err := tmplBox.String("global_settings.html") + if err != nil { + log.Fatal(err) + } + + // create template list templates := make(map[string]*template.Template) - templates["login.html"] = template.Must(template.ParseFiles("templates/login.html")) - templates["clients.html"] = template.Must(template.ParseFiles("templates/clients.html", "templates/base.html")) - templates["server.html"] = template.Must(template.ParseFiles("templates/server.html", "templates/base.html")) - templates["global_settings.html"] = template.Must(template.ParseFiles("templates/global_settings.html", "templates/base.html")) + templates["login.html"] = template.Must(template.New("login").Parse(tmplLoginString)) + templates["clients.html"] = template.Must(template.New("clients").Parse(tmplBaseString + tmplClientsString)) + templates["server.html"] = template.Must(template.New("server").Parse(tmplBaseString + tmplServerString)) + templates["global_settings.html"] = template.Must(template.New("global_settings").Parse(tmplBaseString + tmplGlobalSettingsString)) e.Logger.SetLevel(log.DEBUG) e.Pre(middleware.RemoveTrailingSlash()) @@ -51,7 +79,6 @@ func New() *echo.Echo { AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE}, })) e.Validator = NewValidator() - e.Static("/static", "assets") e.Renderer = &TemplateRegistry{ templates: templates, } diff --git a/util/util.go b/util/util.go index a0dd0ac..7685ad5 100644 --- a/util/util.go +++ b/util/util.go @@ -10,6 +10,7 @@ import ( "text/template" "time" + rice "github.com/GeertJohan/go.rice" externalip "github.com/glendc/go-external-ip" "github.com/ngoduykhanh/wireguard-ui/model" "github.com/sdomino/scribble" @@ -268,7 +269,7 @@ func GetAvailableIP(cidr string, allocatedList []string) (string, error) { } } - return "", errors.New("No more available ip address") + return "", errors.New("no more available ip address") } // ValidateIPAllocation to validate the list of client's ip allocation @@ -312,12 +313,20 @@ func ValidateIPAllocation(serverAddresses []string, ipAllocatedList []string, ip } // WriteWireGuardServerConfig to write Wireguard server config. e.g. wg0.conf -func WriteWireGuardServerConfig(serverConfig model.Server, clientDataList []model.ClientData, globalSettings model.GlobalSetting) error { - t, err := template.ParseFiles("templates/wg.conf") +func WriteWireGuardServerConfig(tmplBox *rice.Box, serverConfig model.Server, clientDataList []model.ClientData, globalSettings model.GlobalSetting) error { + // read wg.conf template file to string + tmplWireguardConf, err := tmplBox.String("wg.conf") if err != nil { return err } + // parse the template + t, err := template.New("wg_config").Parse(tmplWireguardConf) + if err != nil { + return err + } + + // write config file to disk f, err := os.Create(globalSettings.ConfigFilePath) if err != nil { return err