Signup-to-release recording on the landing page and Quickstart !465
merged
merged by cmc on 2026-09-23 23:26 UTC
· krz/gitbay:demo-recording into main
Conversation
Commits 5
Files changed 20
20 files changed, +186 −18
Layout: unified · split
.gitbay/wiki/Quickstart.org
+4
@@ -17,3 +17,7 @@
17 17 4. =gitbay issue create --title "first"= from inside the clone
18 18
19 19 Everything works from stock OpenSSH too: =ssh git@gitbay.org help= .
20
21 #+CAPTION : Signup to release from a terminal. The tapes are in =docs/demo/=.
22 #+ATTR_HTML : :alt A terminal registering an SSH key, verifying the mailed code, creating a repository, pushing, filing an issue, merging a request that closes it, and publishing a release.
23 [[https://gitbay.org/static/img/demo.gif ]]
docs/demo/.gitignore
added
+1
docs/demo/common.tape
added
+14
@@ -0,0 +1,14 @@
1 Set S h e l l b a sh
2 Set FontFamily "Atkinson Hyperlegible Mono"
3 Set FontSize 20
4 Set Width 1280
5 Set Height 720
6 Set Padding 32
7 Set TypingSpeed 35ms
8 Set WaitPattern /\$$/
9 Set Theme { "name": "gitbay", "black": "#000000", "red": "#ff6b6b", "green": "#5fd38d", "yellow": "#ffb454", "blue": "#6cb6ff", "magenta": "#d2a8ff", "cyan": "#76e3ea", "white": "#e6e6e6", "brightBlack": "#6e7681", "brightRed": "#ff8e8e", "brightGreen": "#7ee2a8", "brightYellow": "#ffc777", "brightBlue": "#8cc8ff", "brightMagenta": "#e2c5ff", "brightCyan": "#a5f0f5", "brightWhite": "#ffffff", "background": "#0b0b0b", "foreground": "#e6e6e6", "selection": "#333333", "cursor": "#ff8c1a" }
10
11 Hide
12 Type "source docs/demo/env.sh && clear" Enter
13 Sleep 1 s
14 Show
docs/demo/env.sh
added
+25
@@ -0,0 +1,25 @@
1 # Sourced by the tapes. Gives the recording a home of its own so the
2 # demo account's key, known_hosts, git identity and CLI profile never
3 # touch the operator's. OpenSSH reads ~ from the password database, not
4 # $HOME, so ssh is wrapped to pass -F explicitly. The home stays short:
5 # the CLI's ControlPath lives under it and a Unix socket path caps at 104.
6 DEMO_HOME = ${ DEMO_HOME :- $HOME /.gitbay-demo }
7 mkdir -p " $DEMO_HOME /.ssh " " $DEMO_HOME /bin " " $DEMO_HOME /.config/gitbay " " $DEMO_HOME /work "
8 chmod 700 " $DEMO_HOME /.ssh "
9 [ -f " $DEMO_HOME /.ssh/id_ed25519 " ] ||
10 ssh-keygen -q -t ed25519 -f " $DEMO_HOME /.ssh/id_ed25519 " -N '' -C zerocool
11 [ -s " $DEMO_HOME /.ssh/known_hosts " ] ||
12 ssh-keygen -F gitbay.org | grep -v '^#' > " $DEMO_HOME /.ssh/known_hosts "
13 printf 'Host *\n IdentityFile %s\n IdentitiesOnly yes\n IdentityAgent none\n UserKnownHostsFile %s\n' \
14 " $DEMO_HOME /.ssh/id_ed25519 " " $DEMO_HOME /.ssh/known_hosts " > " $DEMO_HOME /.ssh/config "
15 printf '#!/bin/sh\nexec /usr/bin/ssh -F "%s" "$@"\n' " $DEMO_HOME /.ssh/config " > " $DEMO_HOME /bin/ssh "
16 chmod +x " $DEMO_HOME /bin/ssh "
17 printf '[user]\n\tname = Zero Cool\n\temail = zerocool@gitbay.org\n[init]\n\tdefaultBranch = main\n' \
18 > " $DEMO_HOME /.gitconfig "
19 printf 'default = "gitbay"\n\n[instances.gitbay]\nhost = "gitbay.org"\n' \
20 > " $DEMO_HOME /.config/gitbay/config.toml "
21
22 export HOME = $DEMO_HOME PATH = $DEMO_HOME /bin:$PATH EDITOR = true
23 unset SSH_AUTH_SOCK XDG_CONFIG_HOME
24 PS1 = '\[\e[38;5;208m\]\W\[\e[0m\] $ '
25 cd " $DEMO_HOME /work "
docs/demo/render.sh
added
+30
@@ -0,0 +1,30 @@
1 #!/bin/sh
2 # Render tapes to frames with vhs and encode them here: vhs 0.12's own
3 # ffmpeg step writes nothing under ffmpeg 9. Several tapes are joined in
4 # order. A tape already recorded is reused: the tapes write to gitbay.org,
5 # so delete docs/demo/out/<name> to record one again. Writes
6 # docs/demo/out/demo.{mp4,webm,gif}.
7 #
8 # DEMO_CODE=<mailed code> docs/demo/render.sh signup verify ship
9 set -eu
10 cd " $( dirname " $0 " ) /../.. "
11 out = docs/demo/out
12 bg = 0b0b0b
13 list = $out /concat.txt
14 mkdir -p " $out "
15 : > " $list "
16 for name in " $@ " ; do
17 [ -d " $out / $name " ] || vhs " docs/demo/ $name .tape "
18 ffmpeg -loglevel error -y \
19 -framerate 50 -i " $out / $name /frame-text-%05d.png " \
20 -framerate 50 -i " $out / $name /frame-cursor-%05d.png " \
21 -filter_complex " [0][1]overlay,pad=1248:688:32:32:color= $bg " \
22 -c:v libx264 -pix_fmt yuv420p -r 25 " $out / $name .mp4 "
23 echo " file ' $name .mp4' " >> " $list "
24 done
25 ffmpeg -loglevel error -y -f concat -safe 0 -i " $list " -c copy " $out /demo.mp4 "
26 ffmpeg -loglevel error -y -i " $out /demo.mp4 " -c:v libvpx-vp9 -b:v 0 -crf 40 " $out /demo.webm "
27 ffmpeg -loglevel error -y -i " $out /demo.mp4 " \
28 -vf "fps=10,scale=960:-1:flags=lanczos,split[a][b];[a]palettegen=max_colors=32[p];[b][p]paletteuse=dither=none" \
29 " $out /demo.gif "
30 ls -l " $out " /demo.*
docs/demo/ship.tape
added
+52
@@ -0,0 +1,52 @@
1 # Part three: repository, issue, branch, merge request, merge, release,
2 # as the account verify.tape activated.
3 Output docs/demo/out/ship/
4 S o u r c e d o c s/ d e m o / c o m m o n . t a p e
5
6 Type "gitbay repo create zerocool/hello" Enter
7 Wait
8 Sleep 1.2s
9 Type "git clone -q ssh://git@gitbay.org/zerocool/hello.git && cd hello" Enter
10 Wait
11 Sleep 1.2s
12 Type "echo 'hello, world' > README && git add README && git commit -qm 'Say hello'" Enter
13 Wait
14 Sleep 1.2s
15 Type "git push -q origin main" Enter
16 Wait
17 Sleep 1.2s
18 Ctrl+L
19 Sleep 400ms
20
21 Type "gitbay issue create --title 'Say hello in French' --body 'Bonjour.'" Enter
22 Wait
23 Sleep 1.2s
24 Type "git switch -qc fr && echo 'bonjour, le monde' >> README" Enter
25 Wait
26 Sleep 1.2s
27 Type "git commit -qam 'Say bonjour' && git push -q origin fr" Enter
28 Wait
29 Sleep 1.2s
30 Type "gitbay mr create --source fr --target main --title Bonjour --body 'Closes #1'" Enter
31 Wait
32 Sleep 1.2s
33 Type "gitbay mr merge 1 --strategy squash" Enter
34 Wait
35 Sleep 1.2s
36 Type "gitbay issue list --state closed" Enter
37 Wait
38 Sleep 1.2s
39 Ctrl+L
40 Sleep 400ms
41
42 Type "# ship it" Enter
43 Sleep 600ms
44 Type "git switch -q main && git pull -q && git tag v0.1.0 && git push -q origin v0.1.0" Enter
45 Wait
46 Sleep 1.2s
47 Type "gitbay release create v0.1.0 --notes 'Hello in two languages.'" Enter
48 Wait
49 Sleep 1.2s
50 Type "gitbay release list" Enter
51 W a i t
52 Sleep 1 . 2 s
docs/demo/signup.tape
added
+11
@@ -0,0 +1,11 @@
1 # Part one: an SSH key is the account. Registration mails a code, so the
2 # recording stops here; ship.tape picks up with the code.
3 Output docs/demo/out/signup/
4 S o u r c e d o c s/ d e m o / c o m m o n . t a p e
5
6 Type "# no account yet, just an SSH key" Enter
7 Sleep 800ms
8 Type "ssh git@gitbay.org whoami" Enter
9 Sleep 2.5s
10 Type "ssh git@gitbay.org register --username zerocool --email zerocool@gitbay.org" Enter
11 Sleep 4 s
docs/demo/verify.tape
added
+13
@@ -0,0 +1,13 @@
1 # Part two: the code signup.tape's register mailed, as DEMO_CODE. A code
2 # works once, so a rerun needs a fresh account.
3 Output docs/demo/out/verify/
4 S o u r c e d o c s/ d e m o / c o m m o n . t a p e
5
6 Hide
7 Type "code=$DEMO_CODE && clear" Enter
8 Sleep 500ms
9 Show
10 Type "# the code from the mail" Enter
11 Sleep 600ms
12 Type "ssh git@gitbay.org email verify $code" Enter
13 Sleep 2 s
e2e/design_test.go
+2 −2
@@ -189,8 +189,8 @@ func TestLandingRoutes(t *testing.T) {
189 189 ` class="button primary" href="/explore">Explore repositories</a> ` ,
190 190 ` class="button btn" href="/register">Create an account</a> ` ,
191 191 "web login</code>" ,
192 "/static/img/mr-dark.png" ,
193 "<picture>" ,
192 "/static/img/demo.webm" ,
193 "<video autoplay muted loop playsinline controls" ,
194 194 } {
195 195 if ! strings . Contains ( body , want ) {
196 196 t . Errorf ( "landing lacks %q" , want )
internal/httpd/fonts_test.go
+13 −3
@@ -47,7 +47,7 @@ func TestStylesheetFontsAreServed(t *testing.T) {
47 47 }
48 48
49 49 // TestLandingImagesAreServed: every file under static/img has a route
50 // that answers 200 with an image type and the stylesheet's cache policy.
50 // that answers 200 with an image or video type, and a video answers Range.
51 51 func TestLandingImagesAreServed ( t * testing . T ) {
52 52 s := New ( config . Default ( ) , nil )
53 53 byPattern := map [ string ] http . HandlerFunc { }
@@ -72,8 +72,18 @@ func TestLandingImagesAreServed(t *testing.T) {
72 72 }
73 73 rec := httptest . NewRecorder ( )
74 74 h ( rec , httptest . NewRequest ( "GET" , u , nil ) )
75 if rec . Code != http . StatusOK || ! strings . HasPrefix ( rec . Header ( ) . Get ( "Content-Type" ) , "image/" ) {
76 t . Errorf ( "%s: %d %s" , e . Name ( ) , rec . Code , rec . Header ( ) . Get ( "Content-Type" ) )
75 ct := rec . Header ( ) . Get ( "Content-Type" )
76 if rec . Code != http . StatusOK || ! ( strings . HasPrefix ( ct , "image/" ) || strings . HasPrefix ( ct , "video/" ) ) {
77 t . Errorf ( "%s: %d %q" , e . Name ( ) , rec . Code , ct )
78 }
79 if strings . HasPrefix ( ct , "video/" ) {
80 rec := httptest . NewRecorder ( )
81 req := httptest . NewRequest ( "GET" , u , nil )
82 req . Header . Set ( "Range" , "bytes=0-1" )
83 h ( rec , req )
84 if rec . Code != http . StatusPartialContent || rec . Body . Len ( ) != 2 {
85 t . Errorf ( "%s: Range answered %d with %d bytes" , e . Name ( ) , rec . Code , rec . Body . Len ( ) )
86 }
77 87 }
78 88 }
79 89 }
internal/httpd/web.go
+12 −4
@@ -117,16 +117,24 @@ func (s *Server) font(w http.ResponseWriter, r *http.Request) {
117 117 w . Write ( data )
118 118 }
119 119
120 // image serves the embedded landing pictures with the font cache policy.
120 var staticTypes = map [ string ] string {
121 ".gif" : "image/gif" ,
122 ".webm" : "video/webm" ,
123 ".mp4" : "video/mp4" ,
124 }
125
126 // image serves the embedded landing recording with the font cache policy.
127 // ServeContent answers Range, which Safari needs to play video.
121 128 func ( s * Server ) image ( w http . ResponseWriter , r * http . Request ) {
122 data , err := web . ImageFS . ReadFile ( "static" + r . URL . Path [ len ( "/static" ) : ] )
129 name := "static" + r . URL . Path [ len ( "/static" ) : ]
130 data , err := web . ImageFS . ReadFile ( name )
123 131 if err != nil {
124 132 http . NotFound ( w , r )
125 133 return
126 134 }
127 w . Header ( ) . Set ( "Content-Type" , "image/png" )
135 w . Header ( ) . Set ( "Content-Type" , staticTypes [ path . Ext ( name ) ] )
128 136 w . Header ( ) . Set ( "Cache-Control" , "public, max-age=604800, immutable" )
129 w . Write ( data )
137 http . ServeContent ( w , r , name , time . Time { } , bytes . NewReader ( data ) )
130 138 }
131 139
132 140 // notFound renders the designed 404 page with a 404 status. Falls back to
internal/web/static/img/demo.gif
added
Binary file not shown.
internal/web/static/img/demo.mp4
added
Binary file not shown.
internal/web/static/img/demo.webm
added
Binary file not shown.
internal/web/static/img/mr-dark.png
deleted
Binary file not shown.
internal/web/static/img/mr-light.png
deleted
Binary file not shown.
internal/web/static/style.css
+1 −1
@@ -1703,7 +1703,7 @@ details.refmenu .refdrop a.allrefs {
1703 1703 . lede { font-size : var ( - - fs -4 ) ; color : var ( - - muted ) ; line-height : 1.5 ; max-width : 40 rem ; margin : 0 0 var ( - - sp -5 ) ; }
1704 1704 pre . quickstart { margin : 0 0 var ( - - sp -4 ) ; }
1705 1705 . shot { border : 1 px solid var ( - - line ) ; border-radius : var ( - - r - card ) ; overflow : hidden ; margin : var ( - - sp -5 ) 0 ; }
1706 . shot img { display : block ; width : 100 % ; height : auto ; max-height : 420 px ; object-fit : cover ; object-position : top ; }
1706 . shot video { display : block ; width : 100 % ; height : auto ; }
1707 1707 . shot figcaption {
1708 1708 border-top : 1 px solid var ( - - line ) ;
1709 1709 background : var ( - - surface ) ;
internal/web/templates/landing.html
+6 −6
@@ -4,14 +4,14 @@
4 4 < div class = "landing" >
5 5 < h1 > {{template "mark"}}{{.Site}}< / h1 >
6 6 < p class = "lede" > A git forge you drive from the terminal. Repositories, issues, merge requests and CI over SSH, with a fast, readable web view of the same state.< / p >
7 < pre class = "quickstart" tabindex = "0" > ssh git@{{.Host}} help # every command, no client to install
7 < pre class = "quickstart" tabindex = "0" > ssh git@{{.Host}} help
8 8 git clone ssh://git@{{.Host}}/owner/repo.git< / pre >
9 9 < div class = "routes" > < a class = "button primary" href = "/explore" > Explore repositories< / a > {{if .Signup}}< a class = "button btn" href = "/register" > Create an account< / a > {{end}}< / div >
10 < figure class = "shot" > < picture >
11 < source srcset = "/static/img/mr-dark.png" media = "(prefers-color-scheme: dark)" >
12 < img src = "/static/img/mr-light.png" width = "1280" height = "900" alt = "A merged merge request: the description on the left, and on the right two CI checks reporting success with how long each ran, an approval, and the source and target branches." >
13 < / picture >
14 < figcaption > A merged merge request on this instance. The description is on the left; the column beside it carries the CI checks, the approval that let it merge, and the branches it went between.< / figcaption >
10 < figure class = "shot" > < video autoplay muted loop playsinline controls width = "1248" height = "688" aria-describedby = "demo-caption" >
11 < source src = "/static/img/demo.webm" type = "video/webm" >
12 < source src = "/static/img/demo.mp4" type = "video/mp4" >
13 < / video >
14 < figcaption id = "demo-caption" > Signup to release on gitbay.org from a terminal: register an SSH key, verify the mailed code, create a repository and push, file an issue, open and merge a request that closes it, then tag and publish a release.< / figcaption >
15 15 < / figure >
16 16 < div class = "facets" >
17 17 < section > < h2 > Read< / h2 > < p > Browse and clone any public repository over HTTPS or < code > git://< / code > , no account. Every commit shows whether its signature verified.< / p > < / section >
internal/web/templates/layout.html
+1 −1
@@ -84,7 +84,7 @@
84 84 < / main >
85 85
86 86 < footer >
87 < 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://gitbay.org/krz/gitbay-ios" > iOS app< / a > · < a href = "/privacy" > Privacy< / a > < / p >
87 < 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 >
88 88 < / footer >
89 89 < / div >
90 90 < / div >
internal/web/web.go
+1 −1
@@ -29,7 +29,7 @@ var FaviconSVG []byte
29 29 //go:embed static/fonts/*.woff2
30 30 var FontFS embed . FS
31 31
32 //go:embed static/img/*.png
32 //go:embed static/img/*.gif static/img/*.webm static/img/*.mp4
33 33 var ImageFS embed . FS
34 34
35 35 // version returns the short VCS revision baked into the binary, or "" when
Checks
success
ci/build success 2026-09-23 23:22 UTC · 22s
ci/test success 2026-09-23 23:25 UTC · 3m23s
Revisions
1. e4ba4be971 2026-09-23 23:20 UTC
2. 6c4d1e1454 2026-09-23 23:21 UTC · compare to previous
Source and target
krz/gitbay:demo-recording into main
merged at 6c4d1e1454 · base 9eb3eb0b80 · branch deleted