diff --git a/.cursor/rules/git-flow.mdc b/.cursor/rules/git-flow.mdc index 9160f87..e0a04b7 100644 --- a/.cursor/rules/git-flow.mdc +++ b/.cursor/rules/git-flow.mdc @@ -1,17 +1,14 @@ --- -description: Git flow — develop for work, main for production; releases via tags. +description: Git flow — PRs only; topic → develop → main; tags from main alwaysApply: true --- -# Git flow (GitHub): develop → main and tagged releases +# Git flow (GitHub): PRs into develop, then develop → main, then tags -- **Branches**: Day-to-day work happens on `develop`. The `main` branch is production and must only be updated from `develop` (merge or squash) when the code is ready for release. -- **Releases**: Before publishing to GitHub or releasing a version: - 1. Ensure everything is committed on `develop` and **all tests pass** (`make test` or `go test ./...`). - 2. Merge `develop` into `main` (or open a Pull Request develop → main). - 3. On `main`, create an annotated tag with the version number (e.g. `v1.0.0`): - `git tag -a v1.0.0 -m "Release 1.0.0"` - and push the tag: - `git push origin v1.0.0` -- **Versions**: Use semantic versioning (MAJOR.MINOR.PATCH) for tags, e.g. `v1.2.3`. -- **Never** commit directly to `main` for features or fixes; everything must go through `develop`, then merge into `main` at release time. +- **No direct pushes** to `develop` or `main`. Do not `git push origin develop` or `git push origin main`. Do not commit on those branches. +- **Day-to-day:** From an **up-to-date `develop`**, create a topic branch (`feature/…`, `fix/…`, `chore/…`, `security/…`, `docs/…`). Commit there, push **that** branch, open a **PR into `develop`**. Wait for **green CI**, merge, **delete** the topic branch. Pull `develop`. Never PR a topic branch into `main`. +- **Ship / production:** Only when releasing: open a **PR `develop` → `main`**, wait for **green CI**, merge. Pull `main`. +- **Tags:** After `main` has the merge, create an **annotated** semver tag on `main` (e.g. `v1.2.3`) and push the tag (`git push origin v1.2.3`). Tagging `develop` or a topic branch is wrong. A merge to `main` without a tag does not publish GHCR/GoReleaser. +- **Release checks:** Before the develop → main PR / tag, **`make release-check`** must pass (see `release-tests.mdc`). Do not push a release tag if CI or Security is red. +- **Afterward:** checkout `develop` and pull. If `main` diverged (e.g. squash merge), sync with a **PR `main` → `develop`**, not a direct push. +- **Versions:** Semantic versioning for tags (`vMAJOR.MINOR.PATCH`). The version cut is a **dedicated PR** (`chore/release-X.Y.Z` → `develop`) — **do not mix** with features, fixes, or dependency bumps. Never bump version on `main`. That PR must include: `VERSION`, CHANGELOG `[Unreleased]` → `[X.Y.Z]`, README badge (and install URLs that pin the tag), **VHS** `docs/demo.gif` (`make install && bash -c "vhs docs/demo.tape"`), man **`.TH`** (`contrib/man/man1/pgwd.1`), **`make port-freebsd-sync`** and **`make port-openbsd-sync`**. Official ports-tree filings (Bugzilla, ports@) are **out of this flow** — personal follow-up, not a release step. diff --git a/.cursor/rules/release-tests.mdc b/.cursor/rules/release-tests.mdc index 542ea06..75dd488 100644 --- a/.cursor/rules/release-tests.mdc +++ b/.cursor/rules/release-tests.mdc @@ -16,7 +16,7 @@ Before merging to `main` or creating a release (tag and `make release`), run **` **These are NOT optional.** `make release` depends on `release-check`; if any step fails, the release is blocked. -**When VERSION changes:** (1) Regenerate `docs/demo.gif` with `make install && bash -c "vhs docs/demo.tape"` (run from repo root). The demo shows `pgwd -version`; the GIF must reflect the new version. See `docs/README.md` for VHS setup. (2) Update `contrib/man/man1/pgwd.1` — `.TH` line (date and version). See `.cursor/rules/man-page-sync.mdc`. +**When VERSION changes** (same `chore/release-*` PR): (1) Regenerate `docs/demo.gif` with `make install && bash -c "vhs docs/demo.tape"` from repo root (`bash -c` so zsh does not break VHS). The demo shows `pgwd -version`. See `docs/README.md`. (2) Update `contrib/man/man1/pgwd.1` — `.TH` date and version. See `man-page-sync.mdc`. (3) **`make port-freebsd-sync`** and **`make port-openbsd-sync`** so this repo’s BSD port Makefiles match `VERSION`. Do **not** treat Bugzilla or ports@ as part of the release. CI runs lint and tests. The Security workflow runs govulncheck and Grype on the image; both must pass (no `continue-on-error`). Do not push a release tag if CI or Security is red. diff --git a/AGENTS.md b/AGENTS.md index d8116d6..439075c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -36,16 +36,16 @@ Context and instructions for AI coding agents working on **pgwd** (Postgres Watc ## Git flow -- **Branches:** Work on `develop`. `main` is production and is only updated from `develop` at release time (see `.cursor/rules/git-flow.mdc`). +- **Branches:** No direct pushes to `develop` or `main`. Topic branch → **PR into `develop`** (green CI, merge, delete branch). Production: **PR `develop` → `main`**, then annotated **`v*`** tag on `main` (see `.cursor/rules/git-flow.mdc` and skill `pgwd-release`). - **Commits:** Always show the proposed commit message and wait for user approval before running `git commit`. See `.cursor/rules/commit-message-review.mdc`. - **Releases:** Before releasing: run **`make release-check`** (validates **`VERSION`** semver, then lint, test, **cover-check**, test-integration, test-e2e-kube, **`make docker-scan`**). All must pass — they are MANDATORY. -- **Versioning:** Semantic versioning (MAJOR.MINOR.PATCH) for tags. +- **Versioning:** Semantic versioning (MAJOR.MINOR.PATCH) for tags. The version cut is a **solo PR** into `develop` (`chore/release-X.Y.Z`): `VERSION`, CHANGELOG, README badge, **VHS** `docs/demo.gif`, man **`.TH`**, **`make port-freebsd-sync`** / **`port-openbsd-sync`**. Do not mix with other changes. ## Docker - Build image with version info: `make docker-build` (passes VERSION, COMMIT, BUILDDATE; without it the binary reports `dev`/`unknown`). For **linux/amd64** only (e.g. push to a private registry from another arch): `make docker-buildx-amd64` (`pgwd:amd64` locally) or `make docker-buildx-amd64-push DOCKER_IMAGE=registry/repo:tag` after `docker login`. - Build context is whitelisted via `.dockerignore`: only `go.mod`, `go.sum`, `cmd/`, and `internal/` are sent. -- Dockerfile: multi-stage (Go 1.26.5 build; **distroless/static-debian13:nonroot** runtime), non-root user, no shell/OS packages (HTTPS via bundled CA certs in static image). +- Dockerfile: multi-stage (Go 1.26.6 build; **distroless/static-debian13:nonroot** runtime), non-root user, no shell/OS packages (HTTPS via bundled CA certs in static image). ## Repository structure diff --git a/CHANGELOG.md b/CHANGELOG.md index ee6b0d1..66a325a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Releas ## [Unreleased] +## [1.1.1] - 2026-08-31 + +Security patch for operators pulling the published binary and GHCR image (Go stdlib + `x/net`). Remaining [plan-1.1.1](docs/plan-1.1.1.md) contract-repair work (daemon latch without store, SPEC, YAML examples) is not in this tag. + +### Security + +- **Go toolchain:** bump minimum/build Go to **1.26.6** (stdlib fixes: [GO-2026-6218](https://pkg.go.dev/vuln/GO-2026-6218), [GO-2026-6090](https://pkg.go.dev/vuln/GO-2026-6090), [GO-2026-6089](https://pkg.go.dev/vuln/GO-2026-6089), [GO-2026-6088](https://pkg.go.dev/vuln/GO-2026-6088), [GO-2026-5972](https://pkg.go.dev/vuln/GO-2026-5972), [GO-2026-5026](https://pkg.go.dev/vuln/GO-2026-5026)). +- **Dependencies:** bump transitive `golang.org/x/net` to **v0.56.0** ([GO-2026-5942](https://pkg.go.dev/vuln/GO-2026-5942)) so Grype on the image no longer fails `--fail-on high`. + +### Docs + +- **README / ports:** version badge and install examples → **1.1.1**; FreeBSD/OpenBSD port Makefiles synced from `VERSION`; man `.TH` and `docs/demo.gif`. + ## [1.1.0] - 2026-08-10 Incident hygiene for on-call: PagerDuty incident lifecycle and quieter threshold alerts by default. @@ -528,7 +541,8 @@ Pre-1.0 security and operator polish: removes insecure Kubernetes password disco --- -[Unreleased]: https://github.com/hrodrig/pgwd/compare/v1.1.0...HEAD +[Unreleased]: https://github.com/hrodrig/pgwd/compare/v1.1.1...HEAD +[1.1.1]: https://github.com/hrodrig/pgwd/compare/v1.1.0...v1.1.1 [1.1.0]: https://github.com/hrodrig/pgwd/compare/v1.0.1...v1.1.0 [1.0.1]: https://github.com/hrodrig/pgwd/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/hrodrig/pgwd/compare/v0.9.0...v1.0.0 diff --git a/Dockerfile b/Dockerfile index e32ee32..18053ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile:1 # Local / CI image: compile inside Docker (make docker-build, security workflow Grype scan). # Release images: GoReleaser builds static binaries, then Dockerfile.release packages them (distroless). -FROM golang:1.26.5-alpine AS build +FROM golang:1.26.6-alpine AS build ARG VERSION=dev ARG COMMIT=unknown ARG BUILDDATE=unknown diff --git a/README.md b/README.md index d4367af..e80feba 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,12 @@ Watch your PostgreSQL connections

