Gitea with a dash Of Claude  

A note to self on my Gitea setup.

Basic Setup

I chose to set up manually, using the release binary, following the basic setup instructions from here Gitea docs.

On Ubuntu/Debian

$ adduser \
    --system \
    --shell /bin/bash \
    --gecos 'Gitea Version Control' \
    --group \
    --disabled-password \
    --home /home/gitea \
    gitea

$ mkdir -p /var/lib/gitea/{custom,data,log}
$ chown -R gitea:gitea /var/lib/gitea/
$ chmod -R 750 /var/lib/gitea/
$ mkdir /etc/gitea
$ chown root:gitea /etc/gitea
$ chmod 770 /etc/gitea

$ chmod 750 /etc/gitea
$ chmod 640 /etc/gitea/app.ini

Then, copy the gitea binary from the Gitea’s latest Github release to /usr/share/bin/gitea.

Linux service

Follow the basic instructions from Gitea docs, customizing using the user, and paths above.

Customize the /explore/repos

For personal hosting, a view similar to gitweb, or cgit, that shows categorized views of repos is helpful. With cgit / gitweb, specifying the category in the category file in . Since Gitea’s primary audience is teams, and organizations, it needs a bit of unorthodox coaxing for this customization.

  1. Set the following in app.ini (see app.example.ini):
[ui]
EXPLORE_PAGING_NUM=100
EXPLORE_PAGING_DEFAULT_SORT=alphabetical
  1. Create an Organization (my preference) / User per “category” (see gitweb and cgitrc).

  2. Create a file at $GITEA_CUSTOM/templates/shared/repo/list.tmpl to obtain a customized view that organizes repos by “category” (Organization), with this (mostly) Claude-Code generated template, with the following prompt:

    Customize list.tmpl to organize the repositories by organization (.Name)

list.tmpl:

{{/* Remember to set these in /etc/gitea/app.ini */}}
{{/* [ui] */}}
{{/* EXPLORE_PAGING_NUM = 100 */}}
{{/* EXPLORE_PAGING_DEFAULT_SORT = alphabetically */}}

{{$repos := .Repos}}
{{if $repos}}

  {{/* Track the owner we last emitted a heading for. */}}
  {{$lastOrg := ""}}

  {{range $repo := $repos}}
    {{$ownerName := $repo.Owner.Name}}

    {{/* ── Emit a new group heading when the owner changes ── */}}
    {{if ne $ownerName $lastOrg}}
      {{/* Close the previous group's item-list (skip on first iteration) */}}
      {{if ne $lastOrg ""}}
      </div>{{/* .grouped-repos */}}
      </div>{{/* .ui.segment */}}
      {{end}}

      <div class="ui segment org-group">
        <h3 class="ui dividing header org-group-header">
          {{svg "octicon-organization" 18 "tw-mr-2"}}
          <a href="{{AppSubUrl}}/{{$ownerName}}">{{$ownerName}}</a>
        </h3>
        <div class="grouped-repos item list">
      {{$lastOrg = $ownerName}}
    {{end}}

    {{/* ── Individual repository card ── */}}
    <div class="item">
      <div class="ui grid">
        <div class="sixteen wide column">
          {{if $repo.Avatar}}
             <img class="ui avatar image" src="{{$repo.Avatar}}" alt=""/>
          {{end}}
          <a class="name has-emoji" href="{{$repo.Link}}">
             <span class="text truncate">{{$repo.Name}}</span>
          </a>
          <br />
          {{if $repo.IsPrivate}}
             <span class="ui tiny basic label">{{ctx.Locale.Tr "repo.desc.private"}}</span>
          {{end}}
          {{if $repo.IsTemplate}}
             <span class="ui tiny basic label">{{ctx.Locale.Tr "repo.desc.template"}}</span>
          {{end}}
          {{if $repo.IsArchived}}
             <span class="ui tiny basic label">{{ctx.Locale.Tr "repo.desc.archived"}}</span>
          {{end}}
          {{if $repo.IsFork}}
            <span class="text small">
              {{svg "octicon-repo-forked" 14}}
              {{ctx.Locale.Tr "repo.forked_from"}}
              <a href="{{$repo.BaseRepo.Link}}">{{$repo.BaseRepo.FullName}}</a>
            </span>
          {{end}}
          <div class="meta">
            {{if $repo.PrimaryLanguage}}
              <span class="text grey df ac tw-mr-3">
                <i class="color-icon tw-mr-1" style="background-color: {{$repo.PrimaryLanguage.Color}}"></i>
                {{$repo.PrimaryLanguage.Language}}
              </span>
            {{end}}
            <span class="text grey tw-mr-3">
              {{svg "octicon-star" 14 "tw-mr-1"}}
              {{$repo.NumStars}}
            </span>
            <span class="text grey tw-mr-3">
              {{svg "octicon-git-branch" 14 "tw-mr-1"}}
              {{$repo.NumForks}}
            </span>
            {{if $repo.UpdatedUnix}}
              <span class="text grey">
                {{svg "octicon-clock" 14 "tw-mr-1"}}
                <relative-time format="datetime" year="numeric" month="short" day="numeric"
                  datetime="{{$repo.UpdatedUnix.FormatDate}}"
                  data-tooltip-content="{{$repo.UpdatedUnix.FormatDate}}">
                  {{$repo.UpdatedUnix.FormatDate}}
                </relative-time>
              </span>
            {{end}}
          </div>
        </div>

        {{/* Star / watch actions column */}}
        {{if and $.IsSigned (not $repo.IsEmpty)}}
             <div class="two wide column text right">
            <form method="post"
              action="{{AppSubUrl}}{{$repo.Link}}/action/{{if $.IsStaringRepo}}un{{end}}star?redirect_to={{$.Link}}">
              {{$.CsrfTokenHtml}}
              <button class="ui compact mini basic button" type="submit"
                data-tooltip-content="{{if $.IsStaringRepo}}{{ctx.Locale.Tr "repo.unstar"}}{{else}}{{ctx.Locale.Tr "repo.star"}}{{end}}">
                {{if $.IsStaringRepo}}
                  {{svg "octicon-star-fill" 16}}
                {{else}}
                  {{svg "octicon-star" 16}}
                {{end}}
              </button>
            </form>
             </div>
        {{end}}
      </div>
    </div>
    {{/* end repo card */}}

  {{end}}{{/* end range $repos */}}

  {{/* Close the final open group */}}
  {{if ne $lastOrg ""}}
    </div>{{/* .grouped-repos */}}
    </div>{{/* .ui.segment */}}
  {{end}}

{{else}}
  {{/* No repositories found – mirror the standard empty-state */}}
  <div class="ui secondary segment">
    <p>{{ctx.Locale.Tr "search.no_results"}}</p>
  </div>
{{end}}

<style>
/* Extra styling for the org-group headers */
.org-group {
  margin-bottom: 1.5em !important;
  padding-bottom: 0px;
}
.org-group-header {
  font-size: 1.1rem;
  margin-bottom: 0em !important;
}
.org-group-header a {
  color: inherit;
}
.grouped-repos .item {
  padding: 0.75em 0;
  border-bottom: 1px solid var(--color-secondary);
}
.grouped-repos .item:last-child {
  border-bottom: none;
  padding-bottom: 0px;
}
.sixteen {
  display: flex !important;
  padding-bottom: 10px !important;
  flex-wrap: wrap;
  justify-content: space-between;
}
.name {
  width: 35%;  // Hack for mobile view: force metadata on separate line, by reserving large portion of line for repo name
}
</style>