Compare commits

...

3 Commits

Author SHA1 Message Date
Niklas Ye dcb2a86f9a chart: bind the HTTPRoute to a named gateway listener
Release / test (push) Failing after 7s
Release / build (amd64, darwin) (push) Has been skipped
Release / build (amd64, linux) (push) Has been skipped
Release / build (arm64, darwin) (push) Has been skipped
Release / build (arm64, linux) (push) Has been skipped
Release / docker (push) Has been skipped
Release / chart (push) Has been skipped
Release / release (push) Has been skipped
The route carried no sectionName, so it attached to every listener whose
hostname matched — including the hostname-less plaintext HTTP listener.
On a publicly reachable hostname that means the API accepts bearer tokens
over cleartext.

networking.listener names the listener to bind to. It defaults to empty,
which keeps the previous attach-to-all behaviour.

Also document the Kubernetes install path, which the README omitted.
2026-08-06 12:01:13 +02:00
Niklas Ye be739c319f Check every push and pull request
The release workflow gates a tag, which is the last possible moment: a
commit that breaks the suite stayed green on main until somebody decided
to publish, and then failed the release instead of the change that
caused it.

A CI workflow now runs go vet and go test on pushes to main and on pull
requests.

push is scoped to main rather than left open. A branch pushed as part of
a pull request would otherwise be checked twice, and gh-pages carries the
published chart index with no Go code in it, so go vet there would fail
on a missing go.mod.

Runs for the same ref cancel each other, since a rapid series of pushes
only needs the last one checked.
2026-07-31 07:28:51 +02:00
Niklas Ye 28cf9faf77 Gate the release on vet and tests
CI only ever built and published. The 44 tests in internal/api ran on a
laptop or not at all, so a tag could publish binaries, a container image
and a Helm chart from a commit whose tests had never been run — and the
tests are the only thing holding several documented contracts in place,
including the received_at heartbeat and the alert ordering guard.

A test job now runs go vet and go test, and build, docker and chart all
depend on it. The release job is downstream of build, so a tag that fails
publishes nothing at all rather than publishing three artifacts out of
four.

chart-release.yml is deliberately left alone. It fires on charts/**
pushes and publishes the chart, which contains no Go code and only
references an image tag rather than building one, so gating it on the Go
suite would add a minute to every chart edit for no signal.

This still only runs at release time; nothing checks a push or a pull
request, so a broken commit stays green until somebody tags it.
2026-07-31 07:23:14 +02:00
6 changed files with 87 additions and 1 deletions
+34
View File
@@ -0,0 +1,34 @@
name: CI
# The release workflow gates a tag, which is late: a broken commit sits green
# until somebody decides to publish. This runs the same checks on the way in.
#
# push is scoped to main rather than all branches for two reasons: a branch
# pushed as part of a pull request would otherwise be checked twice, and
# gh-pages holds the published Helm chart index with no Go code in it, so
# `go vet ./...` there would fail on a missing go.mod.
on:
push:
branches: [main]
pull_request:
# A rapid series of pushes only needs the last one checked.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Vet
run: go vet ./...
- name: Test
run: go test ./...
+20
View File
@@ -7,7 +7,25 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
# Gates every publishing job below. A tag that fails here publishes nothing:
# the binaries, the image and the chart are all downstream of it.
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Vet
run: go vet ./...
- name: Test
run: go test ./...
build: build:
needs: test
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
@@ -44,6 +62,7 @@ jobs:
path: terdut-${{ github.ref_name }}-${{ matrix.goos }}-${{ matrix.goarch }} path: terdut-${{ github.ref_name }}-${{ matrix.goos }}-${{ matrix.goarch }}
docker: docker:
needs: test
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
@@ -76,6 +95,7 @@ jobs:
ghcr.io/yeniklas/terdut-server:${{ github.ref_name }} ghcr.io/yeniklas/terdut-server:${{ github.ref_name }}
chart: chart:
needs: test
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
+25
View File
@@ -50,6 +50,31 @@ docker run -p 8080:8080 -v $(pwd)/data:/data \
terdut-server terdut-server
``` ```
### Kubernetes
A Helm chart is published from this repository:
```bash
helm repo add terdut-server https://yeniklas.github.io/terdut-server
helm upgrade --install terdut-server terdut-server/terdut-server \
--namespace terdut-server --create-namespace \
--set networking.hostname=terdut.example.com
```
The chart expects a [Gateway API](https://gateway-api.sigs.k8s.io/) Gateway named `envoy-main` in
the `envoy-gateway-system` namespace to already exist — it renders an `HTTPRoute` against it rather
than an `Ingress`. TLS is terminated at the gateway, so the server itself never sees a certificate.
| Value | Default | Description |
|---|---|---|
| `networking.hostname` | `terdut.example.com` | Hostname the `HTTPRoute` serves |
| `networking.listener` | `""` | Gateway listener (`sectionName`) to bind to. Empty attaches to every matching listener, **including plaintext HTTP** — set it to the HTTPS listener's name to serve TLS only |
| `networking.servicePort` | `8080` | Port the route forwards to; keep in sync with `service.port` |
| `bootstrap.enabled` | `true` | Runs a post-install hook that creates the first user and stores its API key in the `<release>-admin-key` Secret. Already-bootstrapped servers are left alone |
The API key travels in an `Authorization: Bearer` header, so set `networking.listener` whenever the
hostname is reachable outside a trusted network.
--- ---
## Configuration ## Configuration
+1 -1
View File
@@ -2,5 +2,5 @@ apiVersion: v2
name: terdut-server name: terdut-server
description: A Helm chart for Terminal Duty — on-call alert management server description: A Helm chart for Terminal Duty — on-call alert management server
type: application type: application
version: 0.2.0 version: 0.5.0
appVersion: "latest" appVersion: "latest"
@@ -9,6 +9,9 @@ spec:
parentRefs: parentRefs:
- name: envoy-main - name: envoy-main
namespace: envoy-gateway-system namespace: envoy-gateway-system
{{- with .Values.networking.listener }}
sectionName: {{ . | quote }}
{{- end }}
rules: rules:
- backendRefs: - backendRefs:
- name: {{ include "terdut-server.fullname" . }} - name: {{ include "terdut-server.fullname" . }}
+4
View File
@@ -1,6 +1,10 @@
networking: networking:
hostname: "terdut.example.com" hostname: "terdut.example.com"
servicePort: 8080 servicePort: 8080
# Gateway listener to bind the HTTPRoute to. Empty attaches to every matching
# listener, including plaintext HTTP. Set this to the name of the HTTPS
# listener to serve the API over TLS only.
listener: ""
image: image:
repository: ghcr.io/yeniklas/terdut-server repository: ghcr.io/yeniklas/terdut-server