.gitbay/wiki/Users.org

419f6dfdc5489a0c6374e36dd1ebbfca68040056
gitbay/.gitbay/wiki/Users.org rendered · source · history · blame · raw

776 lines · 36431 bytes

  1#+title: gitbay user guide
  2
  3Everything here works from stock OpenSSH — replace =gitbay= with
  4=ssh git@<host>= in any command. The CLI adds convenience (instance
  5profiles, repo inference, =$EDITOR=), nothing more. Two things differ
  6over bare ssh:
  7
  8- ssh joins its arguments with spaces and the server tokenizes the
  9  result, so a value with a space in it needs a second layer of quotes:
 10  =--description "'a widget'"=, not =--description "a widget"=, which
 11  arrives as two arguments.
 12- =--help= is not understood. =ssh git@<host> help= lists every command
 13  the server knows, and =help <noun>= (=help repo=, =help mr=) prints
 14  the usage of each command under that noun.
 15
 16* Installing the CLI
 17
 18#+begin_src sh
 19go install gitbay.org/gitbay/cmd/gitbay@latest   # any platform with Go
 20
 21brew tap krz/tap https://gitbay.org/krz/homebrew-tap.git
 22brew install krz/tap/gitbay                      # Homebrew (macOS/Linux)
 23#+end_src
 24
 25Or build from source: =go build ./cmd/gitbay= in a clone of
 26=https://gitbay.org/krz/gitbay.git=.
 27
 28* Getting an account
 29
 30How you join depends on the instance's registration mode. On instances
 31with web accounts enabled, =/register= offers the same signup as a
 32browser form (paste your SSH public key); everything below works from
 33the terminal alone:
 34
 35- closed :: an admin creates your account on the host and registers your
 36  first SSH key. Nothing for you to do but hand over your public key.
 37- invite :: you receive a single-use code by email. With the SSH key you
 38  want to use:
 39  #+begin_src sh
 40  ssh git@<host> register --username you --invite <code>
 41  #+end_src
 42  Your account is active immediately; the invited address is your
 43  verified email.
 44- open ::
 45  #+begin_src sh
 46  ssh git@<host> register --username you --email you@example.org
 47  #+end_src
 48  A verification code arrives by mail. Until you run
 49  =ssh git@<host> email verify <code>=, the account is pending: you can
 50  run =whoami= and the email commands, and nothing else — no git, no
 51  repos.
 52
 53* SSH keys
 54
 55New to SSH keys? [[SSH-keys]] makes one in two commands.
 56
 57Your key is your identity; there are no passwords anywhere. The SSH
 58username is always =git= — the key alone determines who you are.
 59
 60#+begin_src sh
 61gitbay auth keys list
 62gitbay auth keys add --scope git < ~/.ssh/ci_key.pub   # key on stdin
 63gitbay auth keys add --label laptop < ~/.ssh/id_ed25519.pub
 64gitbay auth keys label SHA256:... "work laptop"
 65gitbay auth keys remove SHA256:...
 66#+end_src
 67
 68A key's label is the comment on its =authorized_keys= line unless
 69=--label= gives one; =keys label= renames a key, and with no text
 70clears the name. Labels are one line of up to 64 bytes.
 71
 72Scopes: =full= (default; git plus every control command), =git= (git
 73transport only — right for automation keys, which then cannot touch
 74issues, settings, or your account), or =runner= (the CI runner's
 75protocol plus read-only git, for the key a =gitbay-runner= host holds;
 76see [[Admin]]).
 77
 78A key belongs to exactly one account instance-wide. Registering a key
 79someone else already holds is refused without telling you whose it is.
 80
 81* Verified commits
 82
 83The commit badge is driven by the *author* email and the signing key:
 84=verified= means the signature is valid, the key is registered to an
 85account, and the author email is a verified address on that account.
 86
 87For OpenPGP signing (git's default):
 88#+begin_src sh
 89gpg --armor --export you@example.org | gitbay auth pgp add
 90#+end_src
 91
 92For SSH signing (=git config gpg.format ssh=): sign with any key
 93registered on your account; your verified addresses act as the principal
 94set. No separate registration step.
 95
 96Add and verify additional addresses with =email add <address>= /
 97=email verify <code>= (requires the instance to have SMTP; otherwise an
 98admin can assert an address for you). =email list= shows them. =email
 99remove <address>= drops one, with two refusals: the primary stays until
100=email primary <address>= names another verified address, and the last
101verified address stays, since activation, login links and commit
102identity all resolve through verified addresses. Removing a verified
103address re-evaluates signature states, so a commit authored from it
104shows as =signed_email_mismatch= afterwards. A removed address is free
105for any account to claim.
106
107The states you will see, in decreasing order of trust: =verified=,
108=signed_unknown_key= (valid signature, key not registered here — register
109it and history upgrades retroactively), =signed_email_mismatch= (real
110key, author line claims someone else), =signed_key_expired= /
111=signed_key_revoked=, =bad_signature=, =unsigned=. Server-created commits
112(web edits, merge commits) are always =unsigned= — the server holds no
113signing key on principle.
114
115=repo bookmark <owner/name>= saves a repository to come back to and
116=repo unbookmark= drops it; =repo bookmarks= lists yours, each with how
117many people have bookmarked it. The web has the control on the
118repository header, the count in the facts bar, and the list at
119=/bookmarks=. Bookmarks are public and counted; pins are private and
120drive the rail. Read access is all a bookmark needs.
121
122A job's result is a property of the commit's *tree*, not its sha: a
123rebase onto a base that touched nothing the branch did gives every
124commit a new sha and the same tree, and a job that already passed for
125that tree is not run again — the new commit gets the earlier result as
126its =ci/<job>= status, naming the build it came from. Scheduled and tag
127jobs are never reused this way; their trigger is the clock or the tag.
128
129* Profiles
130
131A profile is what =/{owner}= shows: a one-line description, a website, a
132set of links, long-form about text, the repositories you can see, org
133membership, and a year of activity. Users and orgs have the same fields.
134Repository rows carry the listing metadata too — topics, license,
135default branch, last commit — so a client renders a profile listing the
136way =/{owner}= does. The web dispatches this command rather than
137assembling the page itself.
138
139#+begin_src sh
140gitbay profile show               # your own
141gitbay profile show alice
142gitbay profile set --description "builds small tools" --website https://alice.example
143#+end_src
144
145The about text is not a field. It is =profile/README.md= or
146=profile/README.org= on the default branch of =<owner>/.gitbay=, a
147repository you own like any other, written by a push or by
148=repo commit-file=:
149
150#+begin_src sh
151gitbay repo create alice/.gitbay
152gitbay repo commit-file alice/.gitbay profile/README.org \
153       --ref main --file - < about.org
154#+end_src
155
156The extension picks the renderer; =.md=, =.org= and =.markdown= are
157resolved in that order, so a =README.md= beside a =README.org= wins.
158=profile show= reports the text as =about=, its format as
159=about_format=, and the file it came from as =about_path=.
160
161Access follows the repository: a private =.gitbay= keeps the about text
162to you and the admins. A repository whose name starts with a dot is
163infrastructure rather than a project, so it stays out of =explore= and
164off the profile's repository list — =repo list= still shows it, and it
165is reachable at its own URL.
166
167Up to five links, each =label|url= or a bare url, http(s) only. Passing
168=--link= replaces the whole set; a single empty one clears it:
169
170#+begin_src sh
171gitbay profile set --link "Mastodon|https://fosstodon.example/@alice" \
172                   --link https://alice.example/now
173gitbay profile set --link ""      # clear
174#+end_src
175
176Every field follows the same rule as the rest of the CLI: a flag you
177leave out is untouched, and ='' clears the one you name. Org profiles
178work the same way and need org admin:
179
180#+begin_src sh
181gitbay org profile krz --description "software and experiments"
182gitbay org profile krz            # no flags shows it
183#+end_src
184
185An org's about text works the same way, in =<org>/.gitbay=.
186
187The settings page edits description, website and links, and points at the
188about file — with a button that creates =<owner>/.gitbay= and its first
189README when you have none, so the file editor has a branch to open. The
190JSON API runs =profile set= like any other write command.
191
192* Repositories
193
194#+begin_src sh
195gitbay repo create you/project [--private]
196gitbay repo clone you/project
197gitbay repo list
198gitbay repo show you/project
199gitbay repo log you/project --limit 20      # commits with signature states
200gitbay repo fork other/project [--name mine]
201gitbay repo rename you/project tool          # clone URLs change
202gitbay repo delete you/project --yes
203#+end_src
204
205=repo show= also reports your own state on the repository — =watch=
206(=watching= or =muted=; =repo unwatch= clears either) and =bookmarked= —
207and =fork_of= when it is a
208fork whose parent you can read, so a client draws a toggle rather than
209two blind buttons. Absent means none.
210
211Pushing is SSH-only. Public repositories are anonymously readable over
212HTTPS (and =git://= where enabled); private repositories exist only over
213SSH and answer "not found" to everyone without access.
214
215Access and settings (owner or =admin= grant):
216#+begin_src sh
217gitbay repo access grant you/project alice write    # read | write | admin
218gitbay repo access revoke you/project alice
219gitbay repo access list you/project                 # everyone who can reach it, role, and via what
220gitbay repo settings protect you/project main       # no force-push, no delete
221gitbay repo settings require-mr you/project on      # protected branches: merge requests only
222gitbay repo settings protect-tag you/project 'v*'   # matching tags: created once, never moved or deleted
223gitbay repo settings default-branch you/project trunk # HEAD, and what the web shows
224gitbay repo settings require-signed you/project on  # every commit must verify
225gitbay repo settings git-daemon you/project on      # expose over git://
226gitbay repo topics add you/project cli forge        # free-form tags, shown on the web
227gitbay repo search forge                            # find repos by name/description/topic
228gitbay repo grep you/project "some string"          # literal git grep over the default branch
229gitbay repo pin you/project                         # pin to your web dashboard
230gitbay repo unpin you/project
231gitbay repo archive you/project                     # read-only: pushes and issue/MR
232gitbay repo unarchive you/project                   #   writes refused, browsing intact
233#+end_src
234
235Import from another forge — git data first, then optionally the issue
236and PR history from GitHub or any Forgejo instance such as Codeberg
237(issues keep state/labels/comments; PRs land as closed or merged MRs
238with their discussion; originals are attributed inline since foreign
239authors have no local account; re-running resumes where it stopped):
240#+begin_src sh
241gitbay repo import you/mirror --from https://github.com/you/repo.git \
242    [--private] [--token-stdin]        # token on stdin, never in the URL
243gitbay repo import-issues you/mirror --from you/repo --token-stdin
244gitbay repo import-issues you/mirror --from you/repo \
245    --api-base https://codeberg.org/api/v1    # Forgejo: the site's /api/v1
246#+end_src
247
248Sourcehut has no API of that shape; =repo import= takes its git data
249and the todo.sr.ht tracker is not read.
250
251Moving between gitbay instances (no lock-in): run on the TARGET, with
252your key registered on both sides. Profile, repos with settings,
253issues, MRs, and comments replay with attribution; git data mirrors
254client-side through your own key. Keys never transfer and emails
255arrive unverified — trust is per-instance. Re-running resumes.
256Push-blocking policies (require-signed, protected branches) are
257deferred and printed for you to re-apply after the data lands.
258=gitbay auth export= alone doubles as a user-level backup.
259
260#+begin_src sh
261gitbay migrate --from old-instance.example [--from-port 22]
262#+end_src
263
264Mirroring keeps a foreign remote in sync during a gradual migration
265(repo admin; https remotes; the token is stored server-side for the
266recurring sync and never echoed back):
267
268#+begin_src sh
269gitbay repo mirror add you/project https://github.com/you/project.git \
270    --direction push --token-stdin    # propagate after every local push
271gitbay repo mirror add you/copy https://github.com/them/theirs.git \
272    --direction pull                  # follow upstream; local pushes refused
273gitbay repo mirror list               # sync status and last error, per mirror
274gitbay repo mirror sync / remove <id>
275#+end_src
276
277Markdown and org files render on the web when opened, the way a README
278does on the repository page, with relative links resolved against the
279file's directory; =source= in the file's action bar (or =?view=source=)
280shows the text instead. Other files show the text with highlighting.
281
282* Organizations
283
284Orgs share the owner namespace with users and own repositories at
285=org/repo=. By default members get write on all org repos; org admins
286get repo admin, create repos under the org, and manage membership. An
287org's existence and member list are public; its teams are visible to
288members, and anyone else asking is refused rather than told the org
289does not exist.
290
291#+begin_src sh
292gitbay org create krz
293gitbay org members add krz alice [--role admin]
294gitbay org show krz
295gitbay org rename krz newname     # clone URLs change
296gitbay org delete krz --yes       # only when it owns no repositories
297#+end_src
298
299Large orgs scope access with teams: set what plain membership implies,
300then grant per-repo roles through named teams (org admins always keep
301admin; the default =write= keeps the simple model):
302
303#+begin_src sh
304gitbay org settings members-role krz none   # write | read | none
305gitbay org team create krz core-devs
306gitbay org team add krz core-devs alice bob # org members only
307gitbay org team grant krz core-devs krz/gitbay write
308gitbay org team show krz core-devs          # members + grants
309gitbay org team revoke / remove / delete ...
310#+end_src
311
312* Issues
313
314Anyone who can read a repository can file and comment. Closing/reopening
315is for the author or anyone with write; labels and assignees need write.
316
317#+begin_src sh
318gitbay issue create --title "it breaks" [--body "..." | --file -]
319gitbay issue list [--state open|closed|all]
320gitbay issue show 4
321gitbay issue comment 4 --message "same here"
322gitbay issue edit 4 --title "better title" [--body|--file -]  # author or write
323gitbay issue close 4 / reopen 4
324gitbay issue label 4 --add bug --remove wontfix
325gitbay issue assign 4 --add alice
326gitbay issue milestone 4 v1.0                 # or "none" to clear
327#+end_src
328
329Inside a clone, the repository is inferred from the =origin= remote —
330that is why no =owner/name= appears above. Anywhere else, pass it as the
331first argument. Long text: =--body= inline, =--file -= from stdin, or
332neither on a terminal and =$EDITOR= opens.
333
334A wiki is the =.gitbay/wiki/= directory on the default branch — no
335separate repository, the same rendering pipeline as READMEs (markdown
336and org). Editing it is editing a file: a push, =repo commit-file=, or
337the web editor on a repository that permits server-authored commits (one
338requiring signed commits is push-only, since the server signs nothing):
339
340#+begin_src sh
341mkdir -p .gitbay/wiki
342# add Home.md (or .org), more pages, images; relative links between
343# pages just work on the web at /you/project/wiki
344git add .gitbay/wiki && git commit -m "wiki" && git push
345#+end_src
346
347Releases anchor notes and binary assets to a pushed tag (write access;
348assets stream over SSH, capped by the instance's =max_asset_bytes=).
349While a release exists its tag can be neither deleted nor moved, whether
350or not the tag is protected: delete the release first.
351
352#+begin_src sh
353gitbay release create v1.0 --title "First light" [--notes|--file -|$EDITOR]
354gitbay release asset add v1.0 tool-linux-amd64 < dist/tool-linux-amd64
355gitbay release asset get v1.0 tool-linux-amd64 > tool   # or the web download link
356gitbay release list / show v1.0 / delete v1.0 --yes
357#+end_src
358
359The web shows them under the repository's =releases= tab with rendered
360notes, sha256 sums, and download links. Feed readers subscribe at
361=/you/project/releases.atom=; commits are at =/you/project/log.atom=
362(the default branch) or =/you/project/log.atom/<ref>=, and an owner's
363activity on their public repositories at =/you/activity.atom=. The
364pages carry the discovery link.
365
366Commit messages act on issues when the commits land on the default
367branch (direct push, MR merge, =repo commit-file= or the web editor):
368=closes/fixes/resolves #4= closes the
369issue with a linking comment, and a bare =#4= leaves a reference
370comment. Each issue/commit pair acts once, ever. =Closes owner/name#N= closes an issue in another repository when
371you hold write there with a key whose scope reaches it; otherwise it
372stays a plain link. A deploy key closes nothing outside the repository
373it is bound to, but naming that repository by full path works as the
374bare form does. The comment left
375on the closed issue links the closing commit by its repository path, so
376closing a public repository's issue from a private one names the private
377repository there. A bare =owner/name#N= links and does nothing.
378
379Milestones group issues and MRs toward a release (write access to
380manage, attach with =issue milestone= / =mr milestone=; progress shows
381on the web at =/owner/name/milestones=):
382
383#+begin_src sh
384gitbay milestone create v1.0 --description "first release" --due 2027-01-01
385gitbay milestone list [--state open|closed|all]
386gitbay milestone close v1.0 / reopen v1.0
387#+end_src
388
389An org holds labels and milestones every repository under it sees
390beside its own. =issue label --add=, =mr label --add=, =issue
391milestone= and =mr milestone= resolve the org's row first; a repository
392cannot create a label or milestone with a name its org holds. Creating
393an org label or milestone whose name repositories under the org already
394use folds them in: their issues and merge requests move to the org's
395row. Org admins manage them; counts span the repositories you can read.
396
397#+begin_src sh
398gitbay org label set acme bug --color cf222e
399gitbay org label list acme / remove acme bug
400gitbay org milestone create acme v2 --due 2027-03-01
401gitbay org milestone list acme [--state open|closed|all]
402gitbay org milestone close acme v2 / reopen acme v2
403#+end_src
404
405On the web: =/acme/-/labels= and =/acme/-/milestones=, read-only.
406
407Issue templates: commit =.gitbay/issue-template.md= (and optional
408=issue-template-<name>.md= variants) to the default branch. =gitbay
409issue create= prefills =$EDITOR= with the default template, the web
410form prefills its textarea, and =gitbay issue templates= lists them.
411
412Lists narrow the same way on every surface: =issue list --label bug
413--assignee bob --author alice --milestone v1= (or =--milestone none=),
414=mr list --label bug --author bob --milestone v1=; the web's issue and
415merge request lists take the same names as query parameters, and each
416active filter shows with a link that drops it.
417
418Labels take a colour: =gitbay label set bug --color cf222e=; =label
419list= shows each with its colour and how many issues and merge requests
420carry it, and =label remove= takes one off all of them. =issue label
421--add= and =mr label --add= still create a colourless label on the fly.
422
423* Merge requests
424
425#+begin_src sh
426gitbay mr create --source feature --target main --title "add thing"
427gitbay mr create other/upstream --source you/fork:feature --target main --title "..."
428gitbay mr list / show 4 / diff 4
429gitbay mr checkout 4              # local branch mr/4 from the MR head
430gitbay mr review 4 --approve      # or --request-changes / --comment
431gitbay mr label 4 --add bug --remove wontfix
432gitbay mr merge 4 [--strategy ff|merge|squash|rebase]
433gitbay mr close 4
434#+end_src
435
436A merge request closed without merging can name the one that carries
437its change forward: =mr close 4 --by 7= records it and both pages show
438it, and =mr edit 4 --superseded-by 7|none= sets or clears it
439afterwards, refused on anything but a closed merge request.
440
441The web list shows each request's combined check state and comment
442count alongside its title, so open work needing attention stands out
443without opening it.
444
445Semantics worth knowing:
446
447- the MR head lives in the *target* repository as
448  =refs/merge-requests/N/head= (fetchable by any reader), so an MR
449  survives deletion of its source branch or fork. It is never removed
450  on merge or close; after a history rewrite an instance admin can
451  drop it with =admin mr prune=.
452- force-pushing the source updates the MR and marks existing reviews
453  stale — unless the diff is the one they reviewed. A rebase onto a
454  target that moved on changes every sha and nothing about the change,
455  so fresh reviews follow it to the new head; a push that changes the
456  diff stales them.
457- default strategy: fast-forward when possible, else a merge commit.
458  Squash makes one commit authored by the MR author, committed by the
459  merger. Rebase replays a linear range preserving authors; it refuses
460  ranges containing merge commits, and when fast-forward is possible it
461  *is* one (original commits and signatures land untouched).
462- on =require_signed_commits= branches only fast-forwards of fully
463  verified commits merge; everything server-created is refused with
464  instructions to rebase locally. =gitbay mr rebase <n>= is those
465  instructions: in a clone of the target it replays the source branch
466  onto the target and force-pushes it, then =mr merge <n>= fast-forwards.
467  The replay is local, so the commits carry your signature and not the
468  server's — it holds no key. A branch living in a fork is refused, with
469  the repository to run it in; rebase it there.
470
471Repo admins can gate merges (=repo settings ...=): =require-approvals
472<n>= (fresh, non-author approvals; each reviewer's latest review is
473their stance, and a fresh request-changes blocks), =require-resolved=
474(no open review threads), =require-checks= (all statuses green), and
475=require-codeowners= (an approval from an owner of every owned changed
476file). Owners come from a =CODEOWNERS= file on the target branch, root
477or =.gitbay/=, gitignore-style patterns, last match wins. The toggle is
478the opt-in, so a repository can carry the file as documentation of who
479to ask without it gating merges; with it on and no file on the target
480branch, the merge is refused and says so. It does not wait on
481=require-approvals=.
482
483=mr show= carries a =gates= block with where the merge request stands
484against all of them — approvals counted and required, owners still
485outstanding per file, open threads, checks, and whether a fast-forward
486is possible — and the merge request page shows the same block. =mr
487merge= names every unmet gate at once rather than the first. =mr
488review= says when a verdict is advisory, which it is from anyone
489without write access: the gates do not count it.
490
491Those gates apply to =mr merge=. A direct push to a protected branch
492passes none of them until =require-mr on=: then an existing protected
493branch refuses every push, including =repo commit-file= and the web
494editor, and the server's merge is its only writer. Creating the branch
495is still a push, since there is nothing to open a merge request against
496yet.
497
498A merge request whose target is another open merge request's source
499branch is stacked on it: =mr create= says so, =mr show= carries
500=stacked_on= and =stacked=, and merging the lower one retargets the
501upper onto =main= with its reviews kept. See [[Stacked-MRs][Stacked
502merge requests]].
503
504Review threads anchor to diff lines:
505
506#+begin_src sh
507gitbay mr diff-comment 4 --path main.go --line 12 --message "use log here"
508gitbay mr diff-comment 4 --reply 7 --message "done"     # join thread 7
509gitbay mr threads 4                                     # threads with staleness
510gitbay mr resolve 4 7 / unresolve 4 7
511#+end_src
512
513Threads render inline on the MR page. A force-push marks them stale
514(shown under "threads on earlier revisions") rather than guessing new
515anchors; =mr show= reports the unresolved count. Resolving is for the
516thread author, the MR author, or anyone with write.
517
518* CI builds
519
520A =.gitbay/ci.yml= in the repo runs jobs on every branch push:
521
522#+begin_src yaml
523jobs:
524  test:
525    steps:
526      - go test ./...
527#+end_src
528
529A config holds at most ten jobs of fifty steps each, each step under
5304096 bytes, in a file under 64 KiB; a longer step runs a script from the
531repository (=- sh ci/publish.sh=). A config past a cap, or otherwise
532unparseable, records a failed =ci/config= status on the commit and
533queues nothing, so look there when a push builds nothing.
534
535Each job becomes a build (=build list=, =build log=, the builds tab on
536the web) and a =ci/<job>= commit status, which =repo settings
537require-checks= can gate merges on. =build list= takes =--ref=,
538=--status= and =--job= to narrow the listing, combinable; the builds
539tab reads the same flags from its =?ref=, =?status= and =?job= query
540parameters and groups the result into one row per commit. Steps run
541with =sh -c= on the instance's runner, stopping at the first failure;
542a broken config surfaces as a failed =ci/config= status. Environment:
543=GITBAY_REPO=,
544=GITBAY_SHA=, =GITBAY_REF=, =GITBAY_JOB=, =CI=true=, and =GITBAY_SSH=,
545the instance's ssh destination as the build reaches it (=git@gitbay.org=
546from a runner elsewhere; inside a container on the server's own runner
547the host is at a private address the runner fills in). A job that
548talks back to the instance — a release asset, a comment, a push to a
549pages branch — uses =$GITBAY_SSH= with a key it holds as a secret;
550the build's container has no key of its own. Two things about that
551container: a secret with newlines (a private key) arrives intact, and
552=ssh= expands =~= from the passwd entry, =/root=, not from =$HOME=,
553which is the build home — so keep an ssh config in the workspace and
554pass it with =ssh -F= (and =GIT_SSH_COMMAND="ssh -F ..."= for git),
555which also works on a runner with no container, where writing to
556=~/.ssh= would edit that machine's own configuration.
557
558Secrets: =repo secret set <owner/name> <NAME>= reads the value from
559stdin (never argv) and injects it into the repo's builds as =$NAME=;
560=repo secret list= shows names only, and the value is never echoed
561back. Anyone with write access can read a secret from inside a build,
562so scope them accordingly.
563
564Schedules: a job with =schedule: "17 11,23 * * *"= (five-field cron,
565server-local time; lists, ranges, and steps supported) runs on its cron
566against the default branch instead of on push. A default-branch push
567registers or updates the schedule. A job with =tags: "v*"= runs when a
568matching tag is pushed — and only then; =schedule= and =tags= are
569mutually exclusive. =build trigger <owner/name> <job>= queues any job
570immediately, and =build cancel <owner/name> <n>= withdraws one, queued
571or running: a running build stops at the runner within seconds and the
572log says who cancelled it. Both need write access.
573
574What each push shape queues, with dedupe, path filters, schedules and
575the reaper together, is one table on [[CI][CI]].
576
577** Your own runner
578
579Builds run on runners attached to the repository. An instance need not
580offer any: install =gitbay-runner= on a machine of yours and attach it.
581
582#+begin_src sh
583brew install krz/tap/gitbay-runner        # or a binary from the release
584gitbay-runner init -remote git@gitbay.org
585#+end_src
586
587=init= generates a key under =~/.config/gitbay-runner/=, writes
588=config.toml= beside it, and prints the public key with the command to
589attach it:
590
591#+begin_src sh
592gitbay repo runner add owner/name < ~/.config/gitbay-runner/id_ed25519.pub
593#+end_src
594
595or paste the key under Runners on the repository's settings page. Then
596=brew services start krz/tap/gitbay-runner=, or run =gitbay-runner= with
597no arguments; it reads the config file, and any flag overrides it.
598
599What it builds: every build for the repositories it is attached to,
600with the repository's secrets, and nothing else. Merge requests from
601forks are untrusted and wait unless the runner runs with =-untrusted=,
602which is only sensible with =-isolation podman -image <ref>= (see
603[[Admin][Admin]]). Attach one runner to several repositories by repeating
604=repo runner add=; run several runners on one account by running =init=
605on each machine. =repo runner list= shows each attached key, when it
606last polled and the build it holds; =repo runner remove <fingerprint>=
607detaches one (the key stays on your account; =keys remove= drops it).
608A runner key reaches only the runner protocol and read-only git, so a
609build step that reads it off disk cannot administer your account.
610
611* Large files (LFS)
612
613Standard Git LFS works over both transports with no setup beyond the
614usual =git lfs track=. SSH remotes authenticate through
615=git-lfs-authenticate= (deploy keys included: ro keys can download, rw
616keys upload); anonymous HTTPS clones of public repositories can fetch
617LFS objects with no credentials. Objects are verified against their
618sha256 on upload and capped at 512MB by default.
619
620* Pages
621
622On instances with =[pages] domain= set, a =pages= branch in any public
623repo is served as a static site: the repo named =pages= at
624=https://<you>.<domain>/=, every other repo at
625=https://<you>.<domain>/<repo>/=. Push HTML to publish; a CI job can
626build and push the branch for automatic deploys. Sites run on a
627separate origin — your scripts work, and the forge's cookies are out of
628reach.
629
630A repo can also serve its pages branch on a domain you own.
631=repo domain add <owner/name> <domain>= claims it and prints a DNS TXT
632challenge (=_gitbay-challenge.<domain>=); create the record, run
633=repo domain verify=, then point the domain's A/AAAA records at the
634instance (DNS-only if the domain sits behind a proxying provider — the
635instance issues its own certificates). Claims are exclusive per
636instance; unverified claims serve nothing and expire after 7 days.
637=repo domain list= reports pending/verified/expired.
638
639* Snippets
640
641A snippet is one or more named text files you own outside any
642repository, for a log or a fragment shared by URL. Create one from a
643file on stdin; the reply is the id and the URL:
644
645#+begin_src sh
646gitbay snippet create build.log --description "failing build" < build.log
647gitbay snippet file set <id> notes.txt < notes.txt   # add or replace a file
648gitbay snippet file get <id> build.log > build.log
649gitbay snippet edit <id> --visibility public
650gitbay snippet list                                  # yours
651gitbay snippet list <owner>                          # their public ones
652gitbay snippet delete <id>
653#+end_src
654
655Visibility is =public= (listed on your page), =unlisted= (anyone with
656the URL, listed nowhere; the default) or =private= (you alone; not
657found to everyone else). Files are text, valid UTF-8, each under the
658instance's =max_snippet_bytes=, at most 64 per snippet. A snippet keeps
659at least one file. There is no history: setting a file replaces it.
660
661On the web, =/<you>/-/snippets= lists yours, each snippet page renders
662its files with a raw link per file, and the same page creates, edits
663and deletes through the commands above.
664
665* Browser sessions
666
667=gitbay web login= mints a one-time URL; the session it opens lasts
668seven days. =gitbay web sessions list= shows each of yours by a short
669id with its creation and expiry, and =gitbay web sessions revoke <id>=
670or =--all= ends them from the terminal, which is where a lost laptop is
671handled.
672
673=web theme set light= or =dark= fixes the web UI's colour scheme for
674your account; =system=, the default, follows the browser's own
675preference. =web theme show= prints it. The account page has the same
676control under Appearance.
677
678* Notifications
679
680When the instance has SMTP configured, activity mails you as well as
681filing the inbox row: someone opens an issue or MR on your repository,
682comments where you are a participant (author, commenter, reviewer, or
683mentioned), reviews, closes, merges, or assigns you.
684=notifications settings mail off= keeps the inbox and stops that mail
685(login links are not activity and still arrive); the account page has
686the same switch.
687=notifications settings watch on= makes you a recipient of every issue
688and merge request on the repositories you can write to, as if you had
689run =repo watch= on each: it is consulted when a notice is delivered,
690so a grant or a revoke needs no watch row, and an explicit watch or
691mute on a repository still wins. Off by default; the account page has
692this switch too. Writing =@name= in an issue, merge request or
693comment files "mentioned you" in that account's inbox and makes them a
694participant of the thread, provided they can read the repository and
695have not muted it; watchers are not told about a mention addressed to
696someone else.
697You are never mailed about your own actions, and only verified primary
698addresses receive anything. Delivery retries on relay failure.
699
700Push is the same activity again, delivered to a phone: the iOS app
701registers a device, and =notifications settings push off= silences it
702the way =mail off= silences mail, without deregistering anything.
703=notifications device list= shows what is registered (token shown
704truncated); =notifications device remove <id>= drops one by hand, from
705the CLI or the account page. A device is also dropped on its own the
706moment Apple reports the token dead, so an app deleted from a phone
707stops costing anything without you having to notice. Push only works
708when the instance operator has configured it: on an instance with
709=[push] enabled = false=, =notifications device add= refuses rather
710than registering a device nothing can deliver to.
711
712Push notifications carry the notice in full: a private repository's
713name and the issue or merge request number reach Apple and can appear
714on a lock screen, the same as any other text a phone shows in a
715notification. This is deliberate, not an oversight — weigh it against
716what you keep in a private repository before registering a device.
717
718* Web vocabulary
719
720Sign in / Log out, Search, sentence-case headings and buttons, product
721name lowercase.
722
723* Output rules
724
725=--json= is the contract; the plain output is for a person at a
726terminal, and follows these rules so every noun reads the same way.
727
728- A list command prints one row per item, tab-separated, no header.
729  Columns run identifier, state, then description; a trailing column
730  may carry a word (=due 2027-01-01=, =via team=). The =gitbay= CLI pads
731  the tabs into aligned columns when stdout is a terminal and leaves
732  them as tabs when piped, so =cut -f= sees the same bytes stock ssh
733  prints. Under =--json= nothing is touched.
734- An empty list prints nothing on stdout and =nothing to list= on
735  stderr.
736- A mutation prints one line: verb, object, identifier
737  (=created krz/gitbay#7=). A second line appears only for something
738  to copy: a URL, a token shown once.
739- A bad invocation prints =usage:= and the command's registered usage,
740  the same text =help <noun>= shows. Exit 2.
741- A refusal says who may and what to do instead: =only admins of acme
742  can manage teams; ask one to add you=. Exit 4. A thing that does not
743  exist, or that you may not know exists, is exit 3.
744- stdout is the result; stderr is everything else: progress, a note
745  that output was truncated, errors.
746- Verbs: =create= and =delete= for things with their own identity
747  (repository, issue, release, team), =add= and =remove= for attaching
748  something to them (a key, a member, a label on an issue), =set= for
749  a value, =revoke= for a credential, =show= and =list= for reads.
750
751* Scripting
752
753Every read command takes =--json= and emits one envelope:
754={"protocol_version": 1, "data": ...}=. stdout is data, stderr is
755messages. Exit codes are stable: 0 ok, 1 failure, 2 usage, 3 not found,
7564 denied, 5 server/protocol error. Usage means the arguments were wrong;
757a refusal — a name already taken, a state that does not allow the change
758— is a failure. Nothing ever prompts; destructive commands take =--yes=.
759
760For HTTP automation see [[API]].
761
762* CLI setup
763
764#+begin_src sh
765gitbay remote add myforge forge.example.org [--port n] [--user u] --default
766gitbay remote list
767gitbay init [name] [--private]   # git init + repo create + origin, in one step
768#+end_src
769
770Configuration lives at =~/.config/gitbay/config.toml=. The CLI shells
771out to your real =ssh=, so =~/.ssh/config=, the agent, and hardware keys
772all apply. It shares one connection per instance through a control
773socket under =~/.ssh=: the first command in five minutes pays the
774handshake and the rest ride it. =no_multiplex = true= on an instance in
775the config turns that off. Man pages: =gitbay man --dir <dir>=; completions:
776=gitbay completion bash|zsh|fish=.