mirror of https://github.com/stashapp/stash.git
Added an onboarding flow
This commit is contained in:
parent
db42e43476
commit
77ffb65681
42
README.md
42
README.md
|
@ -6,23 +6,15 @@
|
|||
|
||||
See a demo [here](https://vimeo.com/275537038) (password is stashapp).
|
||||
|
||||
TODO: This is not match the features of the Rails project quite yet. Consider using that until this project is complete.
|
||||
TODO: This does not match the features of the Rails project quite yet and is still a little buggy. Fall back to the Rails project if you run into issues as an existing user.
|
||||
|
||||
## Setup
|
||||
# Install
|
||||
|
||||
TODO: This is not final. There is more work to be done to ease this process.
|
||||
Stash supports macOS, Windows, and Linux. Download the [latest release here](https://github.com/stashapp/stash/releases).
|
||||
|
||||
### OSX / Linux
|
||||
Simply run the executable and navigate to either https://localhost:9999 or http://localhost:9998 to get started.
|
||||
|
||||
1. `mkdir ~/.stash` && `cd ~/.stash`
|
||||
2. Create a `config.json` file (see below).
|
||||
3. Run stash with `./stash` and visit `http://localhost:9998` or `https://localhost:9999`
|
||||
|
||||
### Windows
|
||||
|
||||
1. Create a new folder at `C:\Users\YourUsername\.stash`
|
||||
2. Create a `config.json` file (see below)
|
||||
3. Run stash with `./stash` and visit `http://localhost:9998` or `https://localhost:9999`
|
||||
*Note for Windows users:* Running the app might present a security prompt since the binary isn't signed yet. Just click more info and then the run anyway button.
|
||||
|
||||
#### FFMPEG
|
||||
|
||||
|
@ -34,29 +26,11 @@ If stash is unable to find or download FFMPEG then download it yourself from the
|
|||
|
||||
The `ffmpeg(.exe)` and `ffprobe(.exe)` files should be placed in `~/.stash` on macOS / Linux or `C:\Users\YourUsername\.stash` on Windows.
|
||||
|
||||
#### Config.json
|
||||
# FAQ
|
||||
|
||||
Example:
|
||||
> Does stash support multiple folders?
|
||||
|
||||
*OSX / Linux*
|
||||
```
|
||||
{
|
||||
"stash": "/Volumes/Drobo/videos",
|
||||
"metadata": "/Volumes/Drobo/stash/metadata",
|
||||
"cache": "/Volumes/Drobo/stash/cache",
|
||||
"downloads": "/Volumes/Drobo/stash/downloads"
|
||||
}
|
||||
```
|
||||
|
||||
*Windows*
|
||||
```
|
||||
{
|
||||
"stash": "C:\\Videos",
|
||||
"metadata": "C:\\stash\\metadata",
|
||||
"cache": "C:\\stash\\cache",
|
||||
"downloads": "C:\\stash\\downloads"
|
||||
}
|
||||
```
|
||||
Not yet, but this will come in the future.
|
||||
|
||||
# Development
|
||||
|
||||
|
|
|
@ -11,9 +11,14 @@ import (
|
|||
"github.com/gobuffalo/packr/v2"
|
||||
"github.com/rs/cors"
|
||||
"github.com/stashapp/stash/logger"
|
||||
"github.com/stashapp/stash/manager"
|
||||
"github.com/stashapp/stash/manager/jsonschema"
|
||||
"github.com/stashapp/stash/models"
|
||||
"github.com/stashapp/stash/utils"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
)
|
||||
|
@ -23,6 +28,7 @@ const httpsPort = "9999"
|
|||
|
||||
var certsBox *packr.Box
|
||||
var uiBox *packr.Box
|
||||
var setupUIBox *packr.Box
|
||||
|
||||
func Start() {
|
||||
//port := os.Getenv("PORT")
|
||||
|
@ -32,6 +38,7 @@ func Start() {
|
|||
|
||||
certsBox = packr.New("Cert Box", "../certs")
|
||||
uiBox = packr.New("UI Box", "../ui/v1/dist/stash-frontend")
|
||||
setupUIBox = packr.New("Setup UI Box", "../ui/setup")
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
|
@ -41,6 +48,7 @@ func Start() {
|
|||
r.Use(middleware.StripSlashes)
|
||||
r.Use(cors.AllowAll().Handler)
|
||||
r.Use(BaseURLMiddleware)
|
||||
r.Use(ConfigCheckMiddleware)
|
||||
|
||||
recoverFunc := handler.RecoverFunc(func(ctx context.Context, err interface{}) error {
|
||||
logger.Error(err)
|
||||
|
@ -66,6 +74,65 @@ func Start() {
|
|||
r.Mount("/scene", sceneRoutes{}.Routes())
|
||||
r.Mount("/studio", studioRoutes{}.Routes())
|
||||
|
||||
// Serve the setup UI
|
||||
r.HandleFunc("/setup*", func(w http.ResponseWriter, r *http.Request) {
|
||||
ext := path.Ext(r.URL.Path)
|
||||
if ext == ".html" || ext == "" {
|
||||
data := setupUIBox.Bytes("index.html")
|
||||
_, _ = w.Write(data)
|
||||
} else {
|
||||
r.URL.Path = strings.Replace(r.URL.Path, "/setup", "", 1)
|
||||
http.FileServer(setupUIBox).ServeHTTP(w, r)
|
||||
}
|
||||
})
|
||||
r.Post("/init", func(w http.ResponseWriter, r *http.Request) {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("error: %s", err), 500)
|
||||
}
|
||||
stash := filepath.Clean(r.Form.Get("stash"))
|
||||
metadata := filepath.Clean(r.Form.Get("metadata"))
|
||||
cache := filepath.Clean(r.Form.Get("cache"))
|
||||
//downloads := filepath.Clean(r.Form.Get("downloads")) // TODO
|
||||
downloads := filepath.Join(metadata, "downloads")
|
||||
|
||||
exists, _ := utils.FileExists(stash)
|
||||
fileInfo, _ := os.Stat(stash)
|
||||
if !exists || !fileInfo.IsDir() {
|
||||
http.Error(w, fmt.Sprintf("the stash path either doesn't exist, or is not a directory <%s>. Go back and try again.", stash), 500)
|
||||
return
|
||||
}
|
||||
|
||||
exists, _ = utils.FileExists(metadata)
|
||||
fileInfo, _ = os.Stat(metadata)
|
||||
if !exists || !fileInfo.IsDir() {
|
||||
http.Error(w, fmt.Sprintf("the metadata path either doesn't exist, or is not a directory <%s> Go back and try again.", metadata), 500)
|
||||
return
|
||||
}
|
||||
|
||||
exists, _ = utils.FileExists(cache)
|
||||
fileInfo, _ = os.Stat(cache)
|
||||
if !exists || !fileInfo.IsDir() {
|
||||
http.Error(w, fmt.Sprintf("the cache path either doesn't exist, or is not a directory <%s> Go back and try again.", cache), 500)
|
||||
return
|
||||
}
|
||||
|
||||
_ = os.Mkdir(downloads, 0755)
|
||||
|
||||
config := &jsonschema.Config{
|
||||
Stash: stash,
|
||||
Metadata: metadata,
|
||||
Cache: cache,
|
||||
Downloads: downloads,
|
||||
}
|
||||
if err := manager.GetInstance().SaveConfig(config); err != nil {
|
||||
http.Error(w, fmt.Sprintf("there was an error saving the config file: %s", err), 500)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/", 301)
|
||||
})
|
||||
|
||||
// Serve the angular app
|
||||
r.HandleFunc("/*", func(w http.ResponseWriter, r *http.Request) {
|
||||
ext := path.Ext(r.URL.Path)
|
||||
|
@ -92,8 +159,10 @@ func Start() {
|
|||
logger.Fatal(server.ListenAndServe())
|
||||
}()
|
||||
|
||||
logger.Infof("stash is running on HTTPS at https://localhost:9999/")
|
||||
logger.Fatal(httpsServer.ListenAndServeTLS("", ""))
|
||||
go func() {
|
||||
logger.Infof("stash is running on HTTPS at https://localhost:9999/")
|
||||
logger.Fatal(httpsServer.ListenAndServeTLS("", ""))
|
||||
}()
|
||||
}
|
||||
|
||||
func makeTLSConfig() *tls.Config {
|
||||
|
@ -136,4 +205,18 @@ func BaseURLMiddleware(next http.Handler) http.Handler {
|
|||
next.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func ConfigCheckMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ext := path.Ext(r.URL.Path)
|
||||
shouldRedirect := ext == "" && r.Method == "GET" && r.URL.Path != "/init"
|
||||
if !manager.HasValidConfig() && shouldRedirect {
|
||||
if !strings.HasPrefix(r.URL.Path, "/setup") {
|
||||
http.Redirect(w, r, "/setup", 301)
|
||||
return
|
||||
}
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
6
main.go
6
main.go
|
@ -12,6 +12,10 @@ import (
|
|||
func main() {
|
||||
managerInstance := manager.Initialize()
|
||||
database.Initialize(managerInstance.StaticPaths.DatabaseFile)
|
||||
|
||||
api.Start()
|
||||
blockForever()
|
||||
}
|
||||
|
||||
func blockForever() {
|
||||
select {}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,16 @@ package jsonschema
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/stashapp/stash/logger"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Stash string `json:"stash"`
|
||||
Stash string `json:"stash"`
|
||||
Metadata string `json:"metadata"`
|
||||
// Generated string `json:"generated"` // TODO: Generated directory instead of metadata
|
||||
Cache string `json:"cache"`
|
||||
Cache string `json:"cache"`
|
||||
Downloads string `json:"downloads"`
|
||||
}
|
||||
|
||||
|
@ -23,6 +24,15 @@ func LoadConfigFile(file string) *Config {
|
|||
}
|
||||
jsonParser := json.NewDecoder(configFile)
|
||||
parseError := jsonParser.Decode(&config)
|
||||
if parseError != nil { panic(parseError) }
|
||||
if parseError != nil {
|
||||
logger.Errorf("config file parse error: %s", parseError)
|
||||
}
|
||||
return &config
|
||||
}
|
||||
|
||||
func SaveConfigFile(filePath string, config *Config) error {
|
||||
if config == nil {
|
||||
return fmt.Errorf("config must not be nil")
|
||||
}
|
||||
return marshalToFile(filePath, config)
|
||||
}
|
|
@ -3,7 +3,9 @@ package manager
|
|||
import (
|
||||
"github.com/stashapp/stash/ffmpeg"
|
||||
"github.com/stashapp/stash/logger"
|
||||
"github.com/stashapp/stash/manager/jsonschema"
|
||||
"github.com/stashapp/stash/manager/paths"
|
||||
"github.com/stashapp/stash/utils"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
@ -24,13 +26,16 @@ func GetInstance() *singleton {
|
|||
|
||||
func Initialize() *singleton {
|
||||
once.Do(func() {
|
||||
configFile := jsonschema.LoadConfigFile(paths.StaticPaths.ConfigFile)
|
||||
instance = &singleton{
|
||||
Status: Idle,
|
||||
Paths: paths.RefreshPaths(),
|
||||
Paths: paths.NewPaths(configFile),
|
||||
StaticPaths: &paths.StaticPaths,
|
||||
JSON: &jsonUtils{},
|
||||
}
|
||||
|
||||
instance.refreshConfig(configFile)
|
||||
|
||||
initFFMPEG()
|
||||
})
|
||||
|
||||
|
@ -56,3 +61,41 @@ The error was: %s
|
|||
instance.StaticPaths.FFMPEG = ffmpegPath
|
||||
instance.StaticPaths.FFProbe = ffprobePath
|
||||
}
|
||||
|
||||
func HasValidConfig() bool {
|
||||
configFileExists, _ := utils.FileExists(instance.StaticPaths.ConfigFile) // TODO: Verify JSON is correct
|
||||
if configFileExists && instance.Paths.Config != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *singleton) SaveConfig(config *jsonschema.Config) error {
|
||||
if err := jsonschema.SaveConfigFile(s.StaticPaths.ConfigFile, config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reload the config
|
||||
s.refreshConfig(config)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *singleton) refreshConfig(config *jsonschema.Config) {
|
||||
if config == nil {
|
||||
config = jsonschema.LoadConfigFile(s.StaticPaths.ConfigFile)
|
||||
}
|
||||
s.Paths = paths.NewPaths(config)
|
||||
|
||||
if HasValidConfig() {
|
||||
_ = utils.EnsureDir(s.Paths.Generated.Screenshots)
|
||||
_ = utils.EnsureDir(s.Paths.Generated.Vtt)
|
||||
_ = utils.EnsureDir(s.Paths.Generated.Markers)
|
||||
_ = utils.EnsureDir(s.Paths.Generated.Transcodes)
|
||||
|
||||
_ = utils.EnsureDir(s.Paths.JSON.Performers)
|
||||
_ = utils.EnsureDir(s.Paths.JSON.Scenes)
|
||||
_ = utils.EnsureDir(s.Paths.JSON.Galleries)
|
||||
_ = utils.EnsureDir(s.Paths.JSON.Studios)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package paths
|
|||
|
||||
import (
|
||||
"github.com/stashapp/stash/manager/jsonschema"
|
||||
"github.com/stashapp/stash/utils"
|
||||
)
|
||||
|
||||
type Paths struct {
|
||||
|
@ -15,14 +14,9 @@ type Paths struct {
|
|||
SceneMarkers *sceneMarkerPaths
|
||||
}
|
||||
|
||||
func RefreshPaths() *Paths {
|
||||
ensureConfigFile()
|
||||
return newPaths()
|
||||
}
|
||||
|
||||
func newPaths() *Paths {
|
||||
func NewPaths(config *jsonschema.Config) *Paths {
|
||||
p := Paths{}
|
||||
p.Config = jsonschema.LoadConfigFile(StaticPaths.ConfigFile)
|
||||
p.Config = config
|
||||
p.Generated = newGeneratedPaths(p)
|
||||
p.JSON = newJSONPaths(p)
|
||||
|
||||
|
@ -30,13 +24,4 @@ func newPaths() *Paths {
|
|||
p.Scene = newScenePaths(p)
|
||||
p.SceneMarkers = newSceneMarkerPaths(p)
|
||||
return &p
|
||||
}
|
||||
|
||||
func ensureConfigFile() {
|
||||
configFileExists, _ := utils.FileExists(StaticPaths.ConfigFile) // TODO: Verify JSON is correct. Pass verified
|
||||
if configFileExists {
|
||||
return
|
||||
}
|
||||
|
||||
panic("No config file found")
|
||||
}
|
|
@ -20,11 +20,6 @@ func newGeneratedPaths(p Paths) *generatedPaths {
|
|||
gp.Markers = filepath.Join(p.Config.Metadata, "markers")
|
||||
gp.Transcodes = filepath.Join(p.Config.Metadata, "transcodes")
|
||||
gp.Tmp = filepath.Join(p.Config.Metadata, "tmp")
|
||||
|
||||
_ = utils.EnsureDir(gp.Screenshots)
|
||||
_ = utils.EnsureDir(gp.Vtt)
|
||||
_ = utils.EnsureDir(gp.Markers)
|
||||
_ = utils.EnsureDir(gp.Transcodes)
|
||||
return &gp
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package paths
|
||||
|
||||
import (
|
||||
"github.com/stashapp/stash/utils"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
|
@ -23,11 +22,6 @@ func newJSONPaths(p Paths) *jsonPaths {
|
|||
jp.Scenes = filepath.Join(p.Config.Metadata, "scenes")
|
||||
jp.Galleries = filepath.Join(p.Config.Metadata, "galleries")
|
||||
jp.Studios = filepath.Join(p.Config.Metadata, "studios")
|
||||
|
||||
_ = utils.EnsureDir(jp.Performers)
|
||||
_ = utils.EnsureDir(jp.Scenes)
|
||||
_ = utils.EnsureDir(jp.Galleries)
|
||||
_ = utils.EnsureDir(jp.Studios)
|
||||
return &jp
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Stash</title>
|
||||
|
||||
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic">
|
||||
<link rel="stylesheet" href="//cdn.rawgit.com/necolas/normalize.css/master/normalize.css">
|
||||
<link rel="stylesheet" href="/setup/milligram.min.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<form action="/init" method="POST">
|
||||
<fieldset>
|
||||
<label for="stash">Where is your porn located (mp4, wmv, zip, etc)?</label>
|
||||
<input name="stash" type="text" placeholder="EX: C:\videos (Windows) or /User/StashApp/Videos (macOS / Linux)" />
|
||||
|
||||
<label for="metadata">Where would you like to save metadata? Metadata includes generated videos / images and backup JSON files.</label>
|
||||
<input name="metadata" type="text" placeholder="EX: C:\stash\metadata (Windows) or /User/StashApp/stash/metadata (macOS / Linux)" />
|
||||
|
||||
<label for="cache">Where do you want to Stash to save cache / temporary files it might need to create?</label>
|
||||
<input name="cache" type="text" placeholder="EX: C:\stash\cache (Windows) or /User/StashApp/stash/cache (macOS / Linux)" />
|
||||
|
||||
<input hidden name="downloads" value="">
|
||||
|
||||
<div>
|
||||
<input class="button button-black" type="submit" value="Submit">
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue