Support subpaths when serving stash through a reverse proxy (#1719)

* Support subpaths when serving stash through a reverse proxy
* Add README documentation

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
InfiniteTF 2021-09-21 06:12:10 +02:00 committed by GitHub
parent f3c8407c40
commit a4ed9515c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 44 additions and 9 deletions

View file

@ -68,6 +68,10 @@ This command would need customizing for your environment. [This link](https://s
Once you have a certificate and key file name them `stash.crt` and `stash.key` and place them in the same directory as the `config.yml` file, or the `~/.stash` directory. Stash detects these and starts up using HTTPS rather than HTTP. Once you have a certificate and key file name them `stash.crt` and `stash.key` and place them in the same directory as the `config.yml` file, or the `~/.stash` directory. Stash detects these and starts up using HTTPS rather than HTTP.
## Basepath rewriting
The basepath defaults to `/`. When running stash via a reverse proxy in a subpath, the basepath can be changed by having the reverse proxy pass `X-Forwarded-Prefix` (and optionally `X-Forwarded-Port`) headers. When detects these headers, it alters the basepath URL of the UI.
# Customization # Customization
## Themes and CSS Customization ## Themes and CSS Customization

View file

@ -258,7 +258,15 @@ func Start() {
if ext == ".html" || ext == "" { if ext == ".html" || ext == "" {
data, _ := uiBox.Find("index.html") data, _ := uiBox.Find("index.html")
_, _ = w.Write(data)
prefix := ""
if r.Header.Get("X-Forwarded-Prefix") != "" {
prefix = strings.TrimRight(r.Header.Get("X-Forwarded-Prefix"), "/")
}
baseURLIndex := strings.Replace(string(data), "%BASE_URL%", prefix+"/", 2)
baseURLIndex = strings.Replace(baseURLIndex, "base href=\"/\"", fmt.Sprintf("base href=\"%s\"", prefix+"/"), 2)
_, _ = w.Write([]byte(baseURLIndex))
} else { } else {
isStatic, _ := path.Match("/static/*/*", r.URL.Path) isStatic, _ := path.Match("/static/*/*", r.URL.Path)
if isStatic { if isStatic {
@ -372,11 +380,22 @@ func BaseURLMiddleware(next http.Handler) http.Handler {
} else { } else {
scheme = "http" scheme = "http"
} }
baseURL := scheme + "://" + r.Host prefix := ""
if r.Header.Get("X-Forwarded-Prefix") != "" {
prefix = strings.TrimRight(r.Header.Get("X-Forwarded-Prefix"), "/")
}
port := ""
forwardedPort := r.Header.Get("X-Forwarded-Port")
if forwardedPort != "" && forwardedPort != "80" && forwardedPort != "8080" {
port = ":" + forwardedPort
}
baseURL := scheme + "://" + r.Host + port + prefix
externalHost := config.GetInstance().GetExternalHost() externalHost := config.GetInstance().GetExternalHost()
if externalHost != "" { if externalHost != "" {
baseURL = externalHost baseURL = externalHost + prefix
} }
r = r.WithContext(context.WithValue(ctx, BaseURLCtxKey, baseURL)) r = r.WithContext(context.WithValue(ctx, BaseURLCtxKey, baseURL))

View file

@ -2,6 +2,7 @@
"name": "stash", "name": "stash",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"homepage": "./",
"sideEffects": false, "sideEffects": false,
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View file

@ -1,6 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<base href="/">
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta <meta
@ -23,6 +24,7 @@
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>Stash</title> <title>Stash</title>
<script>window.STASH_BASE_URL = "%BASE_URL%"</script>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View file

@ -1,6 +1,7 @@
#### 💥 Note: Please check your logs after migrating to this release. A log warning will be generated on startup if duplicate image checksums exist in your system. Search for the images using the logged checksums, and remove the unwanted ones. #### 💥 Note: Please check your logs after migrating to this release. A log warning will be generated on startup if duplicate image checksums exist in your system. Search for the images using the logged checksums, and remove the unwanted ones.
### ✨ New Features ### ✨ New Features
* Support subpaths when serving stash via reverse proxy. ([#1719](https://github.com/stashapp/stash/pull/1719))
* Added options to generate webp and static preview files for markers. ([#1604](https://github.com/stashapp/stash/pull/1604)) * Added options to generate webp and static preview files for markers. ([#1604](https://github.com/stashapp/stash/pull/1604))
* Added sort by option for gallery rating. ([#1720](https://github.com/stashapp/stash/pull/1720)) * Added sort by option for gallery rating. ([#1720](https://github.com/stashapp/stash/pull/1720))
* Added support for querying scene scrapers using keywords. ([#1712](https://github.com/stashapp/stash/pull/1712)) * Added support for querying scene scrapers using keywords. ([#1712](https://github.com/stashapp/stash/pull/1712))

View file

@ -343,7 +343,7 @@ export class ScenePlayerImpl extends React.Component<
<div id="jwplayer-container" className={className}> <div id="jwplayer-container" className={className}>
<ReactJWPlayer <ReactJWPlayer
playerId={JWUtils.playerID} playerId={JWUtils.playerID}
playerScript="/jwplayer/jwplayer.js" playerScript="jwplayer/jwplayer.js"
customProps={this.state.config} customProps={this.state.config}
onReady={this.onReady} onReady={this.onReady}
onSeeked={this.onSeeked} onSeeked={this.onSeeked}

View file

@ -81,8 +81,14 @@ const typePolicies: TypePolicies = {
}, },
}; };
export const getBaseURL = () => {
const baseURL = window.STASH_BASE_URL;
if (baseURL === "%BASE_URL%") return "/";
return baseURL;
};
export const getPlatformURL = (ws?: boolean) => { export const getPlatformURL = (ws?: boolean) => {
const platformUrl = new URL(window.location.origin); const platformUrl = new URL(window.location.origin + getBaseURL());
if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") { if (!process.env.NODE_ENV || process.env.NODE_ENV === "development") {
platformUrl.port = process.env.REACT_APP_PLATFORM_PORT ?? "9999"; platformUrl.port = process.env.REACT_APP_PLATFORM_PORT ?? "9999";
@ -107,8 +113,8 @@ export const createClient = () => {
wsPlatformUrl.protocol = "wss:"; wsPlatformUrl.protocol = "wss:";
} }
const url = `${platformUrl.toString().slice(0, -1)}/graphql`; const url = `${platformUrl.toString()}graphql`;
const wsUrl = `${wsPlatformUrl.toString().slice(0, -1)}/graphql`; const wsUrl = `${wsPlatformUrl.toString()}graphql`;
const httpLink = createUploadLink({ const httpLink = createUploadLink({
uri: url, uri: url,

View file

@ -1,3 +1,5 @@
// eslint-disable-next-line no-var
declare var STASH_BASE_URL: string;
declare module "*.md"; declare module "*.md";
declare module "string.prototype.replaceall"; declare module "string.prototype.replaceall";
declare module "mousetrap-pause"; declare module "mousetrap-pause";

View file

@ -4,14 +4,14 @@ import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { App } from "./App"; import { App } from "./App";
import { getClient } from "./core/StashService"; import { getClient } from "./core/StashService";
import { getPlatformURL } from "./core/createClient"; import { getPlatformURL, getBaseURL } from "./core/createClient";
import "./index.scss"; import "./index.scss";
import * as serviceWorker from "./serviceWorker"; import * as serviceWorker from "./serviceWorker";
ReactDOM.render( ReactDOM.render(
<> <>
<link rel="stylesheet" type="text/css" href={`${getPlatformURL()}css`} /> <link rel="stylesheet" type="text/css" href={`${getPlatformURL()}css`} />
<BrowserRouter> <BrowserRouter basename={getBaseURL()}>
<ApolloProvider client={getClient()}> <ApolloProvider client={getClient()}>
<App /> <App />
</ApolloProvider> </ApolloProvider>