Browse Source

feat: embed static file to go binary

build/docker
Evan 9 months ago
parent
commit
cc1f575649
  1. 3
      .gitignore
  2. 7
      Makefile
  3. 23
      cmd/cmd.go
  4. 1
      cmd/config.go
  5. 3
      go.mod
  6. 4
      go.sum
  7. 16
      internal/util/write_to_config.go
  8. 2
      main.go
  9. 132
      modules/static/spa_middleware.go
  10. 18
      services/services.go

3
.gitignore

@ -19,4 +19,5 @@ vendor/
data
ui/dist
/docs
/docs
/assets/statik

7
Makefile

@ -15,9 +15,8 @@ LDFLAGS=-ldflags="-w $(LDFLAG)"
MAKEFLAGS += --silent
pkger:
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) pkger list
# -include ui/dist -o assets/pkger
statik:
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) statik -src=ui/dist -dest=assets -f
swagger:
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) swag init -g services/services.go
@ -26,7 +25,7 @@ build:
@echo " > Building binary..."
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go build $(LDFLAGS) -o $(GOBIN)/$(PROJECTNAME) $(GOFILES)
all: swagger build
all: swagger statik build
go-get:
@echo " > Checking if there is any missing dependencies..."

23
cmd/cmd.go

@ -1,6 +1,8 @@
package cmd
import (
"github.com/pkg/browser"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -20,23 +22,34 @@ Starts the calendar server`,
RunE: start,
}
var save bool
var (
openBrowser bool
configPath string
)
func init() {
setupConfig()
util.ReadConfig()
RootCmd.Flags().StringP("port", "p", viper.GetString("server.port"), "port to listen on")
viper.BindPFlag("server.port", RootCmd.Flags().Lookup("port"))
RootCmd.Flags().Bool("no-gui", viper.GetBool("ui.disabled"), "Disables browser support")
viper.BindPFlag("ui.disabled", RootCmd.Flags().Lookup("no-gui"))
RootCmd.Flags().BoolVar(&save, "save", false, "Save config for future usage.")
RootCmd.Flags().BoolVarP(&openBrowser, "open", "O", false, "Opens up the browser")
RootCmd.PersistentFlags().Bool("save", false, "Save config for future usage.")
RootCmd.PersistentFlags().StringP("yaml", "y", viper.GetString("data"), "Location of config file to read from.")
}
func start(cmd *cobra.Command, args []string) error {
configPath, _ := cmd.Flags().GetString("yaml")
util.ReadConfig(configPath)
models.InitSqlite()
if save {
util.WriteConfig()
if save, _ := cmd.Flags().GetBool("save"); save {
util.WriteConfig(configPath)
}
if openBrowser && !viper.GetBool("ui.disabled") {
browser.OpenURL("http://localhost:" + viper.GetString("server.port") + "/")
}
services.Start()
return nil

1
cmd/config.go

@ -9,7 +9,6 @@ import (
func setupConfig() {
dataPath := filepath.FromSlash("./data")
viper.AddConfigPath(dataPath)
viper.SetConfigName("config")
viper.SetConfigType("yaml")

3
go.mod

@ -12,8 +12,9 @@ require (
github.com/google/uuid v1.1.2
github.com/json-iterator/go v1.1.10 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/markbates/pkger v0.17.1 // indirect
github.com/mattn/go-sqlite3 v1.14.4 // indirect
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
github.com/rakyll/statik v0.1.7
github.com/spf13/cobra v1.1.1
github.com/spf13/viper v1.7.0
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14

4
go.sum

@ -238,6 +238,8 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -253,6 +255,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ=
github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=

16
internal/util/write_to_config.go

@ -9,7 +9,8 @@ import (
"github.com/spf13/viper"
)
func ReadConfig() {
func ReadConfig(path string) {
viper.AddConfigPath(path)
viper.ReadInConfig()
// if err := viper.ReadInConfig(); err != nil {
// if _, ok := err.(viper.ConfigFileNotFoundError); ok {
@ -20,16 +21,15 @@ func ReadConfig() {
// }
}
func WriteConfig() {
viper.WriteConfigAs(getConfigFile())
func WriteConfig(path string) {
viper.WriteConfigAs(getConfigFile(path))
logger.Inf.Println("Config saved.")
}
func getConfigFile() string {
dir := viper.GetString("data")
configFile := filepath.Join(dir, "config.yaml")
if _, err := os.Stat(dir); os.IsNotExist(err) {
os.Mkdir(dir, 0755)
func getConfigFile(path string) string {
configFile := filepath.Join(path, "config.yaml")
if _, err := os.Stat(path); os.IsNotExist(err) {
os.Mkdir(path, 0755)
}
// if _, err := os.Stat(configFile); !os.IsExist(err) {
// if _, err := os.Create(configFile); err != nil { // perm 0666

2
main.go

@ -4,6 +4,8 @@ import (
"github.com/mutsuki333/calendar/cmd"
)
//go:generate statik -src=ui/dist -dest=assets -f
//go:generate swag init -g services/services.go
func main() {
cmd.RootCmd.Execute()
}

132
modules/static/spa_middleware.go

@ -2,73 +2,99 @@ package static
import (
"net/http"
"os"
"path"
"strings"
"github.com/gin-gonic/gin"
_ "github.com/mutsuki333/calendar/assets/statik"
"github.com/rakyll/statik/fs"
)
const INDEX = "index.html"
var statikFS *http.FileSystem
type ServeFileSystem interface {
http.FileSystem
Exists(prefix string, path string) bool
}
type localFileSystem struct {
http.FileSystem
root string
indexes bool
}
func LocalFile(root string, indexes bool) *localFileSystem {
return &localFileSystem{
FileSystem: gin.Dir(root, indexes),
root: root,
indexes: indexes,
}
}
func (l *localFileSystem) Exists(prefix string, filepath string) bool {
if p := strings.TrimPrefix(filepath, prefix); len(p) < len(filepath) {
name := path.Join(l.root, p)
stats, err := os.Stat(name)
if err != nil {
return false
}
if stats.IsDir() {
if !l.indexes {
index := path.Join(name, INDEX)
_, err := os.Stat(index)
if err != nil {
return false
}
}
}
return true
func ServeEmbed(urlPrefix string) gin.HandlerFunc {
statikFS, err := fs.New()
if err != nil {
panic("Unable to open embedded assets")
}
return false
}
func ServeRoot(urlPrefix, root string) gin.HandlerFunc {
return Serve(urlPrefix, LocalFile(root, false))
}
// Static returns a middleware handler that serves static files in the given directory.
func Serve(urlPrefix string, fs ServeFileSystem) gin.HandlerFunc {
fileserver := http.FileServer(fs)
fileserver := http.FileServer(statikFS)
if urlPrefix != "" {
fileserver = http.StripPrefix(urlPrefix, fileserver)
}
return func(c *gin.Context) {
if fs.Exists(urlPrefix, c.Request.URL.Path) {
fileserver.ServeHTTP(c.Writer, c.Request)
c.Abort()
p := "/" + strings.TrimPrefix(c.Request.URL.Path, urlPrefix)
r, err := statikFS.Open(p)
if err != nil {
return
}
stat, err := r.Stat()
if stat.IsDir() || err != nil {
return
}
fileserver.ServeHTTP(c.Writer, c.Request)
c.Abort()
}
}
// func ServeEmbed(urlPrefix string) gin.HandlerFunc {
// func ReturnIndex(c *gin.Context) {
// r, err := statikFS.Open(p)
// }
// const INDEX = "index.html"
// type ServeFileSystem interface {
// http.FileSystem
// Exists(prefix string, path string) bool
// }
// type localFileSystem struct {
// http.FileSystem
// root string
// indexes bool
// }
// func LocalFile(root string, indexes bool) *localFileSystem {
// return &localFileSystem{
// FileSystem: gin.Dir(root, indexes),
// root: root,
// indexes: indexes,
// }
// }
// func (l *localFileSystem) Exists(prefix string, filepath string) bool {
// if p := strings.TrimPrefix(filepath, prefix); len(p) < len(filepath) {
// name := path.Join(l.root, p)
// stats, err := os.Stat(name)
// if err != nil {
// return false
// }
// if stats.IsDir() {
// if !l.indexes {
// index := path.Join(name, INDEX)
// _, err := os.Stat(index)
// if err != nil {
// return false
// }
// }
// }
// return true
// }
// return false
// }
// func ServeRoot(urlPrefix, root string) gin.HandlerFunc {
// return Serve(urlPrefix, LocalFile(root, false))
// }
// // Static returns a middleware handler that serves static files in the given directory.
// func Serve(urlPrefix string, fs ServeFileSystem) gin.HandlerFunc {
// fileserver := http.FileServer(fs)
// if urlPrefix != "" {
// fileserver = http.StripPrefix(urlPrefix, fileserver)
// }
// return func(c *gin.Context) {
// if fs.Exists(urlPrefix, c.Request.URL.Path) {
// fileserver.ServeHTTP(c.Writer, c.Request)
// c.Abort()
// }
// }
// }

18
services/services.go

@ -33,19 +33,21 @@ func Start() {
api := server.Group("/api")
registerAuthService(api)
registerCalendarService(api)
if viper.GetBool("swagger.enabled") {
docs.SwaggerInfo.Host = ""
docs.SwaggerInfo.BasePath = "/api"
docs.SwaggerInfo.Version = Version
server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
if !viper.GetBool("ui.disabled") {
server.Use(static.Serve("/", static.LocalFile("../ui/dist", true)))
if viper.GetBool("swagger.enabled") {
docs.SwaggerInfo.Host = ""
docs.SwaggerInfo.BasePath = "/api"
docs.SwaggerInfo.Version = Version
server.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}
// server.Use(static.Serve("/", static.LocalFile("../ui/dist", true)))
server.Use(static.ServeEmbed("/"))
server.NoRoute(func(c *gin.Context) {
c.File("../ui/dist/index.html")
})
// Server.Use(static.ServeEmbed("/"))
}
server.Run(fmt.Sprintf(":%s", viper.GetString("server.port")))