fully self-contained

paths are pretty janky, but it works. Also, [files]/lang_files now must
be the path to a directory CONTAINING a "lang/" directory. I'll work
around this at a later date.
pull/61/head
Harvey Tindall 4 years ago
parent 0330540f87
commit 815bdc35ac
No known key found for this signature in database
GPG Key ID: BBC65952848FB1A2

@ -54,7 +54,7 @@ compile:
go1.16rc1 mod download go1.16rc1 mod download
$(info Building) $(info Building)
mkdir -p build mkdir -p build
CGO_ENABLED=0 go1.16rc1 build -o build/jfa-go *.go cd build && CGO_ENABLED=0 go1.16rc1 build -o ./jfa-go ../*.go
compress: compress:
upx --lzma build/jfa-go upx --lzma build/jfa-go
@ -79,5 +79,5 @@ copy:
install: install:
cp -r build $(DESTDIR)/jfa-go cp -r build $(DESTDIR)/jfa-go
all: configuration npm email version typescript bundle-css swagger compile copy all: configuration npm email version typescript bundle-css swagger copy compile
debug: configuration npm email version ts-debug bundle-css swagger compile copy debug: configuration npm email version ts-debug bundle-css swagger copy compile

@ -15,7 +15,7 @@ var emailEnabled = false
func (app *appContext) GetPath(sect, key string) (fs.FS, string) { func (app *appContext) GetPath(sect, key string) (fs.FS, string) {
val := app.config.Section(sect).Key(key).MustString("") val := app.config.Section(sect).Key(key).MustString("")
if strings.HasPrefix(val, "jfa-go:") { if strings.HasPrefix(val, "jfa-go:") {
return app.localFS, strings.TrimPrefix(val, "jfa-go:") return localFS, "build/data/" + strings.TrimPrefix(val, "jfa-go:")
} }
return app.systemFS, val return app.systemFS, val
} }

@ -867,7 +867,7 @@
"requires_restart": true, "requires_restart": true,
"type": "text", "type": "text",
"value": "", "value": "",
"description": "Useful if you want to customize the text in jfa-go. Should follow the same structure as the 'lang' directory, which you can see on GitHub." "description": "The path to a directory CONTAINING a 'lang/' directory, which follow the same form as the internal one. See GitHub for more info."
} }
} }
} }

@ -1,6 +1,6 @@
module github.com/hrfee/jfa-go module github.com/hrfee/jfa-go
go 1.14 go 1.16
replace github.com/hrfee/jfa-go/docs => ./docs replace github.com/hrfee/jfa-go/docs => ./docs
@ -51,8 +51,6 @@ require (
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
golang.org/x/text v0.3.5 // indirect golang.org/x/text v0.3.5 // indirect
golang.org/x/tools v0.1.1-0.20210129181147-0cef57b5b584 // indirect
golang.org/x/tools/gopls v0.0.0-20210201165201-19db92ec3be1 // indirect
google.golang.org/protobuf v1.25.0 // indirect google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/ini.v1 v1.62.0 gopkg.in/ini.v1 v1.62.0
) )

@ -453,40 +453,10 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200923182640-463111b69878 h1:VUw1+Jf6KJPf82mbTQMia6HCnNMv2BbAipkEZ4KTcqQ=
golang.org/x/tools v0.0.0-20200923182640-463111b69878/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20200924182824-0f1c53950d78 h1:3JUoxVhcskhsIDEc7vg0MUUEpmPPN5TfG+E97z/Fn90=
golang.org/x/tools v0.0.0-20200924182824-0f1c53950d78/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20200929191002-f1e51e6b9437 h1:XSFqH8m531iIGazX5lrUC9j3slbwsZ1GFByqdUrLqmI=
golang.org/x/tools v0.0.0-20200929191002-f1e51e6b9437/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201008184944-d01b322e6f06 h1:w9ail9jFLaySAm61Zjhciu0LQ5i8YTy2pimlNLx4uuk=
golang.org/x/tools v0.0.0-20201008184944-d01b322e6f06/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201017001424-6003fad69a88 h1:ZB1XYzdDo7c/O48jzjMkvIjnC120Z9/CwgDWhePjQdQ=
golang.org/x/tools v0.0.0-20201017001424-6003fad69a88/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9 h1:sEvmEcJVKBNUvgCUClbUQeHOAa9U0I2Ce1BooMvVCY4=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201103235415-b653051172e4 h1:Qe0EMgvVYb6tmJhJHljCj3gS96hvSTkGNaIzp/ivq10=
golang.org/x/tools v0.0.0-20201103235415-b653051172e4/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201113202037-1643af1435f3 h1:7R7+wzd5VuLvCNyHZ/MG511kkoP/DBEzkbh8qUsFbY8=
golang.org/x/tools v0.0.0-20201113202037-1643af1435f3/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b h1:Ych5r0Z6MLML1fgf5hTg9p5bV56Xqx9xv9hLgMBATWs=
golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e h1:t96dS3DO8DGjawSLJL/HIdz8CycAd2v07XxqB3UPTi0= golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e h1:t96dS3DO8DGjawSLJL/HIdz8CycAd2v07XxqB3UPTi0=
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210104081019-d8d6ddbec6ee h1:5xKxdl/RhlelmSPaxyVeq5PYSmJ4H14yeQT58qP1F6o=
golang.org/x/tools v0.0.0-20210104081019-d8d6ddbec6ee/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1-0.20210129181147-0cef57b5b584 h1:JAI5SUo/oOtQXK4jvtjJMlwF5opt8qBUpxGa86SJ6zU=
golang.org/x/tools v0.1.1-0.20210129181147-0cef57b5b584/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools/gopls v0.0.0-20210201165201-19db92ec3be1 h1:YRvjnCA/wBOOkQAEKC5ts16/1IJ+NEO9eevhfDF5ues=
golang.org/x/tools/gopls v0.0.0-20210201165201-19db92ec3be1/go.mod h1:DWl5nefYvX46i2mLQVu6Ud0ycJQ3HNPbQKzUHT3VUek=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"crypto/rand" "crypto/rand"
"embed"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"flag" "flag"
@ -51,6 +52,12 @@ type User struct {
Password string `json:"password"` Password string `json:"password"`
} }
//go:embed build/data build/data/html build/data/web build/data/web/css build/data/web/js build/data/web/js/ts
var localFS embed.FS
//go:embed lang/common lang/admin lang/email lang/form lang/setup
var langFS embed.FS
// contains everything the application needs, essentially. Wouldn't do this in the future. // contains everything the application needs, essentially. Wouldn't do this in the future.
type appContext struct { type appContext struct {
// defaults *Config // defaults *Config
@ -60,8 +67,6 @@ type appContext struct {
configBase settings configBase settings
dataPath string dataPath string
systemFS fs.FS systemFS fs.FS
localFS fs.FS
langFS fs.FS
webFS httpFS webFS httpFS
cssClass string cssClass string
jellyfinLogin bool jellyfinLogin bool
@ -86,8 +91,8 @@ type appContext struct {
func (app *appContext) loadHTML(router *gin.Engine) { func (app *appContext) loadHTML(router *gin.Engine) {
customPath := app.config.Section("files").Key("html_templates").MustString("") customPath := app.config.Section("files").Key("html_templates").MustString("")
templatePath := "html" templatePath := "build/data/html"
htmlFiles, err := fs.ReadDir(app.localFS, templatePath) htmlFiles, err := fs.ReadDir(localFS, templatePath)
if err != nil { if err != nil {
app.err.Fatalf("Couldn't access template directory: \"%s\"", templatePath) app.err.Fatalf("Couldn't access template directory: \"%s\"", templatePath)
return return
@ -102,7 +107,7 @@ func (app *appContext) loadHTML(router *gin.Engine) {
loadFiles[i] = filepath.Join(filepath.Join(customPath, f.Name())) loadFiles[i] = filepath.Join(filepath.Join(customPath, f.Name()))
} }
} }
tmpl, err := template.ParseFS(app.localFS, loadFiles...) tmpl, err := template.ParseFS(localFS, loadFiles...)
if err != nil { if err != nil {
app.err.Fatalf("Failed to load templates: %v", err) app.err.Fatalf("Failed to load templates: %v", err)
} }
@ -201,16 +206,15 @@ func start(asDaemon, firstCall bool) {
userConfigDir, _ := os.UserConfigDir() userConfigDir, _ := os.UserConfigDir()
app.dataPath = filepath.Join(userConfigDir, "jfa-go") app.dataPath = filepath.Join(userConfigDir, "jfa-go")
app.configPath = filepath.Join(app.dataPath, "config.ini") app.configPath = filepath.Join(app.dataPath, "config.ini")
executable, _ := os.Executable() // executable, _ := os.Executable()
app.localFS = os.DirFS(filepath.Join(filepath.Dir(executable), "data")) // localFS = os.DirFS(filepath.Join(filepath.Dir(executable), "data"))
app.langFS = os.DirFS(filepath.Join(filepath.Dir(executable), "data", "lang")) // langFS = os.DirFS(filepath.Join(filepath.Dir(executable), "data", "lang"))
app.systemFS = os.DirFS("/") app.systemFS = os.DirFS("/")
wfs := os.DirFS(filepath.Join(filepath.Dir(executable), "data", "web")) // wfs := os.DirFS(filepath.Join(filepath.Dir(executable), "data", "web"))
app.webFS = httpFS{ app.webFS = httpFS{
hfs: http.FS(wfs), hfs: http.FS(localFS),
fs: wfs, fs: localFS,
} }
app.webFS.fs = wfs
app.info = log.New(os.Stdout, "[INFO] ", log.Ltime) app.info = log.New(os.Stdout, "[INFO] ", log.Ltime)
app.err = log.New(os.Stdout, "[ERROR] ", log.Ltime|log.Lshortfile) app.err = log.New(os.Stdout, "[ERROR] ", log.Ltime|log.Lshortfile)
@ -267,7 +271,7 @@ func start(asDaemon, firstCall bool) {
} }
if _, err := os.Stat(app.configPath); os.IsNotExist(err) { if _, err := os.Stat(app.configPath); os.IsNotExist(err) {
firstRun = true firstRun = true
dConfig, err := fs.ReadFile(app.localFS, "config-default.ini") dConfig, err := fs.ReadFile(localFS, "build/data/config-default.ini")
if err != nil { if err != nil {
app.err.Fatalf("Couldn't find default config file") app.err.Fatalf("Couldn't find default config file")
} }
@ -342,16 +346,16 @@ func start(asDaemon, firstCall bool) {
}() }()
} }
app.storage.lang.CommonPath = "common" app.storage.lang.CommonPath = "lang/common"
app.storage.lang.FormPath = "form" app.storage.lang.FormPath = "lang/form"
app.storage.lang.AdminPath = "admin" app.storage.lang.AdminPath = "lang/admin"
app.storage.lang.EmailPath = "email" app.storage.lang.EmailPath = "lang/email"
externalLang := app.config.Section("files").Key("lang_files").MustString("") externalLang := app.config.Section("files").Key("lang_files").MustString("")
var err error var err error
if externalLang == "" { if externalLang == "" {
err = app.storage.loadLang(app.langFS) err = app.storage.loadLang(langFS)
} else { } else {
err = app.storage.loadLang(app.langFS, os.DirFS(externalLang)) err = app.storage.loadLang(langFS, os.DirFS(externalLang))
} }
if err != nil { if err != nil {
app.info.Fatalf("Failed to load language files: %+v\n", err) app.info.Fatalf("Failed to load language files: %+v\n", err)
@ -431,8 +435,8 @@ func start(asDaemon, firstCall bool) {
} }
app.configBasePath = "config-base.json" app.configBasePath = "build/data/config-base.json"
configBase, _ := fs.ReadFile(app.localFS, app.configBasePath) configBase, _ := fs.ReadFile(localFS, app.configBasePath)
json.Unmarshal(configBase, &app.configBase) json.Unmarshal(configBase, &app.configBase)
themes := map[string]string{ themes := map[string]string{
@ -580,8 +584,8 @@ func start(asDaemon, firstCall bool) {
} else { } else {
debugMode = false debugMode = false
address = "0.0.0.0:8056" address = "0.0.0.0:8056"
app.storage.lang.SetupPath = filepath.Join("lang", "setup") app.storage.lang.SetupPath = "lang/setup"
err := app.storage.loadLangSetup() err := app.storage.loadLangSetup(langFS)
if err != nil { if err != nil {
app.info.Fatalf("Failed to load language files: %+v\n", err) app.info.Fatalf("Failed to load language files: %+v\n", err)
} }

@ -120,6 +120,7 @@ func (st *Storage) loadLangSetup(filesystems ...fs.FS) error {
return err return err
} }
english = st.lang.Setup["en-us"] english = st.lang.Setup["en-us"]
setupLoaded := false
for _, filesystem := range filesystems { for _, filesystem := range filesystems {
files, err := fs.ReadDir(filesystem, st.lang.SetupPath) files, err := fs.ReadDir(filesystem, st.lang.SetupPath)
if err != nil { if err != nil {
@ -128,11 +129,14 @@ func (st *Storage) loadLangSetup(filesystems ...fs.FS) error {
for _, f := range files { for _, f := range files {
if f.Name() != "en-us.json" { if f.Name() != "en-us.json" {
err = load(filesystem, f.Name()) err = load(filesystem, f.Name())
if err != nil { if err == nil {
return err setupLoaded = true
} }
} }
} }
} }
if !setupLoaded {
return err
}
return nil return nil
} }

@ -1,6 +1,7 @@
package main package main
import ( import (
"fmt"
"io/fs" "io/fs"
"net/http" "net/http"
"strings" "strings"
@ -14,12 +15,13 @@ type httpFS struct {
} }
func (f httpFS) Open(name string) (http.File, error) { func (f httpFS) Open(name string) (http.File, error) {
return f.hfs.Open(name) fmt.Println("build/data/web" + name)
return f.hfs.Open("build/data/web" + name)
} }
func (f httpFS) Exists(prefix string, filepath string) bool { func (f httpFS) Exists(prefix string, filepath string) bool {
if p := strings.TrimPrefix(filepath, prefix); len(p) < len(filepath) { if p := strings.TrimPrefix(filepath, prefix); len(p) < len(filepath) {
stats, err := fs.Stat(f.fs, p) stats, err := fs.Stat(f.fs, "build/data/web/"+p)
if err != nil { if err != nil {
return false return false
} }

Loading…
Cancel
Save