github.com/troglobit / digit

README.md 10.3K
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# Digit — Can You Dig It?

[![License][lic-badge]][lic-url] [![Release][rel-badge]][rel-url] [![Build & Check][build-badge]][build-url] [![Vulnerability Scan][vuln-badge]][vuln-url]

Digit (*display git*) is a fast, simple web frontend to your git
repositories, made to resemble other popular interfaces.

- Browse repos, files, trees, and commits
- Mirror-tree aware: repos nested `host/org/repo.git` group per org
- File tree view with mode, size, and per-directory readmes
- Blame view with per-commit line groups
- Commit log with full diff output, and message search
- Patch view, append `.patch` to any commit URL
- Tarball and zip downloads of any branch or tag
- Read-only clone over smart HTTP, with a GitHub-style clone menu
- Atom and RSS commit feeds, per repo and site-wide
- Tag and branch listing
- Syntax highlighting, line anchors with visual range marks
- Keyboard shortcuts for commit and log navigation (press `?` for the list)
- GitHub Markdown rendering, with emoji, tables, alerts, and inline images
- Readme quick-links: license, contributing, code of conduct, security
- Templated HTML (fully customizable)
- Dark & Light mode, auto by default with a navbar toggle
- Configurable navbar, per-repo upstream and website links
- Built-in caching: ETag revalidation, immutable hash-pinned URLs

## Screenshots

<table>
  <tr>
    <td width="50%" valign="top">
      <b>Repo overview</b><br>
      <img src="screenshots/overview.png" alt="Repo overview">
    </td>
    <td width="50%" valign="top">
      <b>Tree view</b><br>
      <img src="screenshots/tree-view.png" alt="Tree view">
    </td>
  </tr>
  <tr>
    <td width="50%" valign="top">
      <b>Visual line marks</b><br>
      <img src="screenshots/multiline-mark.png" alt="Visual line marks">
    </td>
    <td width="50%" valign="top">
      <b>Repo viewer</b><br>
      <img src="screenshots/repo-viewer.png" alt="Repo viewer">
    </td>
  </tr>
</table>

## Configuring

Digit looks for `config.yaml` in the current directory, falling back
to `/etc/digit.yaml`; use `--config <path>` to point elsewhere.

```yaml
repo:
  scanPath: /srv/git
  readme:
    - README.md
    - README
  mainBranch:
    - main
    - master
  ignore:
    - foo
  unlisted:
    - private-repo
dirs:
  templates: ./templates
  static: ./static
meta:
  favicon: favicon.svg
  title: Digit
  description: Explore available Git repositories
  syntaxHighlight: default
features:
  blame: true
  archive: true
  feeds: true
  clone: false
  search: false
nav:
  home: https://example.com
  help: https://github.com/troglobit/digit
  links:
    - name: GitHub
      url: https://github.com/example
server:
  name: git.example.com
  ssh: git@git.example.com
  host: 127.0.0.1
  port: 3000
```

