BERT DEV

Documentation for the https://bert133.dev infrastructure

About bert133.dev

This page explains what bert133.dev actually is, and why it's built the way it is. Every choice on this page traces back to a specific constraint the team actually faces — pit-network outages, school-issued iPads, FRC field-network rules. If you just want to start coding, head to Using Coder. If you're curious about the boxes behind the curtain — read on.

What is it?

bert133.dev is a single small computer that the team owns and runs. It lives wherever the team is — the shop during build season, the pit during competition. It hosts everything the programming team needs, all behind one login:

  • Zitadel — accounts and single sign-on (the "Sign in with BERT" button).
  • Forgejo — where the team's code lives.
  • Coder — the cloud-style workspaces that give you VS Code in a browser.
  • Grafana — dashboards at https://dash.bert133.dev for cluster health now, and season-long robot metrics later.
  • A Kubernetes cluster (k3s) under the hood, managed by Flux, that keeps all of the above running and updates them automatically when we push changes to the configuration repo.

The machine itself runs Ubuntu Desktop, not a stripped-down server install. That means in addition to the web services above, the box can be plugged into a monitor and used as a real workstation — for robot simulation, a pit dashboard, driver-station debugging, anything that wants a graphical desktop near the field.

The pieces are deliberately wired together so the team only has to manage one of each thing: one identity provider threads through Coder, Forgejo, and Grafana, so a student logs in once. One git push triggers both the container build and the Flux rollout, so the site you're reading now ships through the same loop as the rest of the cluster. The same physical box is the code host, the workspace host, and the LAN gateway, so a single power cable lights up the whole programming workflow.

Why is it built this way?

Three constraints shaped almost every design decision.

1. It has to work offline, in the pit

Competition venues are loud RF environments and the public internet is often unreachable from the pit. The server is built so that everything important works with the WAN cable unplugged:

  • A local DNS and DHCP server (dnsmasq) runs on the host. It is authoritative for bert133.dev on the LAN, so forge.bert133.dev and code.bert133.dev resolve even with no internet.
  • TLS certificates are issued via Let's Encrypt's DNS-01 challenge during brief windows when the WAN is up. Once issued, they're valid for 90 days — easily covering a multi-week competition trip.
  • Container images are pulled and cached ahead of time. Forgejo, Coder, and the workspace base images all live on local disk by the time we leave the shop.
  • The cluster's CoreDNS is configured to forward bert133.dev queries back to the host's local resolver, so even cluster-internal traffic doesn't depend on outside name servers.

Once bootstrapped, the system can run for weeks without a working internet connection.

2. A student on a school iPad must be able to program the robot

Most students on the team have a school-issued iPad and may not own a laptop. We can't ask them to install Java, the WPILib, or any other heavyweight toolchain — they don't have the device permissions, and the iPad couldn't run it anyway. So:

  • Coder runs in the cluster and serves browser-based VS Code (code-server) over HTTPS.
  • A pre-built frc workspace template uses the WPILib roborio-cross-ubuntu image, which already has Java, Gradle, the cross-compiler, and the simulation runtime installed.
  • Coder's external-auth feature handles Forgejo Git credentials automatically — students authorize once, and git clone / git push just work inside the workspace, no SSH keys to manage.

The result: a student opens Safari on their iPad, taps Sign in with BERT, opens their workspace, and is editing real robot code within a minute. The heavy lifting happens on the server; the iPad is just a screen and keyboard.

3. The network has to comply with FRC field rules

FRC requires the field network to follow the form 10.TE.AM.x/24 — for Team 133, that's 10.1.33.0/24. The server is the LAN gateway and DHCP authority for that subnet:

  • The team number lives in a single Ansible variable; the subnet, server IP (10.1.33.10), and DHCP pool are all derived from it. Forking teams change one number.
  • The LAN interface is pinned with NetworkManager so it keeps 10.1.33.10 even if the shop wifi tries to hand out a different address.
  • Reserved FRC addresses — .1 for the radio, .2 for the roboRIO, .4 for the Driver Station — are kept out of the DHCP pool by default.
  • nftables handles NAT and forwarding so devices on the team subnet can reach the internet when it's available, but unsolicited inbound traffic from outside is dropped.

This means the same server that hosts our code and workspaces can be the network backbone in the pit — no separate router required.

Observability

A kube-prometheus-stack deployment (Prometheus, Alertmanager, node-exporter, kube-state-metrics) runs in the monitoring namespace and feeds Grafana at https://dash.bert133.dev. Sign in with the same BERT account; your role in Zitadel decides whether you land as a Viewer, Editor, or Admin. Prometheus keeps 180 days of history on a 30 GiB volume.

Today, the dashboards cover cluster health only — node CPU and memory, pod counts, namespace state. The longer-term plan is to ingest the robot's WPILib DataLog files after each match and graph performance trends across the whole competition season. That's the niche Grafana fills for us: AdvantageScope is the right tool for picking apart a single match in detail; Grafana is the right tool for asking "is our shooter getting more accurate over the last six events?"

Robot metrics ingestion isn't built yet — that's a future project.

What we deliberately didn't do

A few choices we made not to make, because they would have traded one problem for another:

  • No cloud dependency for day-of-competition workflows. Hosting Coder on a SaaS would have been faster to set up, but a pit with no internet would mean no programming.
  • No second machine for the LAN. Splitting the gateway and the application server onto separate boxes would have been cleaner in theory, but doubles the hardware that has to survive a competition trip.
  • No separate login per service. The easy path is to let every service manage its own accounts and make the student remember four passwords; that turns every "I can't get in" into a mentor support ticket. Standing up Zitadel as a single identity provider was more work up front but pushes the per-student account problem down to one place.

The goal at every step was to solve the problem in front of us without creating a new one for next year's students to inherit.

Where is the source?

Everything is open. The infrastructure repo lives at https://forge.bert133.dev/bert/server; this site's source lives at https://forge.bert133.dev/bert/site. If your team wants to fork any of it, you're welcome to.

Where does this site come from?

The site you're reading is itself a small piece of the same system: a static site built with Seite, packaged into a container by Forgejo Actions, and rolled out to the cluster by Flux. When a mentor edits a page and pushes to main, the new version is live in about three minutes — same loop the rest of the infrastructure uses.