Commit ec2ae93416

ec2ae93416df10c3876972f8b9d494ba932dac80

parent: aaf2234b85

Verified · cmc ci/build: success ci/test: success

cmc <hello@cleberg.net> · 2026-10-02 15:15 UTC

web: footer links to the operator's abuse and terms pages

[web] abuse_url and terms_url, empty by default, add "Report abuse"
and "Terms" to every page's footer. Wiki pages Abuse and Terms are
gitbay.org's.

Closes #323
Closes #324

Layout: unified · split

.gitbay/wiki/Abuse.org added +21
@@ -0,0 +1,21 @@
1#+title: Reporting abuse
2
3This page is for gitbay.org. Another instance links its own page from
4its footer, or none.
5
6Mail =abuse@gitbay.org= with:
7
8- the URL of the repository, issue, merge request, comment or snippet;
9- what is wrong with it: malware, spam, phishing, a copyright or
10 licence claim, illegal content, harassment, or anything else;
11- if you are the rights holder or the person affected, say so.
12
13You do not need an account. One person runs the instance and reads the
14mail. Depending on the report, the content is removed, the repository
15is archived or made private, or the account is disabled
16(=admin user disable=, which refuses SSH, web and API access without
17deleting anything). The account holder is told what was done and why,
18unless that would help them continue.
19
20A security problem in gitbay itself can go to the same address; do not
21file it as a public issue.
.gitbay/wiki/Admin.org +3
@@ -115,6 +115,9 @@ build page's live log arrives only when the build ends;
115115 gitbay itself.
116116- =privacy_notice= — operator text shown on =/privacy= under the fixed
117117 statement.
118- =abuse_url=, =terms_url= (empty) — linked from every page's footer as
119 "Report abuse" and "Terms"; no link when empty. gitbay.org points them
120 at the [[Abuse][Abuse]] and [[Terms][Terms]] wiki pages.
118121- =apple_app_ids= (empty) — iOS app IDs (=TEAMID.bundle.id=) allowed to
119122 open this instance's links, served at
120123 =/.well-known/apple-app-site-association=. Empty leaves the path a
.gitbay/wiki/Terms.org added +51
@@ -0,0 +1,51 @@
1#+title: Terms
2
3These are the terms for gitbay.org, kept informal on purpose. Another
4instance sets its own.
5
6* What this is
7
8One person runs gitbay.org on one server in Nuremberg, Germany. It is
9free, comes with no warranty, and makes no uptime promise. Repositories
10are backed up nightly off the host, but keep your own clone of anything
11that matters.
12
13* What is not allowed
14
15- malware, phishing kits, or anything built to attack other systems
16 (security research and proof-of-concept code with a clear purpose are
17 fine);
18- spam, including accounts or repositories that exist to carry links;
19- content that is illegal in Germany, or that infringes someone's
20 copyright;
21- harassment of other users;
22- using CI, storage or bandwidth as general-purpose hosting: crypto
23 mining, file dumps, or mirrors of large binaries unrelated to a
24 project.
25
26* Limits
27
28Each account may own 250 repositories and 5 GB. An organization has
29the same caps, and an account may create 5 organizations. Ask if you
30need more.
31
32* What the operator may do
33
34Remove content, archive a repository or make it private, or disable an
35account, when it breaks the rules above or puts the instance at risk.
36Reports go to the address on the [[Abuse][abuse page]].
37
38* Leaving
39
40=account export= writes your profile, repositories, issues and merge
41requests as a bundle another gitbay instance can replay
42(=gitbay migrate=), and every repository can be cloned. To have
43the account deleted, mail the abuse address from the account's primary
44email.
45
46If gitbay.org ever shuts down, every account with a verified address
47gets notice and time to export first.
48
49* Privacy
50
51See https://gitbay.org/privacy.
internal/config/config.go +4
@@ -130,6 +130,10 @@ type Web struct {
130130 // PrivacyNotice is operator-provided text shown on /privacy under the
131131 // fixed project-level statement. Plain text; blank paragraphs split.
132132 PrivacyNotice string `toml:"privacy_notice"`
133 // AbuseURL and TermsURL, when set, are linked from every page's
134 // footer: where to report abuse, and the instance's terms of use.
135 AbuseURL string `toml:"abuse_url"`
136 TermsURL string `toml:"terms_url"`
133137 // AppleAppIDs are the iOS apps (TEAMID.bundle.id) that may open this
134138 // instance's links, served in /.well-known/apple-app-site-association.
135139 // Empty leaves the route unregistered.
internal/httpd/accounts.go +3 −3
@@ -90,7 +90,7 @@ func (s *Server) renderLogin(w http.ResponseWriter, errMsg string, sent bool, ne
9090 EmailLogin bool
9191 Sent bool
9292 Next string
93 }{basePage{Site: s.siteName(), Host: s.cfg.SiteHost()},
93 }{s.anonBase(),
9494 s.cfg.Registration.Mode, errMsg, s.emailLoginEnabled(), sent, next})
9595}
9696
@@ -399,7 +399,7 @@ func (s *Server) renderSignup(w http.ResponseWriter, errMsg, username string) {
399399 Mode string // open | invite
400400 Error string
401401 Username string
402 }{basePage{Site: s.siteName(), Host: s.cfg.SiteHost()}, s.cfg.SiteHost(), s.cfg.Registration.Mode, errMsg, username})
402 }{s.anonBase(), s.cfg.SiteHost(), s.cfg.Registration.Mode, errMsg, username})
403403}
404404
405405func (s *Server) signupSubmit(w http.ResponseWriter, r *http.Request) {
@@ -421,7 +421,7 @@ func (s *Server) signupSubmit(w http.ResponseWriter, r *http.Request) {
421421 Username string
422422 Message string
423423 Host string
424 }{basePage{Site: s.siteName(), Host: s.cfg.SiteHost()}, username, msg, s.cfg.SiteHost()})
424 }{s.anonBase(), username, msg, s.cfg.SiteHost()})
425425}
426426
427427// issueNewPage is what the new-issue form renders with, whether that is a
internal/httpd/footer_test.go added +26
@@ -0,0 +1,26 @@
1package httpd
2
3import (
4 "strings"
5 "testing"
6)
7
8// The footer links the operator's terms and abuse pages only when
9// configured, on a page with no viewer and on the 404 page.
10func TestFooterOperatorLinks(t *testing.T) {
11 s, _, _ := newTokenTestServer(t)
12 for _, path := range []string{"/privacy", "/no-such-owner"} {
13 if body := get(t, s.Handler(), path, nil).Body.String(); strings.Contains(body, "Report abuse") || strings.Contains(body, ">Terms<") {
14 t.Errorf("%s: footer links with nothing configured", path)
15 }
16 }
17 s.cfg.Web.AbuseURL = "https://example.test/wiki/Abuse"
18 s.cfg.Web.TermsURL = "https://example.test/wiki/Terms"
19 for _, path := range []string{"/privacy", "/no-such-owner"} {
20 body := get(t, s.Handler(), path, nil).Body.String()
21 if !strings.Contains(body, `<a href="https://example.test/wiki/Abuse">Report abuse</a>`) ||
22 !strings.Contains(body, `<a href="https://example.test/wiki/Terms">Terms</a>`) {
23 t.Errorf("%s: footer lacks the configured links", path)
24 }
25 }
26}
internal/httpd/page.go +11 −2
@@ -26,13 +26,22 @@ type basePage struct {
2626 // Theme is stamped on <html> as data-theme: light or dark when the
2727 // viewer chose one, empty when the browser's own scheme decides.
2828 Theme string
29 // AbuseURL and TermsURL are the operator's footer links, empty when
30 // not configured.
31 AbuseURL string
32 TermsURL string
33}
34
35// anonBase is the layout-wide data with no viewer.
36func (s *Server) anonBase() basePage {
37 return basePage{Site: s.siteName(), Host: s.cfg.SiteHost(), AbuseURL: s.cfg.Web.AbuseURL, TermsURL: s.cfg.Web.TermsURL}
2938}
3039
3140// base builds the layout-wide data for a request that has not already
3241// resolved a viewer.
3342func (s *Server) base(r *http.Request) basePage {
3443 if s.cfg.Web.Mode != "accounts" {
35 return basePage{Site: s.siteName(), Host: s.cfg.SiteHost()}
44 return s.anonBase()
3645 }
3746 return s.baseFor(s.viewer(r))
3847}
@@ -40,7 +49,7 @@ func (s *Server) base(r *http.Request) basePage {
4049// baseFor is base for a handler that already holds the viewer, so the
4150// session lookup is not repeated.
4251func (s *Server) baseFor(viewer store.User) basePage {
43 b := basePage{Site: s.siteName(), Host: s.cfg.SiteHost()}
52 b := s.anonBase()
4453 if viewer.ID == 0 {
4554 return b
4655 }
internal/httpd/web.go +1 −1
@@ -198,7 +198,7 @@ func (s *Server) index(w http.ResponseWriter, r *http.Request) {
198198 Accounts bool
199199 Signup bool
200200 EmailLogin bool
201 }{basePage{Site: s.siteName(), Host: s.cfg.SiteHost()}, host, s.cfg.Web.Mode == "accounts",
201 }{s.anonBase(), host, s.cfg.Web.Mode == "accounts",
202202 s.cfg.Web.Mode == "accounts" && s.cfg.Registration.Mode != "closed",
203203 s.emailLoginEnabled()})
204204}
internal/web/templates/layout.html +1 −1
@@ -88,7 +88,7 @@
8888</main>
8989
9090<footer>
91 <p>Powered by <a href="https://gitbay.org/krz/gitbay">gitbay</a>{{with gitbayVersion}} · <code><a href="https://gitbay.org/krz/gitbay/commit/{{gitbayCommit}}">{{.}}</a></code>{{end}} · <a href="https://apps.apple.com/us/app/gitbay/id6806399095">iOS app</a> · <a href="/privacy">Privacy</a></p>
91 <p>Powered by <a href="https://gitbay.org/krz/gitbay">gitbay</a>{{with gitbayVersion}} · <code><a href="https://gitbay.org/krz/gitbay/commit/{{gitbayCommit}}">{{.}}</a></code>{{end}} · <a href="https://apps.apple.com/us/app/gitbay/id6806399095">iOS app</a> · <a href="/privacy">Privacy</a>{{with field . "TermsURL"}} · <a href="{{.}}">Terms</a>{{end}}{{with field . "AbuseURL"}} · <a href="{{.}}">Report abuse</a>{{end}}</p>
9292</footer>
9393</div>
9494</div>