| Field                  | Description                                                          |
|------------------------|----------------------------------------------------------------------|
| `repo.scanPath`        | Directory containing repos, nested up to `host/org/repo.git`         |
| `repo.readme`          | Readme filenames to look for (first match wins)                      |
| `repo.mainBranch`      | Branch names to try as default branch                                |
| `repo.ignore`          | Repos to exclude entirely (404); `*` globs one path segment          |
| `repo.unlisted`        | Repos to hide from the index (still accessible by URL); globs too    |
| `dirs.templates`       | Path to custom Go html/template files                                |
| `dirs.static`          | Path to custom static assets (CSS, images)                           |
| `meta.syntaxHighlight` | Client-side syntax highlighting (highlight.js); empty = disabled     |
| `features.blame`       | Blame view; disable to save CPU on large files (default on)          |
| `features.archive`     | Tarball downloads; disable to save bandwidth (default on)            |
| `features.feeds`       | Atom/RSS feeds: `/atom`, `/repo/atom[/ref]`, rss alike (default on)  |
| `features.clone`       | Read-only smart HTTP clone (default off)                             |
| `features.search`      | Log search, case-insensitive and verbatim (default off)              |
| `nav.home`             | Navbar brand link, e.g. your homepage (default `/`)                  |
| `nav.help`             | Navbar help link (default: digit's project page)                     |
| `nav.links`            | Extra navbar links (name + url), right-aligned                       |
| `server.name`          | Public host name, for absolute feed links and clone URLs             |
| `server.ssh`           | SSH clone prefix, e.g. `git@git.example.com`; adds SSH to the menu   |
| `server.host`          | Listen address (default `127.0.0.1` — see reverse proxy below)       |
| `server.port`          | Listen port (default `3000`)                                         |

## Mirrored repositories

Repos may live directly in `scanPath` or nested like a mirror tree,
e.g. `<host>/<org>/<repo>.git`, and both styles can be mixed.  Nested
repos get URLs matching their path (`/github.com/troglobit/mg/`), the
overview groups them per org, and every level has its own index page
(`/github.com/`, `/github.com/troglobit/`).  With more than 20 repos
the front page lists orgs with repo counts instead of every repo.

To hide a whole org, glob it:

```yaml
repo:
  ignore:
    - github.com/some-vendor/*
```

## Per-repository metadata

The repo overview and summary pages show the classic gitweb
`description` file, so set it in each repo:

```bash
$ echo "Fast init for Linux systems.  Cookies included." > /srv/git/finit.git/description
```

To link a repo to its upstream, e.g. the GitHub project it mirrors,
or to its website, set the `digit.upstream` and `digit.website` keys
in the repo's git config.  They show up as `upstream ↗` and
`website ↗` in the repo navigation:

```bash
$ git -C /srv/git/finit.git config digit.upstream https://github.com/troglobit/finit
$ git -C /srv/git/finit.git config digit.website https://troglobit.com/projects/finit/
```


## Building

After cloning this repo, build a stand-alone binary with:

```bash
$ cd digit/
$ make
```

The Makefile is a thin wrapper around the Go toolchain (`go build`);
`make check` vets and tests, `make fmt` formats.  No external
dependencies beyond the Go toolchain.

At runtime digit requires `git` to be installed on the server — all
repository access shells out to git plumbing commands.  This is the
same requirement as Gogs, and what keeps CPU usage low: the git binary
uses commit-graph and pack index optimizations that pure-Go git
libraries lack.

## Installation

`make install` installs the binary, a sample `/etc/digit.yaml` (an
existing one is left untouched), and the bundled systemd service,
which runs digit as a restricted `git` user.  `prefix`, `sysconfdir`,
`unitdir`, and `DESTDIR` are honored, for packaging:

```bash
$ sudo make install
$ sudo useradd --system --home /srv/git --shell /usr/sbin/nologin git
```

Point `repo.scanPath` in `/etc/digit.yaml` at your repos — they must
be readable by the `git` user, but need not be *owned* by it: digit
marks everything under `scanPath` as `safe.directory` for its own
git commands, so mirrors synced by another user work out of the box,
without tripping git's dubious-ownership check.  Then:

```bash
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now digit
```

## Cloning

With `features.clone` enabled, every repo can be cloned read-only
over smart HTTP — handy for an office-internal digit:

```bash
$ git clone https://git.example.com/finit
```

Digit proxies the two upload-pack endpoints to `git http-backend`;
push is never routed, so repos cannot be written to.  Set
`server.name` and repo pages get a clone menu next to the branch
selector, with copyable URLs and a tarball download, plus
`go-import` meta tags so Go vanity imports work.

If the server also runs sshd with git access, set `server.ssh` to
the user@host prefix and the menu offers SSH clone URLs too, built
from each repo's real on-disk path.

## Logging and crawlers

Under systemd digit detects the journal and drops its own
timestamps.  Under other init systems, e.g. [Finit][3], start digit
with `-s`/`--syslog` to log via syslog(3) instead of stderr.
Rejected requests are logged with the client address:

```
client 192.0.2.7: GET /finit/nope: 404
```

which fail2ban can match to ban scanners and scrapers:

```ini
# /etc/fail2ban/filter.d/digit.conf
[Definition]
failregex = ^.*client <HOST>: .*: (404|500)
journalmatch = _SYSTEMD_UNIT=digit.service
```

Digit also serves a `/robots.txt` keeping well-behaved crawlers off
the expensive endpoints (archive, blame, raw); override it by
placing your own in `dirs.static`.

## Running behind a reverse proxy

Digit listens on `127.0.0.1:3000` by default, made to sit behind a
TLS-terminating reverse proxy.  All generated links are root-relative
— the listen address never leaks into any page — so no rewriting is
needed.  The one requirement is that digit is served at the *root* of
its host: use a dedicated (sub)domain like `git.example.com`; mounting
under a sub-path such as `example.com/git/` is not supported.

With nginx:

```nginx
server {
    listen 80;
    server_name git.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

With [merecat httpd][2], using virtual hosts:

```conf
virtual-host = true

server https {
    port     = 443
    certfile = /etc/letsencrypt/live/example.com/fullchain.pem
    keyfile  = /etc/letsencrypt/live/example.com/privkey.pem

    proxy-pass "/**" {
        host    = "git.example.com"
        backend = "http://127.0.0.1:3000"
    }
}
```

[2]: https://github.com/troglobit/merecat
[3]: https://github.com/troglobit/finit
[lic-badge]: https://img.shields.io/badge/License-MIT-teal.svg
[lic-url]: https://github.com/troglobit/digit/blob/main/LICENSE
[build-badge]: https://github.com/troglobit/digit/actions/workflows/build.yml/badge.svg
[build-url]: https://github.com/troglobit/digit/actions/workflows/build.yml
[vuln-badge]: https://github.com/troglobit/digit/actions/workflows/vuln.yml/badge.svg
[vuln-url]: https://github.com/troglobit/digit/actions/workflows/vuln.yml
[rel-url]: https://github.com/troglobit/digit/releases
[rel-badge]: https://img.shields.io/github/v/release/troglobit/digit