haproxy golang to render from a template with a csv file so I don't have to create it myself (Too old for that)
  • Go 69.4%
  • Jinja 30.6%
Find a file
minh7a6 0dce31fdec
All checks were successful
Build Go binary / build (push) Successful in 1m59s
Added stuff
2026-10-26 23:51:47 -07:00
.forgejo/workflows set retention for 10 days 2026-10-25 13:40:01 -07:00
template First commit 2026-10-25 13:25:16 -07:00
.gitignore First commit 2026-10-25 13:25:16 -07:00
go.mod First commit 2026-10-25 13:25:16 -07:00
go.sum First commit 2026-10-25 13:25:16 -07:00
README.md Added stuff 2026-10-26 23:51:47 -07:00
render_haproxy.go First commit 2026-10-25 13:25:16 -07:00

HAProxy config renderer

Generate HAProxy HTTP/HTTPS routes and backends from a two-column CSV and template/haproxy.cfg.j2. Every CSV row creates an HTTP backend on port 80 and an HTTPS backend on port 443, plus the corresponding frontend routing rules.

Build

Install Go 1.25 or newer, then run these commands from this directory:

go mod download
go build -o haproxy-render .

The renderer uses Gonja, a Go implementation of Jinja, to evaluate the loops and expressions in the supplied template. The built binary needs the CSV and template files at runtime, but does not need Go installed.

Forgejo build

.forgejo/workflows/build.yml builds a Linux amd64 binary on pushes, pull requests, and manual runs. Enable Actions for the repository and provide a runner with the ubuntu-latest label and a Linux environment supporting Node.js actions. The Go version is read from go.mod.

Download the haproxy-render-linux-amd64 artifact from the workflow run. Forgejo downloads it as a ZIP containing haproxy-render-linux-amd64.tar.gz. Unzip the download first, then extract the tar archive, which includes the executable and template/haproxy.cfg.j2. Supply your own CSV when running from the extracted directory:

unzip haproxy-render-linux-amd64.zip
tar -xzf haproxy-render-linux-amd64.tar.gz
./haproxy-render -csv /path/to/records.csv -output haproxy.generated.cfg

Use the actual downloaded filename in the unzip command if your browser saves it under a different name. Passing the outer ZIP directly to tar -xzf fails because that command expects gzip data.

The artifact step uses the v3 upload action supported by Forgejo Actions.

CSV format

Create records.example.csv using the following example for the six web routes:

url,internal_url
jellyfin.lab.dangleminh.com,jellyfin.lab.dangleminh.com
auth.lab.dangleminh.com,auth.lab.dangleminh.com
s3.lab.dangleminh.com,s3.lab.dangleminh.com
git.lab.dangleminh.com,192.168.10.200
coder.lab.dangleminh.com,192.168.10.200
dangleminh.com,dangleminh.com

url is the public hostname matched against HTTPS SNI and the HTTP Host header. A full URL is also accepted; only its hostname is used. Matching is exact and case insensitive. Duplicate public hostnames are rejected.

Generated backend and ACL names replace hostname dots with underscores, for example host_jellyfin_lab_dangleminh_com_https. Routing hostnames and backend addresses retain their dots.

internal_url supplies the backend destination:

Input example HTTP target HTTPS target
192.168.10.200 192.168.10.200:80 192.168.10.200:443
2001:db8::10 [2001:db8::10]:80 [2001:db8::10]:443
origin.example.com origin.example.com:80 origin.example.com:443
https://origin.example.com/path origin.example.com:80 origin.example.com:443
origin.example.com:8443 origin.example.com:8443 origin.example.com:8443

An explicit port overrides both backend ports. Use this only when intentional: the HTTP backend still sends plain HTTP, and the HTTPS backend still passes through TLS. For an IPv6 address with a port, use [2001:db8::10]:8443. URL schemes do not select the backend transport; paths, queries and fragments do not rewrite requests. URLs containing credentials are rejected.

The header is optional. With a header, use url,internal_url on the first line. The first two columns are used; extra columns are ignored.

Generate a config

Render to a separate file for review:

./haproxy-render -csv records.example.csv -output haproxy.generated.cfg

Or run directly through Go:

go run . -csv records.example.csv -output haproxy.generated.cfg

To write the final haproxy.cfg:

./haproxy-render -csv records.example.csv -output haproxy.cfg

The output file is overwritten if it already exists. Rendering does not install the config or reload HAProxy.

Flag Default Purpose
-csv Required Input CSV path
-template template/haproxy.cfg.j2 Template path
-output haproxy.cfg Generated config path
-ssh-target 192.168.10.200 IPv4 or IPv6 SSH backend address

Paths are relative to the working directory. Use ./haproxy-render -h for help. SSH is separate from the CSV: the template forwards port 2222 to the IP supplied by -ssh-target, also on port 2222.

Routing and DNS

HTTPS uses TCP passthrough. The origin must serve TLS and a certificate for the public hostname; HAProxy does not terminate TLS or rewrite the client's SNI. HTTP requests redirect to HTTPS except for /.well-known/acme-challenge/, which is sent to the matching HTTP backend. The original HTTP Host header is retained.

The example CSV is valid, but its hostname targets must resolve to reachable origin servers. If a target resolves to this HAProxy listener, forwarding can loop back into HAProxy. Use an origin IP or a separate origin DNS name when the public hostname points to the proxy. The git and coder targets require connectivity from HAProxy to 192.168.10.200 on ports 80 and 443.

The generator does not perform DNS lookups. The template configures HAProxy's runtime DNS resolvers as 9.9.9.9 and 1.1.1.1; replace these with internal DNS servers if your backend names are private. Server lines include resolvers mynameservers only for hostname targets. Literal IPv4/IPv6 targets, including IPs inside URLs and the SSH target, omit it. All targets retain check for health checks. Hostname targets are not restricted to AAAA records. HAProxy documents IPv6 as the default runtime DNS family preference, which is not an IPv6-only guarantee. See the HAProxy DNS options.

Validate before use

On the intended HAProxy host, run:

haproxy -c -f haproxy.generated.cfg

The template retains the original system paths, haproxy user/group, and error files under /etc/haproxy/errors/. Those must exist on the target host. A syntax check does not establish origin connectivity or correct DNS; check these from the HAProxy host as well before installing the config through your usual process.