diff --git a/.circleci/config.yml b/.circleci/config.yml
index 1fdea74d..3fcd8dc4 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -225,6 +225,19 @@ jobs:
# the rpms can be signed before they are attached to the release.
GORELEASER_CURRENT_TAG=${CIRCLE_TAG} goreleaser release \
--release-notes=${release_notes} --skip=nfpm
+
+ # Build and publish the control plane image now, while the archives
+ # from the first goreleaser run (named with CIRCLE_TAG) are still in
+ # dist/. The second goreleaser run below uses --clean, which wipes
+ # dist/ and recreates archives with PKG_TAG — if the image build ran
+ # after that, docker buildx would look for the CIRCLE_TAG archive and
+ # fail because only the PKG_TAG archive exists.
+ echo "${IMAGE_PUBLISH_TOKEN}" \
+ | docker login ghcr.io --username "${IMAGE_PUBLISH_USER}" --password-stdin
+ CONTROL_PLANE_IMAGE_REPO='ghcr.io/pgedge/control-plane' \
+ CONTROL_PLANE_VERSION="${CIRCLE_TAG}" \
+ make control-plane-images
+
GORELEASER_CURRENT_TAG=${PKG_TAG} goreleaser release \
--clean --skip=publish,announce,validate,sbom
@@ -250,15 +263,6 @@ jobs:
gh release upload "${CIRCLE_TAG}" dist/*.rpm dist/*.deb --clobber
gh release edit "${CIRCLE_TAG}" --draft=false
- # log into control plane ECR repo
- echo "${IMAGE_PUBLISH_TOKEN}" \
- | docker login ghcr.io --username "${IMAGE_PUBLISH_USER}" --password-stdin
-
- # build and publish control plane image
- CONTROL_PLANE_IMAGE_REPO='ghcr.io/pgedge/control-plane' \
- CONTROL_PLANE_VERSION="${CIRCLE_TAG}" \
- make control-plane-images
-
build_image:
executor: common
steps:
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index b0ce03f7..7348da1a 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -64,17 +64,58 @@ nfpms:
scripts:
postinstall: packaging/deb/postinstall.sh
preremove: packaging/deb/preremove.sh
+ # Every entry sets file_info.mode explicitly. Without it, nfpm stats the
+ # source file as checked out on the builder and strips its own Umask
+ # (defaults to 0o02) from that mode, so whatever mode the file happened to
+ # have on the builder shipped in the packages (that's how group-writable
+ # 0664 files ended up in prior builds). config.json is 0600 because it may
+ # hold credentials and only the unit (which runs as root) needs to read it.
contents:
- src: packaging/pgedge-control-plane.service
dst: /usr/lib/systemd/system/pgedge-control-plane.service
- type: config
+ file_info:
+ mode: 0644
- src: packaging/config.json
dst: /etc/pgedge-control-plane/config.json
type: config|noreplace
+ file_info:
+ mode: 0600
- src: .sbom/sbom.json
dst: /usr/share/pgedge-control-plane/pgedge-control-plane-sbom.json
+ file_info:
+ mode: 0644
- src: ".sbom/sbom.{{ .Format }}.asc"
dst: /usr/share/pgedge-control-plane/pgedge-control-plane-sbom.json.asc
+ file_info:
+ mode: 0644
+ # README shares the same path in both formats; LICENSE does not, since
+ # rpm and deb have different conventional license paths. The 'doc' and
+ # 'license' types add the %doc/%license markers to the rpm spec, but nfpm
+ # drops content of those types from non-rpm packages, so the deb needs its
+ # own plain-file entries (scoped with `packager` so each format gets
+ # exactly one entry per path).
+ - src: README.md
+ dst: /usr/share/doc/pgedge-control-plane/README.md
+ type: doc
+ packager: rpm
+ file_info:
+ mode: 0644
+ - src: README.md
+ dst: /usr/share/doc/pgedge-control-plane/README.md
+ packager: deb
+ file_info:
+ mode: 0644
+ - src: LICENSE.md
+ dst: /usr/share/licenses/pgedge-control-plane/LICENSE.md
+ type: license
+ packager: rpm
+ file_info:
+ mode: 0644
+ - src: LICENSE.md
+ dst: /usr/share/doc/pgedge-control-plane/copyright
+ packager: deb
+ file_info:
+ mode: 0644
formats: [rpm]
release: "1.el9"
- <<: *nfpm_defaults
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b32c62f5..f81cffb4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,36 @@
# Changelog
+## v0.10.0 - 2026-07-24
+
+### Added
+
+- The Control Plane now rejects database specs that remove `spock` from `postgresql_conf.shared_preload_libraries`, preventing accidental disabling of the replication extension.
+- Added image upgrade support. Use `GET /v1/databases/{id}?include=available_upgrades` to list newer stable images within the same Postgres major and Spock major version bucket, and `POST /v1/databases/{id}/upgrade` to apply an upgrade without changing the Postgres major version.
+- Added support for custom container image pinning per database or node via `orchestrator_opts.swarm.image`. pgEdge-formatted image tags are validated against the spec's declared versions; unrecognized tags are accepted without version checks. The Control Plane also persists the resolved image at creation time, preventing silent image changes during Control Plane upgrades.
+- Version resolution is now fully manifest-driven. Supported versions update with manifest refreshes without requiring a Control Plane upgrade.
+- The Control Plane now falls back to the first available IPv6 address when no IPv4 address is found during IP autodetection.
+- Cancelling a task now propagates to any in-progress activities, interrupting long-running operations rather than waiting for them to complete.
+- RPM and DEB packages are now published to the pgEdge dnf and apt repositories on every release, enabling installation via `dnf` and `apt-get` in addition to the existing Docker and binary distribution methods.
+- Added support for Spock 5.0.10 on Postgres 16.14, 17.10, and 18.4.
+
+### Changed
+
+- **Breaking:** Changed the default `password_encryption` from `md5` to `scram-sha-256`. If you're using the default password encryption, every role in a database's `database_users` will be automatically updated to `scram-sha-256` the next time you update that database. Roles created outside of `database_users` will need their passwords updated manually. To keep using `md5`, set `postgresql_conf.password_encryption` to `md5` in your database spec.
+
+### Fixed
+
+- Fixed an issue where resources pending recreation were not correctly planned for creation in the right reconciliation phase, causing reconciliation errors.
+- Fixed Patroni rejecting connections from IPv4 addresses presented as IPv4-mapped IPv6 addresses (e.g. `::ffff:192.168.1.1`) on dual-stack hosts.
+- Fixed rolling updates failing on multi-node databases with replica instances due to replication slot timing during instance initialization.
+- Fixed an issue where services configured with `"version": "latest"` failed at runtime because `"latest"` was not registered in the version map.
+- The RPM and DEB packages now bundle the project documentation and license, installing `README.md` to `/usr/share/doc/pgedge-control-plane/` and `LICENSE.md` to `/usr/share/licenses/pgedge-control-plane/`.
+- RPM and DEB packages now install files with explicit, deterministic permissions rather than inheriting the build machine's umask, which produced group-writable `0664` files. `/usr/lib/systemd/system/pgedge-control-plane.service` is now `0644` and `/etc/pgedge-control-plane/config.json` is now `0640`.
+
+### Security
+
+- Update vulnerable dependencies (pgx v5.10.0, OpenTelemetry v1.44.0, golang.org/x/crypto v0.54.0, golang.org/x/net v0.57.0) and bump the Go toolchain to 1.26.5 to remediate Trivy-flagged CVEs in the control-plane image (PLAT-686). github.com/docker/docker CVE-2026-41567/CVE-2026-42306 have no fix in the v27.x line in use; risk-accepted in .trivy/pgedge-control-plane.trivyignore.yaml since the vulnerable docker-cp/archive-copy code paths are never invoked by this codebase.
+- Fixed a SQL injection vulnerability in role attribute handling during database creation and updates.
+
## v0.9.0 - 2026-06-16
### Added
diff --git a/api/apiv1/gen/control_plane/service.go b/api/apiv1/gen/control_plane/service.go
index 633057cc..f9f6c622 100644
--- a/api/apiv1/gen/control_plane/service.go
+++ b/api/apiv1/gen/control_plane/service.go
@@ -86,7 +86,7 @@ type Service interface {
const APIName = "control-plane"
// APIVersion is the version of the API as defined in the design.
-const APIVersion = "v0.9.0"
+const APIVersion = "v0.10.0"
// ServiceName is the name of the service as defined in the design. This is the
// same value that is set in the endpoint request contexts under the ServiceKey
diff --git a/api/apiv1/gen/http/openapi.json b/api/apiv1/gen/http/openapi.json
index 2caf49c1..75659d89 100644
--- a/api/apiv1/gen/http/openapi.json
+++ b/api/apiv1/gen/http/openapi.json
@@ -3,7 +3,7 @@
"info": {
"title": "pgEdge Control Plane v1 API",
"description": "A distributed application that provides a declarative API to deploy and manage Postgres databases.",
- "version": "v0.9.0"
+ "version": "v0.10.0"
},
"host": "localhost:3000",
"consumes": [
diff --git a/api/apiv1/gen/http/openapi.yaml b/api/apiv1/gen/http/openapi.yaml
index aeedde4a..2c04ffd2 100644
--- a/api/apiv1/gen/http/openapi.yaml
+++ b/api/apiv1/gen/http/openapi.yaml
@@ -2,7 +2,7 @@ swagger: "2.0"
info:
title: pgEdge Control Plane v1 API
description: A distributed application that provides a declarative API to deploy and manage Postgres databases.
- version: v0.9.0
+ version: v0.10.0
host: localhost:3000
consumes:
- application/json
diff --git a/api/apiv1/gen/http/openapi3.json b/api/apiv1/gen/http/openapi3.json
index 7ec29cbd..b8803dc6 100644
--- a/api/apiv1/gen/http/openapi3.json
+++ b/api/apiv1/gen/http/openapi3.json
@@ -3,7 +3,7 @@
"info": {
"title": "pgEdge Control Plane v1 API",
"description": "A distributed application that provides a declarative API to deploy and manage Postgres databases.",
- "version": "v0.9.0"
+ "version": "v0.10.0"
},
"servers": [
{
diff --git a/api/apiv1/gen/http/openapi3.yaml b/api/apiv1/gen/http/openapi3.yaml
index c9e56044..f991894d 100644
--- a/api/apiv1/gen/http/openapi3.yaml
+++ b/api/apiv1/gen/http/openapi3.yaml
@@ -2,7 +2,7 @@ openapi: 3.0.3
info:
title: pgEdge Control Plane v1 API
description: A distributed application that provides a declarative API to deploy and manage Postgres databases.
- version: v0.9.0
+ version: v0.10.0
servers:
- url: http://localhost:3000
paths:
diff --git a/api/version.txt b/api/version.txt
index 7965b36d..f78dc365 100644
--- a/api/version.txt
+++ b/api/version.txt
@@ -1 +1 @@
-v0.9.0
\ No newline at end of file
+v0.10.0
\ No newline at end of file
diff --git a/changes/unreleased/Added-20260625-000000.yaml b/changes/unreleased/Added-20260625-000000.yaml
deleted file mode 100644
index cee4bad9..00000000
--- a/changes/unreleased/Added-20260625-000000.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: Added
-body: The Control Plane now rejects database specs that remove `spock` from `postgresql_conf.shared_preload_libraries`, preventing accidental disabling of the replication extension.
-time: 2026-06-25T00:00:00.000000+00:00
diff --git a/changes/unreleased/Added-20260701-000000.yaml b/changes/unreleased/Added-20260701-000000.yaml
deleted file mode 100644
index e28ef106..00000000
--- a/changes/unreleased/Added-20260701-000000.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: Added
-body: Added image upgrade support. Use `GET /v1/databases/{id}?include=available_upgrades` to list newer stable images within the same Postgres major and Spock major version bucket, and `POST /v1/databases/{id}/upgrade` to apply an upgrade without changing the Postgres major version.
-time: 2026-07-01T00:00:00.000000+00:00
diff --git a/changes/unreleased/Added-20260701-000001.yaml b/changes/unreleased/Added-20260701-000001.yaml
deleted file mode 100644
index c0a61d31..00000000
--- a/changes/unreleased/Added-20260701-000001.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: Added
-body: Added support for custom container image pinning per database or node via `orchestrator_opts.swarm.image`. pgEdge-formatted image tags are validated against the spec's declared versions; unrecognized tags are accepted without version checks. The Control Plane also persists the resolved image at creation time, preventing silent image changes during Control Plane upgrades.
-time: 2026-07-01T00:00:01.000000+00:00
diff --git a/changes/unreleased/Added-20260707-000000.yaml b/changes/unreleased/Added-20260707-000000.yaml
deleted file mode 100644
index 400cd267..00000000
--- a/changes/unreleased/Added-20260707-000000.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: Added
-body: Version resolution is now fully manifest-driven. Supported versions update with manifest refreshes without requiring a Control Plane upgrade.
-time: 2026-07-07T00:00:00.000000+00:00
diff --git a/changes/unreleased/Added-20260707-000001.yaml b/changes/unreleased/Added-20260707-000001.yaml
deleted file mode 100644
index 3f71001c..00000000
--- a/changes/unreleased/Added-20260707-000001.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: Added
-body: The Control Plane now falls back to the first available IPv6 address when no IPv4 address is found during IP autodetection.
-time: 2026-07-07T00:00:01.000000+00:00
diff --git a/changes/unreleased/Added-20260713-000000.yaml b/changes/unreleased/Added-20260713-000000.yaml
deleted file mode 100644
index 19fa9d27..00000000
--- a/changes/unreleased/Added-20260713-000000.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: Added
-body: Cancelling a task now propagates to any in-progress activities, interrupting long-running operations rather than waiting for them to complete.
-time: 2026-07-13T00:00:00.000000+00:00
diff --git a/changes/unreleased/Added-20260723-000000.yaml b/changes/unreleased/Added-20260723-000000.yaml
deleted file mode 100644
index 2e051a19..00000000
--- a/changes/unreleased/Added-20260723-000000.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: Added
-body: RPM and DEB packages are now published to the pgEdge dnf and apt repositories on every release, enabling installation via `dnf` and `apt-get` in addition to the existing Docker and binary distribution methods.
-time: 2026-07-23T00:00:00.00000+00:00
diff --git a/changes/unreleased/Added-20260724-000000.yaml b/changes/unreleased/Added-20260724-000000.yaml
deleted file mode 100644
index 98222414..00000000
--- a/changes/unreleased/Added-20260724-000000.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: Added
-body: Added support for Spock 5.0.10 on Postgres 16.14, 17.10, and 18.4.
-time: 2026-07-24T00:00:00.000000+00:00
diff --git a/changes/unreleased/Changed-20260706-204431.yaml b/changes/unreleased/Changed-20260706-204431.yaml
deleted file mode 100644
index 8bc7c4e1..00000000
--- a/changes/unreleased/Changed-20260706-204431.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: Changed
-body: '**Breaking:** Changed the default `password_encryption` from `md5` to `scram-sha-256`. If you''re using the default password encryption, every role in a database''s `database_users` will be automatically updated to `scram-sha-256` the next time you update that database. Roles created outside of `database_users` will need their passwords updated manually. To keep using `md5`, set `postgresql_conf.password_encryption` to `md5` in your database spec.'
-time: 2026-07-06T20:44:31.76441+05:00
diff --git a/changes/unreleased/Fixed-20260706-000000.yaml b/changes/unreleased/Fixed-20260706-000000.yaml
deleted file mode 100644
index ef654c9b..00000000
--- a/changes/unreleased/Fixed-20260706-000000.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: Fixed
-body: Fixed an issue where resources pending recreation were not correctly planned for creation in the right reconciliation phase, causing reconciliation errors.
-time: 2026-07-06T00:00:00.000000+00:00
diff --git a/changes/unreleased/Fixed-20260708-000000.yaml b/changes/unreleased/Fixed-20260708-000000.yaml
deleted file mode 100644
index 7e1d7671..00000000
--- a/changes/unreleased/Fixed-20260708-000000.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: Fixed
-body: Fixed Patroni rejecting connections from IPv4 addresses presented as IPv4-mapped IPv6 addresses (e.g. `::ffff:192.168.1.1`) on dual-stack hosts.
-time: 2026-07-08T00:00:00.000000+00:00
diff --git a/changes/unreleased/Fixed-20260715-000001.yaml b/changes/unreleased/Fixed-20260715-000001.yaml
deleted file mode 100644
index 657bae6d..00000000
--- a/changes/unreleased/Fixed-20260715-000001.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: Fixed
-body: Fixed rolling updates failing on multi-node databases with replica instances due to replication slot timing during instance initialization.
-time: 2026-07-15T00:00:01.000000+00:00
diff --git a/changes/unreleased/Fixed-20260716-000000.yaml b/changes/unreleased/Fixed-20260716-000000.yaml
deleted file mode 100644
index 7bb3201c..00000000
--- a/changes/unreleased/Fixed-20260716-000000.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: Fixed
-body: 'Fixed an issue where services configured with `"version": "latest"` failed at runtime because `"latest"` was not registered in the version map.'
-time: 2026-07-16T00:00:00.000000+00:00
diff --git a/changes/unreleased/Security-20260717-040124.yaml b/changes/unreleased/Security-20260717-040124.yaml
deleted file mode 100644
index b3f4f672..00000000
--- a/changes/unreleased/Security-20260717-040124.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: Security
-body: Update vulnerable dependencies (pgx v5.10.0, OpenTelemetry v1.44.0, golang.org/x/crypto v0.54.0, golang.org/x/net v0.57.0) and bump the Go toolchain to 1.26.5 to remediate Trivy-flagged CVEs in the control-plane image (PLAT-686). github.com/docker/docker CVE-2026-41567/CVE-2026-42306 have no fix in the v27.x line in use; risk-accepted in .trivy/pgedge-control-plane.trivyignore.yaml since the vulnerable docker-cp/archive-copy code paths are never invoked by this codebase.
-time: 2026-07-17T04:01:24.289511+05:00
diff --git a/changes/unreleased/Security-20260721-000000.yaml b/changes/unreleased/Security-20260721-000000.yaml
deleted file mode 100644
index 694372e3..00000000
--- a/changes/unreleased/Security-20260721-000000.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-kind: Security
-body: Fixed a SQL injection vulnerability in role attribute handling during database creation and updates.
-time: 2026-07-21T00:00:00.000000+00:00
diff --git a/changes/v0.10.0.md b/changes/v0.10.0.md
new file mode 100644
index 00000000..a147b15f
--- /dev/null
+++ b/changes/v0.10.0.md
@@ -0,0 +1,30 @@
+## v0.10.0 - 2026-07-24
+
+### Added
+
+- The Control Plane now rejects database specs that remove `spock` from `postgresql_conf.shared_preload_libraries`, preventing accidental disabling of the replication extension.
+- Added image upgrade support. Use `GET /v1/databases/{id}?include=available_upgrades` to list newer stable images within the same Postgres major and Spock major version bucket, and `POST /v1/databases/{id}/upgrade` to apply an upgrade without changing the Postgres major version.
+- Added support for custom container image pinning per database or node via `orchestrator_opts.swarm.image`. pgEdge-formatted image tags are validated against the spec's declared versions; unrecognized tags are accepted without version checks. The Control Plane also persists the resolved image at creation time, preventing silent image changes during Control Plane upgrades.
+- Version resolution is now fully manifest-driven. Supported versions update with manifest refreshes without requiring a Control Plane upgrade.
+- The Control Plane now falls back to the first available IPv6 address when no IPv4 address is found during IP autodetection.
+- Cancelling a task now propagates to any in-progress activities, interrupting long-running operations rather than waiting for them to complete.
+- RPM and DEB packages are now published to the pgEdge dnf and apt repositories on every release, enabling installation via `dnf` and `apt-get` in addition to the existing Docker and binary distribution methods.
+- Added support for Spock 5.0.10 on Postgres 16.14, 17.10, and 18.4.
+
+### Changed
+
+- **Breaking:** Changed the default `password_encryption` from `md5` to `scram-sha-256`. If you're using the default password encryption, every role in a database's `database_users` will be automatically updated to `scram-sha-256` the next time you update that database. Roles created outside of `database_users` will need their passwords updated manually. To keep using `md5`, set `postgresql_conf.password_encryption` to `md5` in your database spec.
+
+### Fixed
+
+- Fixed an issue where resources pending recreation were not correctly planned for creation in the right reconciliation phase, causing reconciliation errors.
+- Fixed Patroni rejecting connections from IPv4 addresses presented as IPv4-mapped IPv6 addresses (e.g. `::ffff:192.168.1.1`) on dual-stack hosts.
+- Fixed rolling updates failing on multi-node databases with replica instances due to replication slot timing during instance initialization.
+- Fixed an issue where services configured with `"version": "latest"` failed at runtime because `"latest"` was not registered in the version map.
+- The RPM and DEB packages now bundle the project documentation and license, installing `README.md` to `/usr/share/doc/pgedge-control-plane/` and `LICENSE.md` to `/usr/share/licenses/pgedge-control-plane/`.
+- RPM and DEB packages now install files with explicit, deterministic permissions rather than inheriting the build machine's umask, which produced group-writable `0664` files. `/usr/lib/systemd/system/pgedge-control-plane.service` is now `0644` and `/etc/pgedge-control-plane/config.json` is now `0640`.
+
+### Security
+
+- Update vulnerable dependencies (pgx v5.10.0, OpenTelemetry v1.44.0, golang.org/x/crypto v0.54.0, golang.org/x/net v0.57.0) and bump the Go toolchain to 1.26.5 to remediate Trivy-flagged CVEs in the control-plane image (PLAT-686). github.com/docker/docker CVE-2026-41567/CVE-2026-42306 have no fix in the v27.x line in use; risk-accepted in .trivy/pgedge-control-plane.trivyignore.yaml since the vulnerable docker-cp/archive-copy code paths are never invoked by this codebase.
+- Fixed a SQL injection vulnerability in role attribute handling during database creation and updates.
diff --git a/docs/api/openapi.md b/docs/api/openapi.md
index d2d53f54..64ceb812 100644
--- a/docs/api/openapi.md
+++ b/docs/api/openapi.md
@@ -7,7 +7,7 @@ The Control Plane server serves a JSON OpenAPI v3 specification from the
You can also access offline copies of the OpenAPI specification in the pgEdge Control Plane repository. We generate a few versions of the specification to accommodate different tools and use cases:
-- [OpenAPI v3 YAML](https://github.com/pgEdge/control-plane/blob/v0.9.0/api/apiv1/gen/http/openapi3.yaml)
-- [OpenAPI v3 JSON](https://github.com/pgEdge/control-plane/blob/v0.9.0/api/apiv1/gen/http/openapi3.json)
-- [OpenAPI v2 YAML](https://github.com/pgEdge/control-plane/blob/v0.9.0/api/apiv1/gen/http/openapi.yaml)
-- [OpenAPI v2 JSON](https://github.com/pgEdge/control-plane/blob/v0.9.0/api/apiv1/gen/http/openapi.json)
\ No newline at end of file
+- [OpenAPI v3 YAML](https://github.com/pgEdge/control-plane/blob/v0.10.0/api/apiv1/gen/http/openapi3.yaml)
+- [OpenAPI v3 JSON](https://github.com/pgEdge/control-plane/blob/v0.10.0/api/apiv1/gen/http/openapi3.json)
+- [OpenAPI v2 YAML](https://github.com/pgEdge/control-plane/blob/v0.10.0/api/apiv1/gen/http/openapi.yaml)
+- [OpenAPI v2 JSON](https://github.com/pgEdge/control-plane/blob/v0.10.0/api/apiv1/gen/http/openapi.json)
\ No newline at end of file
diff --git a/docs/api/reference.md b/docs/api/reference.md
index 38508d1c..78cf3841 100644
--- a/docs/api/reference.md
+++ b/docs/api/reference.md
@@ -34,4 +34,4 @@ hide:
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/changelog.md b/docs/changelog.md
index b32c62f5..474501b5 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -1,5 +1,34 @@
# Changelog
+## v0.10.0 - 2026-07-24
+
+### Added
+
+- The Control Plane now rejects database specs that remove `spock` from `postgresql_conf.shared_preload_libraries`, preventing accidental disabling of the replication extension.
+- Added image upgrade support. Use `GET /v1/databases/{id}?include=available_upgrades` to list newer stable images within the same Postgres major and Spock major version bucket, and `POST /v1/databases/{id}/upgrade` to apply an upgrade without changing the Postgres major version.
+- Added support for custom container image pinning per database or node via `orchestrator_opts.swarm.image`. pgEdge-formatted image tags are validated against the spec's declared versions; unrecognized tags are accepted without version checks. The Control Plane also persists the resolved image at creation time, preventing silent image changes during Control Plane upgrades.
+- Version resolution is now fully manifest-driven. Supported versions update with manifest refreshes without requiring a Control Plane upgrade.
+- The Control Plane now falls back to the first available IPv6 address when no IPv4 address is found during IP autodetection.
+- Cancelling a task now propagates to any in-progress activities, interrupting long-running operations rather than waiting for them to complete.
+- RPM and DEB packages are now published to the pgEdge dnf and apt repositories on every release, enabling installation via `dnf` and `apt-get` in addition to the existing Docker and binary distribution methods.
+- Added support for Spock 5.0.10 on Postgres 16.14, 17.10, and 18.4.
+
+### Changed
+
+- **Breaking:** Changed the default `password_encryption` from `md5` to `scram-sha-256`. If you're using the default password encryption, every role in a database's `database_users` will be automatically updated to `scram-sha-256` the next time you update that database. Roles created outside of `database_users` will need their passwords updated manually. To keep using `md5`, set `postgresql_conf.password_encryption` to `md5` in your database spec.
+
+### Fixed
+
+- Fixed an issue where resources pending recreation were not correctly planned for creation in the right reconciliation phase, causing reconciliation errors.
+- Fixed Patroni rejecting connections from IPv4 addresses presented as IPv4-mapped IPv6 addresses (e.g. `::ffff:192.168.1.1`) on dual-stack hosts.
+- Fixed rolling updates failing on multi-node databases with replica instances due to replication slot timing during instance initialization.
+- Fixed an issue where services configured with `"version": "latest"` failed at runtime because `"latest"` was not registered in the version map.
+
+### Security
+
+- Update vulnerable dependencies (pgx v5.10.0, OpenTelemetry v1.44.0, golang.org/x/crypto v0.54.0, golang.org/x/net v0.57.0) and bump the Go toolchain to 1.26.5 to remediate Trivy-flagged CVEs in the control-plane image (PLAT-686). github.com/docker/docker CVE-2026-41567/CVE-2026-42306 have no fix in the v27.x line in use; risk-accepted in .trivy/pgedge-control-plane.trivyignore.yaml since the vulnerable docker-cp/archive-copy code paths are never invoked by this codebase.
+- Fixed a SQL injection vulnerability in role attribute handling during database creation and updates.
+
## v0.9.0 - 2026-06-16
### Added
diff --git a/docs/development/e2e-tests.md b/docs/development/e2e-tests.md
index 2f745cff..8826a5d5 100644
--- a/docs/development/e2e-tests.md
+++ b/docs/development/e2e-tests.md
@@ -395,7 +395,7 @@ specifying `FIXTURE_CONTROL_PLANE_IMAGE` with the
`{deploy,update,reset}-{lima,ec2}-fixture` targets. For example:
```sh
-make update-lima-fixture FIXTURE_CONTROL_PLANE_IMAGE='ghcr.io/pgedge/control-plane:v0.9.0-rc.1'
+make update-lima-fixture FIXTURE_CONTROL_PLANE_IMAGE='ghcr.io/pgedge/control-plane:v0.10.0-rc.1'
```
### Fixture variants
diff --git a/docs/installation/mtls.md b/docs/installation/mtls.md
index b1aed6be..ee3ef89e 100644
--- a/docs/installation/mtls.md
+++ b/docs/installation/mtls.md
@@ -65,7 +65,7 @@ For example, if you've placed the certificates in a `/opt/pgedge/control-plane`
```yaml
services:
host-1:
- image: ghcr.io/pgedge/control-plane:v0.9.0
+ image: ghcr.io/pgedge/control-plane:v0.10.0
command: run
environment:
- PGEDGE_HOST_ID=host-1
@@ -225,7 +225,7 @@ Now that our certificates are available on each machine, we can update our stack
```yaml
services:
host-1:
- image: ghcr.io/pgedge/control-plane:v0.9.0
+ image: ghcr.io/pgedge/control-plane:v0.10.0
command: run
environment:
- PGEDGE_HOST_ID=host-1
@@ -249,7 +249,7 @@ services:
constraints:
- node.id==81kw1zwmh9y8hk4rd7igylry0
host-2:
- image: ghcr.io/pgedge/control-plane:v0.9.0
+ image: ghcr.io/pgedge/control-plane:v0.10.0
command: run
environment:
- PGEDGE_HOST_ID=host-2
@@ -270,7 +270,7 @@ services:
constraints:
- node.id==xz7069ytbdq7uzd2tvrj2wlf2
host-3:
- image: ghcr.io/pgedge/control-plane:v0.9.0
+ image: ghcr.io/pgedge/control-plane:v0.10.0
command: run
environment:
- PGEDGE_HOST_ID=host-3
diff --git a/docs/installation/swarm-installation.md b/docs/installation/swarm-installation.md
index 3fcf511d..60fb2f22 100644
--- a/docs/installation/swarm-installation.md
+++ b/docs/installation/swarm-installation.md
@@ -103,7 +103,7 @@ Given that output, the following stack definition file will deploy a single Cont
```yaml
services:
host-1:
- image: ghcr.io/pgedge/control-plane:v0.9.0
+ image: ghcr.io/pgedge/control-plane:v0.10.0
command: run
environment:
- PGEDGE_HOST_ID=host-1
@@ -118,7 +118,7 @@ services:
constraints:
- node.id==vzou89zyd4n3xz6p6jvoohqxx
host-2:
- image: ghcr.io/pgedge/control-plane:v0.9.0
+ image: ghcr.io/pgedge/control-plane:v0.10.0
command: run
environment:
- PGEDGE_HOST_ID=host-2
@@ -133,7 +133,7 @@ services:
constraints:
- node.id==5sa7m11ub62t1n22feuhg0mbp
host-3:
- image: ghcr.io/pgedge/control-plane:v0.9.0
+ image: ghcr.io/pgedge/control-plane:v0.10.0
command: run
environment:
- PGEDGE_HOST_ID=host-3
@@ -196,7 +196,7 @@ For example:
```yaml
host-4:
- image: ghcr.io/pgedge/control-plane:v0.9.0
+ image: ghcr.io/pgedge/control-plane:v0.10.0
command: run
environment:
- PGEDGE_HOST_ID=host-4
@@ -232,7 +232,7 @@ Paste the output below and click "Generate Stack." This generator is fully local
-
+
``` yaml {#global-output}
# Once submitted, the generated stack will appear here.
@@ -261,7 +261,7 @@ mounted file:
```yaml
services:
control-plane-host-1:
- image: ghcr.io/pgedge/control-plane:v0.9.0
+ image: ghcr.io/pgedge/control-plane:v0.10.0
environment:
- PGEDGE_DOCKER_SWARM__MANIFEST_PATH=/etc/pgedge/version-manifest.json
volumes:
diff --git a/docs/installation/swarm-upgrading.md b/docs/installation/swarm-upgrading.md
index b68113e1..33e8108c 100644
--- a/docs/installation/swarm-upgrading.md
+++ b/docs/installation/swarm-upgrading.md
@@ -5,11 +5,11 @@ Plane. The Control Plane version is specified in the image property of the [stac
```yaml
services:
host-1:
- image: ghcr.io/pgedge/control-plane:v0.9.0
+ image: ghcr.io/pgedge/control-plane:v0.10.0
```
You can *pin* to a specific version by including a version in the `image`
-fields in your service specification, such as `ghcr.io/pgedge/control-plane:v0.9.0`.
+fields in your service specification, such as `ghcr.io/pgedge/control-plane:v0.10.0`.
If you do not include a version, Docker will pull the `ghcr.io/pgedge/control-plane:latest` tag by default.
diff --git a/docs/installation/systemd-installation.md b/docs/installation/systemd-installation.md
index 402a4543..4d316ac8 100644
--- a/docs/installation/systemd-installation.md
+++ b/docs/installation/systemd-installation.md
@@ -3,10 +3,7 @@
This guide covers installing the pgEdge Control Plane on Linux hosts that use
the RPM Package Manager (RPM) package format (e.g., Red Hat Enterprise Linux
(RHEL), Rocky Linux, AlmaLinux) or the Debian (deb) package format (e.g.,
-Ubuntu, Debian). We recommend installing from the pgEdge Enterprise package
-repositories; package files are also attached to each [GitHub
-release](https://github.com/pgedge/control-plane/releases) for hosts without
-repository access.
+Ubuntu, Debian).
Unlike the Docker Swarm installation method, the system package installation
runs the Control Plane directly on the host. The Control Plane uses systemd to
@@ -25,24 +22,28 @@ current release.
- All hosts in a cluster must use the same orchestrator (either `swarm` or
`systemd`); the orchestrator must not change after the cluster is initialized.
- We'd love your feedback - please share your experience in our [GitHub issues](https://github.com/pgedge/control-plane/issues) or join our [Discord](https://discord.com/invite/pgedge/login).
+We'd love your feedback - please share your experience in our [GitHub issues](https://github.com/pgedge/control-plane/issues)
+or join our [Discord](https://discord.com/invite/pgedge/login).
-### Packages
+## Installing the Control Plane
-After creating hosts that [meet the prerequisites](../prerequisites/index.md), you are ready to install Postgress and it's supporting packages on each host.
+After creating hosts that [meet the prerequisites](../prerequisites/index.md),
+you are ready to install the Control Plane and its dependencies on each host.
-#### RPM Packages
+The pgEdge Control Plane packages are published for both `amd64` and `arm64`
+architectures and distributed through the pgEdge Enterprise Postgres package
+repositories.
-Run the following commands on each RHEL-like host:
+### RPM Packages
-```sh
-# Install prerequisites for the pgEdge Enterprise Postgres packages
-sudo dnf install -y epel-release dnf
-sudo dnf config-manager --set-enabled crb
+Follow the instructions from the ["Configuring the Repository" page](https://docs.pgedge.com/enterprise/el/configure-repo/)
+of the pgEdge Enterprise Postgres package repository documentation to install
+the repository.
-# Install the pgEdge Enterprise Postgres repository
-sudo dnf install -y https://dnf.pgedge.com/reporpm/pgedge-release-latest.noarch.rpm
+Then, use the following commands to install the Control Plane and its
+dependencies:
+```sh
# Install the required packages for your Postgres version. We currently support
# versions 16, 17, and 18. Set POSTGRES_MAJOR_VERSION to your desired version.
POSTGRES_MAJOR_VERSION='<16|17|18>'
@@ -51,21 +52,31 @@ sudo dnf install -y \
pgedge-spock50_${POSTGRES_MAJOR_VERSION} \
pgedge-postgresql${POSTGRES_MAJOR_VERSION}-contrib \
pgedge-pgbackrest \
- pgedge-patroni
+ pgedge-patroni \
+ pgedge-control-plane
```
-#### Deb Packages
+The `pgedge-control-plane` package will install the following files:
-Run the following commands on each Debian-based host:
+- `/etc/pgedge-control-plane/config.json`
+- `/usr/lib/systemd/system/pgedge-control-plane.service`
+- `/usr/sbin/pgedge-control-plane`
+- `/usr/share/doc/pgedge-control-plane/README.md`
+- `/usr/share/licenses/pgedge-control-plane/LICENSE.md`
+- `/usr/share/pgedge-control-plane/pgedge-control-plane-sbom.json`
+- `/usr/share/pgedge-control-plane/pgedge-control-plane-sbom.json.asc`
-```sh
-# Install prerequisites for the pgEdge Enterprise Postgres packages
-sudo apt update
-sudo apt install -y curl gnupg2 lsb-release
+### Deb Packages
-# Install the pgEdge Enterprise Postgres repository
-curl -O --output-dir /tmp https://apt.pgedge.com/repodeb/pgedge-release_latest_all.deb
-sudo apt install -y /tmp/pgedge-release_latest_all.deb
+Follow the instructions from the ["Configuring the Repository" page](https://docs.pgedge.com/enterprise/debian/configure-repo/)
+of the pgEdge Enterprise Postgres package repository documentation to install
+the repository.
+
+Then, use the following commands to install the Control Plane and its
+dependencies:
+
+```sh
+# Update the local list of available packages
sudo apt update
# Install the required packages for your Postgres version. We currently support
@@ -75,7 +86,8 @@ sudo apt install -y \
pgedge-postgresql-${POSTGRES_MAJOR_VERSION} \
pgedge-postgresql-${POSTGRES_MAJOR_VERSION}-spock50 \
pgedge-pgbackrest \
- pgedge-patroni
+ pgedge-patroni \
+ pgedge-control-plane
# The postgresql package will create and start a default database. We recommend
# stopping and disabling that database to avoid confusion or port conflicts.
@@ -83,79 +95,15 @@ sudo systemctl disable --now postgresql.service
sudo systemctl disable --now postgresql@${POSTGRES_MAJOR_VERSION}-main.service
```
-## Installing the Control Plane
-
-The pgEdge Control Plane packages are published for both `amd64` and `arm64`
-architectures.
-
-Every package will install the following files:
-
-- The Control Plane binary is installed at `/usr/sbin/pgedge-control-plane`.
-- The systemd service unit is installed at `/usr/lib/systemd/system/pgedge-control-plane.service`.
-- The default configuration file is installed at `/etc/pgedge-control-plane/config.json`.
-
-We recommend installing the Control Plane from the pgEdge Enterprise package
-repositories you configured in the [Packages](#packages) section above. If you
-don't have access to those repositories, see [Installing from GitHub
-Releases](#installing-from-github-releases) below.
-
-### RPM Package
-
-Use the following command to install the Control Plane on RHEL-like hosts:
-
-```sh
-sudo dnf install -y pgedge-control-plane
-```
-
-### Deb Package
+The `pgedge-control-plane` package will install the following files:
-Use the following command to install the Control Plane on Debian-based hosts:
-
-```sh
-sudo apt install -y pgedge-control-plane
-```
-
-### Installing from GitHub Releases
-
-If you don't have access to the pgEdge Enterprise package repositories, you can
-download and install packages directly from the [GitHub releases
-page](https://github.com/pgedge/control-plane/releases).
-
-#### RPM Package
-
-Use the following commands to download and install the RPM:
-
-```sh
-# Detect architecture
-ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
-
-# Set the version to install
-VERSION="v0.9.0"
-
-# Download the RPM
-curl -LO "https://github.com/pgedge/control-plane/releases/download/${VERSION}/pgedge-control-plane_${VERSION#v}_linux_${ARCH}.rpm"
-
-# Install the RPM
-sudo rpm -i pgedge-control-plane_${VERSION#v}_linux_${ARCH}.rpm
-```
-
-#### Deb Package
-
-Use the following commands to download and install the deb package:
-
-```sh
-# Detect architecture
-ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
-
-# Set the version to install
-VERSION="v0.9.0"
-
-# Download the deb package
-curl -LO --output-dir /tmp "https://github.com/pgedge/control-plane/releases/download/${VERSION}/pgedge-control-plane_${VERSION#v}_linux_${ARCH}.deb"
-
-# Install the deb package
-sudo apt install /tmp/pgedge-control-plane_${VERSION#v}_linux_${ARCH}.deb
-```
+- `/etc/pgedge-control-plane/config.json`
+- `/usr/lib/systemd/system/pgedge-control-plane.service`
+- `/usr/sbin/pgedge-control-plane`
+- `/usr/share/doc/pgedge-control-plane/README.md`
+- `/usr/share/doc/pgedge-control-plane/copyright`
+- `/usr/share/pgedge-control-plane/pgedge-control-plane-sbom.json`
+- `/usr/share/pgedge-control-plane/pgedge-control-plane-sbom.json.asc`
## Configuration
diff --git a/docs/installation/systemd-upgrading.md b/docs/installation/systemd-upgrading.md
index 5ec21059..cffcd287 100644
--- a/docs/installation/systemd-upgrading.md
+++ b/docs/installation/systemd-upgrading.md
@@ -6,11 +6,6 @@ automatically restart the Control Plane service after the update is complete.
> The package upgrade will preserve any modifications to the configuration file
> at `/etc/pgedge-control-plane/config.json`.
-We recommend updating the Control Plane using the pgEdge Enterprise package
-repositories you configured in the [Packages](systemd-installation.md#packages)
-section. If you installed the Control Plane manually, see [Updating from
-GitHub Releases](#updating-from-github-releases) below.
-
## Updating the Control Plane
### RPM Package
@@ -39,59 +34,6 @@ sudo apt install --only-upgrade -y pgedge-control-plane
curl http://localhost:3000/v1/version
```
-### Updating from GitHub Releases
-
-If you installed the Control Plane manually from GitHub releases, download and
-install the updated package version.
-
-#### RPM Package
-
-Use the following commands to download and install the updated RPM:
-
-```sh
-# (Optional) print the current version via the API
-curl http://localhost:3000/v1/version
-
-# Detect architecture
-ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
-
-# Set the new version to install
-VERSION="v0.9.0"
-
-# Download the RPM
-curl -LO "https://github.com/pgedge/control-plane/releases/download/${VERSION}/pgedge-control-plane_${VERSION#v}_linux_${ARCH}.rpm"
-
-# Install the RPM with the 'upgrade' flag
-sudo rpm -U pgedge-control-plane_${VERSION#v}_linux_${ARCH}.rpm
-
-# (Optional) print the updated version via the API
-curl http://localhost:3000/v1/version
-```
-
-#### Deb Package
-
-Use the following commands to download and install the deb package:
-
-```sh
-# (Optional) print the current version via the API
-curl http://localhost:3000/v1/version
-
-# Detect architecture
-ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
-
-# Set the new version to install
-VERSION="v0.9.0"
-
-# Download the deb package
-curl -LO --output-dir /tmp "https://github.com/pgedge/control-plane/releases/download/${VERSION}/pgedge-control-plane_${VERSION#v}_linux_${ARCH}.deb"
-
-# Install the deb package
-sudo apt install /tmp/pgedge-control-plane_${VERSION#v}_linux_${ARCH}.deb
-
-# (Optional) print the updated version via the API
-curl http://localhost:3000/v1/version
-```
-
## Performing Postgres Minor Version Upgrades
Database upgrades are not yet supported via the Control Plane API, but system