move README into (hugo-powered) website router7.org

This commit is contained in:
Michael Stapelberg 2020-06-21 09:43:13 +02:00
parent 1250211381
commit cb95bb6df8
30 changed files with 903 additions and 160 deletions

161
README.md
View File

@ -8,163 +8,4 @@ router7 is a pure-Go implementation of a small home internet router. It comes wi
Note that this project should be considered a (working!) tech demo. Feature requests will likely not be implemented, and see [CONTRIBUTING.md](CONTRIBUTING.md) for details about which contributions are welcome.
## Motivation
Before starting router7, I was using the [Turris Omnia](https://omnia.turris.cz/en/) router running OpenWrt. That worked fine up until May 2018, when an automated update pulled in a new version of [odhcp6c](https://git.openwrt.org/?p=project/odhcp6c.git;a=shortlog), OpenWrts DHCPv6 client. That version is incompatible with fiber7s DHCP server setup (I think there are shortcomings on both sides).
It was not only quicker to develop my own router than to wait for either side to resolve the issue, but it was also a lot of fun and allowed me to really tailor my router to my needs, experimenting with a bunch of interesting ideas I had.
## Project goals
* Maximize internet connectivity: retain the most recent DHCP configuration across reboots and even after its expiration (chances are the DHCP server will be back before the configuration stops working).
* Unit/integration tests use fiber7 packet capture files to minimize the chance of software changes breaking my connectivity.
* Safe and quick updates
* Auto-rollback of updates which result in loss of connectivity: the diagnostics daemon assesses connectivity state, the update tool reads it and rolls back faulty updates.
* Thanks to kexec, updates translate into merely 13s of internet connectivity loss.
* Easy debugging
* Configuration-related network packets (e.g. DHCP, IPv6 neighbor/router advertisements) are stored in a ring buffer which can be streamed into [Wireshark](https://www.wireshark.org/), allowing for live and retro-active debugging.
* The diagnostics daemon performs common diagnostic steps (ping, traceroute, …) for you.
* All state in the system is stored as human-readable JSON within the `/perm` partition and can be modified.
## Hardware
The reference hardware platform is the [PC Engines™ apu2c4](https://pcengines.ch/apu2c4.htm) system board. It features a 1 GHz quad core amd64 CPU, 4 GB of RAM, 3 Ethernet ports and a DB9 serial port. It conveniently supports PXE boot, the schematics and bootloader sources are available. I recommend the [msata16g](https://pcengines.ch/msata16g.htm) SSD module for reliable persistent storage and the [usbcom1a](https://pcengines.ch/usbcom1a.htm) serial adapter if you dont have one already.
Other hardware might work, too, but is not tested.
### Teensy rebootor
The cheap and widely-available [Teensy++ USB development board](https://www.pjrc.com/store/teensypp.html) comes with a firmware called rebootor, which is used by the [`teensy_loader_cli`](https://www.pjrc.com/teensy/loader_cli.html) program to perform hard resets.
This setup can be used to programmatically reset the apu2c4 (from `rtr7-recover`) by connecting the Teensy++ to the [apu2c4s reset pins](http://pcengines.ch/pdf/apu2.pdf):
* connect the Teensy++s `GND` pin to the apu2c4 J2s pin 4 (`GND`)
* connect the Teensy++s `B7` pin to the apu2c4 J2s pin 5 (`3.3V`, resets when pulled to `GND`)
You can find a working rebootor firmware .hex file at https://github.com/PaulStoffregen/teensy_loader_cli/issues/38
## Architecture
router7 is based on [gokrazy](https://gokrazy.org/): it is an appliance which gets packed into a hard disk image, containing a FAT partition with the kernel, a read-only SquashFS partition for the root file system and an ext4 partition for permanent data.
The individual services can be found in [github.com/rtr7/router7/cmd](https://godoc.org/github.com/rtr7/router7/cmd).
* Each service runs in a separate process.
* Services communicate with each other by persisting state files. E.g., `cmd/dhcp4` writes `/perm/dhcp4/wire/lease.json`.
* A service notifies other services about state changes by sending them signal `SIGUSR1`.
### Configuration files
| File | Consumer(s) | Purpose |
|---|---|---|
| `/perm/interfaces.json` | `netconfigd` | Set IP/MAC addresses of `uplink0` and `lan0` |
| `/perm/portforwardings.json` | `netconfigd` | Configure nftables port forwarding rules |
| `/perm/dhcp6/duid` | `dhcp6` | Set DHCP Unique Identifier (DUID) for obtaining static leases |
### State files
| File | Producer | Consumer(s) | Purpose |
|---|---|---|---|
| `/perm/dhcp4/wire/ack` | `dhcp4` | `dhcp4` | last DHCPACK packet for renewals across restarts |
| `/perm/dhcp4/wire/lease.json` | `dhcp4` | `netconfigd` | Obtained DHCPv4 lease |
| `/perm/dhcp6/wire/lease.json` | `dhcp6` | `netconfigd`, `radvd` | Obtained DHCPv6 lease |
| `/perm/dhcp4d/leases.json` | `dhcp4d` | `dhcp4d`, `dnsd` | DHCPv4 leases handed out (including hostnames) |
### Available ports
| Port | Purpose |
|---|---|
| `<public>:8053` | `dnsd` metrics (forwarded requests)
| `<public>:8066` | `netconfigd` metrics (nftables counters)
| `<private>:80` | gokrazy web interface
| `<private>:67` | `dhcp4d`
| `<private>:58` | `radvd`
| `<private>:53` | `dnsd`
| `<private>:8077` | `backupd` (serve backup.tar.gz)
| `<private>:7733` | `diagd` (perform diagnostics)
| `<private>:5022` | `captured` (serve captured packets)
Heres an example of the diagd output:
<img src="https://github.com/rtr7/router7/raw/master/2018-07-14-diagd.png"
width="800" alt="diagd output">
Heres an example of the metrics when scraped with [Prometheus](https://prometheus.io/) and displayed in [Grafana](https://grafana.com/):
<img src="https://github.com/rtr7/router7/raw/master/2018-07-14-grafana.png"
width="800" alt="metrics in grafana">
## Installation
Connect your serial adapter ([usbcom1a](https://pcengines.ch/usbcom1a.htm) works well if you dont have one already) to the apu2c4 and start a program to use it, e.g. `screen /dev/ttyUSB0 115200`. Then, power on the apu2c4 and configure it to do PXE boot:
* Press `F10` to enter the boot menu
* Press `3` to enter setup
* Press `n` to enable network boot
* Press `c` to move mSATA to the top of the boot order
* Press `e` to move iPXE to the top of the boot order
* Press `s` to save configuration and exit
Connect a network cable on `net0`, the port closest to the serial console port:
<img src="https://github.com/rtr7/router7/raw/master/devsetup.jpg"
width="800" alt="router7 development setup">
Next, build a router7 image:
```
go get -u github.com/gokrazy/tools/cmd/gokr-packer github.com/rtr7/tools/cmd/...
go get -u -d github.com/rtr7/router7
mkdir /tmp/recovery
GOARCH=amd64 gokr-packer \
-hostname=router7 \
-overwrite_boot=/tmp/recovery/boot.img \
-overwrite_mbr=/tmp/recovery/mbr.img \
-overwrite_root=/tmp/recovery/root.img \
-kernel_package=github.com/rtr7/kernel \
-firmware_package=github.com/rtr7/kernel \
-gokrazy_pkgs=github.com/gokrazy/gokrazy/cmd/ntp \
-serial_console=ttyS0,115200n8 \
github.com/rtr7/router7/cmd/...
```
Run `rtr7-recover -boot=/tmp/recovery/boot.img -mbr=/tmp/recovery/mbr.img -root=/tmp/recovery/root.img` to:
* trigger a reset if a Teensy with the rebootor firmware is attached
* serve a DHCP lease to all clients which request PXE boot (i.e., your apu2c4)
* serve via TFTP:
* the PXELINUX bootloader
* the router7 kernel
* an initrd archive containing the rtr7-recovery-init program and mke2fs
* serve via HTTP the boot and root images
* optionally serve via HTTP a backup.tar.gz image containing files for /perm (e.g. for moving to new hardware, rolling back corrupted state, or recovering from a disk failure)
* exit once the router successfully wrote the images to disk
### Updates
Run e.g. `rtr7-safe-update -updates_dir=$HOME/router7/updates` to:
* verify the router currently has connectivity, abort the update otherwise
* download a backup archive of `/perm`
* build a new image
* update the router
* wait until the router restored connectivity, roll back the update using `rtr7-recover` otherwise
The update step uses kexec to reduce the downtime to approximately 15 seconds.
### Manual Recovery
Given `rtr7-safe-update`s safeguards, manual recovery should rarely be required.
To manually roll back to an older image, invoke `rtr7-safe-update` via the
`recover.bash` script in the image directory underneath `-updates_dir`, e.g.:
```
% cd ~/router7/updates/2018-07-03T17:33:52+02:00
% ./recover.bash
```
### Prometheus
See https://github.com/rtr7/router7/tree/master/contrib/prometheus for example
configuration files, and install the [router7 Grafana
Dashboard](https://grafana.com/dashboards/8288).
**For more details, please see [router7.org](https://router7.org/)**

1
docs/CNAME Normal file
View File

@ -0,0 +1 @@
router7.org

View File

@ -0,0 +1,200 @@
<!DOCTYPE html>
<html> <head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="https://router7.org/sass/sidebar.css">
<title>router7: architecture</title>
</head>
<body>
<div id="content">
<div class="container">
<div class="row">
<div class="col-md-10"><nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">router7</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav ml-auto">
<a class="nav-item nav-link " href="/">Home </a>
<a class="nav-item nav-link active" href="/architecture/">Architecture <span class="sr-only">(current)</span></a>
<a class="nav-item nav-link " href="/installation/">Installation </a>
<a class="nav-item nav-link " href="https://github.com/rtr7/router7">GitHub </a>
</div>
</div>
</nav>
<h1 id="architecture">Architecture</h1>
<p>router7 is based on <a href="https://gokrazy.org/">gokrazy</a>: it is an appliance which gets packed into a hard disk image, containing a FAT partition with the kernel, a read-only SquashFS partition for the root file system and an ext4 partition for permanent data.</p>
<p>The individual services can be found in <a href="https://pkg.go.dev/github.com/rtr7/router7/cmd">github.com/rtr7/router7/cmd</a></p>
<ul>
<li>Each service runs in a separate process.</li>
<li>Services communicate with each other by persisting state files. E.g., <code>cmd/dhcp4</code> writes <code>/perm/dhcp4/wire/lease.json</code>.</li>
<li>A service notifies other services about state changes by sending them signal <code>SIGUSR1</code>.</li>
</ul>
<h2 id="configuration-files">Configuration files</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>Consumer(s)</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/perm/interfaces.json</code></td>
<td><code>netconfigd</code></td>
<td>Set IP/MAC addresses of <code>uplink0</code> and <code>lan0</code></td>
</tr>
<tr>
<td><code>/perm/portforwardings.json</code></td>
<td><code>netconfigd</code></td>
<td>Configure nftables port forwarding rules</td>
</tr>
<tr>
<td><code>/perm/dhcp6/duid</code></td>
<td><code>dhcp6</code></td>
<td>Set DHCP Unique Identifier (DUID) for obtaining static leases</td>
</tr>
</tbody>
</table>
<h2 id="state-files">State files</h2>
<table>
<thead>
<tr>
<th>File</th>
<th>Producer</th>
<th>Consumer(s)</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/perm/dhcp4/wire/ack</code></td>
<td><code>dhcp4</code></td>
<td><code>dhcp4</code></td>
<td>last DHCPACK packet for renewals across restarts</td>
</tr>
<tr>
<td><code>/perm/dhcp4/wire/lease.json</code></td>
<td><code>dhcp4</code></td>
<td><code>netconfigd</code></td>
<td>Obtained DHCPv4 lease</td>
</tr>
<tr>
<td><code>/perm/dhcp6/wire/lease.json</code></td>
<td><code>dhcp6</code></td>
<td><code>netconfigd</code>, <code>radvd</code></td>
<td>Obtained DHCPv6 lease</td>
</tr>
<tr>
<td><code>/perm/dhcp4d/leases.json</code></td>
<td><code>dhcp4d</code></td>
<td><code>dhcp4d</code>, <code>dnsd</code></td>
<td>DHCPv4 leases handed out (including hostnames)</td>
</tr>
</tbody>
</table>
<h2 id="available-ports">Available ports</h2>
<table>
<thead>
<tr>
<th>Port</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>&lt;public&gt;:8053</code></td>
<td><code>dnsd</code> metrics (forwarded requests)</td>
</tr>
<tr>
<td><code>&lt;public&gt;:8066</code></td>
<td><code>netconfigd</code> metrics (nftables counters)</td>
</tr>
<tr>
<td><code>&lt;private&gt;:80</code></td>
<td>gokrazy web interface</td>
</tr>
<tr>
<td><code>&lt;private&gt;:67</code></td>
<td><code>dhcp4d</code></td>
</tr>
<tr>
<td><code>&lt;private&gt;:58</code></td>
<td><code>radvd</code></td>
</tr>
<tr>
<td><code>&lt;private&gt;:53</code></td>
<td><code>dnsd</code></td>
</tr>
<tr>
<td><code>&lt;private&gt;:8077</code></td>
<td><code>backupd</code> (serve backup.tar.gz)</td>
</tr>
<tr>
<td><code>&lt;private&gt;:7733</code></td>
<td><code>diagd</code> (perform diagnostics)</td>
</tr>
<tr>
<td><code>&lt;private&gt;:5022</code></td>
<td><code>captured</code> (serve captured packets)</td>
</tr>
</tbody>
</table>
<p>Heres an example of <code>cmd/diagd</code> output:</p>
<p><img src="https://github.com/rtr7/router7/raw/master/2018-07-14-diagd.png"
width="800" alt="diagd output"></p>
<p>Heres an example of <code>cmd/netconfigd</code> metrics when scraped with <a href="https://prometheus.io/">Prometheus</a> and displayed in <a href="https://grafana.com/">Grafana</a>:</p>
<p><img src="https://github.com/rtr7/router7/raw/master/2018-07-14-grafana.png"
width="800" alt="metrics in grafana"></p>
<hr>
<p class="small">
© 2018 Michael Stapelberg and contributors
</p>
</div>
<div class="col-md-2">
<aside class="bd-toc">
<nav id="TableOfContents">
<ul>
<li><a href="#configuration-files">Configuration files</a></li>
<li><a href="#state-files">State files</a></li>
<li><a href="#available-ports">Available ports</a></li>
</ul>
</nav>
</aside>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</body>
</html>
</body>
</html>

94
docs/index.html Normal file
View File

@ -0,0 +1,94 @@
<!DOCTYPE html>
<html> <head>
<meta name="generator" content="Hugo 0.71.0-DEV" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="https://router7.org/sass/sidebar.css">
<title>router7: a small home internet router completely written in Go</title>
</head>
<body>
<div id="content">
<div class="container">
<div class="row">
<div class="col-md-10"><nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">router7</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav ml-auto">
<a class="nav-item nav-link active" href="/">Home <span class="sr-only">(current)</span></a>
<a class="nav-item nav-link " href="/architecture/">Architecture </a>
<a class="nav-item nav-link " href="/installation/">Installation </a>
<a class="nav-item nav-link " href="https://github.com/rtr7/router7">GitHub </a>
</div>
</div>
</nav>
<h1 id="router7">router7</h1>
<p>router7 is a pure-Go implementation of a small home internet router. It comes with all the services required to make a <a href="https://www.init7.net/en/internet/fiber7/">fiber7 internet connection</a> work (DHCPv4, DHCPv6, DNS, etc.).</p>
<p>Note that this project should be considered a (working!) tech demo. Feature requests will likely not be implemented, and see <a href="CONTRIBUTING.md">CONTRIBUTING.md</a> for details about which contributions are welcome.</p>
<h2 id="motivation">Motivation</h2>
<p>Before starting router7, I was using the <a href="https://omnia.turris.cz/en/">Turris Omnia</a> router running OpenWrt. That worked fine up until May 2018, when an automated update pulled in a new version of <a href="https://git.openwrt.org/?p=project/odhcp6c.git;a=shortlog">odhcp6c</a>, OpenWrts DHCPv6 client. That version is incompatible with fiber7s DHCP server setup (I think there are shortcomings on both sides).</p>
<p>It was not only quicker to develop my own router than to wait for either side to resolve the issue, but it was also a lot of fun and allowed me to really tailor my router to my needs, experimenting with a bunch of interesting ideas I had.</p>
<h2 id="project-goals">Project goals</h2>
<ul>
<li>Maximize internet connectivity: retain the most recent DHCP configuration across reboots and even after its expiration (chances are the DHCP server will be back before the configuration stops working).</li>
<li>Unit/integration tests use fiber7 packet capture files to minimize the chance of software changes breaking my connectivity.</li>
<li>Safe and quick updates
<ul>
<li>Auto-rollback of updates which result in loss of connectivity: the diagnostics daemon assesses connectivity state, the update tool reads it and rolls back faulty updates.</li>
<li>Thanks to kexec, updates translate into merely 13s of internet connectivity loss.</li>
</ul>
</li>
<li>Easy debugging
<ul>
<li>Configuration-related network packets (e.g. DHCP, IPv6 neighbor/router advertisements) are stored in a ring buffer which can be streamed into <a href="https://www.wireshark.org/">Wireshark</a>, allowing for live and retro-active debugging.</li>
<li>The diagnostics daemon performs common diagnostic steps (ping, traceroute, …) for you.</li>
<li>All state in the system is stored as human-readable JSON within the <code>/perm</code> partition and can be modified.</li>
</ul>
</li>
</ul>
<h2 id="hardware">Hardware</h2>
<p>The reference hardware platform is the <a href="https://pcengines.ch/apu2c4.htm">PC Engines™ apu2c4</a> system board. It features a 1 GHz quad core amd64 CPU, 4 GB of RAM, 3 Ethernet ports and a DB9 serial port. It conveniently supports PXE boot, the schematics and bootloader sources are available. I recommend the <a href="https://pcengines.ch/msata16g.htm">msata16g</a> SSD module for reliable persistent storage and the <a href="https://pcengines.ch/usbcom1a.htm">usbcom1a</a> serial adapter if you dont have one already.</p>
<p>Other hardware might work, too, but is not tested.</p>
<hr>
<p class="small">
© 2018 Michael Stapelberg and contributors
</p>
</div>
<div class="col-md-2">
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</body>
</html>
</body>
</html>

View File

@ -0,0 +1,146 @@
<!DOCTYPE html>
<html> <head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous">
<link rel="stylesheet" href="https://router7.org/sass/sidebar.css">
<title>router7: installation</title>
</head>
<body>
<div id="content">
<div class="container">
<div class="row">
<div class="col-md-10"><nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">router7</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav ml-auto">
<a class="nav-item nav-link " href="/">Home </a>
<a class="nav-item nav-link " href="/architecture/">Architecture </a>
<a class="nav-item nav-link active" href="/installation/">Installation <span class="sr-only">(current)</span></a>
<a class="nav-item nav-link " href="https://github.com/rtr7/router7">GitHub </a>
</div>
</div>
</nav>
<h1 id="installation">Installation</h1>
<p>Connect your serial adapter (<a href="https://pcengines.ch/usbcom1a.htm">usbcom1a</a> works well if you dont have one already) to the apu2c4 and start a program to use it, e.g. <code>screen /dev/ttyUSB0 115200</code>. Then, power on the apu2c4 and configure it to do PXE boot:</p>
<ul>
<li>Press <code>F10</code> to enter the boot menu</li>
<li>Press <code>3</code> to enter setup</li>
<li>Press <code>n</code> to enable network boot</li>
<li>Press <code>c</code> to move mSATA to the top of the boot order</li>
<li>Press <code>e</code> to move iPXE to the top of the boot order</li>
<li>Press <code>s</code> to save configuration and exit</li>
</ul>
<p>Connect a network cable on <code>net0</code>, the port closest to the serial console port:</p>
<p><img src="https://github.com/rtr7/router7/raw/master/devsetup.jpg"
width="800" alt="router7 development setup"></p>
<p>Next, build a router7 image:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">go get -u github.com/gokrazy/tools/cmd/gokr-packer github.com/rtr7/tools/cmd/...
go get -u -d github.com/rtr7/router7
mkdir /tmp/recovery
GOARCH<span style="color:#f92672">=</span>amd64 gokr-packer <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span> -hostname<span style="color:#f92672">=</span>router7 <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span> -overwrite_boot<span style="color:#f92672">=</span>/tmp/recovery/boot.img <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span> -overwrite_mbr<span style="color:#f92672">=</span>/tmp/recovery/mbr.img <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span> -overwrite_root<span style="color:#f92672">=</span>/tmp/recovery/root.img <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span> -kernel_package<span style="color:#f92672">=</span>github.com/rtr7/kernel <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span> -firmware_package<span style="color:#f92672">=</span>github.com/rtr7/kernel <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span> -gokrazy_pkgs<span style="color:#f92672">=</span>github.com/gokrazy/gokrazy/cmd/ntp <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span> -serial_console<span style="color:#f92672">=</span>ttyS0,115200n8 <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span> github.com/rtr7/router7/cmd/...
</code></pre></div><p>Run <code>rtr7-recover -boot=/tmp/recovery/boot.img -mbr=/tmp/recovery/mbr.img -root=/tmp/recovery/root.img</code> to:</p>
<ul>
<li>trigger a reset <a href="#rebootor">if a Teensy with the rebootor firmware is attached</a></li>
<li>serve a DHCP lease to all clients which request PXE boot (i.e., your apu2c4)</li>
<li>serve via TFTP:
<ul>
<li>the PXELINUX bootloader</li>
<li>the router7 kernel</li>
<li>an initrd archive containing the rtr7-recovery-init program and mke2fs</li>
</ul>
</li>
<li>serve via HTTP the boot and root images</li>
<li>optionally serve via HTTP a backup.tar.gz image containing files for <code>/perm</code> (e.g. for moving to new hardware, rolling back corrupted state, or recovering from a disk failure)</li>
<li>exit once the router successfully wrote the images to disk</li>
</ul>
<h2 id="updates">Updates</h2>
<p>Run e.g. <code>rtr7-safe-update -updates_dir=$HOME/router7/updates</code> to:</p>
<ul>
<li>verify the router currently has connectivity, abort the update otherwise</li>
<li>download a backup archive of <code>/perm</code></li>
<li>build a new image</li>
<li>update the router</li>
<li>wait until the router restored connectivity, roll back the update using <code>rtr7-recover</code> otherwise</li>
</ul>
<p>The update step uses kexec to reduce the downtime to approximately 15 seconds.</p>
<h2 id="manual-recovery">Manual Recovery</h2>
<p>Given <code>rtr7-safe-update</code>s safeguards, manual recovery should rarely be required.</p>
<p>To manually roll back to an older image, invoke <code>rtr7-safe-update</code> via the
<code>recover.bash</code> script in the image directory underneath <code>-updates_dir</code>, e.g.:</p>
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-shell" data-lang="shell">% cd ~/router7/updates/2018-07-03T17:33:52+02:00
% ./recover.bash
</code></pre></div><h2 id="rebootor">Teensy rebootor</h2>
<p>The cheap and widely-available <a href="https://www.pjrc.com/store/teensypp.html">Teensy++ USB development board</a> comes with a firmware called rebootor, which is used by the <a href="https://www.pjrc.com/teensy/loader_cli.html"><code>teensy_loader_cli</code></a> program to perform hard resets.</p>
<p>This setup can be used to programmatically reset the apu2c4 (from <code>rtr7-recover</code>) by connecting the Teensy++ to the <a href="http://pcengines.ch/pdf/apu2.pdf">apu2c4s reset pins</a>:</p>
<ul>
<li>connect the Teensy++s <code>GND</code> pin to the apu2c4 J2s pin 4 (<code>GND</code>)</li>
<li>connect the Teensy++s <code>B7</code> pin to the apu2c4 J2s pin 5 (<code>3.3V</code>, resets when pulled to <code>GND</code>)</li>
</ul>
<p>You can find a working rebootor firmware .hex file at <a href="https://github.com/PaulStoffregen/teensy_loader_cli/issues/38">https://github.com/PaulStoffregen/teensy_loader_cli/issues/38</a></p>
<h2 id="prometheus">Prometheus</h2>
<p>See <a href="https://github.com/rtr7/router7/tree/master/contrib/prometheus">https://github.com/rtr7/router7/tree/master/contrib/prometheus</a> for example
configuration files, and install the <a href="https://grafana.com/dashboards/8288">router7 Grafana
Dashboard</a>.</p>
<hr>
<p class="small">
© 2018 Michael Stapelberg and contributors
</p>
</div>
<div class="col-md-2">
<aside class="bd-toc">
<nav id="TableOfContents">
<ul>
<li><a href="#updates">Updates</a></li>
<li><a href="#manual-recovery">Manual Recovery</a></li>
<li><a href="#rebootor">Teensy rebootor</a></li>
<li><a href="#prometheus">Prometheus</a></li>
</ul>
</nav>
</aside>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</body>
</html>
</body>
</html>

2
docs/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-Agent: *
sitemap: https://router7.org/sitemap.xml

19
docs/sass/sidebar.css Normal file
View File

@ -0,0 +1,19 @@
.bd-toc {
position: sticky;
top: 4rem;
height: calc(100vh - 4rem);
overflow-y: auto; }
.bd-toc ul {
list-style: none;
padding-left: 1em;
border-left: 1px solid #eee; }
.bd-toc li {
margin-top: 1em;
margin-bottom: 1em; }
/* TODO: move this to a separate style sheet */
.bigbutton {
margin-left: 1em;
margin-right: 1em; }

17
docs/sitemap.xml Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://router7.org/</loc>
</url>
<url>
<loc>https://router7.org/architecture/</loc>
</url>
<url>
<loc>https://router7.org/installation/</loc>
</url>
</urlset>

View File

@ -0,0 +1,6 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: true
---

19
website/config.toml Normal file
View File

@ -0,0 +1,19 @@
baseURL = "https://router7.org/"
languageCode = "en-us"
title = "router7"
theme = "router7"
disableKinds = ["RSS", "taxonomyTerm"]
publishDir = "../docs/"
enableRobotsTXT = true
[markup.goldmark.renderer]
# Required for e.g. <img> tags in markdown articles.
unsafe = true
[menu]
[[menu.main]]
name = "github"
title = "GitHub"
url = "https://github.com/rtr7/router7"
weight = 60

37
website/content/_index.md Normal file
View File

@ -0,0 +1,37 @@
---
title: "router7: a small home internet router completely written in Go"
menu:
main:
title: "Home"
weight: 10
---
# router7
router7 is a pure-Go implementation of a small home internet router. It comes with all the services required to make a [fiber7 internet connection](https://www.init7.net/en/internet/fiber7/) work (DHCPv4, DHCPv6, DNS, etc.).
Note that this project should be considered a (working!) tech demo. Feature requests will likely not be implemented, and see [CONTRIBUTING.md](CONTRIBUTING.md) for details about which contributions are welcome.
## Motivation
Before starting router7, I was using the [Turris Omnia](https://omnia.turris.cz/en/) router running OpenWrt. That worked fine up until May 2018, when an automated update pulled in a new version of [odhcp6c](https://git.openwrt.org/?p=project/odhcp6c.git;a=shortlog), OpenWrts DHCPv6 client. That version is incompatible with fiber7s DHCP server setup (I think there are shortcomings on both sides).
It was not only quicker to develop my own router than to wait for either side to resolve the issue, but it was also a lot of fun and allowed me to really tailor my router to my needs, experimenting with a bunch of interesting ideas I had.
## Project goals
* Maximize internet connectivity: retain the most recent DHCP configuration across reboots and even after its expiration (chances are the DHCP server will be back before the configuration stops working).
* Unit/integration tests use fiber7 packet capture files to minimize the chance of software changes breaking my connectivity.
* Safe and quick updates
* Auto-rollback of updates which result in loss of connectivity: the diagnostics daemon assesses connectivity state, the update tool reads it and rolls back faulty updates.
* Thanks to kexec, updates translate into merely 13s of internet connectivity loss.
* Easy debugging
* Configuration-related network packets (e.g. DHCP, IPv6 neighbor/router advertisements) are stored in a ring buffer which can be streamed into [Wireshark](https://www.wireshark.org/), allowing for live and retro-active debugging.
* The diagnostics daemon performs common diagnostic steps (ping, traceroute, …) for you.
* All state in the system is stored as human-readable JSON within the `/perm` partition and can be modified.
## Hardware
The reference hardware platform is the [PC Engines™ apu2c4](https://pcengines.ch/apu2c4.htm) system board. It features a 1 GHz quad core amd64 CPU, 4 GB of RAM, 3 Ethernet ports and a DB9 serial port. It conveniently supports PXE boot, the schematics and bootloader sources are available. I recommend the [msata16g](https://pcengines.ch/msata16g.htm) SSD module for reliable persistent storage and the [usbcom1a](https://pcengines.ch/usbcom1a.htm) serial adapter if you dont have one already.
Other hardware might work, too, but is not tested.

View File

@ -0,0 +1,58 @@
---
title: "router7: architecture"
menu:
main:
title: "Architecture"
weight: 20
---
# Architecture
router7 is based on [gokrazy](https://gokrazy.org/): it is an appliance which gets packed into a hard disk image, containing a FAT partition with the kernel, a read-only SquashFS partition for the root file system and an ext4 partition for permanent data.
The individual services can be found in [github.com/rtr7/router7/cmd](https://pkg.go.dev/github.com/rtr7/router7/cmd)
* Each service runs in a separate process.
* Services communicate with each other by persisting state files. E.g., `cmd/dhcp4` writes `/perm/dhcp4/wire/lease.json`.
* A service notifies other services about state changes by sending them signal `SIGUSR1`.
## Configuration files
| File | Consumer(s) | Purpose |
|---|---|---|
| `/perm/interfaces.json` | `netconfigd` | Set IP/MAC addresses of `uplink0` and `lan0` |
| `/perm/portforwardings.json` | `netconfigd` | Configure nftables port forwarding rules |
| `/perm/dhcp6/duid` | `dhcp6` | Set DHCP Unique Identifier (DUID) for obtaining static leases |
## State files
| File | Producer | Consumer(s) | Purpose |
|---|---|---|---|
| `/perm/dhcp4/wire/ack` | `dhcp4` | `dhcp4` | last DHCPACK packet for renewals across restarts |
| `/perm/dhcp4/wire/lease.json` | `dhcp4` | `netconfigd` | Obtained DHCPv4 lease |
| `/perm/dhcp6/wire/lease.json` | `dhcp6` | `netconfigd`, `radvd` | Obtained DHCPv6 lease |
| `/perm/dhcp4d/leases.json` | `dhcp4d` | `dhcp4d`, `dnsd` | DHCPv4 leases handed out (including hostnames) |
## Available ports
| Port | Purpose |
|---|---|
| `<public>:8053` | `dnsd` metrics (forwarded requests)
| `<public>:8066` | `netconfigd` metrics (nftables counters)
| `<private>:80` | gokrazy web interface
| `<private>:67` | `dhcp4d`
| `<private>:58` | `radvd`
| `<private>:53` | `dnsd`
| `<private>:8077` | `backupd` (serve backup.tar.gz)
| `<private>:7733` | `diagd` (perform diagnostics)
| `<private>:5022` | `captured` (serve captured packets)
Heres an example of `cmd/diagd` output:
<img src="https://github.com/rtr7/router7/raw/master/2018-07-14-diagd.png"
width="800" alt="diagd output">
Heres an example of `cmd/netconfigd` metrics when scraped with [Prometheus](https://prometheus.io/) and displayed in [Grafana](https://grafana.com/):
<img src="https://github.com/rtr7/router7/raw/master/2018-07-14-grafana.png"
width="800" alt="metrics in grafana">

View File

@ -0,0 +1,93 @@
---
title: "router7: installation"
menu:
main:
title: "Installation"
weight: 30
---
# Installation
Connect your serial adapter ([usbcom1a](https://pcengines.ch/usbcom1a.htm) works well if you dont have one already) to the apu2c4 and start a program to use it, e.g. `screen /dev/ttyUSB0 115200`. Then, power on the apu2c4 and configure it to do PXE boot:
* Press `F10` to enter the boot menu
* Press `3` to enter setup
* Press `n` to enable network boot
* Press `c` to move mSATA to the top of the boot order
* Press `e` to move iPXE to the top of the boot order
* Press `s` to save configuration and exit
Connect a network cable on `net0`, the port closest to the serial console port:
<img src="https://github.com/rtr7/router7/raw/master/devsetup.jpg"
width="800" alt="router7 development setup">
Next, build a router7 image:
```shell
go get -u github.com/gokrazy/tools/cmd/gokr-packer github.com/rtr7/tools/cmd/...
go get -u -d github.com/rtr7/router7
mkdir /tmp/recovery
GOARCH=amd64 gokr-packer \
-hostname=router7 \
-overwrite_boot=/tmp/recovery/boot.img \
-overwrite_mbr=/tmp/recovery/mbr.img \
-overwrite_root=/tmp/recovery/root.img \
-kernel_package=github.com/rtr7/kernel \
-firmware_package=github.com/rtr7/kernel \
-gokrazy_pkgs=github.com/gokrazy/gokrazy/cmd/ntp \
-serial_console=ttyS0,115200n8 \
github.com/rtr7/router7/cmd/...
```
Run `rtr7-recover -boot=/tmp/recovery/boot.img -mbr=/tmp/recovery/mbr.img -root=/tmp/recovery/root.img` to:
* trigger a reset [if a Teensy with the rebootor firmware is attached](#rebootor)
* serve a DHCP lease to all clients which request PXE boot (i.e., your apu2c4)
* serve via TFTP:
* the PXELINUX bootloader
* the router7 kernel
* an initrd archive containing the rtr7-recovery-init program and mke2fs
* serve via HTTP the boot and root images
* optionally serve via HTTP a backup.tar.gz image containing files for `/perm` (e.g. for moving to new hardware, rolling back corrupted state, or recovering from a disk failure)
* exit once the router successfully wrote the images to disk
## Updates
Run e.g. `rtr7-safe-update -updates_dir=$HOME/router7/updates` to:
* verify the router currently has connectivity, abort the update otherwise
* download a backup archive of `/perm`
* build a new image
* update the router
* wait until the router restored connectivity, roll back the update using `rtr7-recover` otherwise
The update step uses kexec to reduce the downtime to approximately 15 seconds.
## Manual Recovery
Given `rtr7-safe-update`s safeguards, manual recovery should rarely be required.
To manually roll back to an older image, invoke `rtr7-safe-update` via the
`recover.bash` script in the image directory underneath `-updates_dir`, e.g.:
```shell
% cd ~/router7/updates/2018-07-03T17:33:52+02:00
% ./recover.bash
```
## Teensy rebootor {#rebootor}
The cheap and widely-available [Teensy++ USB development board](https://www.pjrc.com/store/teensypp.html) comes with a firmware called rebootor, which is used by the [`teensy_loader_cli`](https://www.pjrc.com/teensy/loader_cli.html) program to perform hard resets.
This setup can be used to programmatically reset the apu2c4 (from `rtr7-recover`) by connecting the Teensy++ to the [apu2c4s reset pins](http://pcengines.ch/pdf/apu2.pdf):
* connect the Teensy++s `GND` pin to the apu2c4 J2s pin 4 (`GND`)
* connect the Teensy++s `B7` pin to the apu2c4 J2s pin 5 (`3.3V`, resets when pulled to `GND`)
You can find a working rebootor firmware .hex file at https://github.com/PaulStoffregen/teensy_loader_cli/issues/38
## Prometheus
See https://github.com/rtr7/router7/tree/master/contrib/prometheus for example
configuration files, and install the [router7 Grafana
Dashboard](https://grafana.com/dashboards/8288).

View File

@ -0,0 +1,2 @@
User-Agent: *
sitemap: https://router7.org/sitemap.xml

View File

@ -0,0 +1,19 @@
.bd-toc {
position: sticky;
top: 4rem;
height: calc(100vh - 4rem);
overflow-y: auto; }
.bd-toc ul {
list-style: none;
padding-left: 1em;
border-left: 1px solid #eee; }
.bd-toc li {
margin-top: 1em;
margin-bottom: 1em; }
/* TODO: move this to a separate style sheet */
.bigbutton {
margin-left: 1em;
margin-right: 1em; }

View File

@ -0,0 +1 @@
{"Target":"sass/sidebar.css","MediaType":"text/css","Data":{}}

1
website/static/CNAME Normal file
View File

@ -0,0 +1 @@
router7.org

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2020 YOUR_NAME_HERE
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,2 @@
+++
+++

View File

@ -0,0 +1,23 @@
.bd-toc {
position: sticky;
top: 4rem;
height: calc(100vh - 4rem);
overflow-y: auto
}
.bd-toc ul {
list-style: none;
padding-left: 1em;
border-left: 1px solid #eee;
}
.bd-toc li {
margin-top: 1em;
margin-bottom: 1em;
}
/* TODO: move this to a separate style sheet */
.bigbutton {
margin-left: 1em;
margin-right: 1em;
}

View File

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
{{- partial "head.html" . -}}
<body>
<div id="content">
<div class="container">
{{- partial "header.html" . -}}
{{ block "main" . }}
{{ end }}
</div>
</div>
{{- partial "footer.html" . -}}
</body>
</html>

View File

@ -0,0 +1,31 @@
{{ define "main" }}
<div class="row">
<div class="col-md-10">
{{- partial "nav.html" . -}}
{{ .Content }}
<h1>list template</h1>
<ul>
{{ range .Pages }}
<li>
<a href="{{ .Permalink }}">{{ .Title }}</a>
</li>
{{ end }}
</ul>
<hr>
<p class="small">
© 2018 Michael Stapelberg and contributors
</p>
</div>
<div class="col-md-2">
<aside class="bd-toc">
{{ .TableOfContents }}
</aside>
</div>
</div>
{{ end }}

View File

@ -0,0 +1,20 @@
{{ define "main" }}
<div class="row">
<div class="col-md-10">
{{- partial "nav.html" . -}}
{{ .Content }}
<hr>
<p class="small">
© 2018 Michael Stapelberg and contributors
</p>
</div>
<div class="col-md-2">
<aside class="bd-toc">
{{ .TableOfContents }}
</aside>
</div>
</div>
{{ end }}

View File

@ -0,0 +1,17 @@
{{ define "main" }}
<div class="row">
<div class="col-md-10">
{{- partial "nav.html" . -}}
{{ .Content }}
<hr>
<p class="small">
© 2018 Michael Stapelberg and contributors
</p>
</div>
<div class="col-md-2">
</div>
</div>
{{ end }}

View File

@ -0,0 +1,7 @@
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</body>
</html>

View File

@ -0,0 +1,14 @@
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" crossorigin="anonymous">
{{ $sass := resources.Get "sass/sidebar.scss" }}
{{ $style := $sass | resources.ToCSS }}
<link rel="stylesheet" href="{{ $style.Permalink }}">
<title>{{ .Title }}</title>
</head>

View File

@ -0,0 +1,15 @@
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">router7</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav ml-auto">
{{ $current := . }}
{{ range .Site.Menus.main }}
{{ $active := $current.IsMenuCurrent "main" . }}
<a class="nav-item nav-link {{ if $active }}active{{ end }}" href="{{ .URL }}">{{ .Title }} {{ if $active }}<span class="sr-only">(current)</span>{{ end }}</a>
{{ end }}
</div>
</div>
</nav>

View File

@ -0,0 +1,21 @@
# theme.toml template for a Hugo theme
# See https://github.com/gohugoio/hugoThemes#themetoml for an example
name = "Distri"
license = "MIT"
licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE"
description = ""
homepage = "http://example.com/"
tags = []
features = []
min_version = "0.41.0"
[author]
name = ""
homepage = ""
# If porting an existing theme
[original]
name = ""
homepage = ""
repo = ""