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 "content"}}
2{{$d := .Data}}
3<h1 class="pagetitle">Settings</h1>
4
5{{if $d.NewToken}}
6<div class="notice tokennew">
7 <strong>New push token. This is the only time it is shown.</strong>
8 <input type="text" readonly value="{{$d.NewToken}}" onclick="this.select()">
9 <p>Use it as the password. The username is ignored, so type anything:</p>
10 <pre class="cmd">git remote add origin {{$d.CloneURL}}/<name>.git
11git push -u origin main</pre>
12 <p class="hint">
13 To avoid retyping it, let git remember it:
14 <pre class="cmd">git config --global credential.helper store</pre>
15 The token is stored in clear in <code>~/.git-credentials</code>, so treat that file as the secret it is.
16 </p>
17</div>
18{{end}}
19
20{{if $d.Notice}}
21<div class="notice">{{$d.Notice}}</div>
22{{end}}
23
24<section>
25 <h2>What gets mirrored</h2>
26 <p class="hint">
27 A source is a whole GitHub account, or one repository on it. Everything
28 public it finds is cloned here and kept in step; forks and private
29 repositories are skipped, though naming a fork outright mirrors it anyway.
30 {{if $d.MirrorEnabled}}Syncs every {{$d.MirrorEvery}}.{{else}}<strong>The mirror lane is disabled.</strong>{{end}}
31 </p>
32
33 <form method="post" action="/settings/mirrors" class="inline">
34 <input type="text" name="source" placeholder="overshard or overshard/newtab" maxlength="201" required>
35 <button type="submit">Add source</button>
36 </form>
37
38 <table class="tokens">
39 <tbody>
40 {{range $d.Sources}}
41 <tr>
42 <td class="name"><a href="{{.URL}}" rel="noreferrer">{{.Label}}</a></td>
43 <td>{{if eq .Kind "account"}}entire account{{else}}single repository{{end}}</td>
44 <td>{{.Repos}} mirrored{{if .Failing}}, <strong>{{.Failing}} failing</strong>{{end}}</td>
45 <td>{{if .LastSync.IsZero}}never synced{{else}}synced {{humanTime .LastSync}}{{end}}</td>
46 <td>
47 <form method="post" action="/settings/mirrors/{{.ID}}/delete" class="inline">
48 <button type="submit" class="danger">Remove</button>
49 </form>
50 </td>
51 </tr>
52 {{else}}
53 <tr><td colspan="5">No sources. Nothing is being mirrored.</td></tr>
54 {{end}}
55 </tbody>
56 </table>
57
58 <form method="post" action="/settings/mirrors/sync" class="inline">
59 <button type="submit">Sync now</button>
60 </form>
61 <p class="hint">
62 Removing a source stops the syncing and leaves every repository it brought
63 in exactly where it is. Nothing here is deleted because a setting changed.
64 </p>
65</section>
66
67<section>
68 <h2>Push tokens</h2>
69 <p class="hint">
70 A token is the password half of an HTTP Basic credential, which is the only
71 scheme git natively stores and replays. It is a random value, not a
72 password, and only its hash is kept here. Revoking one takes effect on the
73 next push.
74 </p>
75
76 <form method="post" action="/settings/tokens" class="inline">
77 <input type="text" name="label" placeholder="laptop, desktop, CI" maxlength="60" required>
78 <button type="submit">Mint token</button>
79 </form>
80
81 <table class="tokens">
82 <tbody>
83 {{range $d.Tokens}}
84 <tr>
85 <td class="name">{{.Label}}</td>
86 <td><code>{{.Prefix}}…</code></td>
87 <td>created {{humanTime .Created}}</td>
88 <td>{{if .LastUsed.IsZero}}never used{{else}}used {{humanTime .LastUsed}}{{end}}</td>
89 <td>
90 <form method="post" action="/settings/tokens/{{.ID}}/revoke" class="inline">
91 <button type="submit" class="danger">Revoke</button>
92 </form>
93 </td>
94 </tr>
95 {{end}}
96 </tbody>
97 </table>
98</section>
99
100<section>
101 <h2>Pushing a large repository</h2>
102 {{template "pushlimit" (dict "Bridge" $d.Bridge "Name" "<name>")}}
103</section>
104{{end}}