-[![Version](https://img.shields.io/badge/version-1.1.0-blue)](https://github.com/hrodrig/pgwd/releases) +[![Version](https://img.shields.io/badge/version-1.1.1-blue)](https://github.com/hrodrig/pgwd/releases) [![Release](https://img.shields.io/github/v/release/hrodrig/pgwd)](https://github.com/hrodrig/pgwd/releases) [![CI](https://github.com/hrodrig/pgwd/actions/workflows/ci.yml/badge.svg)](https://github.com/hrodrig/pgwd/actions) [![codecov](https://codecov.io/gh/hrodrig/pgwd/graph/badge.svg)](https://codecov.io/gh/hrodrig/pgwd) [![gghstats clones](https://gghstats.hermesrodriguez.com/api/v1/badge/hrodrig/pgwd?metric=clones)](https://gghstats.hermesrodriguez.com/hrodrig/pgwd) -[![Go 1.26.5](https://img.shields.io/badge/go-1.26.5-00ADD8?logo=go)](https://go.dev/) +[![Go 1.26.6](https://img.shields.io/badge/go-1.26.6-00ADD8?logo=go)](https://go.dev/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![pkg.go.dev](https://pkg.go.dev/badge/github.com/hrodrig/pgwd)](https://pkg.go.dev/github.com/hrodrig/pgwd) [![deps.dev](https://img.shields.io/badge/deps.dev-go%20module-blue)](https://deps.dev/go/github.com%2Fhrodrig%2Fpgwd) @@ -188,7 +188,7 @@ The ingest host is [collect.gghstats.com](https://collect.gghstats.com) (shared ```json { - "version": "1.1.0", + "version": "1.1.1", "commit": "abc1234", "build_date": "2026-07-18T12:00:00Z", "hash": "a1b2c3d4e5f67890", @@ -669,17 +669,17 @@ curl -sSL https://raw.githubusercontent.com/hrodrig/pgwd/main/scripts/install.sh | Platform | Command | |----------|---------| | **Homebrew (macOS)** | `brew install hrodrig/pgwd/pgwd` | -| **Debian/Ubuntu** | `wget -q -O /tmp/pgwd.deb https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.deb && sudo dpkg -i /tmp/pgwd.deb` | -| **Fedora / RHEL / AlmaLinux / Rocky / Oracle Linux** | Same `.rpm`: `sudo dnf install https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.rpm` | +| **Debian/Ubuntu** | `wget -q -O /tmp/pgwd.deb https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.deb && sudo dpkg -i /tmp/pgwd.deb` | +| **Fedora / RHEL / AlmaLinux / Rocky / Oracle Linux** | Same `.rpm`: `sudo dnf install https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.rpm` | | **OpenSUSE** | Same `.rpm` via zypper: see [OpenSUSE](#opensuse) | -| **Alpine** | `wget -qO- https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.tar.gz \| tar -xzf - -C /usr/local/bin` — see [Alpine (OpenRC)](#alpine-linux-openrc) | +| **Alpine** | `wget -qO- https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.tar.gz \| tar -xzf - -C /usr/local/bin` — see [Alpine (OpenRC)](#alpine-linux-openrc) | | **OpenBSD** | tarball with rc.d: see [OpenBSD](#openbsd) | | **FreeBSD** | port or tarball: see [FreeBSD](#freebsd) | | **NetBSD** | tarball with rc.d: see [NetBSD](#netbsd) | | **DragonFly BSD** | tarball with rc.d: see [DragonFly BSD](#dragonfly-bsd) | | **illumos / Solaris** | tarball with SMF: see [Solaris](#solaris) | -Replace `v1.1.0` and `amd64` with your desired version and arch (e.g. `arm64`). See [Releases](https://github.com/hrodrig/pgwd/releases) for all assets. If a tag is not published yet, build packages locally with `make snapshot` and install the `.rpm` / `.deb` from `dist/`. **AlmaLinux 8/9:** see [AlmaLinux](#almalinux). +Replace `v1.1.1` and `amd64` with your desired version and arch (e.g. `arm64`). See [Releases](https://github.com/hrodrig/pgwd/releases) for all assets. If a tag is not published yet, build packages locally with `make snapshot` and install the `.rpm` / `.deb` from `dist/`. **AlmaLinux 8/9:** see [AlmaLinux](#almalinux). **Pre-built binaries:** [Releases](https://github.com/hrodrig/pgwd/releases) provide binaries (tar.gz, zip), `.deb`, and `.rpm` packages for Linux, macOS, and Windows (amd64 and arm64). The `.deb` and `.rpm` packages include the man page (`man pgwd`) and install `/etc/pgwd/pgwd.conf` (edit before use). The `.rpm` is the same artifact for Fedora, RHEL, AlmaLinux, Rocky Linux, and Oracle Linux (`dnf`); **AlmaLinux** + **systemd** were validated (install, `pgwd -dry-run -interval 0`, `systemctl enable --now pgwd.service`). @@ -696,11 +696,11 @@ make install **FreeBSD:** `/usr/bin/make` is **BSD Make**; the repo ships a small **`Makefile`** stub that forwards to **`gmake`** (uses **`all`** + **`.DEFAULT`** with **`gmake $@`**, not **`$(.MAKE.CMDGOALS)`**, which can be empty under BSD Make). Install **`devel/gmake`** (`pkg install gmake`) and a **Go** toolchain on **`PATH`** (`pkg install go` — binary is usually **`/usr/local/bin/go`**). Then **`make build`** or **`gmake build`**. Linux, macOS, and CI use **GNU Make**, which reads **`GNUmakefile`** first. -**Release (GitHub):** See [Release steps](#release-steps) below for the full workflow. Quick: from `main`, `git tag v1.1.0`, `make release`. Requires [goreleaser](https://goreleaser.com) (`brew install goreleaser`). For a local snapshot build without publishing: `make snapshot` (outputs to `dist/`). +**Release (GitHub):** See [Release steps](#release-steps) below for the full workflow. Quick: from `main`, `git tag v1.1.1`, `make release`. Requires [goreleaser](https://goreleaser.com) (`brew install goreleaser`). For a local snapshot build without publishing: `make snapshot` (outputs to `dist/`). ### Release steps -Example: releasing **v1.1.0**. Copy, adjust the version and token, then run. +Example: releasing **v1.1.1**. Copy, adjust the version and token, then run. **1. Prerequisites** (install once): @@ -724,13 +724,13 @@ make release-check **3. Update version** — edit `VERSION` and `CHANGELOG.md`: ```bash -echo "1.1.0" > VERSION -# Edit CHANGELOG.md: move [Unreleased] items into [1.1.0], update compare links +echo "1.1.1" > VERSION +# Edit CHANGELOG.md: move [Unreleased] items into [1.1.1], update compare links # Regenerate docs/demo.gif so embedded version matches (from repo root): make install && bash -c "vhs docs/demo.tape" # Update contrib/man/man1/pgwd.1 — .TH date and version (see man-page-sync rule) git add VERSION CHANGELOG.md README.md docs/demo.gif contrib/man/man1/pgwd.1 # README badge if needed -git commit -m "Release 1.1.0" +git commit -m "Release 1.1.1" git push origin develop ``` @@ -742,8 +742,8 @@ git pull origin main git merge develop git push origin main -git tag -a v1.1.0 -m "Release 1.1.0" -git push origin v1.1.0 +git tag -a v1.1.1 -m "Release 1.1.1" +git push origin v1.1.1 ``` **5. Publish release** — tokens required: @@ -776,7 +776,7 @@ From **v0.8.0**, GitHub Releases include **SPDX** and **CycloneDX** SBOMs and ** **Verify the container image** (replace the tag): ```bash -cosign verify ghcr.io/hrodrig/pgwd:v1.1.0 \ +cosign verify ghcr.io/hrodrig/pgwd:v1.1.1 \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ --certificate-identity-regexp '^https://github\.com/hrodrig/pgwd/\.github/workflows/release\.yml@refs/tags/v' ``` @@ -789,7 +789,7 @@ cosign verify-blob \ checksums.txt ``` -**Inspect SBOMs:** download `pgwd__sbom.spdx.json` or `pgwd__sbom.cyclonedx.json` from the release page, or scan with Grype: `grype sbom:pgwd_v1.1.0_sbom.spdx.json`. +**Inspect SBOMs:** download `pgwd__sbom.spdx.json` or `pgwd__sbom.cyclonedx.json` from the release page, or scan with Grype: `grype sbom:pgwd_v1.1.1_sbom.spdx.json`. Local `make release` does not sign artifacts — use the tag-push CI workflow for signed releases. @@ -1029,12 +1029,12 @@ When you use `-db-threshold-levels 75,85,95` (default), pgwd fires one alert per **Published image (each release):** Multi-arch images (linux/amd64, linux/arm64) are published to [GitHub Container Registry](https://github.com/hrodrig/pgwd/pkgs/container/pgwd) as `ghcr.io/hrodrig/pgwd`. Use a version tag or `latest`: ```bash -docker pull ghcr.io/hrodrig/pgwd:v1.1.0 +docker pull ghcr.io/hrodrig/pgwd:v1.1.1 # or docker pull ghcr.io/hrodrig/pgwd:latest ``` -**Build from source:** Multi-stage **Dockerfile** (Go 1.26.5 build; **distroless/static-debian13:nonroot** runtime): static binary, non-root, no Alpine OS packages. Use `make docker-build` locally; release images via GoReleaser + **`Dockerfile.release`** (same distroless base). +**Build from source:** Multi-stage **Dockerfile** (Go 1.26.6 build; **distroless/static-debian13:nonroot** runtime): static binary, non-root, no Alpine OS packages. Use `make docker-build` locally; release images via GoReleaser + **`Dockerfile.release`** (same distroless base). **Image details** @@ -1055,13 +1055,13 @@ This runs `docker build` with `--build-arg VERSION=...`, `--build-arg COMMIT=... **Validate the image** -Use the published image `ghcr.io/hrodrig/pgwd:latest` (or `:v1.1.0`), or `pgwd` if you built locally with `make docker-build`: +Use the published image `ghcr.io/hrodrig/pgwd:latest` (or `:v1.1.1`), or `pgwd` if you built locally with `make docker-build`: ```bash # Help (no DB needed) docker run --rm ghcr.io/hrodrig/pgwd:latest -h -# Version (should show e.g. pgwd v1.1.0 (branch develop, commit ..., built ...)) +# Version (should show e.g. pgwd v1.1.1 (branch develop, commit ..., built ...)) docker run --rm ghcr.io/hrodrig/pgwd:latest --version # Expect "missing database URL" (validates startup path) @@ -1167,14 +1167,14 @@ Debian and Ubuntu use **systemd** and **`.deb`** packages. The same `.deb` works **Install from GitHub** (replace version / arch): ```bash -wget -q -O /tmp/pgwd.deb https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.deb +wget -q -O /tmp/pgwd.deb https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.deb sudo dpkg -i /tmp/pgwd.deb ``` **Local `.deb`** (e.g. from `make snapshot` → `dist/`): ```bash -sudo dpkg -i ./pgwd_v1.1.0_linux_amd64.deb +sudo dpkg -i ./pgwd_v1.1.1_linux_amd64.deb ``` **Configure and test** @@ -1201,13 +1201,13 @@ sudo systemctl status pgwd.service **Install from GitHub** (replace version / arch): ```bash -sudo dnf install -y "https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.rpm" +sudo dnf install -y "https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.rpm" ``` **Local `.rpm`** (e.g. from `make snapshot` → `dist/`, when a release is not on GitHub yet): ```bash -sudo dnf install -y ./pgwd_v1.1.0_linux_amd64.rpm +sudo dnf install -y ./pgwd_v1.1.1_linux_amd64.rpm ``` **Configure and test** @@ -1233,13 +1233,13 @@ sudo systemctl status pgwd.service ```bash sudo zypper --non-interactive install --allow-unsigned-rpm \ - "https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.rpm" + "https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.rpm" ``` **Local `.rpm`** (e.g. from `make snapshot` → `dist/`): ```bash -sudo zypper --non-interactive install --allow-unsigned-rpm ./pgwd_v1.1.0_linux_amd64.rpm +sudo zypper --non-interactive install --allow-unsigned-rpm ./pgwd_v1.1.1_linux_amd64.rpm ``` **Configure and test** @@ -1263,10 +1263,10 @@ sudo systemctl status pgwd.service Arch Linux uses **systemd**. There is no official **`pacman`** package in the Arch repos yet; install the **Linux release tarball** from [Releases](https://github.com/hrodrig/pgwd/releases) or a community **[AUR](https://aur.archlinux.org/)** package (e.g. `pgwd-bin`) when one exists — verify the PKGBUILD and checksums. -**Tarball install** — extract the archive, then install the binary and config layout (replace `v1.1.0` / `amd64` as needed): +**Tarball install** — extract the archive, then install the binary and config layout (replace `v1.1.1` / `amd64` as needed): ```bash -wget -qO- https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.tar.gz | tar -xzf - +wget -qO- https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.tar.gz | tar -xzf - sudo install -Dm755 pgwd /usr/local/bin/pgwd sudo ln -sf /usr/local/bin/pgwd /usr/bin/pgwd sudo install -Dm644 share/man/man1/pgwd.1 /usr/local/share/man/man1/pgwd.1 @@ -1300,7 +1300,7 @@ Alpine uses **OpenRC** (rc.d), not systemd. Config: `/etc/pgwd/pgwd.conf`. **Install** — tar.gz (binario estático, musl-compatible): ```bash -wget -qO- https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_linux_amd64.tar.gz | tar -xzf - -C /usr/local/bin +wget -qO- https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_linux_amd64.tar.gz | tar -xzf - -C /usr/local/bin # arm64: replace amd64 with arm64 ``` @@ -1336,7 +1336,7 @@ OpenBSD uses **rc.d**, not systemd. Config: `/etc/pgwd/pgwd.conf`. Supports `-ku **Install** — tarball includes binary, rc.d script, and config example: ```bash -tar xzf pgwd_v1.1.0_openbsd_amd64.tar.gz +tar xzf pgwd_v1.1.1_openbsd_amd64.tar.gz doas install -m755 pgwd /usr/local/bin/ doas install -m555 share/openbsd/rc.d/pgwd /etc/rc.d/pgwd doas mkdir -p /etc/pgwd @@ -1374,7 +1374,7 @@ make install **Install from tarball** (or use the [one-liner](#install) which works on FreeBSD and installs only the binary): ```bash -fetch -o /tmp/pgwd.tgz https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_freebsd_amd64.tar.gz +fetch -o /tmp/pgwd.tgz https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_freebsd_amd64.tar.gz tar -xzf /tmp/pgwd.tgz -C /tmp sudo install -m755 /tmp/pgwd /usr/local/bin/ sudo mkdir -p /usr/local/etc/pgwd @@ -1403,7 +1403,7 @@ NetBSD uses **rc.d**, not systemd. Config: `/etc/pgwd/pgwd.conf`. Supports `-kub **Install** — tarball includes binary, rc.d script, and config example: ```bash -tar xzf pgwd_v1.1.0_netbsd_amd64.tar.gz +tar xzf pgwd_v1.1.1_netbsd_amd64.tar.gz install -m755 pgwd /usr/local/bin/ install -m555 share/netbsd/rc.d/pgwd /etc/rc.d/pgwd mkdir -p /etc/pgwd @@ -1426,7 +1426,7 @@ See [contrib/netbsd/README.md](contrib/netbsd/README.md) for details. **Install** — tarball includes binary, rc.d script, and config example: ```bash -tar xzf pgwd_v1.1.0_dragonfly_amd64.tar.gz +tar xzf pgwd_v1.1.1_dragonfly_amd64.tar.gz install -m755 pgwd /usr/local/bin/ install -m555 share/dragonfly/rc.d/pgwd /etc/rc.d/pgwd mkdir -p /etc/pgwd @@ -1451,7 +1451,7 @@ See [contrib/dragonflybsd/README.md](contrib/dragonflybsd/README.md) for details **Install** — tarball includes binary, SMF manifest, method script, and config example: ```bash -curl -L -o /tmp/pgwd.tar.gz "https://github.com/hrodrig/pgwd/releases/download/v1.1.0/pgwd_v1.1.0_solaris_amd64.tar.gz" +curl -L -o /tmp/pgwd.tar.gz "https://github.com/hrodrig/pgwd/releases/download/v1.1.1/pgwd_v1.1.1_solaris_amd64.tar.gz" cd /tmp && tar xzf pgwd.tar.gz pfexec mkdir -p /usr/local/bin /lib/svc/manifest/site /etc/pgwd @@ -1476,7 +1476,7 @@ See [contrib/solaris/README.md](contrib/solaris/README.md) for details. **Canonical roadmap:** **[ROADMAP.md](ROADMAP.md)** — current release, release bands (0.8 → 1.0), calendar, key decisions, document map. -Summary: **v1.1.0** (PagerDuty dedup/resolve + threshold anti-spam latch). Stable API: **v1.0.0**. Behavior contract: [SPECIFICATIONS.md](SPECIFICATIONS.md). Shipped releases: [CHANGELOG.md](CHANGELOG.md). +Summary: **v1.1.1** (Go 1.26.6 + `x/net` security patch). Stable API: **v1.0.0**. Behavior contract: [SPECIFICATIONS.md](SPECIFICATIONS.md). Shipped releases: [CHANGELOG.md](CHANGELOG.md). | Band | Status | Plan | |------|--------|------| @@ -1484,9 +1484,10 @@ Summary: **v1.1.0** (PagerDuty dedup/resolve + threshold anti-spam latch). Stabl | **1.0.0** | ✅ Ready Jul 2026 | [plan-1.0.x.md](docs/plan-1.0.x.md) · [CHANGELOG](CHANGELOG.md#100---2026-07-18) | | **1.0.1** | ✅ Ready Aug 2026 | [CHANGELOG](CHANGELOG.md#101---2026-08-01) | | **1.1.0** | ✅ Ready Aug 2026 | [plan-1.1.x.md](docs/plan-1.1.x.md) · [CHANGELOG](CHANGELOG.md#110---2026-08-10) | +| **1.1.1** | ✅ Ready Aug 2026 | [CHANGELOG](CHANGELOG.md#111---2026-08-31) |
-Shipped history (0.4 – 1.1.0) +Shipped history (0.4 – 1.1.1) | Version | Target | Scope | |---------|--------|-------| @@ -1502,6 +1503,7 @@ Summary: **v1.1.0** (PagerDuty dedup/resolve + threshold anti-spam latch). Stabl | **1.0.0** | Jul 2026 ✅ | Stable API; remove `db:` / total-active thresholds / notify-on-connect flag; exit 2/3; compare docs | | **1.0.1** | Aug 2026 ✅ | `golang.org/x/text` security bump; README/man/ports docs | | **1.1.0** | Aug 2026 ✅ | PagerDuty dedup/resolve; threshold alert latch (`repeat_while_firing`) | +| **1.1.1** | Aug 2026 ✅ | Go 1.26.6; `golang.org/x/net` v0.56.0 (GO-2026-5942) |
diff --git a/ROADMAP.md b/ROADMAP.md index 5b3549d..395967c 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,10 +1,10 @@ # pgwd roadmap -**Current release:** [v1.1.0](VERSION) (ready on `develop`; tag from `main` after `make release-check`) · **Branch:** `develop` +**Current release:** [v1.1.1](VERSION) · **Branch:** `develop` · **Next:** remaining [plan-1.1.1](docs/plan-1.1.1.md) contract repair (latch without store, SPEC, YAML), then [1.2.x](docs/plan-1.2.x.md) (hygiene) -**Status (2026-08-10):** **v1.1.0** ready — incident hygiene (PagerDuty `dedup_key`/`resolve` + threshold anti-spam latch). Stable API remains **v1.0.0** (+ additive 1.1 keys). Distro packaging continues in **1.x** (not a hard tag gate). +**Status (2026-08-31):** **v1.1.1** shipped (Go **1.26.6** + `golang.org/x/net` **v0.56.0**). **v1.1.0** incident hygiene remains. Leftover **plan-1.1.1** work (daemon latch without store, SPEC dry-run/Stats, YAML examples) is not in this tag. **1.2.x** planned — Dependabot, KnownFields, env honesty, HTTP timeouts, docs/packaging sync. Stable API remains **v1.0.0** (+ additive 1.1+ keys). Distro packaging continues in **1.x** (not a hard tag gate). -This file is the **single roadmap index**. Shipped behavior: [SPECIFICATIONS.md](SPECIFICATIONS.md) (v1.0.0 contract; **v1.1.0** latch/PagerDuty). Shipped releases: [CHANGELOG.md](CHANGELOG.md). Implementation detail per band: [docs/plan-0.7.x.md](docs/plan-0.7.x.md) → [docs/plan-1.0.x.md](docs/plan-1.0.x.md) → [docs/plan-1.1.x.md](docs/plan-1.1.x.md). +This file is the **single roadmap index**. Shipped behavior: [SPECIFICATIONS.md](SPECIFICATIONS.md). Shipped releases: [CHANGELOG.md](CHANGELOG.md). Implementation detail per band: [docs/plan-0.7.x.md](docs/plan-0.7.x.md) → … → [docs/plan-1.1.x.md](docs/plan-1.1.x.md) → [docs/plan-1.1.1.md](docs/plan-1.1.1.md) → [docs/plan-1.2.x.md](docs/plan-1.2.x.md). --- @@ -23,7 +23,9 @@ flowchart LR C --> D["0.9.0 ✅"] D --> E["1.0.0 ✅"] E --> F["1.0.1 ✅"] - F --> G["1.1.0 ✅ ready"] + F --> G["1.1.0 ✅"] + G --> H["1.1.1 ✅"] + H --> I["1.2.0 planned"] ``` | Band | Status | Target | Theme | Plan | @@ -34,7 +36,9 @@ flowchart LR | **0.9.x** | ✅ Ready (v0.9.0) | Jul 2026 | Pre-1.0 polish, DISCOVER removal, profiles, `--strict`, collector, SPEC audit | [plan-0.9.x.md](docs/plan-0.9.x.md) · [CHANGELOG](CHANGELOG.md#090---2026-07-13) | | **1.0.0** | ✅ Ready (v1.0.0) | Jul 2026 | Breaking stable API, compare docs, **start official distro packaging** | [plan-1.0.x.md](docs/plan-1.0.x.md) · [CHANGELOG](CHANGELOG.md#100---2026-07-18) | | **1.0.1** | ✅ Shipped | Aug 2026 | `golang.org/x/text` security bump + docs | [CHANGELOG](CHANGELOG.md#101---2026-08-01) | -| **1.1.x** | ✅ Ready (v1.1.0) | Aug 2026 | Incident hygiene: PagerDuty dedup/resolve + threshold anti-spam latch | [plan-1.1.x.md](docs/plan-1.1.x.md) · [CHANGELOG](CHANGELOG.md#110---2026-08-10) | +| **1.1.x** | ✅ Shipped (v1.1.0) | Aug 2026 | Incident hygiene: PagerDuty dedup/resolve + threshold anti-spam latch | [plan-1.1.x.md](docs/plan-1.1.x.md) · [CHANGELOG](CHANGELOG.md#110---2026-08-10) | +| **1.1.1** | ✅ Shipped | Aug 2026 | Security: Go 1.26.6 + `x/net` v0.56.0. Remaining latch/SPEC/YAML in [plan-1.1.1.md](docs/plan-1.1.1.md) | [CHANGELOG](CHANGELOG.md#111---2026-08-31) | +| **1.2.x** | 📋 Planned | — | Hygiene: Dependabot, KnownFields, env honesty, HTTP timeouts, docs/packaging | [plan-1.2.x.md](docs/plan-1.2.x.md) | **Suggested calendar** (from band plans — **slip OK**; 0.7.x started 2026-07-02): @@ -46,7 +50,9 @@ flowchart LR | Jul 13 | v0.9.0 ✅ | | Jul 18 | v1.0.0 ✅ | | Aug 1 | v1.0.1 ✅ | -| Aug 10 | v1.1.0 ✅ ready (tag from `main` after gates) | +| Aug 10 | v1.1.0 ✅ | +| Aug 31 | v1.1.1 ✅ (security; leftover [plan-1.1.1.md](docs/plan-1.1.1.md) after this tag) | +| After 1.1.1 | v1.2.0 planned ([plan-1.2.x.md](docs/plan-1.2.x.md)) | Each band: design → implement → test → `make release-check` → docs → tag from `main`. @@ -134,21 +140,31 @@ Acceptance timelines are external (reviewers, freeze windows) — **not** a hard → [plan-1.0.x.md](docs/plan-1.0.x.md) -### 1.1.x — incident hygiene (on-call ready) ✅ (v1.1.0 ready) - -Theme from 2026-08-09 multi-auditor review: product gap is **sustained-outage noise**, not code craft. +### 1.1.x — incident hygiene (on-call ready) ✅ (v1.1.0) | Item | Notes | |------|--------| | **PagerDuty `dedup_key` + `resolve`** ✅ | Stable key per target/problem; resolution uses `event_action: resolve` (not `trigger`+`info`) | -| **Threshold anti-spam latch** ✅ | Default: notify on transition / escalation / de-escalation only; escape hatch `notifications.repeat_while_firing` | +| **Threshold anti-spam latch** ✅ | Filter + config shipped; **cross-tick without store** → [1.1.1](docs/plan-1.1.1.md) | | **SPEC fix** ✅ | Known Limitations row corrected; latch + PagerDuty resolve documented | -| **Doc drift (C5)** | Partial — man/example/SPEC done; OCI/nfpm one-liners may still say “Slack/Loki” only (follow-up) | - -**Not in 1.1.x:** Dependabot, `slog`, cli split, per-DB kube, new channels — see Post-1.0 / later minors. +| **Doc drift (C5)** | Partial — OCI/nfpm one-liners → [1.2.x](docs/plan-1.2.x.md) | → [plan-1.1.x.md](docs/plan-1.1.x.md) · [CHANGELOG](CHANGELOG.md#110---2026-08-10) +### 1.1.1 — security patch ✅ (v1.1.1) + leftover contract repair 📋 + +**Shipped in v1.1.1:** Go 1.26.6; transitive `golang.org/x/net` v0.56.0. + +**Still in** [plan-1.1.1.md](docs/plan-1.1.1.md): daemon latch wiring without metrics store; SPEC dry-run + Stats self-exclude; `db:` / wrong keys in operator examples. + +→ [CHANGELOG](CHANGELOG.md#111---2026-08-31) · [plan-1.1.1.md](docs/plan-1.1.1.md) + +### 1.2.x — audit hygiene 📋 + +Dependabot (`gomod` + Actions), `KnownFields`, honest env int parse, HTTP `ReadHeaderTimeout`, packaging/docs sync (five notifiers, distroless README). + +→ [plan-1.2.x.md](docs/plan-1.2.x.md) + --- ## Shipped history (0.4 → 0.7.0) @@ -202,15 +218,16 @@ See [SPECIFICATIONS.md §2](SPECIFICATIONS.md#2-scope). ## Post-1.0 (ideas, not committed) -**Shipped:** [1.1.x incident hygiene](docs/plan-1.1.x.md) (dedup/resolve + latch) in **v1.1.0**. +**Shipped:** [1.1.x incident hygiene](docs/plan-1.1.x.md) in **v1.1.0**; [v1.1.1](CHANGELOG.md#111---2026-08-31) Go/`x/net` security patch. + +**Scheduled:** leftover [1.1.1 contract repair](docs/plan-1.1.1.md) · [1.2.x hygiene](docs/plan-1.2.x.md) (Dependabot, KnownFields, timeouts, docs sync). Still ideas (not scheduled): - Per-database `kube.postgres` in `databases:` - Additional Prometheus series or OpenMetrics (today: text exposition on HTTP `/metrics`) - Discord, email, additional channels via same notifier pattern as 0.7.x -- Structured logging (`slog`) — audit Band C -- Dependabot/Renovate + pin CI tool versions — audit Band B +- Structured logging (`slog`) - Finish / expand **official distro** coverage if any 1.x submissions still pending (see [plan-1.0.x.md](docs/plan-1.0.x.md) § Distro packaging) Track via GitHub issues; promote to a band plan when scheduled. @@ -224,7 +241,7 @@ Track via GitHub issues; promote to a band plan when scheduled. | **ROADMAP.md** (this file) | Where we are, where we go, band index | | **[SPECIFICATIONS.md](SPECIFICATIONS.md)** | Observable behavior contract for **shipped** code (v1.0.0) | | **[CHANGELOG.md](CHANGELOG.md)** | What actually shipped per version | -| **[docs/plan-0.7.x.md](docs/plan-0.7.x.md) … [plan-1.1.x.md](docs/plan-1.1.x.md)** | Implementation checklists per band | +| **[docs/plan-0.7.x.md](docs/plan-0.7.x.md) … [plan-1.1.x.md](docs/plan-1.1.x.md) · [plan-1.1.1.md](docs/plan-1.1.1.md) · [plan-1.2.x.md](docs/plan-1.2.x.md)** | Implementation checklists per band | | **[docs/use-cases.md](docs/use-cases.md)** | Operator scenario matrix (single/multi DB, K8s, credentials) | | **[docs/compare.md](docs/compare.md)** | pgwd vs postgres_exporter, pgwatch, hosted APM, cloud alarms, DIY cron | | **[docs/kubernetes-passwords.md](docs/kubernetes-passwords.md)** | K8s credentials + DISCOVER migration | diff --git a/SPECIFICATIONS.md b/SPECIFICATIONS.md index bda95bf..c9c2153 100644 --- a/SPECIFICATIONS.md +++ b/SPECIFICATIONS.md @@ -532,14 +532,14 @@ Requires an active metrics store (sqlite.path or metrics_store.driver+dsn). ### Build - Go module: `github.com/hrodrig/pgwd` -- Minimum Go: 1.26.5 (as of 0.8.0) +- Minimum Go: 1.26.6 (as of Unreleased / next patch) - `make build`: reads `VERSION`, injects `Version`/`Commit`/`BuildDate`/`Branch` via ldflags - `make install`: installs to `$GOBIN` - Cross-compile: `make build-linux`, `make build-darwin`, `make build-windows`, `make build-all` (output in `dist/`) ### Docker -- Multi-stage build: `golang:1.26.5-alpine` → `gcr.io/distroless/static-debian13:nonroot` +- Multi-stage build: `golang:1.26.6-alpine` → `gcr.io/distroless/static-debian13:nonroot` - **Static binary** (`CGO_ENABLED=0`); runtime image has **no shell, kubectl, or OS packages** - **HTTPS notifiers** (Slack, Loki, PagerDuty, etc.): CA bundle included in distroless/static - **Kubernetes in-container:** `-kube-postgres` / `-kube-loki` use **client-go** (port-forward, API calls). **No kubectl binary** — mount kubeconfig or use in-cluster ServiceAccount + RBAC. **`DISCOVER_MY_PASSWORD` / `pods/exec` removed in 0.9.x**; use Secret-backed DSN or `kube.password_from_secret`. diff --git a/VERSION b/VERSION index 9084fa2..524cb55 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.0 +1.1.1 diff --git a/contrib/freebsd/Makefile b/contrib/freebsd/Makefile index 68946e1..0040104 100644 --- a/contrib/freebsd/Makefile +++ b/contrib/freebsd/Makefile @@ -1,5 +1,5 @@ PORTNAME= pgwd -PORTVERSION= 1.1.0 +PORTVERSION= 1.1.1 DISTVERSIONPREFIX= v CATEGORIES= sysutils # Local test before release: copy the tarball into DISTDIR (see `make -V DISTDIR` diff --git a/contrib/man/man1/pgwd.1 b/contrib/man/man1/pgwd.1 index ff7f452..dd0331f 100644 --- a/contrib/man/man1/pgwd.1 +++ b/contrib/man/man1/pgwd.1 @@ -1,6 +1,6 @@ .\" pgwd(1) - Postgres Watch Dog .\" Manual page for pgwd -.TH PGWD 1 "2026-08-10" "pgwd v1.1.0" "User Commands" +.TH PGWD 1 "2026-08-31" "pgwd v1.1.1" "User Commands" .SH NAME pgwd \- monitor PostgreSQL connections and notify via Slack, Loki, PagerDuty, Teams, or generic webhook .SH SYNOPSIS diff --git a/contrib/openbsd/port/Makefile b/contrib/openbsd/port/Makefile index 7c751c9..092081a 100644 --- a/contrib/openbsd/port/Makefile +++ b/contrib/openbsd/port/Makefile @@ -4,8 +4,8 @@ # Uses pledge() for security. COMMENT = postgres connection watchdog with Slack/Loki alerts -DISTNAME = pgwd_v1.1.0_openbsd_${MACHINE_ARCH:S/aarch64/arm64/} -PKGNAME = pgwd-1.1.0 +DISTNAME = pgwd_v1.1.1_openbsd_${MACHINE_ARCH:S/aarch64/arm64/} +PKGNAME = pgwd-1.1.1 CATEGORIES = sysutils HOMEPAGE = https://github.com/hrodrig/pgwd MAINTAINER = hrodrig@usb.ve @@ -17,8 +17,8 @@ PERMIT_DISTFILES = Yes # Local test before release: copy tarball to DISTDIR or use # make fetch MASTER_SITES=file:///absolute/path/to/dir/ # See README.md "Test with a local tarball". -MASTER_SITES = https://github.com/hrodrig/pgwd/releases/download/v1.1.0/ -DISTFILES = pgwd_v1.1.0_openbsd_${MACHINE_ARCH:S/aarch64/arm64/}.tar.gz +MASTER_SITES = https://github.com/hrodrig/pgwd/releases/download/v1.1.1/ +DISTFILES = pgwd_v1.1.1_openbsd_${MACHINE_ARCH:S/aarch64/arm64/}.tar.gz EXTRACT_SUFX = .tar.gz ONLY_FOR_ARCHS = amd64 arm64 aarch64 diff --git a/docs/demo.gif b/docs/demo.gif index f44c659..7491c9b 100644 Binary files a/docs/demo.gif and b/docs/demo.gif differ diff --git a/docs/plan-1.1.1.md b/docs/plan-1.1.1.md new file mode 100644 index 0000000..6509d41 --- /dev/null +++ b/docs/plan-1.1.1.md @@ -0,0 +1,184 @@ +# pgwd plan 1.1.1 — contract repair (patch) + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Roadmap index:** [ROADMAP.md](../ROADMAP.md) · **Band:** 1.1.1 (patch; restores promises of [plan-1.1.x.md](./plan-1.1.x.md) + Go toolchain) + +**Goal:** Make the v1.1.0 “quiet by default” latch work across daemon ticks **without** a metrics store; align SPEC with dry-run and connection counting; fix broken post-1.0 YAML examples; ship **Go 1.26.6** (already on `develop`). + +**Architecture:** (1) Daemon keeps per-target check runner state (`MakeRunFunc` closure / `memPrev`) across ticker intervals instead of creating and discarding a new closure every tick. (2) `postgres.Stats` excludes the monitor backend (`pid <> pg_backend_pid()`), matching `LongQueryCount`. (3) SPEC documents that `-dry-run` sends **no** outbound HTTP (including connect failure). (4) Operator-facing YAML examples use `databases:` and current key names only. + +**Tech Stack:** Go 1.26.6, existing `internal/cli`, `internal/run`, `internal/postgres`, `internal/store` (optional). + +**Baseline:** v1.1.0 on `main` / `develop` (+ commit bumping `go.mod` to 1.26.6) · **Previous band:** [plan-1.1.x.md](./plan-1.1.x.md) · **Target tag:** `v1.1.1` after `make release-check` on `main` + +--- + +## Global Constraints + +- English only in code, comments, commits, docs (repo rule). +- Work on `develop`; never commit features to `main`. +- Show proposed commit message; wait for explicit user approval before `git commit`. +- Cyclomatic complexity ≤ 14 (`gocyclo -over 14`); `gofmt -s`; `go vet ./...`. +- Library coverage gate remains ≥ 80% (`make cover-check`). +- No new direct dependencies. +- No Dependabot / `KnownFields` / packaging one-liner sweep beyond broken YAML examples (those belong in [plan-1.2.x.md](./plan-1.2.x.md)). +- Do not require metrics store for latch; store remains preferred when configured. + +--- + +## Behavior contract (to land in SPECIFICATIONS.md) + +### Latch across daemon ticks (no store) + +| Mode | Previous firing state source | +|------|------------------------------| +| Metrics store configured | `LastStates(..., 1)` (unchanged; survives process restart) | +| No store, **daemon** | In-memory state held for the lifetime of the daemon loop **per target** (survives ticker intervals) | +| No store, one-shot | Single check; latch N/A across process runs | + +**Bug today:** `runOneTarget` invokes `MakeRunFunc(...)()` once and drops the closure; `runTickerLoop` calls `runOneTarget` every interval → `memPrev` always empty → `ApplyFiringRepeatFilter` never suppresses. + +**Fix:** Reuse the check runner (or an equivalent per-target `memPrev` + stable `MakeRunFunc`) across ticks. Pool lifecycle may be created once per target in daemon mode (preferred) or equivalent; one-shot behavior unchanged. + +### Dry-run and connect failure + +| Path | `-dry-run` | +|------|------------| +| Threshold / resolution / long_query / force-notification | No outbound HTTP (log `[dry-run] would send: …` where applicable) | +| `connect_failure` / `too_many_clients` | **Also** no outbound HTTP | + +Remove SPEC/CHANGELOG claims that infrastructure failures “bypass dry-run”. Code in `NotifyConnectFailure` already returns early on `cfg.DryRun`. + +### Connection Stats + +- **Total / active / idle:** exclude the monitor’s own backend (`pid <> pg_backend_pid()`), as already documented for Total and as already implemented for long-query counts. + +### Docs / examples + +- No `db:` key in current operator examples (removed in v1.0; hard load error). +- Slack YAML key is `notifications.slack.webhook` (not `webhook_url`). +- HTTP health path key is `http.healthz_path` (not `health_path`). + +--- + +## Out of scope (this band) + +- Dependabot / Renovate / pinning CI `@latest` tools → [plan-1.2.x.md](./plan-1.2.x.md) +- `yaml.KnownFields(true)`, `envInt` fail-loud → 1.2.x +- `ReadHeaderTimeout`, OCI/nfpm “five channels” one-liners → 1.2.x +- `nokube` build tag, `slog`, new notifiers, per-DB kube + +--- + +## File map + +| File | Role | +|------|------| +| `internal/cli/cli.go` | Daemon tick loop: reuse runner / pool per target | +| `internal/run/run.go` | Unchanged filter logic if wiring fixed; adjust only if API needed | +| `internal/cli/*_test.go` or dedicated daemon-path test | Two ticks, **no store**, same severity → no second threshold send | +| `internal/postgres/stats.go` | Exclude `pg_backend_pid()` | +| `internal/postgres/stats_*_test.go` | Cover self-exclusion (unit/mock or integration) | +| `SPECIFICATIONS.md` | Latch in-memory wording; dry-run table; Stats | +| `CHANGELOG.md` | `[1.1.1]` Security (Go) + Fixed | +| `docs/kubernetes-passwords.md`, `contrib/k8s/README.md`, `contrib/netbsd/README.md`, `contrib/solaris/README.md` | Replace `db:` / wrong keys in “current” examples | +| `VERSION`, README badge/install, `contrib/man/man1/pgwd.1`, BSD ports | 1.1.1 sync | +| `ROADMAP.md` | Band status | +| `docs/plan-1.1.x.md` | Pointer: in-memory cross-tick latch completed in 1.1.1 | + +--- + +## Design notes (locked) + +1. **Prefer fix wiring over “require sqlite”.** SPEC already promises in-memory daemon state; requiring a store would be a product regression for minimal installs. +2. **Existing `TestMakeRunFunc_*` that call `fn(); fn()` on one closure stay valid** but do **not** prove production wiring. Add a test that exercises the **daemon path** (or extracted helper used by `runTickerLoop`) without store. +3. **Dry-run = zero HTTP** matches README “no HTTP calls” and the name dry-run; SPEC was wrong. +4. **Go 1.26.6** may already be on `develop`; still document under `[1.1.1]` Security when tagging. + +--- + +### Task 1: Failing test — latch without store across two daemon ticks + +**Files:** +- Modify or create: test under `internal/cli` or `internal/run` that mirrors production wiring (new helper OK if it keeps `gocyclo` ≤ 14) + +**Want:** + +```text +st == nil +repeat_while_firing == false +tick 1: threshold alert → Send called +tick 2: same severity → Send NOT called for connection-threshold events +``` + +Must **fail** on current `runOneTarget` + `runTickerLoop` behavior (or fail until helper used by production is fixed). + +- [ ] **Step 1: Write failing test** +- [ ] **Step 2: Run — expect FAIL** + +```bash +go test ./internal/cli/ ./internal/run/ -count=1 -run Latch|Daemon|TwoTick +``` + +--- + +### Task 2: Fix daemon wiring + +**Files:** +- Modify: `internal/cli/cli.go` (`runTickerLoop` / `runOneTarget`) + +**Approach (pick one; prefer A):** + +- **A:** Per target, create pool once, `fn := run.MakeRunFunc(...)`, call `fn()` each tick; `defer` close pools on daemon exit. +- **B:** Keep reconnect-per-tick but pass/update a shared `map[targetKey]*string` for `memPrev` into an extended `MakeRunFunc` / check entrypoint. + +- [ ] **Step 1: Implement** +- [ ] **Step 2: Test PASS** +- [ ] **Step 3: Commit** (after approval) + +--- + +### Task 3: Stats exclude monitor PID + +**Files:** +- Modify: `internal/postgres/stats.go` +- Modify: unit/integration tests + +- [ ] **Step 1: Add `AND pid <> pg_backend_pid()` to Stats query** +- [ ] **Step 2: Test + `go test ./internal/postgres/ -count=1`** +- [ ] **Step 3: Commit** (after approval) + +--- + +### Task 4: SPEC + CHANGELOG + YAML example sweep + +**Files:** +- `SPECIFICATIONS.md` (dry-run, latch in-memory, Stats) +- `CHANGELOG.md` → section `[1.1.1]` +- Operator examples still showing `db:` / wrong Slack/health keys (SPEC §15 samples, kubernetes/kubernetes-passwords.md “after” examples, contrib READMEs listed in file map) + +- [ ] **Step 1: Align SPEC dry-run + latch wording** +- [ ] **Step 2: Fix YAML examples to v1.0+ keys** +- [ ] **Step 3: CHANGELOG `[1.1.1]` Fixed + Security (Go 1.26.6)** +- [ ] **Step 4: Commit** (after approval) + +--- + +### Task 5: Release prep 1.1.1 + +- [ ] **Step 1:** `VERSION` → `1.1.1`; man `.TH`; README badge/install examples; FreeBSD/OpenBSD port `PORTVERSION` / equivalent +- [ ] **Step 2:** Note in [plan-1.1.x.md](./plan-1.1.x.md); ROADMAP row **1.1.1** +- [ ] **Step 3:** `make release-check` on `main` path per git-flow +- [ ] **Step 4:** PR `develop` → `main`; annotated tag `v1.1.1` + +--- + +## Success criteria + +- [ ] Daemon without metrics store does not re-notify same connection-threshold severity every interval +- [ ] New test covers production wiring (not only reused closure) +- [ ] `Stats` excludes monitor PID; SPEC matches +- [ ] Dry-run documented as zero HTTP including connect failure +- [ ] No `db:` in current operator copy-paste examples listed above +- [ ] Tag `v1.1.1` with Go 1.26.6 diff --git a/docs/plan-1.1.x.md b/docs/plan-1.1.x.md index e1b3ad8..da5ec4d 100644 --- a/docs/plan-1.1.x.md +++ b/docs/plan-1.1.x.md @@ -10,7 +10,7 @@ **Tech Stack:** Go 1.26.x, existing `internal/notify`, `internal/run`, `internal/store`, `httptest` for PD unit tests, SQLite metrics store for latch persistence across process restarts. -**Source audits:** `.no-va-al-repo/20260809/` (Kimi, GLM, DeepSeek, MiniMax) — consensus items A1/A2/A3. +**Follow-up:** In-memory latch across daemon ticks without a metrics store is completed in **[plan-1.1.1.md](./plan-1.1.1.md)** (v1.1.0 filter existed; production wiring discarded the closure each tick). **Baseline:** v1.0.1 on `develop` · **Previous band:** [plan-1.0.x.md](./plan-1.0.x.md) · **Target tag:** v1.1.0 after `make release-check` on `main` diff --git a/docs/plan-1.2.x.md b/docs/plan-1.2.x.md new file mode 100644 index 0000000..d3bd1b2 --- /dev/null +++ b/docs/plan-1.2.x.md @@ -0,0 +1,146 @@ +# pgwd plan 1.2.x — audit hygiene (minor) + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Roadmap index:** [ROADMAP.md](../ROADMAP.md) · **Band:** 1.2.x (minor; process + config honesty + docs/packaging sync) + +**Goal:** Automate dependency and Actions updates; make config parsing fail honestly; harden optional HTTP server timeouts; sync public packaging/docs one-liners and man/README drift left after 1.1.x. + +**Architecture:** (1) GitHub Dependabot for `gomod` and `github-actions` with grouped PRs. (2) Strict YAML (`KnownFields`) and non-silent env int/duration parse. (3) Log store errors that today are discarded with `_` on hysteresis/resolution paths. (4) `http.Server` timeouts. (5) Docs/OCI/nfpm/Homebrew describe all five notifiers; README image details match distroless. + +**Tech Stack:** Existing Go module; GitHub Dependabot; no new runtime dependencies required. + +**Baseline:** v1.1.1 (contract repair) · **Previous band:** [plan-1.1.1.md](./plan-1.1.1.md) · **Target tag:** `v1.2.0` after `make release-check` on `main` + +--- + +## Global Constraints + +- English only in code, comments, commits, docs (repo rule). +- Work on `develop`; never commit features to `main`. +- Show proposed commit message; wait for explicit user approval before `git commit`. +- Cyclomatic complexity ≤ 14; `gofmt -s`; `go vet ./...`. +- Library coverage gate ≥ 80% (`make cover-check`). +- `KnownFields(true)` is **operator-visible**: configs with unknown YAML keys start failing load — document under CHANGELOG **Changed** and a short UPGRADE note or README tip. +- Do not flip `enable_update_check` default in this band (separate product decision). +- Do not add `nokube` build tag, `slog` migration, or new notifier channels. + +--- + +## Behavior contract (to land in SPECIFICATIONS.md / docs) + +### Dependabot + +- Repo ships `.github/dependabot.yml` for: + - `package-ecosystem: gomod` (directory `/`) + - `package-ecosystem: github-actions` (directory `/`) +- Weekly schedule; group Go module updates where Dependabot allows to reduce PR noise. + +### Config honesty + +| Input | Behavior after 1.2 | +|-------|-------------------| +| Unknown YAML keys | Load error (`KnownFields(true)`) | +| `PGWD_*` int/duration set but unparseable | Do **not** silently use `0`; reject or keep previous/default with explicit log/error (prefer fail validation / skip apply with warning — pick one and lock in Task 2) | +| `LastStates` / MaxConn / Stale query errors | Log at error or warn; do not fail open without signal | + +### HTTP server + +- When `http.listen` is set: `ReadHeaderTimeout` (and sensible `ReadTimeout` / `IdleTimeout`) on `http.Server`. + +### Public surface sync + +- OCI / nfpm / Homebrew descriptions list Slack, Loki, PagerDuty, Teams, and generic webhook (not “Slack/Loki” only). +- README “Image details” describes distroless runtime (not Alpine BusyBox runtime). +- Man page: CSV export not “SQLite only”; document `-notifications-repeat-while-firing` and other missing flags as needed. +- README: remove obsolete `-kube-password-*` rows; add 1.1 latch flag to parameter table if still missing. +- `docs/compare.md` snapshot version line → 1.2.0 era. + +--- + +## Out of scope (this band) + +- Latch / Stats / dry-run contract fixes → [plan-1.1.1.md](./plan-1.1.1.md) +- `nokube` binary split, Patroni, per-DB kube, Discord/email +- Blocking `golangci-lint` (optional non-blocking job may be parked) +- Query `statement_timeout` / `db.query_timeout` (nice-to-have; park unless XS leftover) +- Marketing / co-maintainer process + +--- + +## File map + +| File | Role | +|------|------| +| `.github/dependabot.yml` | **Create** | +| `.github/workflows/*.yml`, `GNUmakefile` | Pin tool versions where `@latest` today | +| `internal/config/file.go` | `yaml.NewDecoder` + `KnownFields(true)` | +| `internal/config/config.go` | `envInt` / `envDuration` honesty | +| `internal/config/*_test.go` | Unknown key fails; bad env behavior | +| `internal/run/run.go` | Log discarded store/query errors | +| `internal/httpsrv/server.go` | Server timeouts | +| `Dockerfile`, `Dockerfile.release`, `.goreleaser.yaml` | Description strings | +| `README.md`, `contrib/man/man1/pgwd.1`, `docs/compare.md` | Drift fixes | +| `contrib/systemd/pgwd.service` | Optional XS hardening | +| `SPECIFICATIONS.md`, `CHANGELOG.md`, `ROADMAP.md`, `VERSION` | 1.2.0 | + +--- + +## Design notes (locked) + +1. **Dependabot over Renovate** for this band (native GitHub, one YAML file). Revisit Renovate only if grouping/noise becomes painful. +2. **KnownFields** may break configs that carried typo keys or forward-compat junk — that is intended; document migration (“remove unknown keys”). +3. **envInt:** returning `0` on parse failure is worse than default (turns interval into one-shot). Prefer: if env set and parse fails → log + keep `def`, **or** fail validation at startup when that key was required. Lock in Task 2 implementation notes. +4. Pinning Actions by SHA is desirable; if too heavy for one PR, pin tool **install** versions in Makefile/CI first and leave Action SHA pinning as follow-up checkbox. + +--- + +### Task 1: Dependabot + tool pins + +- [ ] **Step 1: Add `.github/dependabot.yml`** (`gomod` + `github-actions`, weekly, groups for gomod) +- [ ] **Step 2: Replace `@latest` govulncheck/gocyclo (and document goreleaser-action version policy)** +- [ ] **Step 3: Commit** (after approval) + +--- + +### Task 2: KnownFields + env parse + store error logs + +- [ ] **Step 1: Failing tests** — unknown YAML key; `PGWD_INTERVAL=abc` does not become `0` +- [ ] **Step 2: Implement decoder KnownFields + envInt/envDuration fix** +- [ ] **Step 3: Log errors instead of `_` on LastStates / related paths in `internal/run`** +- [ ] **Step 4: Tests PASS; commit** (after approval) + +--- + +### Task 3: HTTP timeouts + +- [ ] **Step 1: Set `ReadHeaderTimeout` (e.g. 10s) on `http.Server` in `httpsrv`** +- [ ] **Step 2: Test or smoke that server still starts; commit** (after approval) + +--- + +### Task 4: Docs / packaging sync + +- [ ] **Step 1: OCI / goreleaser / Homebrew descriptions → five notifiers** +- [ ] **Step 2: README image details → distroless; parameter table + drop dead kube-password flags** +- [ ] **Step 3: Man page CSV + missing flags; compare.md snapshot** +- [ ] **Step 4: Optional systemd hardening XS** +- [ ] **Step 5: Commit** (after approval) + +--- + +### Task 5: Release 1.2.0 + +- [ ] **Step 1:** `VERSION` 1.2.0; CHANGELOG; SPEC notes; ROADMAP; man/badge/ports +- [ ] **Step 2:** `make release-check` +- [ ] **Step 3:** PR → `main`; tag `v1.2.0` + +--- + +## Success criteria + +- [ ] Dependabot config merged; first PR cadence possible +- [ ] Unknown YAML keys fail load; bad env ints do not silently become `0` +- [ ] HTTP metrics server has header timeout +- [ ] Public one-liners and README/man match distroless + five notifiers +- [ ] Tag `v1.2.0` diff --git a/go.mod b/go.mod index 397dd56..c5c2e0f 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ module github.com/hrodrig/pgwd -go 1.26.5 +go 1.26.6 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/go-sql-driver/mysql v1.8.1 github.com/jackc/pgx/v5 v5.9.2 github.com/ncruces/go-sqlite3 v0.32.0 - golang.org/x/sys v0.45.0 + golang.org/x/sys v0.46.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.35.0 k8s.io/apimachinery v0.35.0 @@ -43,10 +43,10 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.55.0 // indirect + golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.21.0 // indirect - golang.org/x/term v0.43.0 // indirect + golang.org/x/term v0.44.0 // indirect golang.org/x/text v0.39.0 // indirect golang.org/x/time v0.9.0 // indirect google.golang.org/protobuf v1.36.8 // indirect diff --git a/go.sum b/go.sum index 9a19cb1..821d2b1 100644 --- a/go.sum +++ b/go.sum @@ -109,16 +109,16 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus= golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= diff --git a/testing/platforms/inventory/group_vars/all.yml b/testing/platforms/inventory/group_vars/all.yml index 296af8c..d5b0338 100644 --- a/testing/platforms/inventory/group_vars/all.yml +++ b/testing/platforms/inventory/group_vars/all.yml @@ -2,6 +2,6 @@ # Shared defaults for all platform test hosts. # Override in hosts.yml or per-host vars as needed. -pgwd_version: "1.1.0" +pgwd_version: "1.1.1" pgwd_release_url: "https://github.com/hrodrig/pgwd/releases/download/v{{ pgwd_version }}" notification_mock_port: 9999