Skip to content

Commit af080dc

Browse files
committed
Fixed broken release tests, updated go version in docs
1 parent ddc9300 commit af080dc

4 files changed

Lines changed: 54 additions & 18 deletions

File tree

CHANGELOG.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
99
### Added
1010

1111
- New documented deployment methods for Tailscale and EdgeVPN, with example `docker-compose.yml` files and wrapper scripts in `docker/tailscale/` and `docker/edgevpn/`.
12-
- New builder command `qu builder`, which generates a standalone HTML alert-template builder
12+
- New builder command `qu builder`, which generates a standalone HTML alert-template builder. The builder ships with a rich expression palette (more logic operators, comparison helpers, and `with` blocks) so you can author multi-branch subject / body templates and copy the rendered Go `text/template` directly into `qu alert add … --body-file`.
1313
- **Custom DNS resolvers for check target resolution.** Every probe
1414
(HTTP / TCP / TLS / ICMP / DNS) can now bypass the host's stub
1515
resolver and the local cache by pointing at explicit DNS servers.
@@ -49,6 +49,38 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
4949

5050
- **TUI modals now scroll** when a form is taller than the terminal (e.g. the SMTP "Add alert" form on a short window). The view auto-centres on the focused field and shows `↑/↓ N more` indicators when content is clipped above or below. #24
5151
- TUI main page no longer overflows on very short terminals — the body shrinks all the way down to a single row instead of pinning to 5.
52+
- Release workflows on both Gitea and GitHub now treat `-beta` tags as pre-releases in addition to `-rc`, so a `v0.3.0-beta1` push lands as a pre-release without manual flagging.
53+
54+
### Upgrade notes
55+
56+
Existing clusters keep working with no operator action beyond rolling
57+
out the new binary — the three new fields (`checks[].disabled`,
58+
`checks[].resolvers`, `cluster.yaml.resolvers`) are all `omitempty`,
59+
and `alerts[].disabled` is the negation of "enabled" so unset entries
60+
stay enabled.
61+
62+
Things to know during the rollout:
63+
64+
- **Don't issue toggle or resolver edits mid-rollout.** A v0.2.x
65+
daemon doesn't know `MutationSetResolvers` and will reject it
66+
outright with "unknown mutation kind" if it holds master when the
67+
CLI submits. Worse: if a v0.2.x master applies any
68+
`MutationReplaceConfig` from the manual-edit watcher (an operator
69+
saving `cluster.yaml` on an upgraded follower), it will write the
70+
file back **without** the new fields — silently dropping any
71+
`disabled` flags, per-check `resolvers`, or `cluster.yaml.resolvers`
72+
that an upgraded node had just contributed. Finish the rolling
73+
upgrade everywhere, then start using `qu check enable|disable`,
74+
`qu alert enable|disable`, `qu cluster resolvers …`, and per-check
75+
`--resolvers`. Pause hand-edits to `cluster.yaml` during the
76+
upgrade window.
77+
- **Existing `dns_resolver` on DNS checks is preserved.** It now acts
78+
as a legacy single-entry fallback used only when both
79+
`checks[].resolvers` and `cluster.yaml.resolvers` are empty. No
80+
config change required to keep current DNS-check behaviour.
81+
- **ICMP behaviour is unchanged when no resolver override is set.**
82+
pro-bing still does its own lookup against the system resolver for
83+
checks that don't configure `resolvers`.
5284

5385
## [v0.2.3] — 2026-05-19
5486

@@ -344,4 +376,5 @@ Initial public release.
344376
[v0.2.0]: https://git.cer.sh/axodouble/quptime/releases/tag/v0.2.0
345377
[v0.2.1]: https://git.cer.sh/axodouble/quptime/releases/tag/v0.2.1
346378
[v0.2.2]: https://git.cer.sh/axodouble/quptime/releases/tag/v0.2.2
347-
[v0.2.3]: https://git.cer.sh/axodouble/quptime/releases/tag/v0.2.3
379+
[v0.2.3]: https://git.cer.sh/axodouble/quptime/releases/tag/v0.2.3
380+
[v0.3.0]: https://git.cer.sh/axodouble/quptime/releases/tag/v0.3.0

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ and the daemon will replicate the edit cluster-wide.
139139

140140
## Build
141141

142-
Requires Go 1.24.2 or newer.
142+
Requires Go 1.26.3 or newer.
143143

144144
```sh
145145
go build -o qu ./cmd/qu

docs/installation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ What it does:
8282

8383
## Build from source
8484

85-
Requires Go 1.24.2 or newer.
85+
Requires Go 1.26.3 or newer.
8686

8787
```sh
8888
# Either remote — Gitea is canonical, GitHub is a push-mirror.

internal/checks/resolver_test.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package checks
33
import (
44
"context"
55
"net"
6-
"sync/atomic"
76
"testing"
87
"time"
98

@@ -101,25 +100,26 @@ func TestResolverFailover_DialLevel(t *testing.T) {
101100
badAddr := bad.Addr().String()
102101
bad.Close() // released; subsequent dials will be refused
103102

104-
// Working listener — we just accept and immediately close, so the
105-
// dial succeeds even though it'll be useless for DNS. That's enough
106-
// to prove the failover loop.
103+
// Working listener — we accept once, signal via channel, then close.
104+
// Signalling through a channel is deterministic; observing a counter
105+
// after Dial returns races with the Accept goroutine still running.
107106
good, err := net.Listen("tcp", "127.0.0.1:0")
108107
if err != nil {
109108
t.Fatal(err)
110109
}
111110
defer good.Close()
112111
goodAddr := good.Addr().String()
113-
var accepted atomic.Int32
112+
accepted := make(chan struct{}, 1)
114113
go func() {
115-
for {
116-
conn, err := good.Accept()
117-
if err != nil {
118-
return
119-
}
120-
accepted.Add(1)
121-
conn.Close()
114+
conn, err := good.Accept()
115+
if err != nil {
116+
return
117+
}
118+
select {
119+
case accepted <- struct{}{}:
120+
default:
122121
}
122+
conn.Close()
123123
}()
124124

125125
r := resolverFor([]string{badAddr, goodAddr}, 1*time.Second)
@@ -134,8 +134,11 @@ func TestResolverFailover_DialLevel(t *testing.T) {
134134
t.Fatalf("Dial fell through both: %v", err)
135135
}
136136
conn.Close()
137-
if accepted.Load() == 0 {
138-
t.Errorf("expected the working listener to be the one that accepted")
137+
select {
138+
case <-accepted:
139+
// ok — the working listener was the one that accepted
140+
case <-time.After(2 * time.Second):
141+
t.Errorf("good listener did not accept within 2s; failover loop did not reach it")
139142
}
140143
}
141144

0 commit comments

Comments
 (0)