Compare commits
4 Commits
56d97f13af
..
v1.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 9080826b06 | |||
| 39a0875d58 | |||
| 320be34970 | |||
| 25aefe90d6 |
+6
-15
@@ -3,7 +3,7 @@
|
||||
# Notes for anyone editing this file:
|
||||
#
|
||||
# Custom runner image
|
||||
# Every job runs inside `gitea.dcglab.co.uk/steve/ci-runner-go`
|
||||
# Every job runs inside `docker.dcglab.co.uk/public/ci-runner-go:latest`
|
||||
# (recipe: https://gitea.dcglab.co.uk/steve/ci/src/branch/main/images/ci-runner-go).
|
||||
# That image already ships:
|
||||
# * Go on PATH at /usr/local/go/bin (so `actions/setup-go` is
|
||||
@@ -11,8 +11,8 @@
|
||||
# otherwise re-download Go on every job)
|
||||
# * Node.js + npm (used by docs / e2e workflows)
|
||||
# * Docker CLI, Buildx, Compose v2 (used by docker-build steps)
|
||||
# When bumping the Go floor, push a new ci-runner-go image with
|
||||
# the matching Go version and bump the date pin in IMAGE below.
|
||||
# The runner image is intentionally tracked via `latest`: it is shared CI
|
||||
# infrastructure, and updates should propagate to consuming repositories.
|
||||
#
|
||||
# Self-hosted runner expectations
|
||||
# Each runner host bind-mounts persistent volumes for
|
||||
@@ -71,10 +71,7 @@ jobs:
|
||||
name: Test (${{ matrix.name }})
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker.dcglab.co.uk/ci-runner-go:2026-05-15
|
||||
credentials:
|
||||
username: ${{ secrets.ZOT_USERNAME }}
|
||||
password: ${{ secrets.ZOT_PASSWORD }}
|
||||
image: docker.dcglab.co.uk/public/ci-runner-go:latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -110,10 +107,7 @@ jobs:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker.dcglab.co.uk/ci-runner-go:2026-05-15
|
||||
credentials:
|
||||
username: ${{ secrets.ZOT_USERNAME }}
|
||||
password: ${{ secrets.ZOT_PASSWORD }}
|
||||
image: docker.dcglab.co.uk/public/ci-runner-go:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: golangci/golangci-lint-action@v7
|
||||
@@ -130,10 +124,7 @@ jobs:
|
||||
name: Build (${{ matrix.goos }}/${{ matrix.goarch }})
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker.dcglab.co.uk/ci-runner-go:2026-05-15
|
||||
credentials:
|
||||
username: ${{ secrets.ZOT_USERNAME }}
|
||||
password: ${{ secrets.ZOT_PASSWORD }}
|
||||
image: docker.dcglab.co.uk/public/ci-runner-go:latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
e2e:
|
||||
name: Playwright vs docker-compose
|
||||
runs-on: ubuntu-latest
|
||||
container: gitea.dcglab.co.uk/steve/ci-runner-go:2026-05-08
|
||||
container: docker.dcglab.co.uk/public/ci-runner-go:latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
# plus install.sh / install.ps1 / the systemd unit baked in under
|
||||
# /opt/restic-manager/dist (the read-only fallback path the server
|
||||
# handlers use when <DataDir>/... is empty).
|
||||
# * Pushes to zot OCI registry (docker.dcglab.co.uk).
|
||||
# * Pushes to the public namespace in the zot OCI registry
|
||||
# (docker.dcglab.co.uk/public/restic-manager). Pulls are anonymous;
|
||||
# pushes remain authenticated.
|
||||
#
|
||||
# Tag fan-out
|
||||
# * tag push: :vX.Y.Z, :X.Y, :X
|
||||
@@ -29,7 +31,7 @@ on:
|
||||
|
||||
env:
|
||||
REGISTRY: docker.dcglab.co.uk
|
||||
IMAGE_NAME: restic-manager
|
||||
IMAGE_NAME: public/restic-manager
|
||||
|
||||
# Force bash as the default shell — see ci.yml header.
|
||||
defaults:
|
||||
@@ -41,10 +43,7 @@ jobs:
|
||||
name: Build + push image
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: docker.dcglab.co.uk/ci-runner-go:2026-05-15
|
||||
credentials:
|
||||
username: ${{ secrets.ZOT_USERNAME }}
|
||||
password: ${{ secrets.ZOT_PASSWORD }}
|
||||
image: docker.dcglab.co.uk/public/ci-runner-go:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
||||
+40
-1
@@ -6,6 +6,40 @@ and the project follows [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.2.0] - 2026-08-22
|
||||
|
||||
### Added
|
||||
|
||||
- Fleet-wide agent updates are now discoverable from Settings and the
|
||||
dashboard, with arbitrary host subsets, name/tag/version/eligibility
|
||||
filtering, explicit exclusion reasons, server-side membership validation,
|
||||
and a canary-first pause after the first verified reconnect. Halted rolls
|
||||
expose retry and resume actions. ([#43])
|
||||
- Running jobs with no recent persisted activity are detected server-side and
|
||||
surfaced through deduplicated alerts. Kind-aware thresholds protect normal
|
||||
long-running work, terminal jobs self-resolve, and Prometheus exports stuck
|
||||
job count and oldest inactivity age. ([#41])
|
||||
- Snapshot projections now retain host-level refresh timestamps—including
|
||||
authoritative empty reports—expose a derived stale flag, refresh after
|
||||
backup, forget, and prune, and support an explicit operator reconciliation
|
||||
endpoint. ([#40])
|
||||
|
||||
### Fixed
|
||||
|
||||
- Raised the bounded WebSocket read limit on both peers so ordinary large
|
||||
snapshot reports and restic events no longer disconnect otherwise healthy
|
||||
agents. Regression coverage exercises payloads beyond the library's former
|
||||
32 KiB default in both directions. ([#44])
|
||||
- Fleet-update timeout verification now performs a final authoritative read of
|
||||
the agent version delivered by the reconnect `hello`, avoiding a poll/deadline
|
||||
race and reporting the last observed version when verification fails. ([#43])
|
||||
|
||||
### Changed
|
||||
|
||||
- CI runner images and release images use the anonymous-pull `public`
|
||||
namespace at `docker.dcglab.co.uk`; registry authentication remains required
|
||||
only for publishing.
|
||||
|
||||
## [1.1.1] - 2026-08-22
|
||||
|
||||
### Fixed
|
||||
@@ -140,7 +174,8 @@ with a web UI, JSON API, and self-updating agent fleet.
|
||||
go vet, golangci-lint).
|
||||
- Threat model published (`docs/threat-model.md`).
|
||||
|
||||
[Unreleased]: https://gitea.dcglab.co.uk/steve/restic-manager/compare/v1.1.1...HEAD
|
||||
[Unreleased]: https://gitea.dcglab.co.uk/steve/restic-manager/compare/v1.2.0...HEAD
|
||||
[1.2.0]: https://gitea.dcglab.co.uk/steve/restic-manager/compare/v1.1.1...v1.2.0
|
||||
[1.1.1]: https://gitea.dcglab.co.uk/steve/restic-manager/compare/v1.1.0...v1.1.1
|
||||
[1.1.0]: https://gitea.dcglab.co.uk/steve/restic-manager/releases/tag/v1.1.0
|
||||
[1.0.0]: https://gitea.dcglab.co.uk/steve/restic-manager/releases/tag/v1.0.0
|
||||
@@ -148,3 +183,7 @@ with a web UI, JSON API, and self-updating agent fleet.
|
||||
[#37]: https://gitea.dcglab.co.uk/steve/restic-manager/issues/37
|
||||
[#36]: https://gitea.dcglab.co.uk/steve/restic-manager/issues/36
|
||||
[#34]: https://gitea.dcglab.co.uk/steve/restic-manager/issues/34
|
||||
[#40]: https://gitea.dcglab.co.uk/steve/restic-manager/issues/40
|
||||
[#41]: https://gitea.dcglab.co.uk/steve/restic-manager/issues/41
|
||||
[#43]: https://gitea.dcglab.co.uk/steve/restic-manager/issues/43
|
||||
[#44]: https://gitea.dcglab.co.uk/steve/restic-manager/issues/44
|
||||
|
||||
@@ -61,9 +61,20 @@ type Engine struct {
|
||||
stuckThresholds map[string]time.Duration
|
||||
|
||||
closeOnce sync.Once
|
||||
notifyWG sync.WaitGroup
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (e *Engine) dispatchNotification(ctx context.Context, payload notification.Payload) {
|
||||
e.notifyWG.Add(1)
|
||||
go func() {
|
||||
defer e.notifyWG.Done()
|
||||
e.hub.Dispatch(ctx, payload)
|
||||
}()
|
||||
}
|
||||
|
||||
func (e *Engine) waitNotifications() { e.notifyWG.Wait() }
|
||||
|
||||
// NewEngine builds the engine. agentOfflineFloor + tickPeriod default
|
||||
// to 15min and 60s respectively when zero.
|
||||
func NewEngine(st *store.Store, hub *notification.Hub) *Engine {
|
||||
|
||||
@@ -60,7 +60,7 @@ func (e *Engine) raiseAndNotify(ctx context.Context, hostID, kind, dedupKey, sev
|
||||
if err == nil {
|
||||
hostName = host.Name
|
||||
}
|
||||
go e.hub.Dispatch(ctx, notification.Payload{
|
||||
e.dispatchNotification(ctx, notification.Payload{
|
||||
Event: notification.EventRaised,
|
||||
AlertID: id,
|
||||
Severity: severity,
|
||||
@@ -85,7 +85,7 @@ func (e *Engine) Acknowledge(ctx context.Context, alertID, userID string, when t
|
||||
return nil //nolint:nilerr
|
||||
}
|
||||
p := alertPayload(ctx, e.store, notification.EventAcknowledged, a)
|
||||
go e.hub.Dispatch(context.WithoutCancel(ctx), p)
|
||||
e.dispatchNotification(context.WithoutCancel(ctx), p)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func (e *Engine) Resolve(ctx context.Context, alertID string, when time.Time) er
|
||||
return nil
|
||||
}
|
||||
p := alertPayload(ctx, e.store, notification.EventResolved, a)
|
||||
go e.hub.Dispatch(context.WithoutCancel(ctx), p)
|
||||
e.dispatchNotification(context.WithoutCancel(ctx), p)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ func (e *Engine) resolveAndNotify(ctx context.Context, hostID, kind, dedupKey st
|
||||
if a.Kind != kind || a.DedupKey != dedupKey {
|
||||
continue
|
||||
}
|
||||
go e.hub.Dispatch(ctx, notification.Payload{
|
||||
e.dispatchNotification(ctx, notification.Payload{
|
||||
Event: notification.EventResolved,
|
||||
AlertID: a.ID,
|
||||
Severity: a.Severity,
|
||||
|
||||
@@ -25,6 +25,7 @@ func setupEngine(t *testing.T) (*Engine, *store.Store, string) {
|
||||
aead, _ := crypto.NewAEAD(key)
|
||||
hub := notification.NewHub(st, aead, "https://rm.example")
|
||||
eng := NewEngine(st, hub)
|
||||
t.Cleanup(eng.waitNotifications)
|
||||
hostID := ulid.Make().String()
|
||||
if err := st.CreateHost(context.Background(), store.Host{
|
||||
ID: hostID, Name: "alfa-01", OS: "linux", Arch: "amd64",
|
||||
|
||||
Reference in New Issue
Block a user