Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add -base-path flag #762

Merged
merged 18 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/goatcounter/saas.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ func cmdSaas(f zli.Flags, ready chan<- struct{}, stop chan struct{}) error {
hosts := map[string]http.Handler{
d: zhttp.RedirectHost("//www." + domain),
"www." + d: handlers.NewWebsite(db, dev),
"*": handlers.NewBackend(db, acmeh, dev, c.GoatcounterCom, websocket, c.DomainStatic, 15, apiMax),
"*": handlers.NewBackend(db, acmeh, dev, c.GoatcounterCom, websocket, c.DomainStatic, c.BasePath, 15, apiMax),
}
if dev {
hosts[znet.RemovePort(domainStatic)] = handlers.NewStatic(chi.NewRouter(), dev, true)
hosts[znet.RemovePort(domainStatic)] = handlers.NewStatic(chi.NewRouter(), dev, true, c.BasePath)
}

return doServe(ctx, db, listen, listenTLS, tlsc, hosts, stop, func() {
Expand Down
24 changes: 20 additions & 4 deletions cmd/goatcounter/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ Flags:
-public-port Port your site is publicly accessible on. Only needed if it's
not 80 or 443.

-base-path Path under which GoatCounter is available. Usually GoatCounter
runs on its own domain or subdomain ("stats.example.com"), but in
some cases it's useful to run GoatCounter under a path
("example.com/stats"), in which case you'll need to set this to
"/stats".

-automigrate Automatically run all pending migrations on startup.

-smtp SMTP relay server, as URL (e.g. "smtp://user:pass@server").
Expand Down Expand Up @@ -164,18 +170,25 @@ func cmdServe(f zli.Flags, ready chan<- struct{}, stop chan struct{}) error {
var (
// TODO(depr): -port is for compat with <2.0
port = f.Int(0, "public-port", "port").Pointer()
basePath = f.String("/", "base-path").Pointer()
domainStatic = f.String("", "static").Pointer()
)
dbConnect, dbConn, dev, automigrate, listen, flagTLS, from, websocket, apiMax, err := flagsServe(f, &v)
if err != nil {
return err
}

return func(port int, domainStatic string) error {
return func(port int, basePath, domainStatic string) error {
if flagTLS == "" {
flagTLS = map[bool]string{true: "http", false: "acme,rdr"}[dev]
}

basePath = strings.Trim(basePath, "/")
if basePath != "" {
basePath = "/" + basePath
}
mk12 marked this conversation as resolved.
Show resolved Hide resolved
zhttp.BasePath = basePath

var domainCount, urlStatic string
if domainStatic != "" {
if p := strings.Index(domainStatic, ":"); p > -1 {
Expand All @@ -185,6 +198,8 @@ func cmdServe(f zli.Flags, ready chan<- struct{}, stop chan struct{}) error {
}
urlStatic = "//" + domainStatic
domainCount = domainStatic
} else {
urlStatic = basePath
}

//from := flagFrom(from, "cfg.Domain", &v)
Expand All @@ -206,17 +221,18 @@ func cmdServe(f zli.Flags, ready chan<- struct{}, stop chan struct{}) error {
c.DomainStatic = domainStatic
c.Dev = dev
c.URLStatic = urlStatic
c.BasePath = basePath
c.DomainCount = domainCount
c.Websocket = websocket

// Set up HTTP handler and servers.
hosts := map[string]http.Handler{
"*": handlers.NewBackend(db, acmeh, dev, c.GoatcounterCom, websocket, c.DomainStatic, 60, apiMax),
"*": handlers.NewBackend(db, acmeh, dev, c.GoatcounterCom, websocket, c.DomainStatic, c.BasePath, 60, apiMax),
}
if domainStatic != "" {
// May not be needed, but just in case the DomainStatic isn't an
// external CDN.
hosts[znet.RemovePort(domainStatic)] = handlers.NewStatic(chi.NewRouter(), dev, false)
hosts[znet.RemovePort(domainStatic)] = handlers.NewStatic(chi.NewRouter(), dev, false, c.BasePath)
}

cnames, err := lsSites(ctx)
Expand All @@ -238,7 +254,7 @@ func cmdServe(f zli.Flags, ready chan<- struct{}, stop chan struct{}) error {
}
ready <- struct{}{}
})
}(*port, *domainStatic)
}(*port, *basePath, *domainStatic)
}

func doServe(ctx context.Context, db zdb.DB,
Expand Down
1 change: 1 addition & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type GlobalConfig struct {
Domain string
DomainStatic string
DomainCount string
BasePath string
URLStatic string
Dev bool
GoatcounterCom bool
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ require (
zgo.at/zcache v1.2.0
zgo.at/zcache/v2 v2.1.0
zgo.at/zdb v0.0.0-20240818155550-1a862f98cab0
zgo.at/zhttp v0.0.0-20240812113805-6333261ded60
zgo.at/zhttp v0.0.0-20240819012318-b761c83c740e
zgo.at/zli v0.0.0-20240614180544-47534b1ce136
zgo.at/zlog v0.0.0-20211017235224-dd4772ddf860
zgo.at/zprof v0.0.0-20211217104121-c3c12596d8f0
Expand Down
4 changes: 2 additions & 2 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 13 additions & 4 deletions handlers/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,28 @@ import (
"zgo.at/zstd/zfs"
)

func NewBackend(db zdb.DB, acmeh http.HandlerFunc, dev, goatcounterCom, websocket bool, domainStatic string, dashTimeout, apiMax int) chi.Router {
r := chi.NewRouter()
func NewBackend(db zdb.DB, acmeh http.HandlerFunc, dev, goatcounterCom, websocket bool,
domainStatic string, basePath string, dashTimeout, apiMax int,
) chi.Router {

root := chi.NewRouter()
r := root
if basePath != "" {
r = chi.NewRouter()
root.Mount(basePath, r)
}

backend{dashTimeout, websocket}.Mount(r, db, dev, domainStatic, dashTimeout, apiMax)

if acmeh != nil {
r.Get("/.well-known/acme-challenge/{key}", acmeh)
}

if !goatcounterCom {
NewStatic(r, dev, goatcounterCom)
NewStatic(r, dev, goatcounterCom, basePath)
}

return r
return root
}

type backend struct {
Expand Down
2 changes: 1 addition & 1 deletion handlers/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,5 +249,5 @@ func BenchmarkCount(b *testing.B) {
}

func newBackend(db zdb.DB) chi.Router {
return NewBackend(db, nil, true, true, false, "example.com", 10, 0)
return NewBackend(db, nil, true, true, false, "example.com", "", 10, 0)
}
5 changes: 1 addition & 4 deletions handlers/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,7 @@ func (h backend) dashboard(w http.ResponseWriter, r *http.Request) error {

cd := goatcounter.Config(r.Context()).DomainCount
if cd == "" {
cd = Site(r.Context()).Domain(r.Context())
if goatcounter.Config(r.Context()).Port != "" {
cd += ":" + goatcounter.Config(r.Context()).Port
}
cd = Site(r.Context()).SchemelessURL(r.Context())
}

args := widgets.Args{
Expand Down
16 changes: 13 additions & 3 deletions handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type Globals struct {
User *goatcounter.User
Site *goatcounter.Site
Path string
Base string
Flash *zhttp.FlashMessage
Static string
StaticDomain string
Expand All @@ -79,11 +80,17 @@ func (g Globals) T(msg string, data ...any) template.HTML {

func newGlobals(w http.ResponseWriter, r *http.Request) Globals {
ctx := r.Context()
base := goatcounter.Config(ctx).BasePath
path := strings.TrimPrefix(r.URL.Path, base)
if path == "" {
path = "/"
}
g := Globals{
Context: ctx,
User: goatcounter.GetUser(ctx),
Site: goatcounter.GetSite(ctx),
Path: r.URL.Path,
Path: path,
Base: base,
Flash: zhttp.ReadFlash(w, r),
Static: goatcounter.Config(ctx).URLStatic,
Domain: goatcounter.Config(ctx).Domain,
Expand Down Expand Up @@ -124,7 +131,7 @@ func newGlobals(w http.ResponseWriter, r *http.Request) Globals {
return g
}

func NewStatic(r chi.Router, dev, goatcounterCom bool) chi.Router {
func NewStatic(r chi.Router, dev, goatcounterCom bool, basePath string) chi.Router {
var cache map[string]int
if !dev {
cache = map[string]int{
Expand All @@ -141,6 +148,9 @@ func NewStatic(r chi.Router, dev, goatcounterCom bool) chi.Router {
s.Header("/count.js", map[string]string{
"Cross-Origin-Resource-Policy": "cross-origin",
})
r.Get("/*", s.ServeHTTP)
r.Get("/*", func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = strings.TrimPrefix(r.URL.Path, basePath)
s.ServeHTTP(w, r)
})
return r
}
2 changes: 1 addition & 1 deletion handlers/i18n.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func (h i18n) show(w http.ResponseWriter, r *http.Request) error {

return zhttp.Template(w, "i18n_show.gohtml", struct {
Globals
Base msgfile.File
BaseFile msgfile.File
File msgfile.File
TOMLFile string
FormatLink func(string) string
Expand Down
9 changes: 5 additions & 4 deletions handlers/mw.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ var Started time.Time
var (
redirect = func(w http.ResponseWriter, r *http.Request) error {
zhttp.Flash(w, "Need to log in")
return guru.Errorf(303, "/user/new")
return guru.Errorf(303, goatcounter.Config(r.Context()).BasePath+"/user/new")
}

loggedIn = auth.Filter(func(w http.ResponseWriter, r *http.Request) error {
Expand Down Expand Up @@ -76,7 +76,7 @@ var (
Secure: zhttp.CookieSecure,
SameSite: zhttp.CookieSameSite,
})
return guru.Errorf(303, "/")
return guru.Errorf(303, goatcounter.Config(r.Context()).BasePath+"/")
}
if c, err := r.Cookie("access-token"); err == nil && s.Settings.CanView(c.Value) {
return nil
Expand Down Expand Up @@ -220,7 +220,7 @@ func addctx(db zdb.DB, loadSite bool, dashTimeout int) func(http.Handler) http.H

func noSites(db zdb.DB, w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
w.Header().Set("Location", "/")
w.Header().Set("Location", goatcounter.Config(r.Context()).BasePath+"/")
w.WriteHeader(307)
return
}
Expand Down Expand Up @@ -302,11 +302,12 @@ func noSites(db zdb.DB, w http.ResponseWriter, r *http.Request) {
}

err := zhttp.Template(w, "serve_newsite.gohtml", struct {
Globals
Validate *zvalidate.Validator
Error error
Email string
Cname string
}{&v, tplErr, args.Email, args.Cname})
}{newGlobals(w, r), &v, tplErr, args.Email, args.Cname})
if err != nil {
zlog.Error(err)
}
Expand Down
2 changes: 1 addition & 1 deletion handlers/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ func (h user) verify(w http.ResponseWriter, r *http.Request) error {
return zhttp.SeeOther(w, "/")
}

// Make sure to use the currect cookie, since both "custom.example.com" and
// Make sure to use the correct cookie, since both "custom.example.com" and
// "example.goatcounter.com" will work if you're using a custom domain.
func cookieDomain(site *goatcounter.Site, r *http.Request) string {
if r.Host == site.Domain(r.Context()) {
Expand Down
8 changes: 2 additions & 6 deletions handlers/website.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ func (h website) doSignup(w http.ResponseWriter, r *http.Request) error {
}
})

return zhttp.SeeOther(w, fmt.Sprintf("%s/user/new", site.URL(r.Context())))
return zhttp.SeeOther(w, site.URL(r.Context())+"/user/new")
}

func (h website) forgot(err error, email, turingTest string) zhttp.HandlerFunc {
Expand Down Expand Up @@ -481,11 +481,7 @@ func (h website) help(w http.ResponseWriter, r *http.Request) error {

dc := goatcounter.Config(r.Context()).DomainCount
if dc == "" {
dc = Site(r.Context()).Domain(r.Context())
port := goatcounter.Config(r.Context()).Port
if port != "" {
dc += port
}
dc = Site(r.Context()).SchemelessURL(r.Context())
}

cp := chi.URLParam(r, "*")
Expand Down
13 changes: 7 additions & 6 deletions public/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
$(document).ready(function() {
window.I18N = JSON.parse($('#js-i18n').text())
window.USER_SETTINGS = JSON.parse($('#js-settings').text())
window.BASE_PATH = $('#js-settings').attr('data-base-path') || ""
window.CSRF = $('#js-settings').attr('data-csrf')
window.TZ_OFFSET = parseInt($('#js-settings').attr('data-offset'), 10) || 0
window.SITE_FIRST_HIT_AT = $('#js-settings').attr('data-first-hit-at') * 1000
Expand All @@ -25,9 +26,9 @@
var report_errors = function() {
window.onerror = on_error
$(document).on('ajaxError', function(e, xhr, settings, err) {
if (settings.url === '/jserr') // Just in case, otherwise we'll be stuck.
if (settings.url === BASE_PATH + '/jserr') // Just in case, otherwise we'll be stuck.
return
if (settings.url === '/load-widget')
if (settings.url === BASE_PATH + '/load-widget')
return
var msg = T("error/load-url", {url: settings.url, error: err})
console.error(msg)
Expand All @@ -53,7 +54,7 @@
return

jQuery.ajax({
url: '/jserr',
url: BASE_PATH + '/jserr',
method: 'POST',
data: {msg: msg, url: url, line: line, column: column, stack: (err||{}).stack, ua: navigator.userAgent, loc: window.location+''},
})
Expand Down Expand Up @@ -125,7 +126,7 @@
e.preventDefault()

jQuery.ajax({
url: '/settings/main/ip',
url: BASE_PATH + '/settings/main/ip',
success: function(data) {
var input = $('[name="settings.ignore_ips"]'),
current = input.val().split(',').
Expand Down Expand Up @@ -161,7 +162,7 @@

// Update redirect link.
$('#settings-secret').on('change', function(e) {
$('#secret-url').val(`${location.protocol}//${location.host}?access-token=${this.value}`)
$('#secret-url').val(`${location.protocol}//${location.host}${BASE_PATH}?access-token=${this.value}`)
}).trigger('change')
}

Expand Down Expand Up @@ -193,7 +194,7 @@
return

jQuery.ajax({
url: '/user/dashboard/widget/' + this.selectedOptions[0].value,
url: BASE_PATH + '/user/dashboard/widget/' + this.selectedOptions[0].value,
success: function(data) {
var i = 1 + $('.index').toArray().map((e) => parseInt(e.value, 10)).sort().pop(),
html = $(data.replace(/widgets([\[_])0([\]_])/g, `widgets$1${i}$2`))
Expand Down
Loading