mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-10-30 01:26:43 +08:00
feature (syncthing): syncthing integration
This commit is contained in:
@ -45,7 +45,6 @@ func Init(a *App) {
|
|||||||
admin.HandleFunc("/config", NewMiddlewareChain(PrivateConfigUpdateHandler, middlewares, *a)).Methods("POST")
|
admin.HandleFunc("/config", NewMiddlewareChain(PrivateConfigUpdateHandler, middlewares, *a)).Methods("POST")
|
||||||
middlewares = []Middleware{ IndexHeaders }
|
middlewares = []Middleware{ IndexHeaders }
|
||||||
admin.HandleFunc("/log", NewMiddlewareChain(FetchLogHandler, middlewares, *a)).Methods("GET")
|
admin.HandleFunc("/log", NewMiddlewareChain(FetchLogHandler, middlewares, *a)).Methods("GET")
|
||||||
r.PathPrefix("/admin").Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, *a))).Methods("GET")
|
|
||||||
|
|
||||||
// API for File management
|
// API for File management
|
||||||
files := r.PathPrefix("/api/files").Subrouter()
|
files := r.PathPrefix("/api/files").Subrouter()
|
||||||
@ -106,7 +105,8 @@ func Init(a *App) {
|
|||||||
}
|
}
|
||||||
initPluginsRoutes(r, a)
|
initPluginsRoutes(r, a)
|
||||||
|
|
||||||
r.PathPrefix("/").Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, *a))).Methods("GET")
|
r.PathPrefix("/admin").Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, *a))).Methods("GET")
|
||||||
|
r.PathPrefix("/" ).Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, *a))).Methods("GET")
|
||||||
|
|
||||||
// Routes are served via plugins to avoid getting stuck with plain HTTP. The idea is to
|
// Routes are served via plugins to avoid getting stuck with plain HTTP. The idea is to
|
||||||
// support many more protocols in the future: HTTPS, HTTP2, TOR or whatever that sounds
|
// support many more protocols in the future: HTTPS, HTTP2, TOR or whatever that sounds
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_starter_tor"
|
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_starter_tor"
|
||||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_video_transcoder"
|
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_video_transcoder"
|
||||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_editor_onlyoffice"
|
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_editor_onlyoffice"
|
||||||
|
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_syncthing"
|
||||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_image_light"
|
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_image_light"
|
||||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_backblaze"
|
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_backblaze"
|
||||||
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_dav"
|
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_dav"
|
||||||
|
|||||||
161
server/plugin/plg_handler_syncthing/index.go
Normal file
161
server/plugin/plg_handler_syncthing/index.go
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* This plugin expose syncthing to the admin user
|
||||||
|
*/
|
||||||
|
package plg_handler_syncthing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
. "github.com/mickael-kerjean/filestash/server/common"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const SYNCTHING_URI = "/admin/syncthing"
|
||||||
|
var syncthing_enable func() bool = 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 the office suite to manage word, excel and powerpoint documents. This setting requires a restart to comes into effect"
|
||||||
|
f.Default = false
|
||||||
|
if u := os.Getenv("SYNCTHING_URL"); u != "" {
|
||||||
|
f.Default = true
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}).Bool()
|
||||||
|
}
|
||||||
|
func init() {
|
||||||
|
syncthing_enable()
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
Hooks.Register.HttpEndpoint(func(r *mux.Router, _ *App) error {
|
||||||
|
r.HandleFunc(SYNCTHING_URI, func (res http.ResponseWriter, req *http.Request) {
|
||||||
|
http.Redirect(res, req, SYNCTHING_URI + "/", http.StatusTemporaryRedirect)
|
||||||
|
})
|
||||||
|
r.Handle(SYNCTHING_URI + "/", AuthBasic(
|
||||||
|
func() (string, string) { return "admin", Config.Get("auth.admin").String() },
|
||||||
|
http.HandlerFunc(SyncthingProxyHandler),
|
||||||
|
))
|
||||||
|
|
||||||
|
r.PathPrefix(SYNCTHING_URI + "/").HandlerFunc(SyncthingProxyHandler)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func AuthBasic(credentials func() (string, string), fn http.Handler) http.HandlerFunc {
|
||||||
|
var notAuthorised = func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
res.Header().Set("WWW-Authenticate", `Basic realm="User protect", charset="UTF-8"`)
|
||||||
|
res.WriteHeader(http.StatusUnauthorized)
|
||||||
|
res.Write([]byte("Not Authorised"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
if syncthing_enable() == false {
|
||||||
|
http.NotFoundHandler().ServeHTTP(res, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
auth := req.Header.Get("Authorization")
|
||||||
|
if strings.HasPrefix(auth, "Basic ") == false {
|
||||||
|
notAuthorised(res, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
auth = strings.TrimPrefix(auth, "Basic ")
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(auth)
|
||||||
|
if err != nil {
|
||||||
|
notAuthorised(res, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
auth = string(decoded)
|
||||||
|
stuffs := strings.Split(auth, ":")
|
||||||
|
if len(stuffs) < 2 {
|
||||||
|
notAuthorised(res, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
username := stuffs[0]
|
||||||
|
password := strings.Join(stuffs[1:], ":")
|
||||||
|
refUsername, refPassword := credentials()
|
||||||
|
if refUsername != username {
|
||||||
|
notAuthorised(res, req)
|
||||||
|
return
|
||||||
|
} else if err = bcrypt.CompareHashAndPassword([]byte(refPassword), []byte(password)); err != nil {
|
||||||
|
notAuthorised(res, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fn.ServeHTTP(res, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SyncthingProxyHandler(res http.ResponseWriter, req *http.Request) {
|
||||||
|
req.URL.Path = strings.TrimPrefix(req.URL.Path, SYNCTHING_URI)
|
||||||
|
req.Header.Set("X-Forwarded-Host", req.Host + SYNCTHING_URI)
|
||||||
|
req.Header.Set("X-Forwarded-Proto", func() string {
|
||||||
|
if scheme := req.Header.Get("X-Forwarded-Proto"); scheme != "" {
|
||||||
|
return scheme
|
||||||
|
} else if req.TLS != nil {
|
||||||
|
return "https"
|
||||||
|
}
|
||||||
|
return "http"
|
||||||
|
}())
|
||||||
|
u, err := url.Parse(Config.Get("features.syncthing.server_url").String())
|
||||||
|
if err != nil {
|
||||||
|
SendErrorResult(res, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reverseProxy := &httputil.ReverseProxy{
|
||||||
|
Director: func(rq *http.Request) {
|
||||||
|
rq.URL.Scheme = "http"
|
||||||
|
rq.URL.Host = u.Host
|
||||||
|
rq.URL.Path = func(a, b string) string {
|
||||||
|
aslash := strings.HasSuffix(a, "/")
|
||||||
|
bslash := strings.HasPrefix(b, "/")
|
||||||
|
switch {
|
||||||
|
case aslash && bslash:
|
||||||
|
return a + b[1:]
|
||||||
|
case !aslash && !bslash:
|
||||||
|
return a + "/" + b
|
||||||
|
}
|
||||||
|
return a + b
|
||||||
|
}(u.Path, rq.URL.Path)
|
||||||
|
if u.RawQuery == "" || rq.URL.RawQuery == "" {
|
||||||
|
rq.URL.RawQuery = u.RawQuery + rq.URL.RawQuery
|
||||||
|
} else {
|
||||||
|
rq.URL.RawQuery = u.RawQuery + "&" + rq.URL.RawQuery
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
reverseProxy.ErrorHandler = func(rw http.ResponseWriter, rq *http.Request, err error) {
|
||||||
|
Log.Warning("[syncthing] %s", err.Error())
|
||||||
|
SendErrorResult(rw, NewError(err.Error(), http.StatusBadGateway))
|
||||||
|
}
|
||||||
|
reverseProxy.ServeHTTP(res, req)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user