mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-20 08:51:01 +00:00
Support path prefix (#1714)
closes #1636 closes #1429 supersedes #1586 Uses a different approach: just take the index.html compiled by vite and replace the paths to js and other files using regex. This is not compatible with the dev proxy which is also the reason why we can't use go templates for this.
This commit is contained in:
parent
4384344c22
commit
b90e7904a5
9 changed files with 46 additions and 11 deletions
|
@ -45,6 +45,11 @@ var flags = []cli.Flag{
|
|||
Name: "server-host",
|
||||
Usage: "server fully qualified url (<scheme>://<host>)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_ROOT_URL"},
|
||||
Name: "root-url",
|
||||
Usage: "server url root (used for statics loading when having a url path prefix)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
EnvVars: []string{"WOODPECKER_SERVER_ADDR"},
|
||||
Name: "server-addr",
|
||||
|
|
|
@ -354,6 +354,7 @@ func setupEvilGlobals(c *cli.Context, v store.Store, f forge.Forge) {
|
|||
server.Config.Server.StatusContext = c.String("status-context")
|
||||
server.Config.Server.StatusContextFormat = c.String("status-context-format")
|
||||
server.Config.Server.SessionExpires = c.Duration("session-expires")
|
||||
server.Config.Server.RootURL = strings.TrimSuffix(c.String("root-url"), "/")
|
||||
server.Config.Pipeline.Networks = c.StringSlice("network")
|
||||
server.Config.Pipeline.Volumes = c.StringSlice("volume")
|
||||
server.Config.Pipeline.Privileged = c.StringSlice("escalate")
|
||||
|
|
|
@ -174,3 +174,5 @@ A [Prometheus endpoint](./90-prometheus.md) is exposed.
|
|||
## Behind a proxy
|
||||
|
||||
See the [proxy guide](./70-proxy.md) if you want to see a setup behind Apache, Nginx, Caddy or ngrok.
|
||||
|
||||
In the case you need to use Woodpecker with a URL path prefix (like: https://example.org/woodpecker/), you can use the option [`WOODPECKER_ROOT_URL`](./10-server-config.md#woodpecker_root_url).
|
||||
|
|
|
@ -404,6 +404,12 @@ Specify a configuration service endpoint, see [Configuration Extension](./100-ex
|
|||
|
||||
Specify how many seconds before timeout when fetching the Woodpecker configuration from a Forge
|
||||
|
||||
### `WOODPECKER_ROOT_URL`
|
||||
> Default: ``
|
||||
|
||||
Server URL path prefix (used for statics loading when having a url path prefix), should start with `/`
|
||||
|
||||
Example: `WOODPECKER_ROOT_URL=/woodpecker`
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -66,6 +66,7 @@ var Config = struct {
|
|||
StatusContext string
|
||||
StatusContextFormat string
|
||||
SessionExpires time.Duration
|
||||
RootURL string
|
||||
// Open bool
|
||||
// Orgs map[string]struct{}
|
||||
// Admins map[string]struct{}
|
||||
|
|
|
@ -40,11 +40,12 @@ func Config(c *gin.Context) {
|
|||
}
|
||||
|
||||
configData := map[string]interface{}{
|
||||
"user": user,
|
||||
"csrf": csrf,
|
||||
"docs": server.Config.Server.Docs,
|
||||
"version": version.String(),
|
||||
"forge": server.Config.Services.Forge.Name(),
|
||||
"user": user,
|
||||
"csrf": csrf,
|
||||
"docs": server.Config.Server.Docs,
|
||||
"version": version.String(),
|
||||
"forge": server.Config.Services.Forge.Name(),
|
||||
"root_url": server.Config.Server.RootURL,
|
||||
}
|
||||
|
||||
// default func map with json parser.
|
||||
|
@ -61,7 +62,10 @@ func Config(c *gin.Context) {
|
|||
if err := tmpl.Execute(c.Writer, configData); err != nil {
|
||||
log.Error().Err(err).Msgf("could not execute template")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
const configTemplate = `
|
||||
|
@ -70,4 +74,5 @@ window.WOODPECKER_CSRF = "{{ .csrf }}";
|
|||
window.WOODPECKER_VERSION = "{{ .version }}";
|
||||
window.WOODPECKER_DOCS = "{{ .docs }}";
|
||||
window.WOODPECKER_FORGE = "{{ .forge }}";
|
||||
window.WOODPECKER_ROOT_URL = "{{ .root_url }}";
|
||||
`
|
||||
|
|
|
@ -18,21 +18,27 @@ import (
|
|||
"crypto/md5"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/woodpecker-ci/woodpecker/server"
|
||||
"github.com/woodpecker-ci/woodpecker/web"
|
||||
)
|
||||
|
||||
// etag is an identifier for a resource version
|
||||
// it lets caches determine if resource is still the same and not send it again
|
||||
var etag = fmt.Sprintf("%x", md5.Sum([]byte(time.Now().String())))
|
||||
var (
|
||||
etag = fmt.Sprintf("%x", md5.Sum([]byte(time.Now().String())))
|
||||
indexHTML []byte
|
||||
)
|
||||
|
||||
// New returns a gin engine to serve the web frontend.
|
||||
func New() (*gin.Engine, error) {
|
||||
e := gin.New()
|
||||
indexHTML = parseIndex()
|
||||
|
||||
e.Use(setupCache)
|
||||
|
||||
|
@ -64,15 +70,22 @@ func redirect(location string, status ...int) func(ctx *gin.Context) {
|
|||
|
||||
func handleIndex(c *gin.Context) {
|
||||
rw := c.Writer
|
||||
rw.Header().Set("Content-Type", "text/html; charset=UTF-8")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
if _, err := rw.Write(indexHTML); err != nil {
|
||||
log.Error().Err(err).Msg("can not write index.html")
|
||||
}
|
||||
}
|
||||
|
||||
func parseIndex() []byte {
|
||||
data, err := web.Lookup("index.html")
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("can not find index.html")
|
||||
}
|
||||
rw.Header().Set("Content-Type", "text/html; charset=UTF-8")
|
||||
rw.WriteHeader(200)
|
||||
if _, err := rw.Write(data); err != nil {
|
||||
log.Error().Err(err).Msg("can not write index.html")
|
||||
if server.Config.Server.RootURL == "" {
|
||||
return data
|
||||
}
|
||||
return regexp.MustCompile(`/\S+\.(js|css|png|svg)`).ReplaceAll(data, []byte(server.Config.Server.RootURL+"$0"))
|
||||
}
|
||||
|
||||
func setupCache(c *gin.Context) {
|
||||
|
|
|
@ -7,7 +7,7 @@ let apiClient: WoodpeckerClient | undefined;
|
|||
export default (): WoodpeckerClient => {
|
||||
if (!apiClient) {
|
||||
const config = useConfig();
|
||||
const server = '';
|
||||
const server = config.rootURL ?? '';
|
||||
const token = null;
|
||||
const csrf = config.csrf || null;
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ declare global {
|
|||
WOODPECKER_VERSION: string | undefined;
|
||||
WOODPECKER_CSRF: string | undefined;
|
||||
WOODPECKER_FORGE: string | undefined;
|
||||
WOODPECKER_ROOT_URL: string | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,4 +17,5 @@ export default () => ({
|
|||
version: window.WOODPECKER_VERSION,
|
||||
csrf: window.WOODPECKER_CSRF || null,
|
||||
forge: window.WOODPECKER_FORGE || null,
|
||||
rootURL: window.WOODPECKER_ROOT_URL || null,
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue