feature (dynamic): make configuration dynamic

This commit is contained in:
MickaelK
2024-03-12 23:52:16 +11:00
parent 845c4584d3
commit 9e142d5de5
31 changed files with 631 additions and 542 deletions

View File

@ -27,6 +27,11 @@ func start(routes *mux.Router) {
os.Exit(1)
return
}
InitConfig()
InitPluginList(embed.EmbedPluginList)
for _, fn := range Hooks.Get.Onload() {
fn()
}
var wg sync.WaitGroup
for _, obj := range Hooks.Get.Starter() {
wg.Add(1)
@ -35,11 +40,5 @@ func start(routes *mux.Router) {
wg.Done()
}()
}
go func() {
InitPluginList(embed.EmbedPluginList)
for _, fn := range Hooks.Get.Onload() {
go fn()
}
}()
wg.Wait()
}

View File

@ -49,7 +49,7 @@ type FormElement struct {
Required bool `json:"required"`
}
func init() {
func InitConfig() {
Config = NewConfiguration()
Config.Load()
Config.Initialise()

View File

@ -21,16 +21,17 @@ import (
"os"
)
var (
configPath string = GetAbsolutePath(CONFIG_PATH, "config.json")
configKeysToEncrypt []string = []string{
"middleware.identity_provider.params",
"middleware.attribute_mapping.params",
}
)
var configKeysToEncrypt []string = []string{
"middleware.identity_provider.params",
"middleware.attribute_mapping.params",
}
func configPath() string {
return GetAbsolutePath(CONFIG_PATH, "config.json")
}
func LoadConfig() ([]byte, error) {
file, err := os.OpenFile(configPath, os.O_RDONLY, os.ModePerm)
file, err := os.OpenFile(configPath(), os.O_RDONLY, os.ModePerm)
if err != nil {
if os.IsNotExist(err) {
os.MkdirAll(GetAbsolutePath(CONFIG_PATH), os.ModePerm)
@ -70,12 +71,12 @@ func LoadConfig() ([]byte, error) {
}
func SaveConfig(v []byte) error {
file, err := os.Create(configPath)
file, err := os.Create(configPath())
if err != nil {
return fmt.Errorf(
"Filestash needs to be able to create/edit its own configuration which it can't at the moment. "+
"Change the permission for filestash to create and edit `%s`",
configPath,
configPath(),
)
}

View File

@ -17,17 +17,32 @@ const (
)
var (
LOG_PATH = "data/state/log/"
CONFIG_PATH = "data/state/config/"
DB_PATH = "data/state/db/"
FTS_PATH = "data/state/search/"
CERT_PATH = "data/state/certs/"
TMP_PATH = "data/cache/tmp/"
CONFIG_PATH = "state/config/"
CERT_PATH = "state/certs/"
DB_PATH = "state/db/"
FTS_PATH = "state/search/"
LOG_PATH = "state/log/"
TMP_PATH = "cache/tmp/"
)
func init() {
os.MkdirAll(filepath.Join(GetCurrentDir(), LOG_PATH), os.ModePerm)
// STEP1: setup app path
rootPath := "data/"
if p := os.Getenv("FILESTASH_PATH"); p != "" {
rootPath = p
}
LOG_PATH = filepath.Join(rootPath, LOG_PATH)
CONFIG_PATH = filepath.Join(rootPath, CONFIG_PATH)
DB_PATH = filepath.Join(rootPath, DB_PATH)
FTS_PATH = filepath.Join(rootPath, FTS_PATH)
CERT_PATH = filepath.Join(rootPath, CERT_PATH)
TMP_PATH = filepath.Join(rootPath, TMP_PATH)
// STEP2: initialise the config
os.MkdirAll(filepath.Join(GetCurrentDir(), CERT_PATH), os.ModePerm)
os.MkdirAll(filepath.Join(GetCurrentDir(), DB_PATH), os.ModePerm)
os.MkdirAll(filepath.Join(GetCurrentDir(), FTS_PATH), os.ModePerm)
os.MkdirAll(filepath.Join(GetCurrentDir(), LOG_PATH), os.ModePerm)
os.RemoveAll(filepath.Join(GetCurrentDir(), TMP_PATH))
os.MkdirAll(filepath.Join(GetCurrentDir(), TMP_PATH), os.ModePerm)
}

View File

@ -1,12 +1,13 @@
package common
import (
"github.com/gorilla/mux"
"io"
"io/fs"
"net/http"
"path/filepath"
"strings"
"github.com/gorilla/mux"
)
type Plugin struct {

View File

@ -40,7 +40,7 @@ func generateNewCertificate(root *x509.Certificate, key *rsa.PrivateKey) (*x509.
}
func pullCertificateFromFS() (*x509.Certificate, []byte, error) {
file, err := os.OpenFile(certPEMPath, os.O_RDONLY, os.ModePerm)
file, err := os.OpenFile(certPEMPath(), os.O_RDONLY, os.ModePerm)
if err != nil {
return nil, nil, err
}
@ -58,7 +58,7 @@ func pullCertificateFromFS() (*x509.Certificate, []byte, error) {
}
func saveCertificateToFS(certPEM []byte) error {
file, err := os.OpenFile(certPEMPath, os.O_WRONLY|os.O_CREATE, 0600)
file, err := os.OpenFile(certPEMPath(), os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
@ -69,5 +69,5 @@ func saveCertificateToFS(certPEM []byte) error {
}
func clearCert() {
os.Remove(certPEMPath)
os.Remove(certPEMPath())
}

View File

@ -2,14 +2,20 @@ package ssl
import (
. "github.com/mickael-kerjean/filestash/server/common"
"os"
)
var keyPEMPath string = GetAbsolutePath(CERT_PATH, "key.pem")
var certPEMPath string = GetAbsolutePath(CERT_PATH, "cert.pem")
var (
keyPEMPath func() string
certPEMPath func() string
)
func init() {
os.MkdirAll(GetAbsolutePath(CERT_PATH), os.ModePerm)
keyPEMPath = func() string {
return GetAbsolutePath(CERT_PATH, "key.pem")
}
certPEMPath = func() string {
return GetAbsolutePath(CERT_PATH, "cert.pem")
}
}
func Clear() {

View File

@ -37,7 +37,7 @@ func generateNewPrivateKey() (*rsa.PrivateKey, []byte, error) {
}
func pullPrivateKeyFromFS() (*rsa.PrivateKey, []byte, error) {
file, err := os.OpenFile(keyPEMPath, os.O_RDONLY, os.ModePerm)
file, err := os.OpenFile(keyPEMPath(), os.O_RDONLY, os.ModePerm)
if err != nil {
return nil, nil, err
}
@ -56,7 +56,7 @@ func pullPrivateKeyFromFS() (*rsa.PrivateKey, []byte, error) {
}
func savePrivateKeyToFS(privatePEM []byte) error {
file, err := os.OpenFile(keyPEMPath, os.O_WRONLY|os.O_CREATE, 0600)
file, err := os.OpenFile(keyPEMPath(), os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return err
}
@ -69,5 +69,5 @@ func savePrivateKeyToFS(privatePEM []byte) error {
}
func clearPrivateKey() {
os.Remove(keyPEMPath)
os.Remove(keyPEMPath())
}

View File

@ -32,11 +32,6 @@ var (
)
func init() {
FileCache = NewAppCache()
cachePath := GetAbsolutePath(TMP_PATH)
FileCache.OnEvict(func(key string, value interface{}) {
os.RemoveAll(filepath.Join(cachePath, key))
})
ZipTimeout = func() int {
return Config.Get("features.protection.zip_timeout").Schema(func(f *FormElement) *FormElement {
if f == nil {
@ -50,7 +45,13 @@ func init() {
return f
}).Int()
}
ZipTimeout()
FileCache = NewAppCache()
FileCache.OnEvict(func(key string, value interface{}) {
os.RemoveAll(filepath.Join(GetAbsolutePath(TMP_PATH), key))
})
Hooks.Register.Onload(func() {
ZipTimeout()
})
}
func FileLs(ctx *App, res http.ResponseWriter, req *http.Request) {

View File

@ -9,6 +9,19 @@ import (
"time"
)
var telemetry = Telemetry{Data: make([]LogEntry, 0)}
func init() {
Hooks.Register.Onload(func() {
go func() {
for {
time.Sleep(10 * time.Second)
telemetry.Flush()
}
}()
})
}
type Middleware func(func(*App, http.ResponseWriter, *http.Request)) func(*App, http.ResponseWriter, *http.Request)
func NewMiddlewareChain(fn func(*App, http.ResponseWriter, *http.Request), m []Middleware, app App) http.HandlerFunc {
@ -108,7 +121,7 @@ func Logger(ctx App, res http.ResponseWriter, req *http.Request) {
RequestID: func() string {
defer func() string {
if r := recover(); r != nil {
Log.Debug("middleware::index get header '%s'", r)
return "oops"
}
return "null"
}()
@ -161,14 +174,3 @@ func (this *Telemetry) Flush() {
}
resp.Body.Close()
}
var telemetry Telemetry = Telemetry{Data: make([]LogEntry, 0)}
func init() {
go func() {
for {
time.Sleep(10 * time.Second)
telemetry.Flush()
}
}()
}

View File

@ -4,39 +4,38 @@ import (
"database/sql"
. "github.com/mickael-kerjean/filestash/server/common"
_ "modernc.org/sqlite"
"os"
"time"
)
var DB *sql.DB
func init() {
cachePath := GetAbsolutePath(DB_PATH)
os.MkdirAll(cachePath, os.ModePerm)
var err error
if DB, err = sql.Open("sqlite", cachePath+"/share.sql?_fk=true"); err != nil {
Log.Error("model::index sqlite open error '%s'", err.Error())
return
}
Hooks.Register.Onload(func() {
var err error
if DB, err = sql.Open("sqlite", GetAbsolutePath(DB_PATH)+"/share.sql?_fk=true"); err != nil {
Log.Error("model::index sqlite open error '%s'", err.Error())
return
}
if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Location(backend VARCHAR(16), path VARCHAR(512), CONSTRAINT pk_location PRIMARY KEY(backend, path))"); err == nil {
stmt.Exec()
}
if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Share(id VARCHAR(64) PRIMARY KEY, related_backend VARCHAR(16), related_path VARCHAR(512), params JSON, auth VARCHAR(4093) NOT NULL, FOREIGN KEY (related_backend, related_path) REFERENCES Location(backend, path) ON UPDATE CASCADE ON DELETE CASCADE)"); err == nil {
stmt.Exec()
}
if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Verification(key VARCHAR(512), code VARCHAR(4), expire DATETIME DEFAULT (datetime('now', '+10 minutes')))"); err == nil {
stmt.Exec()
if stmt, err = DB.Prepare("CREATE INDEX idx_verification ON Verification(code, expire)"); err == nil {
if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Location(backend VARCHAR(16), path VARCHAR(512), CONSTRAINT pk_location PRIMARY KEY(backend, path))"); err == nil {
stmt.Exec()
}
}
go func() {
autovacuum()
}()
if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Share(id VARCHAR(64) PRIMARY KEY, related_backend VARCHAR(16), related_path VARCHAR(512), params JSON, auth VARCHAR(4093) NOT NULL, FOREIGN KEY (related_backend, related_path) REFERENCES Location(backend, path) ON UPDATE CASCADE ON DELETE CASCADE)"); err == nil {
stmt.Exec()
}
if stmt, err := DB.Prepare("CREATE TABLE IF NOT EXISTS Verification(key VARCHAR(512), code VARCHAR(4), expire DATETIME DEFAULT (datetime('now', '+10 minutes')))"); err == nil {
stmt.Exec()
if stmt, err = DB.Prepare("CREATE INDEX idx_verification ON Verification(code, expire)"); err == nil {
stmt.Exec()
}
}
go func() {
autovacuum()
}()
})
}
func autovacuum() {

View File

@ -10,28 +10,20 @@ package model
import (
"context"
"fmt"
. "github.com/mickael-kerjean/filestash/server/common"
"github.com/mickael-kerjean/net/webdav"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"time"
. "github.com/mickael-kerjean/filestash/server/common"
"github.com/mickael-kerjean/net/webdav"
)
const DAVCachePath = "data/cache/webdav/"
var (
cachePath string
webdavCache AppCache
)
var webdavCache AppCache
func init() {
cachePath = GetAbsolutePath(DAVCachePath) + "/"
os.RemoveAll(cachePath)
os.MkdirAll(cachePath, os.ModePerm)
webdavCache = NewQuickCache(20, 10)
webdavCache.OnEvict(func(filename string, _ interface{}) {
os.Remove(filename)
@ -64,7 +56,7 @@ func (this WebdavFs) Mkdir(ctx context.Context, name string, perm os.FileMode) e
}
func (this *WebdavFs) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
cachePath := fmt.Sprintf("%stmp_%s", cachePath, Hash(this.id+name, 20))
cachePath := filepath.Join(GetAbsolutePath(TMP_PATH), "webdav_"+Hash(this.id+name, 20))
fwriteFile := func() *os.File {
if this.req.Method == "PUT" {
f, err := os.OpenFile(cachePath+"_writer", os.O_WRONLY|os.O_CREATE|os.O_EXCL, os.ModePerm)
@ -119,7 +111,7 @@ func (this *WebdavFs) Stat(ctx context.Context, name string) (os.FileInfo, error
this.webdavFile = &WebdavFile{
path: fullname,
backend: this.backend,
cache: fmt.Sprintf("%stmp_%s", cachePath, Hash(this.id+name, 20)),
cache: filepath.Join(GetAbsolutePath(TMP_PATH), "webdav_"+Hash(this.id+name, 20)),
}
return this.webdavFile.Stat()
}

View File

@ -19,6 +19,7 @@ import (
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_local"
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_mysql"
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_nfs"
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_nfs4"
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_nop"
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_s3"
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_samba"
@ -39,5 +40,5 @@ import (
)
func init() {
Log.Debug("Plugin loader")
Hooks.Register.Onload(func() { Log.Debug("plugins loaded") })
}

View File

@ -6,21 +6,20 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
. "github.com/mickael-kerjean/filestash/server/common"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
. "github.com/mickael-kerjean/filestash/server/common"
)
var (
BackblazeCachePath string = "data/cache/tmp/"
BackblazeCache AppCache
)
var BackblazeCache AppCache
type Backblaze struct {
params map[string]string
@ -41,9 +40,6 @@ type BackblazeError struct {
func init() {
Backend.Register("backblaze", Backblaze{})
BackblazeCache = NewAppCache()
cachePath := GetAbsolutePath(BackblazeCachePath)
os.RemoveAll(cachePath)
os.MkdirAll(cachePath, os.ModePerm)
}
func (this Backblaze) Init(params map[string]string, app *App) (IBackend, error) {
@ -429,7 +425,10 @@ func (this Backblaze) Save(path string, file io.Reader) error {
ContentLength int64
Sha1 []byte
}{}
backblazeFileDetail.path = GetAbsolutePath(BackblazeCachePath + "data_" + QuickString(20) + ".dat")
backblazeFileDetail.path = filepath.Join(
GetAbsolutePath(TMP_PATH),
"data_"+QuickString(20)+".dat",
)
f, err := os.OpenFile(backblazeFileDetail.path, os.O_CREATE|os.O_RDWR, os.ModePerm)
if err != nil {
return err

View File

@ -1,17 +1,18 @@
package plg_backend_ftp_only
import (
"crypto/tls"
"fmt"
. "github.com/mickael-kerjean/filestash/server/common"
//"github.com/secsy/goftp" <- FTP issue with microsoft FTP
"github.com/prasad83/goftp"
"io"
"os"
"regexp"
"strconv"
"strings"
"time"
. "github.com/mickael-kerjean/filestash/server/common"
//"github.com/secsy/goftp" <- FTP issue with microsoft FTP
"github.com/prasad83/goftp"
)
var FtpCache AppCache

View File

@ -17,8 +17,6 @@ import (
"time"
)
const GitCachePath = "data/cache/git/"
var GitCache AppCache
type Git struct {
@ -29,9 +27,6 @@ func init() {
Backend.Register("git", Git{})
GitCache = NewAppCache()
cachePath := GetAbsolutePath(GitCachePath)
os.RemoveAll(cachePath)
os.MkdirAll(cachePath, os.ModePerm)
GitCache.OnEvict(func(key string, value interface{}) {
g := value.(*Git)
g.Close()
@ -94,7 +89,10 @@ func (git Git) Init(params map[string]string, app *App) (IBackend, error) {
}
hash := GenerateID(app)
p.basePath = GetAbsolutePath(GitCachePath + "repo_" + hash + "/")
p.basePath = filepath.Join(
GetAbsolutePath(TMP_PATH),
"git_"+hash,
) + "/"
repo, err := g.git.open(p, p.basePath)
g.git.repo = repo

View File

@ -195,7 +195,7 @@ func (this Mysql) Ls(path string) ([]os.FileInfo, error) {
}
rows, err := this.db.Query(fmt.Sprintf(
"SELECT CONCAT(%s) as filename %sFROM %s.%s %s LIMIT 15000",
"SELECT CONCAT(%s) as filename %sFROM %s.%s %s LIMIT 500000",
func() string {
q := strings.Join(extractNamePlus(sqlFields.Select), ", ' - ', ")
if len(sqlFields.Esthetics) != 0 {

View File

@ -154,7 +154,7 @@ func (this Nfs4Share) Rm(path string) error {
}
func (this Nfs4Share) Mv(from string, to string) error {
return ErrNotImplemented
return this.client.Rename(from, to)
}
func (this Nfs4Share) Touch(path string) error {

View File

@ -23,6 +23,10 @@ import (
var (
SECRET_KEY_DERIVATE_FOR_ONLYOFFICE string
OnlyOfficeCache *cache.Cache
plugin_enable func() bool
server_url func() string
can_download func() bool
)
type OnlyOfficeCacheData struct {
@ -32,7 +36,9 @@ type OnlyOfficeCacheData struct {
}
func init() {
plugin_enable := func() bool {
SECRET_KEY_DERIVATE_FOR_ONLYOFFICE = Hash("ONLYOFFICE_"+SECRET_KEY, len(SECRET_KEY))
OnlyOfficeCache = cache.New(720*time.Minute, 720*time.Minute)
plugin_enable = func() bool {
return Config.Get("features.office.enable").Schema(func(f *FormElement) *FormElement {
if f == nil {
f = &FormElement{}
@ -47,71 +53,73 @@ func init() {
}
return f
}).Bool()
}()
Config.Get("features.office.onlyoffice_server").Schema(func(f *FormElement) *FormElement {
if f == nil {
f = &FormElement{}
}
f.Id = "onlyoffice_server"
f.Name = "onlyoffice_server"
f.Type = "text"
f.Description = "Location of your OnlyOffice server"
f.Default = "http://127.0.0.1:8080"
f.Placeholder = "Eg: http://127.0.0.1:8080"
if u := os.Getenv("ONLYOFFICE_URL"); u != "" {
f.Default = u
f.Placeholder = fmt.Sprintf("Default: '%s'", u)
}
return f
})
Config.Get("features.office.can_download").Schema(func(f *FormElement) *FormElement {
if f == nil {
f = &FormElement{}
}
f.Id = "onlyoffice_can_download"
f.Name = "can_download"
f.Type = "boolean"
f.Description = "Display Download button in onlyoffice"
f.Default = true
return f
})
if plugin_enable == false {
return
}
server_url = func() string {
return Config.Get("features.office.onlyoffice_server").Schema(func(f *FormElement) *FormElement {
if f == nil {
f = &FormElement{}
}
f.Id = "onlyoffice_server"
f.Name = "onlyoffice_server"
f.Type = "text"
f.Description = "Location of your OnlyOffice server"
f.Default = "http://127.0.0.1:8080"
f.Placeholder = "Eg: http://127.0.0.1:8080"
if u := os.Getenv("ONLYOFFICE_URL"); u != "" {
f.Default = u
f.Placeholder = fmt.Sprintf("Default: '%s'", u)
}
return f
}).String()
}
can_download = func() bool {
return Config.Get("features.office.can_download").Schema(func(f *FormElement) *FormElement {
if f == nil {
f = &FormElement{}
}
f.Id = "onlyoffice_can_download"
f.Name = "can_download"
f.Type = "boolean"
f.Description = "Display Download button in onlyoffice"
f.Default = true
return f
}).Bool()
}
SECRET_KEY_DERIVATE_FOR_ONLYOFFICE = Hash("ONLYOFFICE_"+SECRET_KEY, len(SECRET_KEY))
Hooks.Register.HttpEndpoint(func(r *mux.Router, app *App) error {
oods := r.PathPrefix("/onlyoffice").Subrouter()
oods.PathPrefix("/static/").HandlerFunc(StaticHandler).Methods("GET", "POST")
oods.HandleFunc("/event", OnlyOfficeEventHandler).Methods("POST")
oods.HandleFunc("/content", FetchContentHandler).Methods("GET")
Hooks.Register.Onload(func() {
if plugin_enable() == false {
return
}
Hooks.Register.HttpEndpoint(func(r *mux.Router, app *App) error {
oods := r.PathPrefix("/onlyoffice").Subrouter()
oods.PathPrefix("/static/").HandlerFunc(StaticHandler).Methods("GET", "POST")
oods.HandleFunc("/event", OnlyOfficeEventHandler).Methods("POST")
oods.HandleFunc("/content", FetchContentHandler).Methods("GET")
r.HandleFunc(
COOKIE_PATH+"onlyoffice/iframe",
NewMiddlewareChain(
IframeContentHandler,
[]Middleware{SessionStart, LoggedInOnly},
*app,
),
).Methods("GET")
return nil
})
Hooks.Register.XDGOpen(`
if(mime === "application/word" || mime === "application/msword" ||
mime === "application/vnd.oasis.opendocument.text" || mime === "application/vnd.oasis.opendocument.spreadsheet" ||
mime === "application/excel" || mime === "application/vnd.ms-excel" || mime === "application/powerpoint" ||
mime === "application/vnd.ms-powerpoint" || mime === "application/vnd.oasis.opendocument.presentation" ) {
r.HandleFunc(
COOKIE_PATH+"onlyoffice/iframe",
NewMiddlewareChain(
IframeContentHandler,
[]Middleware{SessionStart, LoggedInOnly},
*app,
),
).Methods("GET")
return nil
})
Hooks.Register.XDGOpen(`
if(mime === "application/word" || mime === "application/msword" ||
mime === "application/vnd.oasis.opendocument.text" || mime === "application/vnd.oasis.opendocument.spreadsheet" ||
mime === "application/excel" || mime === "application/vnd.ms-excel" || mime === "application/powerpoint" ||
mime === "application/vnd.ms-powerpoint" || mime === "application/vnd.oasis.opendocument.presentation" ) {
return ["appframe", {"endpoint": "/api/onlyoffice/iframe"}];
}
`)
OnlyOfficeCache = cache.New(720*time.Minute, 720*time.Minute)
}
`)
})
}
func StaticHandler(res http.ResponseWriter, req *http.Request) {
req.URL.Path = strings.TrimPrefix(req.URL.Path, "/onlyoffice/static")
oodsLocation := Config.Get("features.office.onlyoffice_server").String()
u, err := url.Parse(oodsLocation)
u, err := url.Parse(server_url())
if err != nil {
SendErrorResult(res, err)
return
@ -161,7 +169,7 @@ func IframeContentHandler(ctx *App, res http.ResponseWriter, req *http.Request)
if model.CanRead(ctx) == false {
SendErrorResult(res, ErrPermissionDenied)
return
} else if oodsLocation := Config.Get("features.office.onlyoffice_server").String(); oodsLocation == "" {
} else if server_url() == "" {
res.WriteHeader(http.StatusServiceUnavailable)
res.Write([]byte("<p>The Onlyoffice server hasn't been configured</p>"))
res.Write([]byte("<style>p {color: white; text-align: center; margin-top: 50px; font-size: 20px; opacity: 0.6; font-family: monospace; } </style>"))
@ -365,7 +373,7 @@ func IframeContentHandler(ctx *App, res http.ResponseWriter, req *http.Request)
filetype,
key,
func() string {
if Config.Get("features.office.can_download").Bool() {
if can_download() {
return "true"
}
return "false"

View File

@ -8,11 +8,6 @@ import (
_ "embed"
"encoding/base64"
"encoding/json"
"github.com/creack/pty"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
. "github.com/mickael-kerjean/filestash/server/common"
"golang.org/x/crypto/bcrypt"
"io"
"net/http"
"os"
@ -21,6 +16,13 @@ import (
"syscall"
"time"
"unsafe"
. "github.com/mickael-kerjean/filestash/server/common"
"github.com/creack/pty"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"golang.org/x/crypto/bcrypt"
)
//go:embed src/app.css
@ -47,18 +49,19 @@ var console_enable = func() bool {
}
func init() {
console_enable()
Hooks.Register.HttpEndpoint(func(r *mux.Router, _ *App) error {
Hooks.Register.Onload(func() {
if console_enable() == false {
return nil
return
}
r.PathPrefix("/admin/tty/").Handler(
AuthBasic(
func() (string, string) { return "admin", Config.Get("auth.admin").String() },
TTYHandler("/admin/tty/"),
),
)
return nil
Hooks.Register.HttpEndpoint(func(r *mux.Router, _ *App) error {
r.PathPrefix("/admin/tty/").Handler(
AuthBasic(
func() (string, string) { return "admin", Config.Get("auth.admin").String() },
TTYHandler("/admin/tty/"),
),
)
return nil
})
})
}

View File

@ -19,40 +19,53 @@ import (
const SYNCTHING_URI = "/admin/syncthing"
var (
plugin_enable func() bool
server_url func() string
)
func init() {
plugin_enable := Config.Get("features.syncthing.enable").Schema(func(f *FormElement) *FormElement {
if f == nil {
f = &FormElement{}
}
f.Name = "enable"
f.Type = "enable"
f.Target = []string{"syncthing_server_url"}
f.Description = "Enable/Disable integration with the syncthing server. This will make your syncthing server available at `/admin/syncthing`"
f.Default = false
if u := os.Getenv("SYNCTHING_URL"); u != "" {
f.Default = true
}
return f
}).Bool()
Config.Get("features.syncthing.server_url").Schema(func(f *FormElement) *FormElement {
if f == nil {
f = &FormElement{}
}
f.Id = "syncthing_server_url"
f.Name = "server_url"
f.Type = "text"
f.Description = "Location of your Syncthing server"
f.Default = ""
f.Placeholder = "Eg: http://127.0.0.1:8080"
if u := os.Getenv("SYNCTHING_URL"); u != "" {
f.Default = u
f.Placeholder = fmt.Sprintf("Default: '%s'", u)
}
return f
plugin_enable = func() bool {
return Config.Get("features.syncthing.enable").Schema(func(f *FormElement) *FormElement {
if f == nil {
f = &FormElement{}
}
f.Name = "enable"
f.Type = "enable"
f.Target = []string{"syncthing_server_url"}
f.Description = "Enable/Disable integration with the syncthing server. This will make your syncthing server available at `/admin/syncthing`"
f.Default = false
if u := os.Getenv("SYNCTHING_URL"); u != "" {
f.Default = true
}
return f
}).Bool()
}
server_url = func() string {
return Config.Get("features.syncthing.server_url").Schema(func(f *FormElement) *FormElement {
if f == nil {
f = &FormElement{}
}
f.Id = "syncthing_server_url"
f.Name = "server_url"
f.Type = "text"
f.Description = "Location of your Syncthing server"
f.Default = ""
f.Placeholder = "Eg: http://127.0.0.1:8080"
if u := os.Getenv("SYNCTHING_URL"); u != "" {
f.Default = u
f.Placeholder = fmt.Sprintf("Default: '%s'", u)
}
return f
}).String()
}
Hooks.Register.Onload(func() {
plugin_enable()
server_url()
})
Hooks.Register.HttpEndpoint(func(r *mux.Router, _ *App) error {
if plugin_enable == false {
if plugin_enable() == false {
return nil
}
r.HandleFunc(SYNCTHING_URI, func(res http.ResponseWriter, req *http.Request) {
@ -121,7 +134,7 @@ func SyncthingProxyHandler(res http.ResponseWriter, req *http.Request) {
}
return "http"
}())
u, err := url.Parse(Config.Get("features.syncthing.server_url").String())
u, err := url.Parse(server_url())
if err != nil {
SendErrorResult(res, err)
return

View File

@ -2,15 +2,15 @@ package plg_image_light
import (
"fmt"
. "github.com/mickael-kerjean/filestash/server/common"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
)
const ImageCachePath = "data/cache/image/"
. "github.com/mickael-kerjean/filestash/server/common"
)
func init() {
plugin_enable := func() bool {
@ -25,8 +25,6 @@ func init() {
return f
}).Bool()
}
plugin_enable()
thumb_size := func() int {
return Config.Get("features.image.thumbnail_size").Schema(func(f *FormElement) *FormElement {
if f == nil {
@ -41,8 +39,6 @@ func init() {
return f
}).Int()
}
thumb_size()
thumb_quality := func() int {
return Config.Get("features.image.thumbnail_quality").Schema(func(f *FormElement) *FormElement {
if f == nil {
@ -57,8 +53,6 @@ func init() {
return f
}).Int()
}
thumb_quality()
thumb_caching := func() int {
return Config.Get("features.image.thumbnail_caching").Schema(func(f *FormElement) *FormElement {
if f == nil {
@ -73,8 +67,6 @@ func init() {
return f
}).Int()
}
thumb_caching()
image_quality := func() int {
return Config.Get("features.image.image_quality").Schema(func(f *FormElement) *FormElement {
if f == nil {
@ -89,8 +81,6 @@ func init() {
return f
}).Int()
}
image_quality()
image_caching := func() int {
return Config.Get("features.image.image_caching").Schema(func(f *FormElement) *FormElement {
if f == nil {
@ -105,11 +95,14 @@ func init() {
return f
}).Int()
}
image_caching()
cachePath := GetAbsolutePath(ImageCachePath)
os.RemoveAll(cachePath)
os.MkdirAll(cachePath, os.ModePerm)
Hooks.Register.Onload(func() {
plugin_enable()
thumb_size()
thumb_quality()
thumb_caching()
image_quality()
image_caching()
})
Hooks.Register.ProcessFileContentBeforeSend(func(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
if plugin_enable() == false {
@ -134,7 +127,7 @@ func init() {
/////////////////////////
// Specify transformation
transform := &Transform{
Input: GetAbsolutePath(ImageCachePath + "imagein_" + QuickString(10)),
Input: filepath.Join(GetAbsolutePath(TMP_PATH), "imagein_"+QuickString(10)),
Size: thumb_size(),
Crop: true,
Quality: thumb_quality(),

View File

@ -37,7 +37,6 @@ func init() {
return f
}).Bool()
}
SEARCH_ENABLE()
SEARCH_PROCESS_MAX = func() int {
return Config.Get("features.search.process_max").Schema(func(f *FormElement) *FormElement {
if f == nil {
@ -52,7 +51,6 @@ func init() {
return f
}).Int()
}
SEARCH_PROCESS_MAX()
SEARCH_PROCESS_PAR = func() int {
return Config.Get("features.search.process_par").Schema(func(f *FormElement) *FormElement {
if f == nil {
@ -67,7 +65,6 @@ func init() {
return f
}).Int()
}
SEARCH_PROCESS_PAR()
SEARCH_REINDEX = func() int {
return Config.Get("features.search.reindex_time").Schema(func(f *FormElement) *FormElement {
if f == nil {
@ -82,7 +79,6 @@ func init() {
return f
}).Int()
}
SEARCH_REINDEX()
CYCLE_TIME = func() int {
return Config.Get("features.search.cycle_time").Schema(func(f *FormElement) *FormElement {
if f == nil {
@ -97,7 +93,6 @@ func init() {
return f
}).Int()
}
CYCLE_TIME()
MAX_INDEXING_FSIZE = func() int {
return Config.Get("features.search.max_size").Schema(func(f *FormElement) *FormElement {
if f == nil {
@ -112,7 +107,6 @@ func init() {
return f
}).Int()
}
MAX_INDEXING_FSIZE()
INDEXING_EXT = func() string {
return Config.Get("features.search.indexer_ext").Schema(func(f *FormElement) *FormElement {
if f == nil {
@ -127,32 +121,41 @@ func init() {
return f
}).String()
}
INDEXING_EXT()
onChange := Config.ListenForChange()
runner := func() {
startSearch := false
for {
if SEARCH_ENABLE() == false {
select {
case <-onChange.Listener:
startSearch = SEARCH_ENABLE()
Hooks.Register.Onload(func() {
SEARCH_ENABLE()
SEARCH_PROCESS_MAX()
SEARCH_PROCESS_PAR()
SEARCH_REINDEX()
CYCLE_TIME()
MAX_INDEXING_FSIZE()
INDEXING_EXT()
onChange := Config.ListenForChange()
runner := func() {
startSearch := false
for {
if SEARCH_ENABLE() == false {
select {
case <-onChange.Listener:
startSearch = SEARCH_ENABLE()
}
if startSearch == false {
continue
}
}
if startSearch == false {
sidx := SProc.Peek()
if sidx == nil {
time.Sleep(5 * time.Second)
continue
}
sidx.mu.Lock()
sidx.Execute()
sidx.mu.Unlock()
}
sidx := SProc.Peek()
if sidx == nil {
time.Sleep(5 * time.Second)
continue
}
sidx.mu.Lock()
sidx.Execute()
sidx.mu.Unlock()
}
}
for i := 0; i < SEARCH_PROCESS_PAR(); i++ {
go runner()
}
for i := 0; i < SEARCH_PROCESS_PAR(); i++ {
go runner()
}
})
}

View File

@ -25,6 +25,7 @@ func init() {
return f
}).Int()) * time.Millisecond
}
SEARCH_TIMEOUT()
Hooks.Register.Onload(func() {
SEARCH_TIMEOUT()
})
}

File diff suppressed because one or more lines are too long

View File

@ -8,8 +8,12 @@ import (
"regexp"
)
var (
disable_svg func() bool
)
func init() {
disable_svg := func() bool {
disable_svg = func() bool {
return Config.Get("features.protection.disable_svg").Schema(func(f *FormElement) *FormElement {
if f == nil {
f = &FormElement{}
@ -23,22 +27,25 @@ func init() {
return f
}).Bool()
}
disable_svg()
Hooks.Register.ProcessFileContentBeforeSend(func(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
if GetMimeType(req.URL.Query().Get("path")) != "image/svg+xml" {
return reader, nil
} else if disable_svg() == true {
return reader, ErrNotAllowed
Hooks.Register.Onload(func() {
if disable_svg() == false {
return
}
Hooks.Register.ProcessFileContentBeforeSend(func(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
if GetMimeType(req.URL.Query().Get("path")) != "image/svg+xml" {
return reader, nil
} else if disable_svg() == true {
return reader, ErrNotAllowed
}
// XSS
(*res).Header().Set("Content-Security-Policy", "script-src 'none'; default-src 'none'; img-src 'self'")
// XML bomb
txt, _ := ioutil.ReadAll(reader)
if regexp.MustCompile("(?is)entity").Match(txt) {
txt = []byte("")
}
return NewReadCloserFromBytes(txt), nil
// XSS
(*res).Header().Set("Content-Security-Policy", "script-src 'none'; default-src 'none'; img-src 'self'")
// XML bomb
txt, _ := ioutil.ReadAll(reader)
if regexp.MustCompile("(?is)entity").Match(txt) {
txt = []byte("")
}
return NewReadCloserFromBytes(txt), nil
})
})
}

View File

@ -2,22 +2,26 @@ package plg_starter_http
import (
"fmt"
"github.com/gorilla/mux"
. "github.com/mickael-kerjean/filestash/server/common"
"net/http"
"time"
. "github.com/mickael-kerjean/filestash/server/common"
"github.com/gorilla/mux"
)
func init() {
port := Config.Get("general.port").Int()
Hooks.Register.Starter(func(r *mux.Router) {
Log.Info("[http] starting ...")
port := Config.Get("general.port").Int()
srv := &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: r,
}
go ensureAppHasBooted(fmt.Sprintf("http://127.0.0.1:%d/about", port), fmt.Sprintf("[http] listening on :%d", port))
go ensureAppHasBooted(
fmt.Sprintf("http://127.0.0.1:%d/about", port),
fmt.Sprintf("[http] listening on :%d", port),
)
if err := srv.ListenAndServe(); err != nil {
Log.Error("error: %v", err)
return

View File

@ -9,26 +9,37 @@ package plg_starter_http2
import (
"crypto/tls"
"fmt"
"github.com/gorilla/mux"
. "github.com/mickael-kerjean/filestash/server/common"
"github.com/mickael-kerjean/filestash/server/common/ssl"
"golang.org/x/crypto/acme/autocert"
"net/http"
"os"
"path/filepath"
"time"
. "github.com/mickael-kerjean/filestash/server/common"
"github.com/mickael-kerjean/filestash/server/common/ssl"
"github.com/gorilla/mux"
"golang.org/x/crypto/acme/autocert"
)
var SSL_PATH string = GetAbsolutePath(CERT_PATH, "ssl")
var (
SSL_PATH string
config_port func() int
)
func init() {
os.MkdirAll(SSL_PATH, os.ModePerm)
domain := Config.Get("general.host").String()
config_port = func() int {
return Config.Get("general.port").Int()
}
Hooks.Register.Onload(func() {
SSL_PATH = filepath.Join(GetAbsolutePath(CERT_PATH), "ssl")
os.MkdirAll(SSL_PATH, os.ModePerm)
})
Hooks.Register.Starter(func(r *mux.Router) {
domain := Config.Get("general.host").String()
Log.Info("[https] starting ...%s", domain)
srv := &http.Server{
Addr: fmt.Sprintf(":https"),
Addr: fmt.Sprintf(":%d", config_port()),
Handler: r,
TLSConfig: &DefaultTLSConfig,
ErrorLog: NewNilLogger(),
@ -56,29 +67,32 @@ func init() {
srv.TLSConfig.GetCertificate = mngr.GetCertificate
}
go ensureAppHasBooted("https://127.0.0.1/about", fmt.Sprintf("[https] started"))
go func() {
if err := srv.ListenAndServeTLS("", ""); err != nil {
Log.Error("[https]: listen_serve %v", err)
srv = &http.Server{
Addr: fmt.Sprintf(":http"),
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Connection", "close")
http.Redirect(
w,
req,
"https://"+req.Host+req.URL.String(),
http.StatusMovedPermanently,
)
}),
}
if err := srv.ListenAndServe(); err != nil {
return
}
}()
srv := http.Server{
Addr: fmt.Sprintf(":http"),
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Connection", "close")
http.Redirect(
w,
req,
"https://"+req.Host+req.URL.String(),
http.StatusMovedPermanently,
)
}),
}
if err := srv.ListenAndServe(); err != nil {
Log.Error("[https]: http_redirect %v", err)
go ensureAppHasBooted(
fmt.Sprintf("https://127.0.0.1:%d/about", config_port()),
fmt.Sprintf("[https] started"),
)
if err := srv.ListenAndServeTLS("", ""); err != nil {
Log.Error("[https]: listen_serve %v", err)
return
}
})

View File

@ -11,10 +11,9 @@ import (
)
func init() {
domain := Config.Get("general.host").String()
port := Config.Get("general.port").Int()
Hooks.Register.Starter(func(r *mux.Router) {
domain := Config.Get("general.host").String()
port := Config.Get("general.port").Int()
Log.Info("[https] starting ...%s", domain)
srv := &http.Server{
Addr: fmt.Sprintf(":%d", port),

View File

@ -7,15 +7,16 @@ import (
. "github.com/mickael-kerjean/filestash/server/common"
"net/http"
"os"
"path/filepath"
"time"
)
var TOR_PATH string = GetAbsolutePath(CERT_PATH, "tor")
var (
enable_plugin func() bool
tor_url func() string
)
func init() {
os.MkdirAll(TOR_PATH, os.ModePerm)
enable_tor := func() bool {
enable_plugin = func() bool {
return Config.Get("features.server.tor_enable").Schema(func(f *FormElement) *FormElement {
if f == nil {
f = &FormElement{}
@ -29,29 +30,37 @@ func init() {
return f
}).Bool()
}
enable_tor()
Config.Get("features.server.tor_url").Schema(func(f *FormElement) *FormElement {
if f == nil {
f = &FormElement{}
}
f.Id = "tor_url"
f.Name = "tor_url"
f.Type = "text"
f.Target = []string{}
f.Description = "Your onion site"
f.ReadOnly = true
f.Placeholder = "LOADING... Refresh the page in a few seconds"
return f
})
tor_url = func() string {
return Config.Get("features.server.tor_url").Schema(func(f *FormElement) *FormElement {
if f == nil {
f = &FormElement{}
}
f.Id = "tor_url"
f.Name = "tor_url"
f.Type = "text"
f.Target = []string{}
f.Description = "Your onion site"
f.ReadOnly = true
f.Placeholder = "LOADING... Refresh the page in a few seconds"
return f
}).String()
}
Hooks.Register.Onload(func() {
tor_url()
enable_plugin()
})
Hooks.Register.Starter(func(r *mux.Router) {
if enable_tor() == false {
torPath := GetAbsolutePath(CERT_PATH, "tor")
os.MkdirAll(torPath, os.ModePerm)
if enable_plugin() == false {
startTor := false
onChange := Config.ListenForChange()
for {
select {
case <-onChange.Listener:
startTor = enable_tor()
startTor = enable_plugin()
}
if startTor == true {
break
@ -62,7 +71,7 @@ func init() {
Log.Info("[tor] starting ...")
t, err := tor.Start(nil, &tor.StartConf{
DataDir: TOR_PATH,
DataDir: torPath,
})
if err != nil {
Log.Error("[tor] Unable to start Tor: %v", err)

View File

@ -2,8 +2,10 @@ package plg_video_transcoder
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"math"
@ -20,11 +22,17 @@ import (
)
const (
HLS_SEGMENT_LENGTH = 30
HLS_SEGMENT_LENGTH = 5
FPS = 30
CLEAR_CACHE_AFTER = 12
VideoCachePath = "data/cache/video/"
)
var (
plugin_enable func() bool
blacklist_format func() string
)
func init() {
ffmpegIsInstalled := false
ffprobeIsInstalled := false
@ -34,7 +42,7 @@ func init() {
if _, err := exec.LookPath("ffprobe"); err == nil {
ffprobeIsInstalled = true
}
plugin_enable := func() bool {
plugin_enable = func() bool {
return Config.Get("features.video.enable_transcoder").Schema(func(f *FormElement) *FormElement {
if f == nil {
f = &FormElement{}
@ -50,8 +58,7 @@ func init() {
return f
}).Bool()
}
blacklist_format := func() string {
blacklist_format = func() string {
return Config.Get("features.video.blacklist_format").Schema(func(f *FormElement) *FormElement {
if f == nil {
f = &FormElement{}
@ -67,50 +74,52 @@ func init() {
return f
}).String()
}
blacklist_format()
if plugin_enable() == false {
return
} else if ffmpegIsInstalled == false {
Log.Warning("[plugin video transcoder] ffmpeg needs to be installed")
return
} else if ffprobeIsInstalled == false {
Log.Warning("[plugin video transcoder] ffprobe needs to be installed")
return
}
Hooks.Register.Onload(func() {
blacklist_format()
if plugin_enable() == false {
return
} else if ffmpegIsInstalled == false {
Log.Warning("[plugin video transcoder] ffmpeg needs to be installed")
return
} else if ffprobeIsInstalled == false {
Log.Warning("[plugin video transcoder] ffprobe needs to be installed")
return
}
cachePath := GetAbsolutePath(VideoCachePath)
os.RemoveAll(cachePath)
os.MkdirAll(cachePath, os.ModePerm)
cachePath := GetAbsolutePath(VideoCachePath)
os.RemoveAll(cachePath)
os.MkdirAll(cachePath, os.ModePerm)
Hooks.Register.ProcessFileContentBeforeSend(hls_playlist)
Hooks.Register.HttpEndpoint(func(r *mux.Router, app *App) error {
r.PathPrefix("/hls/hls_{segment}.ts").Handler(NewMiddlewareChain(
hls_transcode,
[]Middleware{SecureHeaders},
*app,
)).Methods("GET")
return nil
})
Hooks.Register.HttpEndpoint(func(r *mux.Router, app *App) error {
r.HandleFunc(OverrideVideoSourceMapper, func(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", GetMimeType(req.URL.String()))
res.Write([]byte(`window.overrides["video-map-sources"] = function(sources){`))
res.Write([]byte(` return sources.map(function(source){`))
blacklists := strings.Split(blacklist_format(), ",")
for i := 0; i < len(blacklists); i++ {
blacklists[i] = strings.TrimSpace(blacklists[i])
res.Write([]byte(fmt.Sprintf(`if(source.type == "%s"){ return source; } `, GetMimeType("."+blacklists[i]))))
}
res.Write([]byte(` source.src = source.src + "&transcode=hls";`))
res.Write([]byte(` source.type = "application/x-mpegURL";`))
res.Write([]byte(` return source;`))
res.Write([]byte(` })`))
res.Write([]byte(`}`))
Hooks.Register.ProcessFileContentBeforeSend(hls_playlist)
Hooks.Register.HttpEndpoint(func(r *mux.Router, app *App) error {
r.PathPrefix("/hls/hls_{segment}.ts").Handler(NewMiddlewareChain(
hls_transcode,
[]Middleware{SecureHeaders},
*app,
)).Methods("GET")
return nil
})
Hooks.Register.HttpEndpoint(func(r *mux.Router, app *App) error {
r.HandleFunc(OverrideVideoSourceMapper, func(res http.ResponseWriter, req *http.Request) {
res.Header().Set("Content-Type", GetMimeType(req.URL.String()))
res.Write([]byte(`window.overrides["video-map-sources"] = function(sources){`))
res.Write([]byte(` return sources.map(function(source){`))
blacklists := strings.Split(blacklist_format(), ",")
for i := 0; i < len(blacklists); i++ {
blacklists[i] = strings.TrimSpace(blacklists[i])
res.Write([]byte(fmt.Sprintf(`if(source.type == "%s"){ return source; } `, GetMimeType("."+blacklists[i]))))
}
res.Write([]byte(` source.src = source.src + "&transcode=hls";`))
res.Write([]byte(` source.type = "application/x-mpegURL";`))
res.Write([]byte(` return source;`))
res.Write([]byte(` })`))
res.Write([]byte(`}`))
})
return nil
})
return nil
})
}