orchard
mirrorEvery site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.
blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics
1{{define "hero"}}
2{{$d := .Data}}
3{{if not $.LoggedIn}}
4{{/*
5 The signed out view of this page is an advert for running the thing yourself,
6 so it opens the way the other sites here open. Signed in it is a working repo
7 list and the hero would be in the way every single visit.
8*/}}
9<section class="hero">
10 <div class="wrap">
11 <span class="hero-eyebrow">
12 <span class="eyebrow-dot" aria-hidden="true"></span>
13 {{len $d.Repos}} repositories · self-hosted
14 </span>
15 <h1 class="hero-title">
16 A git remote for operators who host their <span class="accent">own code</span>.
17 </h1>
18 <p class="hero-sub">
19 Push over HTTPS with a token and a repository is created on arrival. Browse
20 branches, tags, files, diffs, and an Atom feed. Your data never leaves your
21 infrastructure.
22 </p>
23 <div class="hero-ctas">
24 <a href="#repositories" class="btn btn-primary">Browse repositories</a>
25 <a href="{{$.SourceURL}}" target="_blank" rel="noopener" class="btn btn-ghost">View source</a>
26 </div>
27 </div>
28</section>
29
30<section class="stat-strip">
31 <div class="wrap stat-row">
32 <div class="stat">
33 <div class="stat-label">repositories</div>
34 <div class="stat-value">{{len $d.Repos}}</div>
35 </div>
36 <div class="stat">
37 <div class="stat-label">mirrored</div>
38 <div class="stat-value">{{$d.Mirrors}}</div>
39 </div>
40 <div class="stat">
41 <div class="stat-label">total size</div>
42 <div class="stat-value">{{humanBytes $d.TotalSize}}</div>
43 </div>
44 <div class="stat">
45 <div class="stat-label">last push</div>
46 <div class="stat-value stat-value-sm">{{humanTime $d.LastPush}}</div>
47 </div>
48 </div>
49</section>
50{{end}}
51{{end}}
52
53{{define "content"}}
54{{$d := .Data}}
55
56{{if and $.LoggedIn (not $d.HasTokens)}}
57<div class="notice">
58 <strong>No push token yet.</strong>
59 Mint one in <a href="/settings">Settings</a> before your first push.
60</div>
61{{end}}
62
63<div class="pagehead" id="repositories">
64 <h1 class="pagetitle">Repositories <span class="count">{{len $d.Repos}}</span></h1>
65 {{if $.LoggedIn}}{{template "firstpush" (dict "CloneURL" $d.CloneURL "Bridge" $d.Bridge "Name" "<name>")}}{{end}}
66</div>
67
68{{if not $d.Repos}}
69<div class="emptystate">
70 <h2>Nothing here yet</h2>
71 <p>Push a repository and it will be created:</p>
72 <pre class="cmd">git remote add origin {{$d.CloneURL}}/<name>.git
73git push -u origin main</pre>
74 {{if $.LoggedIn}}{{template "pushlimit" (dict "Bridge" $d.Bridge "Name" "<name>")}}{{end}}
75</div>
76{{end}}
77
78<ul class="repolist">
79{{range $d.Repos}}
80 <li class="repocard">
81 <div class="row">
82 <a class="name" href="/{{.Name}}">{{.Name}}</a>
83 {{if .Mirror}}<span class="badge mirror">mirror</span>{{end}}
84 {{if .Archived}}<span class="badge archived">archived</span>{{end}}
85 {{if .UpstreamGone}}<span class="badge gone" title="No longer on GitHub. This is the only copy.">only copy</span>{{end}}
86 {{if .Empty}}<span class="badge empty">empty</span>{{end}}
87 </div>
88
89 {{if .Description}}<p class="desc">{{.Description}}</p>{{end}}
90 {{if .Topics}}<p class="topics">{{range .Topics}}<span class="topic">{{.}}</span>{{end}}</p>{{end}}
91
92 {{/*
93 One span per fact, so the flex gap falls between facts rather than
94 inside one. Zero counts are dropped: "0 tags" on every row is noise that
95 makes the real numbers harder to spot.
96 */}}
97 <p class="meta">
98 <span>{{humanBytes .Size}}</span>
99 <span>{{.Branches}} {{pluralize .Branches "branch" "branches"}}</span>
100 {{if .Tags}}<span>{{.Tags}} {{pluralize .Tags "tag" "tags"}}</span>{{end}}
101 <span>updated <time title="{{.LastPush.Format "2006-01-02 15:04:05 MST"}}">{{humanTime .LastPush}}</time></span>
102 {{if .Mirror}}<span>synced {{humanTime .LastSync}}</span>{{end}}
103 </p>
104
105 {{if .LastSyncErr}}<p class="syncerr">Last sync failed: {{.LastSyncErr}}</p>{{end}}
106 </li>
107{{end}}
108</ul>
109{{end}}