vinhedo1 build runbook

Host: vinhedo1.apibr.com144.22.174.243 · Ubuntu 24.04 · OCI VM.Standard.E2.1.Micro (1 GB RAM, 2 vCPU) · rebuilt August 2026

vinhedo1 is the fleet baseline plus a WireGuard server. This page covers only what is specific to it; follow the baseline for everything else, in this order:

  1. Baseline Phase 0 — DNS first. The rebuild gave vinhedo1 a new public IP. Until the A record points at 144.22.174.243, PiVPN’s endpoint, Certbot and every existing WireGuard client config are broken. Also open 51820/udp in the OCI Security List; the other hosts don’t need it.
  2. Baseline Phases 1–6 (base system, SSH, UFW, Fail2Ban, NGINX, Certbot). In Phase 3.3 include the 51820/udp rule; in Phase 4 keep the WireGuard subnet (10.141.230.0/24 — check ip -4 addr show wg0 after PiVPN picks it) in ignoreip so a fumbled login over the VPN can’t ban the tunnel.
  3. PiVPN + WireGuard — below. Must come after UFW is enabled.
  4. Baseline Phases 7–9 (collector, updates, verification) plus the extra checks at the end of this page.

PiVPN + WireGuard

Why it exists: GitHub Actions runners need to reach the MariaDB on the cPanel host at Nuvem Hospedagem (osasco, 177.73.237.236), which only accepts connections from whitelisted IPs. vinhedo1 is that whitelisted IP; runners connect to it over WireGuard and get NATed out.

PiVPN works on Ubuntu 24.04 — WireGuard is in the kernel, so no DKMS module and the old wireguard-dkms install failure doesn’t apply.

Order matters: install PiVPN after UFW is enabled. PiVPN detects UFW and writes the NAT/masquerade rules into /etc/ufw/before.rules plus a route allow rule itself. Install it before UFW and you get a tunnel with no routing.

wget -qO /tmp/install-pivpn.sh https://install.pivpn.io
less /tmp/install-pivpn.sh          # actually read it
sudo bash /tmp/install-pivpn.sh

Wizard answers for this box:

Prompt Answer
Static IP Accept the current DHCP address — OCI hands out the same one; don’t let PiVPN rewrite netplan
Local user ubuntu
VPN type WireGuard
Port 51820
DNS provider Cloudflare or Quad9 (this box is not a DNS server)
Public endpoint DNS namevinhedo1.apibr.com
Unattended upgrades Yes (baseline Phase 8.1 overrides the policy afterwards anyway)
Reboot Yes — then re-check sudo ufw status and sudo wg show

Choosing the DNS name over the IP is the one that matters: client configs then survive the next rebuild, which is exactly what bit the GitHub Actions database views this time.

Create clients

One client per consumer, named for what it is — gha_<project> for a GitHub Actions workflow, apibr_<service> for an API BR service, a person’s device by name (iphone_beatriz). Never share one config between two consumers; you lose the ability to revoke or debug them apart.

sudo pivpn add -n gha_pm
sudo pivpn -qr gha_pm                # or grab /home/ubuntu/configs/gha_pm.conf
sudo pivpn -c                        # list clients + last handshake
sudo pivpn -r gha_pm                 # revoke

Route MariaDB through the tunnel

In the client config, set AllowedIPs to the tunnel subnet plus the database host only — split tunnel, so the runner doesn’t push all its traffic through a 1 GB micro instance:

[Peer]
AllowedIPs = 10.141.230.0/24, 177.73.237.236/32
Endpoint   = vinhedo1.apibr.com:51820
PersistentKeepalive = 25

WireGuard routes by IP, not name. If the client connects to the database by hostname, pin it in /etc/hosts on the runner or use the IP — otherwise the packets bypass the tunnel entirely.

Remember to whitelist 144.22.174.243 in cPanel’s Remote MySQL on the Nuvem Hospedagem side (the old vinhedo1 IP is still listed there until you remove it).

Verify forwarding and NAT

sysctl net.ipv4.ip_forward                        # 1
sudo ufw show added | grep route                  # route allow in on wg0 out on ens3
sudo iptables -t nat -L POSTROUTING -n -v         # a MASQUERADE for 10.141.230.0/24
sudo ufw status numbered | grep 51820

PiVPN adds a ufw route allow rule for wg0 rather than changing the global forward policy, so DEFAULT_FORWARD_POLICY in /etc/default/ufw stays DROP and that is fine. Only if the route rule is missing and you’d rather not add one:

sudo sed -i 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/' /etc/default/ufw
sudo ufw reload

If NAT is missing, add it at the top of /etc/ufw/before.rules and sudo ufw reload:

*nat
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 10.141.230.0/24 -o ens3 -j MASQUERADE
COMMIT

ip route get 1.1.1.1 tells you the real interface name — it is ens3 on this fleet.


Grimoire API (later)

vinhedo1 will host Grimoire API (.NET) behind the baseline NGINX site. When it’s deployed, the proxy block to add inside the vinhedo1.apibr.com server block is just:

location /api/ {
    proxy_pass http://127.0.0.1:5000/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Then add the unit name to /etc/projects-monitor/services.conf so the collector reports it (the setup script’s auto-detection only runs on first provision and already matches grimoire* unit names).


Verification

On top of baseline Phase 9:

sudo wg show                                  # wg0 up, listening on 51820
sudo pivpn -c                                 # client listed, recent handshake
sysctl net.ipv4.ip_forward                    # 1
sudo systemctl is-enabled wg-quick@wg0
sudo ufw status verbose | grep 51820

From a GitHub Actions runner (or any client with the config loaded), a connection to 177.73.237.236:3306 should succeed, and sudo pivpn -c on vinhedo1 should show the handshake time update.


This site uses Just the Docs, a documentation theme for Jekyll.