[SCM] postgis.net branch website updated. clarity-final-196-g217dee2
git at osgeo.org
git at osgeo.org
Sat Jul 18 14:50:36 PDT 2026
This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "postgis.net".
The branch, website has been updated
via 217dee2563abf6d7a647eaf284772dd3ce81ca4f (commit)
via f408f1c6044f509ce3f2db72143ad7e630aa27f2 (commit)
via 14e191cb663b4ac834d5e9a55d8f10d8a3b48aef (commit)
via d84d612b1d0635693e71fa2bd21bab2a2ff2fa39 (commit)
via 15acda307e7529ba119ebd46ece68e975e428718 (commit)
via 6b7a8cae26747b99279001b5d9210bd884b8afea (commit)
via ed7997f10d3ca0659866c3ea4c2c2f2b52d0352e (commit)
from 274fef0fc9fdceddb40510fa99d4093062142f77 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 217dee2563abf6d7a647eaf284772dd3ce81ca4f
Merge: 274fef0 f408f1c
Author: Darafei Praliaskouski <komzpa at gmail.com>
Date: Sat Jul 18 14:50:35 2026 -0700
Merge pull request 'Publish developer docs and CI status from PostGIS' (!9) from Komzpa/postgis.net:publisher-postgis-docs-ci into website
Reviewed-on: https://gitea.osgeo.org/postgis/postgis.net/pulls/9
commit f408f1c6044f509ce3f2db72143ad7e630aa27f2
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sun Jul 19 01:44:23 2026 +0400
refactor: simplify site publishing
diff --git a/.gitignore b/.gitignore
index 9f2564d..0e4a390 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
public
+/content/development/docs/
.hugo_build.lock
resources
.vscode
diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index fa981f8..694a343 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -6,9 +6,9 @@ steps:
build:
image: hugomods/hugo
commands:
- - apk add --no-cache bash coreutils git make
- - git clone --bare --depth 1 --branch master https://gitea.osgeo.org/postgis/postgis.git /tmp/postgis.git
- - make build-site PUBLISH_ROOT=/tmp/postgis-publisher POSTGIS_REFRESH=0 POSTGIS_GIT=/tmp/postgis.git
+ - apk add --no-cache git make
+ - git clone --depth 1 https://gitea.osgeo.org/postgis/postgis.git /tmp/postgis
+ - make site POSTGIS=/tmp/postgis
check-md5:
image: badouralix/curl-jq
diff --git a/Makefile b/Makefile
index 06c2f3a..9241317 100644
--- a/Makefile
+++ b/Makefile
@@ -1,294 +1,33 @@
.DEFAULT_GOAL := help
-SHELL := /bin/bash
-.SHELLFLAGS := -Eeuo pipefail -c
-.ONESHELL:
+POSTGIS ?= ../postgis
+DOCS := content/development/docs
-.PHONY: help check check-releases validate-config refresh-postgis \
- build-site build-ci-status publish publish-site publish-ci-status
+.PHONY: help check check-releases publish site docs ci
-# Publisher configuration
-
-PUBLISH_ROOT ?= /var/www/postgis-publisher
-POSTGIS_REPOSITORY ?= https://gitea.osgeo.org/postgis/postgis.git
-POSTGIS_GIT ?= $(PUBLISH_ROOT)/sources/postgis.git
-POSTGIS_REF ?= refs/heads/master
-POSTGIS_REFRESH ?= 1
-PUBLISH_KEEP ?= 3
-PUBLISHER_SCHEMA ?= 1
-
-SITE_SOURCE ?= $(CURDIR)
-SITE_OUTPUT ?= $(CURDIR)/public
-SITE_LIVE ?= /var/www/postgis_website/public
-CI_OUTPUT ?= $(CURDIR)/ci-status
-CI_LIVE ?= $(PUBLISH_ROOT)/current/ci
-
-HUGO ?= hugo
-PYTHON ?= python3
-
-ifeq ($(origin PUBLISH_RUN_STAMP), undefined)
-PUBLISH_RUN_STAMP := $(shell date -u +%Y%m%dT%H%M%SZ)
-endif
-ifeq ($(origin PUBLISH_RUN_PID), undefined)
-PUBLISH_RUN_PID := $(shell /bin/bash -c 'printf "%s" "$$BASHPID"')
-endif
-PUBLISH_RUN_ID = $(PUBLISH_RUN_STAMP)-$(PUBLISH_RUN_PID)
-PUBLISH_PIN_ROOT = $(PUBLISH_ROOT)/pins
-SOURCE_PIN = $(PUBLISH_PIN_ROOT)/$(PUBLISH_RUN_ID).sha
-POSTGIS_GIT_CMD = git --git-dir="$(POSTGIS_GIT)"
-
-.INTERMEDIATE: $(SOURCE_PIN)
-
-# Shared recipe fragments. These stay here so the public targets share one
-# implementation without recursive Make or a second publisher file.
-
-define render_site
-(
- output="$(1)"
- website_sha="$(2)"
- postgis_sha="$(3)"
- hugo_version="$(4)"
- canonical_output=$$(realpath -m -- "$$output")
- [[ "$$canonical_output" == "$$output" && "$$canonical_output" != / ]]
- [[ "$$website_sha" =~ ^[0-9a-f]{40}$$ ]]
- [[ "$$postgis_sha" =~ ^[0-9a-f]{40}$$ ]]
- git -C "$(SITE_SOURCE)" cat-file -e "$$website_sha^{commit}"
- $(POSTGIS_GIT_CMD) cat-file -e "$$postgis_sha^{commit}"
-
- build_root=$$(mktemp -d "$(PUBLISH_ROOT)/staging.site-build.XXXXXXXX")
- trap 'rm -rf -- "$$build_root"' EXIT
- install -d -m 0755 "$$build_root/site" "$$build_root/postgis" "$$output"
- git -C "$(SITE_SOURCE)" archive "$$website_sha" | tar -x -C "$$build_root/site"
- $(POSTGIS_GIT_CMD) archive "$$postgis_sha" doc/development | \
- tar -x -C "$$build_root/postgis"
- install -d -m 0755 "$$build_root/site/content/development/docs"
- cp -a "$$build_root/postgis/doc/development/." \
- "$$build_root/site/content/development/docs/"
- rm -- "$$build_root/site/content/development/docs/SKILL.md"
- mv -- "$$build_root/site/content/development/docs/README.md" \
- "$$build_root/site/content/development/docs/_index.md"
-
- "$(HUGO)" --source "$$build_root/site" --destination "$$output"
- {
- printf 'schema=%s\n' "$(PUBLISHER_SCHEMA)"
- printf 'website_sha=%s\n' "$$website_sha"
- printf 'postgis_sha=%s\n' "$$postgis_sha"
- printf 'hugo_version=%s\n' "$$hugo_version"
- } >"$$output/.postgis-publisher"
- chmod -R a+rX "$$output"
-
- [[ -s "$$output/index.html" ]]
- [[ -s "$$output/development/docs/index.html" ]]
- [[ ! -e "$$output/development/docs/SKILL/index.html" ]]
- printf 'Built site at %s\n' "$$output"
-)
-endef
-
-define render_ci_status
-(
- output="$(1)"
- postgis_sha="$(2)"
- canonical_output=$$(realpath -m -- "$$output")
- [[ "$$canonical_output" == "$$output" && "$$canonical_output" != / ]]
- [[ "$$postgis_sha" =~ ^[0-9a-f]{40}$$ ]]
- $(POSTGIS_GIT_CMD) cat-file -e "$$postgis_sha^{commit}"
-
- build_root=$$(mktemp -d "$(PUBLISH_ROOT)/staging.ci-build.XXXXXXXX")
- trap 'rm -rf -- "$$build_root"' EXIT
- install -d -m 0755 "$$build_root/source" "$$output"
- $(POSTGIS_GIT_CMD) archive "$$postgis_sha" \
- utils/ci-status.py utils/ci-status.json | tar -x -C "$$build_root/source"
- cd "$(POSTGIS_GIT)"
- "$(PYTHON)" "$$build_root/source/utils/ci-status.py" \
- --config "$$build_root/source/utils/ci-status.json" \
- --format html --output-dir "$$output"
- [[ -s "$$output/index.html" ]]
- [[ -s "$$output/status.json" ]]
- "$(PYTHON)" -m json.tool "$$output/status.json" >/dev/null
- {
- printf 'schema=%s\n' "$(PUBLISHER_SCHEMA)"
- printf 'postgis_sha=%s\n' "$$postgis_sha"
- printf 'generated_at=%s\n' "$$(date -u +%Y-%m-%dT%H:%M:%SZ)"
- } >"$$output/.postgis-publisher"
- chmod -R a+rX "$$output"
- printf 'Built CI status at %s\n' "$$output"
-)
-endef
-
-define publish_release
-(
- source="$(1)"
- live_pointer="$(2)"
- release="$(3)"
- release_root="$(4)"
- publisher_root=$$(realpath -e -- "$(PUBLISH_ROOT)")
- canonical_source=$$(realpath -e -- "$$source")
- canonical_release_root=$$(realpath -e -- "$$release_root")
- release_parent=$$(realpath -e -- "$$(dirname -- "$$release")")
- canonical_release="$$release_parent/$$(basename -- "$$release")"
- live_parent=$$(realpath -m -- "$$(dirname -- "$$live_pointer")")
- live_name=$$(basename -- "$$live_pointer")
- canonical_live="$${live_parent%/}/$$live_name"
-
- [[ "$$publisher_root" != / ]]
- [[ "$$canonical_source" == "$$publisher_root"/staging.*-publish.*/output ]]
- [[ -d "$$canonical_source" && ! -L "$$canonical_source" ]]
- [[ "$$canonical_release_root" == "$$publisher_root"/releases/* ]]
- [[ "$$release_parent" == "$$canonical_release_root" ]]
- [[ "$$canonical_release" == "$$release" ]]
- [[ ! -e "$$canonical_release" && ! -L "$$canonical_release" ]]
- [[ "$$live_parent" != / && "$$canonical_live" == "$$live_pointer" ]]
- [[ ! -e "$$live_pointer" || -L "$$live_pointer" ]]
-
- mv -T -- "$$canonical_source" "$$canonical_release"
- temporary="$$live_parent/.$$live_name.new.$${BASHPID}"
- install -d -m 0755 "$$live_parent"
- trap 'rm -f -- "$$temporary"' EXIT
- ln -s "$$canonical_release" "$$temporary"
- mv -Tf -- "$$temporary" "$$live_pointer"
-
- current=$$(readlink -f -- "$$live_pointer")
- [[ "$$(dirname -- "$$current")" == "$$canonical_release_root" ]]
- [[ -d "$$current" && ! -L "$$current" ]]
-
- keep_previous=$$(($(PUBLISH_KEEP) - 1))
- kept=0
- while IFS= read -r release_name; do
- [[ $$release_name =~ ^[0-9]{8}T[0-9]{6}Z-[0-9a-f]{12}(-[0-9a-f]{12})?-[0-9]+$$ ]] || continue
- path="$$canonical_release_root/$$release_name"
- [[ -d "$$path" && ! -L "$$path" ]] || continue
- canonical_path=$$(realpath -e -- "$$path")
- [[ "$$(dirname -- "$$canonical_path")" == "$$canonical_release_root" ]]
- [[ "$$canonical_path" != "$$current" ]] || continue
- if ((kept < keep_previous)); then
- ((kept += 1))
- continue
- fi
- if ! rm -rf -- "$$canonical_path"; then
- printf 'warning: could not prune %s\n' "$$canonical_path" >&2
- fi
- done < <(find "$$canonical_release_root" -mindepth 1 -maxdepth 1 -type d \
- -exec basename {} \; | LC_ALL=C sort -r)
-)
-endef
-
-# User-facing target graph
-
-help: ## List available targets
- @grep -E '^[$$() a-zA-Z_0-9-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
- awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-24s\033[0m %s\n", $$1, $$2}'
+help: ## Show this help
+ @grep -E '^[a-z][a-z-]*:.*?## .*$$' $(MAKEFILE_LIST) | \
+ awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
check: check-releases ## Run all tests
check-releases: ## Check release checksums
- @utils/check_releases_md5.sh
+ utils/check_releases_md5.sh
-validate-config:
- @[[ "$(PUBLISH_RUN_STAMP)" =~ ^[0-9]{8}T[0-9]{6}Z$$ ]]
- [[ "$(PUBLISH_RUN_PID)" =~ ^[0-9]+$$ ]]
- [[ "$(POSTGIS_REFRESH)" =~ ^[01]$$ ]]
- [[ "$(PUBLISH_KEEP)" =~ ^[1-9][0-9]*$$ ]]
- [[ -n "$(POSTGIS_REF)" ]]
+publish: site ci ## Build the site and CI status in parallel
+ chown -R www-data:www-data public
- publisher_root=$$(realpath -m -- "$(PUBLISH_ROOT)")
- postgis_git=$$(realpath -m -- "$(POSTGIS_GIT)")
- site_source=$$(realpath -e -- "$(SITE_SOURCE)")
- [[ "$$publisher_root" == "$(PUBLISH_ROOT)" && "$$publisher_root" != / ]]
- [[ "$$postgis_git" == "$(POSTGIS_GIT)" && "$$postgis_git" != / ]]
- [[ "$$site_source" == "$(SITE_SOURCE)" && -d "$$site_source" ]]
+site: docs ## Build the Hugo site with developer docs
+ hugo -D
- site_live_parent=$$(realpath -m -- "$$(dirname -- "$(SITE_LIVE)")")
- site_live="$${site_live_parent%/}/$$(basename -- "$(SITE_LIVE)")"
- ci_live_parent=$$(realpath -m -- "$$(dirname -- "$(CI_LIVE)")")
- ci_live="$${ci_live_parent%/}/$$(basename -- "$(CI_LIVE)")"
- [[ "$$site_live_parent" != / && "$$site_live" == "$(SITE_LIVE)" ]]
- [[ "$$ci_live_parent" != / && "$$ci_live" == "$(CI_LIVE)" ]]
- [[ "$$site_live" != "$$ci_live" ]]
+docs: ## Copy developer docs from the PostGIS checkout
+ rm -rf "$(DOCS)"
+ cp -a "$(POSTGIS)/doc/development" "$(DOCS)"
+ rm -f "$(DOCS)/SKILL.md"
+ mv "$(DOCS)/README.md" "$(DOCS)/_index.md"
-$(SOURCE_PIN): validate-config
- @install -d -m 0755 "$(PUBLISH_ROOT)/locks" "$(PUBLISH_PIN_ROOT)"
- exec 9>"$(PUBLISH_ROOT)/locks/source.lock"
- flock 9
- if [[ "$(POSTGIS_REFRESH)" == 1 ]]; then
- if [[ ! -d "$(POSTGIS_GIT)" ]]; then
- install -d -m 0755 "$$(dirname -- "$(POSTGIS_GIT)")"
- git clone --mirror "$(POSTGIS_REPOSITORY)" "$(POSTGIS_GIT)"
- else
- [[ "$$($(POSTGIS_GIT_CMD) rev-parse --is-bare-repository)" == true ]]
- [[ "$$($(POSTGIS_GIT_CMD) remote get-url origin)" == "$(POSTGIS_REPOSITORY)" ]]
- fi
- $(POSTGIS_GIT_CMD) remote update --prune
- fi
-
- website_sha=$$(git -C "$(SITE_SOURCE)" rev-parse --verify 'HEAD^{commit}')
- postgis_sha=$$($(POSTGIS_GIT_CMD) rev-parse --verify "$(POSTGIS_REF)^{commit}")
- [[ "$$website_sha" =~ ^[0-9a-f]{40}$$ ]]
- [[ "$$postgis_sha" =~ ^[0-9a-f]{40}$$ ]]
- pin_tmp="$@.new.$${BASHPID}"
- trap 'rm -f -- "$$pin_tmp"' EXIT
- printf '%s %s\n' "$$website_sha" "$$postgis_sha" >"$$pin_tmp"
- [[ ! -e "$@" && ! -L "$@" ]]
- mv -T -- "$$pin_tmp" "$@"
- printf 'Pinned website %s and PostGIS %s\n' "$$website_sha" "$$postgis_sha"
-
-refresh-postgis: $(SOURCE_PIN) ## Refresh and pin the sources shared by both publish lanes
-
-build-site: refresh-postgis ## Build Hugo with the PostGIS developer docs overlay
- @read -r website_sha postgis_sha <"$(SOURCE_PIN)"
- hugo_version=$$("$(HUGO)" version)
- $(call render_site,$(SITE_OUTPUT),$$website_sha,$$postgis_sha,$$hugo_version)
-
-build-ci-status: refresh-postgis ## Generate the PostGIS CI status page
- @read -r _ postgis_sha <"$(SOURCE_PIN)"
- $(call render_ci_status,$(CI_OUTPUT),$$postgis_sha)
-
-publish: publish-site publish-ci-status ## Publish both independent lanes with make -j
-
-publish-site: refresh-postgis ## Atomically publish Hugo and developer docs
- @[[ ! -e "$(SITE_LIVE)" || -L "$(SITE_LIVE)" ]]
- exec 9>"$(PUBLISH_ROOT)/locks/site.lock"
- flock 9
-
- read -r website_sha postgis_sha <"$(SOURCE_PIN)"
- hugo_version=$$("$(HUGO)" version)
- manifest="$(SITE_LIVE)/.postgis-publisher"
- if [[ -L "$(SITE_LIVE)" ]] && \
- grep -Fqx -- "schema=$(PUBLISHER_SCHEMA)" "$$manifest" && \
- grep -Fqx -- "website_sha=$$website_sha" "$$manifest" && \
- grep -Fqx -- "postgis_sha=$$postgis_sha" "$$manifest" && \
- grep -Fqx -- "hugo_version=$$hugo_version" "$$manifest" && \
- [[ -s "$(SITE_LIVE)/index.html" ]] && \
- [[ -s "$(SITE_LIVE)/development/docs/index.html" ]] && \
- [[ -L "$(SITE_LIVE)/ci" ]] && \
- [[ $$(readlink -- "$(SITE_LIVE)/ci") == "$(CI_LIVE)" ]]; then
- printf 'Site already matches website %s and PostGIS %s\n' "$$website_sha" "$$postgis_sha"
- exit 0
- fi
-
- publish_stage=$$(mktemp -d "$(PUBLISH_ROOT)/staging.site-publish.XXXXXXXX")
- trap 'rm -rf -- "$$publish_stage"' EXIT
- $(call render_site,$$publish_stage/output,$$website_sha,$$postgis_sha,$$hugo_version)
- [[ ! -e "$$publish_stage/output/ci" && ! -L "$$publish_stage/output/ci" ]]
- ln -s "$(CI_LIVE)" "$$publish_stage/output/ci"
-
- release_root="$(PUBLISH_ROOT)/releases/site"
- install -d -m 0755 "$$release_root"
- release="$$release_root/$(PUBLISH_RUN_STAMP)-$${website_sha:0:12}-$${postgis_sha:0:12}-$(PUBLISH_RUN_PID)"
- $(call publish_release,$$publish_stage/output,$(SITE_LIVE),$$release,$$release_root)
- printf 'Published site release %s\n' "$$release"
-
-publish-ci-status: refresh-postgis ## Atomically publish the generated CI status page
- @[[ ! -e "$(CI_LIVE)" || -L "$(CI_LIVE)" ]]
- exec 9>"$(PUBLISH_ROOT)/locks/ci-status.lock"
- flock 9
-
- read -r _ postgis_sha <"$(SOURCE_PIN)"
- publish_stage=$$(mktemp -d "$(PUBLISH_ROOT)/staging.ci-publish.XXXXXXXX")
- trap 'rm -rf -- "$$publish_stage"' EXIT
- $(call render_ci_status,$$publish_stage/output,$$postgis_sha)
-
- release_root="$(PUBLISH_ROOT)/releases/ci-status"
- install -d -m 0755 "$$release_root"
- release="$$release_root/$(PUBLISH_RUN_STAMP)-$${postgis_sha:0:12}-$(PUBLISH_RUN_PID)"
- $(call publish_release,$$publish_stage/output,$(CI_LIVE),$$release,$$release_root)
- printf 'Published CI status release %s\n' "$$release"
+ci: ## Generate the CI status page
+ cd "$(POSTGIS)" && python3 utils/ci-status.py \
+ --config utils/ci-status.json \
+ --format html \
+ --output-dir "$(CURDIR)/public/ci"
diff --git a/README.md b/README.md
index 6d81a35..d2af5f6 100644
--- a/README.md
+++ b/README.md
@@ -45,49 +45,29 @@ Run `make check` after changes, before push
## Production publishing
-The production publisher composes the site from two repositories without
-committing generated files to either one:
+The build reads developer documentation and the CI-status generator from a
+sibling checkout of `postgis/postgis`. Generated files stay in `public/` and
+are not committed to this repository. `make -j publish` runs the Hugo and CI
+branches in parallel, then restores the ownership expected by nginx.
-* this repository supplies the Hugo site;
-* `postgis/postgis:doc/development` is overlaid at
- `/development/docs/` during the Hugo build;
-* `postgis/postgis:utils/ci-status.py` generates `/ci/` independently of the
- website source revision.
-
-`make -j publish` refreshes one local mirror of the PostGIS repository, pins
-one immutable commit from each repository for the whole invocation, and then
-runs the site and CI-status publishers in parallel. The Makefile defines the
-complete refresh, build, validation, atomic switch, and pruning graph; no separate
-deployment executable is involved. Each publisher builds in its own staging
-directory, so a failed build leaves the previous release served. Each
-publisher retains its current release and two rollback releases by default;
-set `PUBLISH_KEEP` to change that count. The publisher requires Bash, GNU
-coreutils, Git, `flock`, Hugo, and Python 3. Doxygen and release-manual
-generation remain owned by their existing Jenkins jobs and are not part of
-these targets.
-
-The defaults are for Debbie:
+Create the source checkout once on Debbie:
```sh
-make -j publish
+git clone https://gitea.osgeo.org/postgis/postgis.git /var/www/postgis
```
-For an isolated build or test, override the paths and use an existing PostGIS
-Git directory without refreshing it:
+Replace the current root cron entry:
-```sh
-make build-site \
- PUBLISH_ROOT=/tmp/postgis-publisher \
- POSTGIS_REFRESH=0 \
- POSTGIS_GIT=/path/to/postgis/.git \
- POSTGIS_REF=refs/remotes/upstream/master \
- SITE_OUTPUT=/tmp/postgis-site
+```cron
+*/5 * * * * sh /var/www/postgis_website_update.sh
```
-The first production activation is deliberately not automatic. The existing
-`/var/www/postgis_website/public` directory must be preserved and moved aside
-before `publish-site` can replace that path with an atomic symlink. The
-publisher refuses to overwrite a real directory. Publish `/ci/` successfully
-before switching the site pointer, then verify both URLs and only then replace
-the old cron entry. Restoring the preserved directory and the previous cron
-entry is the rollback path.
+with:
+
+```cron
+*/5 * * * * cd /var/www/postgis_website && git clean -fdx && git pull --ff-only && git -C ../postgis pull --ff-only && make -j publish
+```
+
+This also refreshes `/development/docs/` and `/ci/` when only the PostGIS
+repository changes. Existing Jenkins Doxygen and release-manual jobs are
+unchanged.
commit 14e191cb663b4ac834d5e9a55d8f10d8a3b48aef
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sun Jul 19 00:59:53 2026 +0400
fix: make publisher graph deterministic
diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index a6a33bb..fa981f8 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -6,7 +6,7 @@ steps:
build:
image: hugomods/hugo
commands:
- - apk add --no-cache bash git make
+ - apk add --no-cache bash coreutils git make
- git clone --bare --depth 1 --branch master https://gitea.osgeo.org/postgis/postgis.git /tmp/postgis.git
- make build-site PUBLISH_ROOT=/tmp/postgis-publisher POSTGIS_REFRESH=0 POSTGIS_GIT=/tmp/postgis.git
diff --git a/Makefile b/Makefile
index e75f000..06c2f3a 100644
--- a/Makefile
+++ b/Makefile
@@ -4,9 +4,10 @@ SHELL := /bin/bash
.SHELLFLAGS := -Eeuo pipefail -c
.ONESHELL:
-.PHONY: help check check-releases refresh-postgis \
- build-site build-ci-status publish publish-site publish-ci-status \
- _build-site _build-ci-status _switch-release _prune-releases
+.PHONY: help check check-releases validate-config refresh-postgis \
+ build-site build-ci-status publish publish-site publish-ci-status
+
+# Publisher configuration
PUBLISH_ROOT ?= /var/www/postgis-publisher
POSTGIS_REPOSITORY ?= https://gitea.osgeo.org/postgis/postgis.git
@@ -24,18 +25,157 @@ CI_LIVE ?= $(PUBLISH_ROOT)/current/ci
HUGO ?= hugo
PYTHON ?= python3
+
+ifeq ($(origin PUBLISH_RUN_STAMP), undefined)
+PUBLISH_RUN_STAMP := $(shell date -u +%Y%m%dT%H%M%SZ)
+endif
+ifeq ($(origin PUBLISH_RUN_PID), undefined)
+PUBLISH_RUN_PID := $(shell /bin/bash -c 'printf "%s" "$$BASHPID"')
+endif
+PUBLISH_RUN_ID = $(PUBLISH_RUN_STAMP)-$(PUBLISH_RUN_PID)
+PUBLISH_PIN_ROOT = $(PUBLISH_ROOT)/pins
+SOURCE_PIN = $(PUBLISH_PIN_ROOT)/$(PUBLISH_RUN_ID).sha
POSTGIS_GIT_CMD = git --git-dir="$(POSTGIS_GIT)"
-export PUBLISH_ROOT POSTGIS_REPOSITORY POSTGIS_GIT POSTGIS_REF
-export POSTGIS_REFRESH PUBLISH_KEEP PUBLISHER_SCHEMA
-export SITE_SOURCE SITE_OUTPUT SITE_LIVE CI_OUTPUT CI_LIVE
-export HUGO PYTHON
+.INTERMEDIATE: $(SOURCE_PIN)
-# User-facing targets
+# Shared recipe fragments. These stay here so the public targets share one
+# implementation without recursive Make or a second publisher file.
+
+define render_site
+(
+ output="$(1)"
+ website_sha="$(2)"
+ postgis_sha="$(3)"
+ hugo_version="$(4)"
+ canonical_output=$$(realpath -m -- "$$output")
+ [[ "$$canonical_output" == "$$output" && "$$canonical_output" != / ]]
+ [[ "$$website_sha" =~ ^[0-9a-f]{40}$$ ]]
+ [[ "$$postgis_sha" =~ ^[0-9a-f]{40}$$ ]]
+ git -C "$(SITE_SOURCE)" cat-file -e "$$website_sha^{commit}"
+ $(POSTGIS_GIT_CMD) cat-file -e "$$postgis_sha^{commit}"
+
+ build_root=$$(mktemp -d "$(PUBLISH_ROOT)/staging.site-build.XXXXXXXX")
+ trap 'rm -rf -- "$$build_root"' EXIT
+ install -d -m 0755 "$$build_root/site" "$$build_root/postgis" "$$output"
+ git -C "$(SITE_SOURCE)" archive "$$website_sha" | tar -x -C "$$build_root/site"
+ $(POSTGIS_GIT_CMD) archive "$$postgis_sha" doc/development | \
+ tar -x -C "$$build_root/postgis"
+ install -d -m 0755 "$$build_root/site/content/development/docs"
+ cp -a "$$build_root/postgis/doc/development/." \
+ "$$build_root/site/content/development/docs/"
+ rm -- "$$build_root/site/content/development/docs/SKILL.md"
+ mv -- "$$build_root/site/content/development/docs/README.md" \
+ "$$build_root/site/content/development/docs/_index.md"
+
+ "$(HUGO)" --source "$$build_root/site" --destination "$$output"
+ {
+ printf 'schema=%s\n' "$(PUBLISHER_SCHEMA)"
+ printf 'website_sha=%s\n' "$$website_sha"
+ printf 'postgis_sha=%s\n' "$$postgis_sha"
+ printf 'hugo_version=%s\n' "$$hugo_version"
+ } >"$$output/.postgis-publisher"
+ chmod -R a+rX "$$output"
+
+ [[ -s "$$output/index.html" ]]
+ [[ -s "$$output/development/docs/index.html" ]]
+ [[ ! -e "$$output/development/docs/SKILL/index.html" ]]
+ printf 'Built site at %s\n' "$$output"
+)
+endef
+
+define render_ci_status
+(
+ output="$(1)"
+ postgis_sha="$(2)"
+ canonical_output=$$(realpath -m -- "$$output")
+ [[ "$$canonical_output" == "$$output" && "$$canonical_output" != / ]]
+ [[ "$$postgis_sha" =~ ^[0-9a-f]{40}$$ ]]
+ $(POSTGIS_GIT_CMD) cat-file -e "$$postgis_sha^{commit}"
+
+ build_root=$$(mktemp -d "$(PUBLISH_ROOT)/staging.ci-build.XXXXXXXX")
+ trap 'rm -rf -- "$$build_root"' EXIT
+ install -d -m 0755 "$$build_root/source" "$$output"
+ $(POSTGIS_GIT_CMD) archive "$$postgis_sha" \
+ utils/ci-status.py utils/ci-status.json | tar -x -C "$$build_root/source"
+ cd "$(POSTGIS_GIT)"
+ "$(PYTHON)" "$$build_root/source/utils/ci-status.py" \
+ --config "$$build_root/source/utils/ci-status.json" \
+ --format html --output-dir "$$output"
+ [[ -s "$$output/index.html" ]]
+ [[ -s "$$output/status.json" ]]
+ "$(PYTHON)" -m json.tool "$$output/status.json" >/dev/null
+ {
+ printf 'schema=%s\n' "$(PUBLISHER_SCHEMA)"
+ printf 'postgis_sha=%s\n' "$$postgis_sha"
+ printf 'generated_at=%s\n' "$$(date -u +%Y-%m-%dT%H:%M:%SZ)"
+ } >"$$output/.postgis-publisher"
+ chmod -R a+rX "$$output"
+ printf 'Built CI status at %s\n' "$$output"
+)
+endef
+
+define publish_release
+(
+ source="$(1)"
+ live_pointer="$(2)"
+ release="$(3)"
+ release_root="$(4)"
+ publisher_root=$$(realpath -e -- "$(PUBLISH_ROOT)")
+ canonical_source=$$(realpath -e -- "$$source")
+ canonical_release_root=$$(realpath -e -- "$$release_root")
+ release_parent=$$(realpath -e -- "$$(dirname -- "$$release")")
+ canonical_release="$$release_parent/$$(basename -- "$$release")"
+ live_parent=$$(realpath -m -- "$$(dirname -- "$$live_pointer")")
+ live_name=$$(basename -- "$$live_pointer")
+ canonical_live="$${live_parent%/}/$$live_name"
+
+ [[ "$$publisher_root" != / ]]
+ [[ "$$canonical_source" == "$$publisher_root"/staging.*-publish.*/output ]]
+ [[ -d "$$canonical_source" && ! -L "$$canonical_source" ]]
+ [[ "$$canonical_release_root" == "$$publisher_root"/releases/* ]]
+ [[ "$$release_parent" == "$$canonical_release_root" ]]
+ [[ "$$canonical_release" == "$$release" ]]
+ [[ ! -e "$$canonical_release" && ! -L "$$canonical_release" ]]
+ [[ "$$live_parent" != / && "$$canonical_live" == "$$live_pointer" ]]
+ [[ ! -e "$$live_pointer" || -L "$$live_pointer" ]]
+
+ mv -T -- "$$canonical_source" "$$canonical_release"
+ temporary="$$live_parent/.$$live_name.new.$${BASHPID}"
+ install -d -m 0755 "$$live_parent"
+ trap 'rm -f -- "$$temporary"' EXIT
+ ln -s "$$canonical_release" "$$temporary"
+ mv -Tf -- "$$temporary" "$$live_pointer"
+
+ current=$$(readlink -f -- "$$live_pointer")
+ [[ "$$(dirname -- "$$current")" == "$$canonical_release_root" ]]
+ [[ -d "$$current" && ! -L "$$current" ]]
+
+ keep_previous=$$(($(PUBLISH_KEEP) - 1))
+ kept=0
+ while IFS= read -r release_name; do
+ [[ $$release_name =~ ^[0-9]{8}T[0-9]{6}Z-[0-9a-f]{12}(-[0-9a-f]{12})?-[0-9]+$$ ]] || continue
+ path="$$canonical_release_root/$$release_name"
+ [[ -d "$$path" && ! -L "$$path" ]] || continue
+ canonical_path=$$(realpath -e -- "$$path")
+ [[ "$$(dirname -- "$$canonical_path")" == "$$canonical_release_root" ]]
+ [[ "$$canonical_path" != "$$current" ]] || continue
+ if ((kept < keep_previous)); then
+ ((kept += 1))
+ continue
+ fi
+ if ! rm -rf -- "$$canonical_path"; then
+ printf 'warning: could not prune %s\n' "$$canonical_path" >&2
+ fi
+ done < <(find "$$canonical_release_root" -mindepth 1 -maxdepth 1 -type d \
+ -exec basename {} \; | LC_ALL=C sort -r)
+)
+endef
+
+# User-facing target graph
help: ## List available targets
@grep -E '^[$$() a-zA-Z_0-9-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
- grep -v '^_' | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-24s\033[0m %s\n", $$1, $$2}'
check: check-releases ## Run all tests
@@ -43,17 +183,35 @@ check: check-releases ## Run all tests
check-releases: ## Check release checksums
@utils/check_releases_md5.sh
-refresh-postgis: ## Refresh the PostGIS mirror shared by both publish lanes
- @[[ "$(PUBLISH_ROOT)" = /* && "$(PUBLISH_ROOT)" != / ]]
- [[ "$(POSTGIS_GIT)" = /* && "$(POSTGIS_GIT)" != / ]]
- install -d -m 0755 "$(PUBLISH_ROOT)/locks"
- if [[ ! -d "$(POSTGIS_GIT)" ]]; then
- install -d -m 0755 "$$(dirname "$(POSTGIS_GIT)")"
- fi
+validate-config:
+ @[[ "$(PUBLISH_RUN_STAMP)" =~ ^[0-9]{8}T[0-9]{6}Z$$ ]]
+ [[ "$(PUBLISH_RUN_PID)" =~ ^[0-9]+$$ ]]
+ [[ "$(POSTGIS_REFRESH)" =~ ^[01]$$ ]]
+ [[ "$(PUBLISH_KEEP)" =~ ^[1-9][0-9]*$$ ]]
+ [[ -n "$(POSTGIS_REF)" ]]
+
+ publisher_root=$$(realpath -m -- "$(PUBLISH_ROOT)")
+ postgis_git=$$(realpath -m -- "$(POSTGIS_GIT)")
+ site_source=$$(realpath -e -- "$(SITE_SOURCE)")
+ [[ "$$publisher_root" == "$(PUBLISH_ROOT)" && "$$publisher_root" != / ]]
+ [[ "$$postgis_git" == "$(POSTGIS_GIT)" && "$$postgis_git" != / ]]
+ [[ "$$site_source" == "$(SITE_SOURCE)" && -d "$$site_source" ]]
+
+ site_live_parent=$$(realpath -m -- "$$(dirname -- "$(SITE_LIVE)")")
+ site_live="$${site_live_parent%/}/$$(basename -- "$(SITE_LIVE)")"
+ ci_live_parent=$$(realpath -m -- "$$(dirname -- "$(CI_LIVE)")")
+ ci_live="$${ci_live_parent%/}/$$(basename -- "$(CI_LIVE)")"
+ [[ "$$site_live_parent" != / && "$$site_live" == "$(SITE_LIVE)" ]]
+ [[ "$$ci_live_parent" != / && "$$ci_live" == "$(CI_LIVE)" ]]
+ [[ "$$site_live" != "$$ci_live" ]]
+
+$(SOURCE_PIN): validate-config
+ @install -d -m 0755 "$(PUBLISH_ROOT)/locks" "$(PUBLISH_PIN_ROOT)"
exec 9>"$(PUBLISH_ROOT)/locks/source.lock"
flock 9
if [[ "$(POSTGIS_REFRESH)" == 1 ]]; then
if [[ ! -d "$(POSTGIS_GIT)" ]]; then
+ install -d -m 0755 "$$(dirname -- "$(POSTGIS_GIT)")"
git clone --mirror "$(POSTGIS_REPOSITORY)" "$(POSTGIS_GIT)"
else
[[ "$$($(POSTGIS_GIT_CMD) rev-parse --is-bare-repository)" == true ]]
@@ -61,167 +219,76 @@ refresh-postgis: ## Refresh the PostGIS mirror shared by both publish lanes
fi
$(POSTGIS_GIT_CMD) remote update --prune
fi
- $(POSTGIS_GIT_CMD) rev-parse --verify "$(POSTGIS_REF)^{commit}" >/dev/null
- printf 'PostGIS source: %s\n' "$$($(POSTGIS_GIT_CMD) rev-parse "$(POSTGIS_REF)^{commit}")"
+
+ website_sha=$$(git -C "$(SITE_SOURCE)" rev-parse --verify 'HEAD^{commit}')
+ postgis_sha=$$($(POSTGIS_GIT_CMD) rev-parse --verify "$(POSTGIS_REF)^{commit}")
+ [[ "$$website_sha" =~ ^[0-9a-f]{40}$$ ]]
+ [[ "$$postgis_sha" =~ ^[0-9a-f]{40}$$ ]]
+ pin_tmp="$@.new.$${BASHPID}"
+ trap 'rm -f -- "$$pin_tmp"' EXIT
+ printf '%s %s\n' "$$website_sha" "$$postgis_sha" >"$$pin_tmp"
+ [[ ! -e "$@" && ! -L "$@" ]]
+ mv -T -- "$$pin_tmp" "$@"
+ printf 'Pinned website %s and PostGIS %s\n' "$$website_sha" "$$postgis_sha"
+
+refresh-postgis: $(SOURCE_PIN) ## Refresh and pin the sources shared by both publish lanes
build-site: refresh-postgis ## Build Hugo with the PostGIS developer docs overlay
- @$(MAKE) --no-print-directory _build-site OUTPUT="$(SITE_OUTPUT)"
+ @read -r website_sha postgis_sha <"$(SOURCE_PIN)"
+ hugo_version=$$("$(HUGO)" version)
+ $(call render_site,$(SITE_OUTPUT),$$website_sha,$$postgis_sha,$$hugo_version)
build-ci-status: refresh-postgis ## Generate the PostGIS CI status page
- @$(MAKE) --no-print-directory _build-ci-status OUTPUT="$(CI_OUTPUT)"
+ @read -r _ postgis_sha <"$(SOURCE_PIN)"
+ $(call render_ci_status,$(CI_OUTPUT),$$postgis_sha)
publish: publish-site publish-ci-status ## Publish both independent lanes with make -j
publish-site: refresh-postgis ## Atomically publish Hugo and developer docs
@[[ ! -e "$(SITE_LIVE)" || -L "$(SITE_LIVE)" ]]
- [[ "$(PUBLISH_KEEP)" =~ ^[1-9][0-9]*$$ ]]
- install -d -m 0755 "$(PUBLISH_ROOT)/locks"
exec 9>"$(PUBLISH_ROOT)/locks/site.lock"
flock 9
- website_sha=$$(git -C "$(SITE_SOURCE)" rev-parse --verify 'HEAD^{commit}')
- postgis_sha=$$($(POSTGIS_GIT_CMD) rev-parse --verify "$(POSTGIS_REF)^{commit}")
+ read -r website_sha postgis_sha <"$(SOURCE_PIN)"
hugo_version=$$("$(HUGO)" version)
manifest="$(SITE_LIVE)/.postgis-publisher"
if [[ -L "$(SITE_LIVE)" ]] && \
grep -Fqx -- "schema=$(PUBLISHER_SCHEMA)" "$$manifest" && \
grep -Fqx -- "website_sha=$$website_sha" "$$manifest" && \
grep -Fqx -- "postgis_sha=$$postgis_sha" "$$manifest" && \
- grep -Fqx -- "hugo_version=$$hugo_version" "$$manifest"; then
+ grep -Fqx -- "hugo_version=$$hugo_version" "$$manifest" && \
+ [[ -s "$(SITE_LIVE)/index.html" ]] && \
+ [[ -s "$(SITE_LIVE)/development/docs/index.html" ]] && \
+ [[ -L "$(SITE_LIVE)/ci" ]] && \
+ [[ $$(readlink -- "$(SITE_LIVE)/ci") == "$(CI_LIVE)" ]]; then
printf 'Site already matches website %s and PostGIS %s\n' "$$website_sha" "$$postgis_sha"
exit 0
fi
- stage=$$(mktemp -d "$(PUBLISH_ROOT)/staging.site-publish.XXXXXXXX")
- trap 'rm -rf -- "$$stage"' EXIT
- $(MAKE) --no-print-directory _build-site OUTPUT="$$stage/output"
- [[ ! -e "$$stage/output/ci" && ! -L "$$stage/output/ci" ]]
- ln -s "$(CI_LIVE)" "$$stage/output/ci"
+ publish_stage=$$(mktemp -d "$(PUBLISH_ROOT)/staging.site-publish.XXXXXXXX")
+ trap 'rm -rf -- "$$publish_stage"' EXIT
+ $(call render_site,$$publish_stage/output,$$website_sha,$$postgis_sha,$$hugo_version)
+ [[ ! -e "$$publish_stage/output/ci" && ! -L "$$publish_stage/output/ci" ]]
+ ln -s "$(CI_LIVE)" "$$publish_stage/output/ci"
release_root="$(PUBLISH_ROOT)/releases/site"
install -d -m 0755 "$$release_root"
- release="$$release_root/$$(date -u +%Y%m%dT%H%M%SZ)-$${website_sha:0:12}-$${postgis_sha:0:12}-$${BASHPID}"
- mv -- "$$stage/output" "$$release"
- $(MAKE) --no-print-directory _switch-release \
- LIVE_POINTER="$(SITE_LIVE)" RELEASE="$$release" RELEASE_ROOT="$$release_root"
- $(MAKE) --no-print-directory _prune-releases \
- LIVE_POINTER="$(SITE_LIVE)" RELEASE_ROOT="$$release_root"
+ release="$$release_root/$(PUBLISH_RUN_STAMP)-$${website_sha:0:12}-$${postgis_sha:0:12}-$(PUBLISH_RUN_PID)"
+ $(call publish_release,$$publish_stage/output,$(SITE_LIVE),$$release,$$release_root)
printf 'Published site release %s\n' "$$release"
publish-ci-status: refresh-postgis ## Atomically publish the generated CI status page
@[[ ! -e "$(CI_LIVE)" || -L "$(CI_LIVE)" ]]
- [[ "$(PUBLISH_KEEP)" =~ ^[1-9][0-9]*$$ ]]
- install -d -m 0755 "$(PUBLISH_ROOT)/locks"
exec 9>"$(PUBLISH_ROOT)/locks/ci-status.lock"
flock 9
- stage=$$(mktemp -d "$(PUBLISH_ROOT)/staging.ci-publish.XXXXXXXX")
- trap 'rm -rf -- "$$stage"' EXIT
- postgis_sha=$$($(POSTGIS_GIT_CMD) rev-parse --verify "$(POSTGIS_REF)^{commit}")
- $(MAKE) --no-print-directory _build-ci-status OUTPUT="$$stage/output"
+ read -r _ postgis_sha <"$(SOURCE_PIN)"
+ publish_stage=$$(mktemp -d "$(PUBLISH_ROOT)/staging.ci-publish.XXXXXXXX")
+ trap 'rm -rf -- "$$publish_stage"' EXIT
+ $(call render_ci_status,$$publish_stage/output,$$postgis_sha)
release_root="$(PUBLISH_ROOT)/releases/ci-status"
install -d -m 0755 "$$release_root"
- release="$$release_root/$$(date -u +%Y%m%dT%H%M%SZ)-$${postgis_sha:0:12}-$${BASHPID}"
- mv -- "$$stage/output" "$$release"
- $(MAKE) --no-print-directory _switch-release \
- LIVE_POINTER="$(CI_LIVE)" RELEASE="$$release" RELEASE_ROOT="$$release_root"
- $(MAKE) --no-print-directory _prune-releases \
- LIVE_POINTER="$(CI_LIVE)" RELEASE_ROOT="$$release_root"
+ release="$$release_root/$(PUBLISH_RUN_STAMP)-$${postgis_sha:0:12}-$(PUBLISH_RUN_PID)"
+ $(call publish_release,$$publish_stage/output,$(CI_LIVE),$$release,$$release_root)
printf 'Published CI status release %s\n' "$$release"
-
-# Reused implementation targets
-
-_build-site: ## Internal: render one site output directory
- @[[ "$(OUTPUT)" = /* && "$(OUTPUT)" != / ]]
- stage=$$(mktemp -d "$(PUBLISH_ROOT)/staging.site-build.XXXXXXXX")
- trap 'rm -rf -- "$$stage"' EXIT
- website_sha=$$(git -C "$(SITE_SOURCE)" rev-parse --verify 'HEAD^{commit}')
- postgis_sha=$$($(POSTGIS_GIT_CMD) rev-parse --verify "$(POSTGIS_REF)^{commit}")
- hugo_version=$$("$(HUGO)" version)
-
- install -d -m 0755 "$$stage/site" "$$stage/postgis" "$(OUTPUT)"
- git -C "$(SITE_SOURCE)" archive "$$website_sha" | tar -x -C "$$stage/site"
- $(POSTGIS_GIT_CMD) archive "$$postgis_sha" doc/development | tar -x -C "$$stage/postgis"
- install -d -m 0755 "$$stage/site/content/development/docs"
- cp -a "$$stage/postgis/doc/development/." "$$stage/site/content/development/docs/"
- rm -- "$$stage/site/content/development/docs/SKILL.md"
- mv -- "$$stage/site/content/development/docs/README.md" \
- "$$stage/site/content/development/docs/_index.md"
-
- "$(HUGO)" --source "$$stage/site" --destination "$(OUTPUT)"
- {
- printf 'schema=%s\n' "$(PUBLISHER_SCHEMA)"
- printf 'website_sha=%s\n' "$$website_sha"
- printf 'postgis_sha=%s\n' "$$postgis_sha"
- printf 'hugo_version=%s\n' "$$hugo_version"
- } >"$(OUTPUT)/.postgis-publisher"
- chmod -R a+rX "$(OUTPUT)"
-
- [[ -s "$(OUTPUT)/index.html" ]]
- [[ -s "$(OUTPUT)/development/docs/index.html" ]]
- [[ ! -e "$(OUTPUT)/development/docs/SKILL/index.html" ]]
- printf 'Built site at %s\n' "$(OUTPUT)"
-
-_build-ci-status: ## Internal: render one CI status output directory
- @[[ "$(OUTPUT)" = /* && "$(OUTPUT)" != / ]]
- stage=$$(mktemp -d "$(PUBLISH_ROOT)/staging.ci-build.XXXXXXXX")
- trap 'rm -rf -- "$$stage"' EXIT
- postgis_sha=$$($(POSTGIS_GIT_CMD) rev-parse --verify "$(POSTGIS_REF)^{commit}")
-
- install -d -m 0755 "$$stage/source" "$(OUTPUT)"
- $(POSTGIS_GIT_CMD) archive "$$postgis_sha" utils/ci-status.py utils/ci-status.json | \
- tar -x -C "$$stage/source"
- cd "$(POSTGIS_GIT)"
- "$(PYTHON)" "$$stage/source/utils/ci-status.py" \
- --config "$$stage/source/utils/ci-status.json" \
- --format html --output-dir "$(OUTPUT)"
- [[ -s "$(OUTPUT)/index.html" ]]
- [[ -s "$(OUTPUT)/status.json" ]]
- "$(PYTHON)" -m json.tool "$(OUTPUT)/status.json" >/dev/null
- {
- printf 'schema=%s\n' "$(PUBLISHER_SCHEMA)"
- printf 'postgis_sha=%s\n' "$$postgis_sha"
- printf 'generated_at=%s\n' "$$(date -u +%Y-%m-%dT%H:%M:%SZ)"
- } >"$(OUTPUT)/.postgis-publisher"
- chmod -R a+rX "$(OUTPUT)"
- printf 'Built CI status at %s\n' "$(OUTPUT)"
-
-_switch-release: ## Internal: atomically switch one live pointer
- @[[ "$(LIVE_POINTER)" = /* && "$(LIVE_POINTER)" != / ]]
- [[ ! -e "$(LIVE_POINTER)" || -L "$(LIVE_POINTER)" ]]
- [[ "$(RELEASE_ROOT)" = /* && "$(RELEASE_ROOT)" != / ]]
- [[ "$(RELEASE_ROOT)" == "$(PUBLISH_ROOT)"/releases/* ]]
- [[ "$(RELEASE)" == "$(RELEASE_ROOT)"/* && -d "$(RELEASE)" ]]
-
- parent=$$(dirname "$(LIVE_POINTER)")
- name=$$(basename "$(LIVE_POINTER)")
- temporary="$$parent/.$$name.new.$${BASHPID}"
- install -d -m 0755 "$$parent"
- trap 'rm -f -- "$$temporary"' EXIT
- ln -s "$(RELEASE)" "$$temporary"
- mv -Tf -- "$$temporary" "$(LIVE_POINTER)"
- trap - EXIT
-
-_prune-releases: ## Internal: retain current and previous releases
- @[[ "$(LIVE_POINTER)" = /* && -L "$(LIVE_POINTER)" ]]
- [[ "$(RELEASE_ROOT)" = /* && "$(RELEASE_ROOT)" != / ]]
- [[ "$(RELEASE_ROOT)" == "$(PUBLISH_ROOT)"/releases/* ]]
- [[ "$(PUBLISH_KEEP)" =~ ^[1-9][0-9]*$$ ]]
-
- current=$$(readlink -f "$(LIVE_POINTER)")
- keep_previous=$$(($(PUBLISH_KEEP) - 1))
- kept=0
- while IFS= read -r release_name; do
- [[ $$release_name =~ ^[0-9]{8}T[0-9]{6}Z-[0-9a-f]{12}(-[0-9a-f]{12})?-[0-9]+$$ ]] || continue
- path="$(RELEASE_ROOT)/$$release_name"
- [[ $$(readlink -f "$$path") != "$$current" ]] || continue
- if ((kept < keep_previous)); then
- ((kept += 1))
- continue
- fi
- if ! rm -rf -- "$$path"; then
- printf 'warning: could not prune %s\n' "$$path" >&2
- fi
- done < <(find "$(RELEASE_ROOT)" -mindepth 1 -maxdepth 1 -type d \
- -exec basename {} \; | LC_ALL=C sort -r)
diff --git a/README.md b/README.md
index a6436f4..6d81a35 100644
--- a/README.md
+++ b/README.md
@@ -54,14 +54,17 @@ committing generated files to either one:
* `postgis/postgis:utils/ci-status.py` generates `/ci/` independently of the
website source revision.
-`make -j publish` refreshes one local mirror of the PostGIS repository and
-then runs the site and CI-status publishers in parallel. The Makefile defines
-the complete refresh, build, validation, atomic switch, and pruning graph; no
-separate deployment executable is involved. Each publisher builds in its own
-staging directory, so a failed build leaves the previous release served. Each
+`make -j publish` refreshes one local mirror of the PostGIS repository, pins
+one immutable commit from each repository for the whole invocation, and then
+runs the site and CI-status publishers in parallel. The Makefile defines the
+complete refresh, build, validation, atomic switch, and pruning graph; no separate
+deployment executable is involved. Each publisher builds in its own staging
+directory, so a failed build leaves the previous release served. Each
publisher retains its current release and two rollback releases by default;
-set `PUBLISH_KEEP` to change that count. Doxygen and release-manual generation
-remain owned by their existing Jenkins jobs and are not part of these targets.
+set `PUBLISH_KEEP` to change that count. The publisher requires Bash, GNU
+coreutils, Git, `flock`, Hugo, and Python 3. Doxygen and release-manual
+generation remain owned by their existing Jenkins jobs and are not part of
+these targets.
The defaults are for Debbie:
commit d84d612b1d0635693e71fa2bd21bab2a2ff2fa39
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sat Jul 18 23:59:25 2026 +0400
refactor: express site publishing in Make
diff --git a/Makefile b/Makefile
index 1af7d9c..e75f000 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,12 @@
-.PHONY: help check check-releases refresh-postgis build-site publish \
- publish-site publish-ci-status
+.DEFAULT_GOAL := help
+
+SHELL := /bin/bash
+.SHELLFLAGS := -Eeuo pipefail -c
+.ONESHELL:
+
+.PHONY: help check check-releases refresh-postgis \
+ build-site build-ci-status publish publish-site publish-ci-status \
+ _build-site _build-ci-status _switch-release _prune-releases
PUBLISH_ROOT ?= /var/www/postgis-publisher
POSTGIS_REPOSITORY ?= https://gitea.osgeo.org/postgis/postgis.git
@@ -7,50 +14,214 @@ POSTGIS_GIT ?= $(PUBLISH_ROOT)/sources/postgis.git
POSTGIS_REF ?= refs/heads/master
POSTGIS_REFRESH ?= 1
PUBLISH_KEEP ?= 3
+PUBLISHER_SCHEMA ?= 1
+
SITE_SOURCE ?= $(CURDIR)
SITE_OUTPUT ?= $(CURDIR)/public
SITE_LIVE ?= /var/www/postgis_website/public
+CI_OUTPUT ?= $(CURDIR)/ci-status
CI_LIVE ?= $(PUBLISH_ROOT)/current/ci
+
HUGO ?= hugo
PYTHON ?= python3
+POSTGIS_GIT_CMD = git --git-dir="$(POSTGIS_GIT)"
export PUBLISH_ROOT POSTGIS_REPOSITORY POSTGIS_GIT POSTGIS_REF
-export POSTGIS_REFRESH PUBLISH_KEEP SITE_SOURCE SITE_OUTPUT SITE_LIVE CI_LIVE
+export POSTGIS_REFRESH PUBLISH_KEEP PUBLISHER_SCHEMA
+export SITE_SOURCE SITE_OUTPUT SITE_LIVE CI_OUTPUT CI_LIVE
export HUGO PYTHON
-help:
- @echo "Available targets:"
+# User-facing targets
+
+help: ## List available targets
@grep -E '^[$$() a-zA-Z_0-9-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
- sed 's/$$(IMAGES)/$(IMAGES)/' | \
- sed 's/$$(IMAGES_PUSH)/$(IMAGES_PUSH)/' | \
- awk \
- 'BEGIN {FS = ":.*?## "}; \
- { printf "\033[36m%-30s\033[0m%s%s\n", $$1, "\n ", $$2 }'
+ grep -v '^_' | \
+ awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-24s\033[0m %s\n", $$1, $$2}'
check: check-releases ## Run all tests
-check-releases: ## Check MD5 of releases
- utils/check_releases_md5.sh
+check-releases: ## Check release checksums
+ @utils/check_releases_md5.sh
-refresh-postgis: ## Refresh the local mirror used for developer docs and CI status
- @install -d -m 0755 "$(PUBLISH_ROOT)/locks"
- @if [ "$(POSTGIS_REFRESH)" = 1 ]; then \
- flock "$(PUBLISH_ROOT)/locks/source.lock" \
- utils/site-publisher refresh-postgis; \
- else \
- utils/site-publisher check-postgis; \
+refresh-postgis: ## Refresh the PostGIS mirror shared by both publish lanes
+ @[[ "$(PUBLISH_ROOT)" = /* && "$(PUBLISH_ROOT)" != / ]]
+ [[ "$(POSTGIS_GIT)" = /* && "$(POSTGIS_GIT)" != / ]]
+ install -d -m 0755 "$(PUBLISH_ROOT)/locks"
+ if [[ ! -d "$(POSTGIS_GIT)" ]]; then
+ install -d -m 0755 "$$(dirname "$(POSTGIS_GIT)")"
+ fi
+ exec 9>"$(PUBLISH_ROOT)/locks/source.lock"
+ flock 9
+ if [[ "$(POSTGIS_REFRESH)" == 1 ]]; then
+ if [[ ! -d "$(POSTGIS_GIT)" ]]; then
+ git clone --mirror "$(POSTGIS_REPOSITORY)" "$(POSTGIS_GIT)"
+ else
+ [[ "$$($(POSTGIS_GIT_CMD) rev-parse --is-bare-repository)" == true ]]
+ [[ "$$($(POSTGIS_GIT_CMD) remote get-url origin)" == "$(POSTGIS_REPOSITORY)" ]]
+ fi
+ $(POSTGIS_GIT_CMD) remote update --prune
+ fi
+ $(POSTGIS_GIT_CMD) rev-parse --verify "$(POSTGIS_REF)^{commit}" >/dev/null
+ printf 'PostGIS source: %s\n' "$$($(POSTGIS_GIT_CMD) rev-parse "$(POSTGIS_REF)^{commit}")"
+
+build-site: refresh-postgis ## Build Hugo with the PostGIS developer docs overlay
+ @$(MAKE) --no-print-directory _build-site OUTPUT="$(SITE_OUTPUT)"
+
+build-ci-status: refresh-postgis ## Generate the PostGIS CI status page
+ @$(MAKE) --no-print-directory _build-ci-status OUTPUT="$(CI_OUTPUT)"
+
+publish: publish-site publish-ci-status ## Publish both independent lanes with make -j
+
+publish-site: refresh-postgis ## Atomically publish Hugo and developer docs
+ @[[ ! -e "$(SITE_LIVE)" || -L "$(SITE_LIVE)" ]]
+ [[ "$(PUBLISH_KEEP)" =~ ^[1-9][0-9]*$$ ]]
+ install -d -m 0755 "$(PUBLISH_ROOT)/locks"
+ exec 9>"$(PUBLISH_ROOT)/locks/site.lock"
+ flock 9
+
+ website_sha=$$(git -C "$(SITE_SOURCE)" rev-parse --verify 'HEAD^{commit}')
+ postgis_sha=$$($(POSTGIS_GIT_CMD) rev-parse --verify "$(POSTGIS_REF)^{commit}")
+ hugo_version=$$("$(HUGO)" version)
+ manifest="$(SITE_LIVE)/.postgis-publisher"
+ if [[ -L "$(SITE_LIVE)" ]] && \
+ grep -Fqx -- "schema=$(PUBLISHER_SCHEMA)" "$$manifest" && \
+ grep -Fqx -- "website_sha=$$website_sha" "$$manifest" && \
+ grep -Fqx -- "postgis_sha=$$postgis_sha" "$$manifest" && \
+ grep -Fqx -- "hugo_version=$$hugo_version" "$$manifest"; then
+ printf 'Site already matches website %s and PostGIS %s\n' "$$website_sha" "$$postgis_sha"
+ exit 0
fi
-build-site: refresh-postgis ## Build Hugo with developer docs from the PostGIS repository
- utils/site-publisher build-site
+ stage=$$(mktemp -d "$(PUBLISH_ROOT)/staging.site-publish.XXXXXXXX")
+ trap 'rm -rf -- "$$stage"' EXIT
+ $(MAKE) --no-print-directory _build-site OUTPUT="$$stage/output"
+ [[ ! -e "$$stage/output/ci" && ! -L "$$stage/output/ci" ]]
+ ln -s "$(CI_LIVE)" "$$stage/output/ci"
-publish: publish-site publish-ci-status ## Publish the site and CI status (use make -j2)
+ release_root="$(PUBLISH_ROOT)/releases/site"
+ install -d -m 0755 "$$release_root"
+ release="$$release_root/$$(date -u +%Y%m%dT%H%M%SZ)-$${website_sha:0:12}-$${postgis_sha:0:12}-$${BASHPID}"
+ mv -- "$$stage/output" "$$release"
+ $(MAKE) --no-print-directory _switch-release \
+ LIVE_POINTER="$(SITE_LIVE)" RELEASE="$$release" RELEASE_ROOT="$$release_root"
+ $(MAKE) --no-print-directory _prune-releases \
+ LIVE_POINTER="$(SITE_LIVE)" RELEASE_ROOT="$$release_root"
+ printf 'Published site release %s\n' "$$release"
-publish-site: refresh-postgis ## Atomically publish Hugo and PostGIS developer docs
- @install -d -m 0755 "$(PUBLISH_ROOT)/locks"
- flock "$(PUBLISH_ROOT)/locks/site.lock" utils/site-publisher publish-site
+publish-ci-status: refresh-postgis ## Atomically publish the generated CI status page
+ @[[ ! -e "$(CI_LIVE)" || -L "$(CI_LIVE)" ]]
+ [[ "$(PUBLISH_KEEP)" =~ ^[1-9][0-9]*$$ ]]
+ install -d -m 0755 "$(PUBLISH_ROOT)/locks"
+ exec 9>"$(PUBLISH_ROOT)/locks/ci-status.lock"
+ flock 9
-publish-ci-status: refresh-postgis ## Atomically refresh the generated PostGIS CI status page
- @install -d -m 0755 "$(PUBLISH_ROOT)/locks"
- flock "$(PUBLISH_ROOT)/locks/ci-status.lock" \
- utils/site-publisher publish-ci-status
+ stage=$$(mktemp -d "$(PUBLISH_ROOT)/staging.ci-publish.XXXXXXXX")
+ trap 'rm -rf -- "$$stage"' EXIT
+ postgis_sha=$$($(POSTGIS_GIT_CMD) rev-parse --verify "$(POSTGIS_REF)^{commit}")
+ $(MAKE) --no-print-directory _build-ci-status OUTPUT="$$stage/output"
+
+ release_root="$(PUBLISH_ROOT)/releases/ci-status"
+ install -d -m 0755 "$$release_root"
+ release="$$release_root/$$(date -u +%Y%m%dT%H%M%SZ)-$${postgis_sha:0:12}-$${BASHPID}"
+ mv -- "$$stage/output" "$$release"
+ $(MAKE) --no-print-directory _switch-release \
+ LIVE_POINTER="$(CI_LIVE)" RELEASE="$$release" RELEASE_ROOT="$$release_root"
+ $(MAKE) --no-print-directory _prune-releases \
+ LIVE_POINTER="$(CI_LIVE)" RELEASE_ROOT="$$release_root"
+ printf 'Published CI status release %s\n' "$$release"
+
+# Reused implementation targets
+
+_build-site: ## Internal: render one site output directory
+ @[[ "$(OUTPUT)" = /* && "$(OUTPUT)" != / ]]
+ stage=$$(mktemp -d "$(PUBLISH_ROOT)/staging.site-build.XXXXXXXX")
+ trap 'rm -rf -- "$$stage"' EXIT
+ website_sha=$$(git -C "$(SITE_SOURCE)" rev-parse --verify 'HEAD^{commit}')
+ postgis_sha=$$($(POSTGIS_GIT_CMD) rev-parse --verify "$(POSTGIS_REF)^{commit}")
+ hugo_version=$$("$(HUGO)" version)
+
+ install -d -m 0755 "$$stage/site" "$$stage/postgis" "$(OUTPUT)"
+ git -C "$(SITE_SOURCE)" archive "$$website_sha" | tar -x -C "$$stage/site"
+ $(POSTGIS_GIT_CMD) archive "$$postgis_sha" doc/development | tar -x -C "$$stage/postgis"
+ install -d -m 0755 "$$stage/site/content/development/docs"
+ cp -a "$$stage/postgis/doc/development/." "$$stage/site/content/development/docs/"
+ rm -- "$$stage/site/content/development/docs/SKILL.md"
+ mv -- "$$stage/site/content/development/docs/README.md" \
+ "$$stage/site/content/development/docs/_index.md"
+
+ "$(HUGO)" --source "$$stage/site" --destination "$(OUTPUT)"
+ {
+ printf 'schema=%s\n' "$(PUBLISHER_SCHEMA)"
+ printf 'website_sha=%s\n' "$$website_sha"
+ printf 'postgis_sha=%s\n' "$$postgis_sha"
+ printf 'hugo_version=%s\n' "$$hugo_version"
+ } >"$(OUTPUT)/.postgis-publisher"
+ chmod -R a+rX "$(OUTPUT)"
+
+ [[ -s "$(OUTPUT)/index.html" ]]
+ [[ -s "$(OUTPUT)/development/docs/index.html" ]]
+ [[ ! -e "$(OUTPUT)/development/docs/SKILL/index.html" ]]
+ printf 'Built site at %s\n' "$(OUTPUT)"
+
+_build-ci-status: ## Internal: render one CI status output directory
+ @[[ "$(OUTPUT)" = /* && "$(OUTPUT)" != / ]]
+ stage=$$(mktemp -d "$(PUBLISH_ROOT)/staging.ci-build.XXXXXXXX")
+ trap 'rm -rf -- "$$stage"' EXIT
+ postgis_sha=$$($(POSTGIS_GIT_CMD) rev-parse --verify "$(POSTGIS_REF)^{commit}")
+
+ install -d -m 0755 "$$stage/source" "$(OUTPUT)"
+ $(POSTGIS_GIT_CMD) archive "$$postgis_sha" utils/ci-status.py utils/ci-status.json | \
+ tar -x -C "$$stage/source"
+ cd "$(POSTGIS_GIT)"
+ "$(PYTHON)" "$$stage/source/utils/ci-status.py" \
+ --config "$$stage/source/utils/ci-status.json" \
+ --format html --output-dir "$(OUTPUT)"
+ [[ -s "$(OUTPUT)/index.html" ]]
+ [[ -s "$(OUTPUT)/status.json" ]]
+ "$(PYTHON)" -m json.tool "$(OUTPUT)/status.json" >/dev/null
+ {
+ printf 'schema=%s\n' "$(PUBLISHER_SCHEMA)"
+ printf 'postgis_sha=%s\n' "$$postgis_sha"
+ printf 'generated_at=%s\n' "$$(date -u +%Y-%m-%dT%H:%M:%SZ)"
+ } >"$(OUTPUT)/.postgis-publisher"
+ chmod -R a+rX "$(OUTPUT)"
+ printf 'Built CI status at %s\n' "$(OUTPUT)"
+
+_switch-release: ## Internal: atomically switch one live pointer
+ @[[ "$(LIVE_POINTER)" = /* && "$(LIVE_POINTER)" != / ]]
+ [[ ! -e "$(LIVE_POINTER)" || -L "$(LIVE_POINTER)" ]]
+ [[ "$(RELEASE_ROOT)" = /* && "$(RELEASE_ROOT)" != / ]]
+ [[ "$(RELEASE_ROOT)" == "$(PUBLISH_ROOT)"/releases/* ]]
+ [[ "$(RELEASE)" == "$(RELEASE_ROOT)"/* && -d "$(RELEASE)" ]]
+
+ parent=$$(dirname "$(LIVE_POINTER)")
+ name=$$(basename "$(LIVE_POINTER)")
+ temporary="$$parent/.$$name.new.$${BASHPID}"
+ install -d -m 0755 "$$parent"
+ trap 'rm -f -- "$$temporary"' EXIT
+ ln -s "$(RELEASE)" "$$temporary"
+ mv -Tf -- "$$temporary" "$(LIVE_POINTER)"
+ trap - EXIT
+
+_prune-releases: ## Internal: retain current and previous releases
+ @[[ "$(LIVE_POINTER)" = /* && -L "$(LIVE_POINTER)" ]]
+ [[ "$(RELEASE_ROOT)" = /* && "$(RELEASE_ROOT)" != / ]]
+ [[ "$(RELEASE_ROOT)" == "$(PUBLISH_ROOT)"/releases/* ]]
+ [[ "$(PUBLISH_KEEP)" =~ ^[1-9][0-9]*$$ ]]
+
+ current=$$(readlink -f "$(LIVE_POINTER)")
+ keep_previous=$$(($(PUBLISH_KEEP) - 1))
+ kept=0
+ while IFS= read -r release_name; do
+ [[ $$release_name =~ ^[0-9]{8}T[0-9]{6}Z-[0-9a-f]{12}(-[0-9a-f]{12})?-[0-9]+$$ ]] || continue
+ path="$(RELEASE_ROOT)/$$release_name"
+ [[ $$(readlink -f "$$path") != "$$current" ]] || continue
+ if ((kept < keep_previous)); then
+ ((kept += 1))
+ continue
+ fi
+ if ! rm -rf -- "$$path"; then
+ printf 'warning: could not prune %s\n' "$$path" >&2
+ fi
+ done < <(find "$(RELEASE_ROOT)" -mindepth 1 -maxdepth 1 -type d \
+ -exec basename {} \; | LC_ALL=C sort -r)
diff --git a/README.md b/README.md
index 2223d0c..a6436f4 100644
--- a/README.md
+++ b/README.md
@@ -54,19 +54,19 @@ committing generated files to either one:
* `postgis/postgis:utils/ci-status.py` generates `/ci/` independently of the
website source revision.
-`make -j2 publish` refreshes one local mirror of the PostGIS repository and
-then runs the site and CI-status publishers in parallel. Each publisher builds
-in its own staging directory and atomically switches a symlink only after
-validation succeeds. A failed build therefore leaves the previous release
-served. Each publisher retains its current release and two rollback releases by
-default; set `PUBLISH_KEEP` to change that count. Doxygen and release-manual
-generation remain owned by their existing Jenkins jobs and are not part of
-these targets.
+`make -j publish` refreshes one local mirror of the PostGIS repository and
+then runs the site and CI-status publishers in parallel. The Makefile defines
+the complete refresh, build, validation, atomic switch, and pruning graph; no
+separate deployment executable is involved. Each publisher builds in its own
+staging directory, so a failed build leaves the previous release served. Each
+publisher retains its current release and two rollback releases by default;
+set `PUBLISH_KEEP` to change that count. Doxygen and release-manual generation
+remain owned by their existing Jenkins jobs and are not part of these targets.
The defaults are for Debbie:
```sh
-make -j2 publish
+make -j publish
```
For an isolated build or test, override the paths and use an existing PostGIS
diff --git a/utils/site-publisher b/utils/site-publisher
deleted file mode 100755
index befff41..0000000
--- a/utils/site-publisher
+++ /dev/null
@@ -1,302 +0,0 @@
-#!/usr/bin/env bash
-
-set -Eeuo pipefail
-
-PUBLISH_ROOT=${PUBLISH_ROOT:-/var/www/postgis-publisher}
-POSTGIS_REPOSITORY=${POSTGIS_REPOSITORY:-https://gitea.osgeo.org/postgis/postgis.git}
-POSTGIS_GIT=${POSTGIS_GIT:-$PUBLISH_ROOT/sources/postgis.git}
-POSTGIS_REF=${POSTGIS_REF:-refs/heads/master}
-PUBLISH_KEEP=${PUBLISH_KEEP:-3}
-SITE_SOURCE=${SITE_SOURCE:-$(pwd)}
-SITE_OUTPUT=${SITE_OUTPUT:-$SITE_SOURCE/public}
-SITE_LIVE=${SITE_LIVE:-/var/www/postgis_website/public}
-CI_LIVE=${CI_LIVE:-$PUBLISH_ROOT/current/ci}
-HUGO=${HUGO:-hugo}
-PYTHON=${PYTHON:-python3}
-PUBLISHER_SCHEMA=1
-
-stage=
-pointer_tmp=
-
-cleanup() {
- if [[ -n $pointer_tmp && -L $pointer_tmp ]]; then
- rm -- "$pointer_tmp"
- fi
- if [[ -n $stage && -d $stage ]]; then
- rm -rf -- "$stage"
- fi
-}
-trap cleanup EXIT
-
-die() {
- echo "site-publisher: $*" >&2
- exit 1
-}
-
-require_command() {
- command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
-}
-
-check_root() {
- [[ $PUBLISH_ROOT = /* && $PUBLISH_ROOT != / ]] ||
- die "PUBLISH_ROOT must be an absolute path other than /"
-}
-
-core_git() {
- git --git-dir="$POSTGIS_GIT" "$@"
-}
-
-check_postgis() {
- [[ -d $POSTGIS_GIT ]] || die "PostGIS Git directory not found: $POSTGIS_GIT"
- core_git rev-parse --git-dir >/dev/null
- core_git rev-parse --verify "$POSTGIS_REF^{commit}" >/dev/null
-}
-
-refresh_postgis() {
- check_root
- require_command git
- install -d -m 0755 "$(dirname "$POSTGIS_GIT")"
- if [[ ! -d $POSTGIS_GIT ]]; then
- git clone --mirror "$POSTGIS_REPOSITORY" "$POSTGIS_GIT"
- else
- core_git rev-parse --is-bare-repository | grep -qx true ||
- die "managed PostGIS source is not a bare mirror: $POSTGIS_GIT"
- [[ $(core_git remote get-url origin) == "$POSTGIS_REPOSITORY" ]] ||
- die "PostGIS mirror origin does not match POSTGIS_REPOSITORY"
- fi
- core_git remote update --prune
- check_postgis
- echo "PostGIS source: $(core_git rev-parse "$POSTGIS_REF^{commit}")"
-}
-
-new_stage() {
- check_root
- install -d -m 0755 "$PUBLISH_ROOT/staging"
- stage=$(mktemp -d "$PUBLISH_ROOT/staging/publish.XXXXXXXX")
-}
-
-site_head() {
- git -C "$SITE_SOURCE" rev-parse --verify 'HEAD^{commit}'
-}
-
-postgis_head() {
- core_git rev-parse --verify "$POSTGIS_REF^{commit}"
-}
-
-write_site_manifest() {
- local destination=$1
- local website_sha=$2
- local postgis_sha=$3
- local hugo_version=$4
- {
- printf 'schema=%s\n' "$PUBLISHER_SCHEMA"
- printf 'website_sha=%s\n' "$website_sha"
- printf 'postgis_sha=%s\n' "$postgis_sha"
- printf 'hugo_version=%s\n' "$hugo_version"
- } >"$destination/.postgis-publisher"
-}
-
-build_site_to() {
- local destination=$1
- local website_sha=$2
- local postgis_sha=$3
- local hugo_version=$4
- local source=$stage/site-source
- local core=$stage/postgis-source
-
- install -d -m 0755 "$source" "$core" "$destination"
- git -C "$SITE_SOURCE" archive "$website_sha" | tar -x -C "$source"
- core_git archive "$postgis_sha" doc/development | tar -x -C "$core"
-
- install -d -m 0755 "$source/content/development/docs"
- cp -a "$core/doc/development/." "$source/content/development/docs/"
- rm -- "$source/content/development/docs/SKILL.md"
- mv -- "$source/content/development/docs/README.md" \
- "$source/content/development/docs/_index.md"
-
- "$HUGO" --source "$source" --destination "$destination"
- write_site_manifest "$destination" "$website_sha" "$postgis_sha" "$hugo_version"
- [[ -s $destination/index.html ]] || die "Hugo did not generate index.html"
- [[ -s $destination/development/docs/index.html ]] ||
- die "Hugo did not generate the developer documentation index"
- [[ ! -e $destination/development/docs/SKILL/index.html ]] ||
- die "SKILL.md was unexpectedly published"
-}
-
-check_pointer_target() {
- local pointer=$1
- [[ $pointer = /* && $pointer != / ]] ||
- die "live pointer must be an absolute path other than /: $pointer"
- if [[ -e $pointer && ! -L $pointer ]]; then
- die "$pointer exists and is not a symlink; preserve or move it before bootstrap"
- fi
-}
-
-switch_pointer() {
- local pointer=$1
- local release=$2
- local parent
- local name
-
- check_pointer_target "$pointer"
- parent=$(dirname "$pointer")
- name=$(basename "$pointer")
- install -d -m 0755 "$parent"
- pointer_tmp=$parent/.$name.new.$$
- [[ ! -e $pointer_tmp && ! -L $pointer_tmp ]] ||
- die "temporary pointer already exists: $pointer_tmp"
- ln -s "$release" "$pointer_tmp"
- mv -Tf -- "$pointer_tmp" "$pointer"
- pointer_tmp=
-}
-
-prune_releases() {
- local release_root=$1
- local pointer=$2
- local current
- local keep_previous
- local kept=0
- local name
- local path
-
- [[ $PUBLISH_KEEP =~ ^[1-9][0-9]*$ ]] ||
- die "PUBLISH_KEEP must be a positive integer"
- [[ $release_root == "$PUBLISH_ROOT"/releases/* ]] ||
- die "refusing to prune unexpected release root: $release_root"
- current=$(readlink -f "$pointer") || return 1
- keep_previous=$((PUBLISH_KEEP - 1))
- while IFS= read -r name; do
- [[ $name =~ ^[0-9]{8}T[0-9]{6}Z-[0-9a-f]{12}(-[0-9a-f]{12})?-[0-9]+$ ]] ||
- continue
- path=$release_root/$name
- [[ $(readlink -f "$path") != "$current" ]] || continue
- if ((kept < keep_previous)); then
- ((kept += 1))
- continue
- fi
- rm -rf -- "$path" || return 1
- done < <(find "$release_root" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' |
- LC_ALL=C sort -r)
-}
-
-publish_site() {
- local website_sha
- local postgis_sha
- local hugo_version
- local release
- local release_name
-
- check_pointer_target "$SITE_LIVE"
- new_stage
- website_sha=$(site_head)
- postgis_sha=$(postgis_head)
- hugo_version=$("$HUGO" version)
- install -d -m 0755 "$stage/expected"
- write_site_manifest "$stage/expected" "$website_sha" "$postgis_sha" "$hugo_version"
- if [[ -L $SITE_LIVE ]] &&
- cmp -s "$stage/expected/.postgis-publisher" "$SITE_LIVE/.postgis-publisher"; then
- echo "Site already matches website $website_sha and PostGIS $postgis_sha"
- return
- fi
-
- build_site_to "$stage/output" "$website_sha" "$postgis_sha" "$hugo_version"
- chmod -R a+rX "$stage/output"
- [[ ! -e $stage/output/ci && ! -L $stage/output/ci ]] ||
- die "Hugo output already contains the reserved /ci path"
- ln -s "$CI_LIVE" "$stage/output/ci"
- install -d -m 0755 "$PUBLISH_ROOT/releases/site"
- release_name=$(date -u +%Y%m%dT%H%M%SZ)-${website_sha:0:12}-${postgis_sha:0:12}-$$
- release=$PUBLISH_ROOT/releases/site/$release_name
- mv -- "$stage/output" "$release"
- switch_pointer "$SITE_LIVE" "$release"
- if ! prune_releases "$PUBLISH_ROOT/releases/site" "$SITE_LIVE"; then
- echo "site-publisher: warning: could not prune old site releases" >&2
- fi
- echo "Published site release $release"
-}
-
-build_site() {
- local website_sha
- local postgis_sha
- local hugo_version
-
- [[ $SITE_OUTPUT = /* && $SITE_OUTPUT != / ]] ||
- die "SITE_OUTPUT must be an absolute path other than /"
- new_stage
- website_sha=$(site_head)
- postgis_sha=$(postgis_head)
- hugo_version=$("$HUGO" version)
- build_site_to "$SITE_OUTPUT" "$website_sha" "$postgis_sha" "$hugo_version"
- echo "Built site at $SITE_OUTPUT"
-}
-
-publish_ci_status() {
- local postgis_sha
- local release
- local release_name
-
- check_pointer_target "$CI_LIVE"
- new_stage
- postgis_sha=$(postgis_head)
- install -d -m 0755 "$stage/source" "$stage/output"
- core_git archive "$postgis_sha" utils/ci-status.py utils/ci-status.json |
- tar -x -C "$stage/source"
- (
- cd "$POSTGIS_GIT"
- "$PYTHON" "$stage/source/utils/ci-status.py" \
- --config "$stage/source/utils/ci-status.json" \
- --format html --output-dir "$stage/output"
- )
- [[ -s $stage/output/index.html ]] || die "CI status generator did not write index.html"
- [[ -s $stage/output/status.json ]] || die "CI status generator did not write status.json"
- "$PYTHON" -m json.tool "$stage/output/status.json" >/dev/null
- {
- printf 'schema=%s\n' "$PUBLISHER_SCHEMA"
- printf 'postgis_sha=%s\n' "$postgis_sha"
- printf 'generated_at=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
- } >"$stage/output/.postgis-publisher"
- chmod -R a+rX "$stage/output"
- install -d -m 0755 "$PUBLISH_ROOT/releases/ci-status"
- release_name=$(date -u +%Y%m%dT%H%M%SZ)-${postgis_sha:0:12}-$$
- release=$PUBLISH_ROOT/releases/ci-status/$release_name
- mv -- "$stage/output" "$release"
- switch_pointer "$CI_LIVE" "$release"
- if ! prune_releases "$PUBLISH_ROOT/releases/ci-status" "$CI_LIVE"; then
- echo "site-publisher: warning: could not prune old CI status releases" >&2
- fi
- echo "Published CI status release $release"
-}
-
-usage() {
- echo "usage: $0 {check-postgis|refresh-postgis|build-site|publish-site|publish-ci-status}" >&2
- exit 2
-}
-
-require_command git
-require_command tar
-case ${1:-} in
- check-postgis)
- check_postgis
- ;;
- refresh-postgis)
- refresh_postgis
- ;;
- build-site)
- require_command "$HUGO"
- check_postgis
- build_site
- ;;
- publish-site)
- require_command "$HUGO"
- check_postgis
- publish_site
- ;;
- publish-ci-status)
- require_command "$PYTHON"
- check_postgis
- publish_ci_status
- ;;
- *)
- usage
- ;;
-esac
commit 15acda307e7529ba119ebd46ece68e975e428718
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sat Jul 18 23:18:45 2026 +0400
fix: publish developer docs at /development/docs
diff --git a/README.md b/README.md
index 5dbdda3..2223d0c 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,7 @@ committing generated files to either one:
* this repository supplies the Hugo site;
* `postgis/postgis:doc/development` is overlaid at
- `/development/devdocs/` during the Hugo build;
+ `/development/docs/` during the Hugo build;
* `postgis/postgis:utils/ci-status.py` generates `/ci/` independently of the
website source revision.
diff --git a/content/development/developer_docs.md b/content/development/developer_docs.md
index 49666dc..2b41810 100644
--- a/content/development/developer_docs.md
+++ b/content/development/developer_docs.md
@@ -6,7 +6,7 @@ geekdocHidden: false
layout: toplevel
---
-* [PostGIS Developer Documentation](/development/devdocs/)
+* [PostGIS Developer Documentation](/development/docs/)
* [Current CI Status](/ci/)
* [Style Guide](https://git.osgeo.org/gitea/postgis/postgis/src/branch/master/STYLE)
* [Testing with Docker](https://trac.osgeo.org/postgis/wiki/DevWikiDockerTesting)
diff --git a/utils/site-publisher b/utils/site-publisher
index 2badeb9..befff41 100755
--- a/utils/site-publisher
+++ b/utils/site-publisher
@@ -108,18 +108,18 @@ build_site_to() {
git -C "$SITE_SOURCE" archive "$website_sha" | tar -x -C "$source"
core_git archive "$postgis_sha" doc/development | tar -x -C "$core"
- install -d -m 0755 "$source/content/development/devdocs"
- cp -a "$core/doc/development/." "$source/content/development/devdocs/"
- rm -- "$source/content/development/devdocs/SKILL.md"
- mv -- "$source/content/development/devdocs/README.md" \
- "$source/content/development/devdocs/_index.md"
+ install -d -m 0755 "$source/content/development/docs"
+ cp -a "$core/doc/development/." "$source/content/development/docs/"
+ rm -- "$source/content/development/docs/SKILL.md"
+ mv -- "$source/content/development/docs/README.md" \
+ "$source/content/development/docs/_index.md"
"$HUGO" --source "$source" --destination "$destination"
write_site_manifest "$destination" "$website_sha" "$postgis_sha" "$hugo_version"
[[ -s $destination/index.html ]] || die "Hugo did not generate index.html"
- [[ -s $destination/development/devdocs/index.html ]] ||
+ [[ -s $destination/development/docs/index.html ]] ||
die "Hugo did not generate the developer documentation index"
- [[ ! -e $destination/development/devdocs/SKILL/index.html ]] ||
+ [[ ! -e $destination/development/docs/SKILL/index.html ]] ||
die "SKILL.md was unexpectedly published"
}
commit 6b7a8cae26747b99279001b5d9210bd884b8afea
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sat Jul 18 21:56:23 2026 +0400
ci: use the publisher default output path
diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 9a737fb..a6a33bb 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -8,7 +8,7 @@ steps:
commands:
- apk add --no-cache bash git make
- git clone --bare --depth 1 --branch master https://gitea.osgeo.org/postgis/postgis.git /tmp/postgis.git
- - make build-site PUBLISH_ROOT=/tmp/postgis-publisher POSTGIS_REFRESH=0 POSTGIS_GIT=/tmp/postgis.git SITE_OUTPUT=public
+ - make build-site PUBLISH_ROOT=/tmp/postgis-publisher POSTGIS_REFRESH=0 POSTGIS_GIT=/tmp/postgis.git
check-md5:
image: badouralix/curl-jq
commit ed7997f10d3ca0659866c3ea4c2c2f2b52d0352e
Author: Darafei Praliaskouski <me at komzpa.net>
Date: Sat Jul 18 21:35:34 2026 +0400
feat: publish developer docs and CI status
Compose repository-maintained developer documentation into the Hugo site and publish the generated CI dashboard from the same PostGIS source snapshot. Keep both publishers isolated, locked, and atomically swappable without committing generated output.
diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml
index 6c62263..9a737fb 100644
--- a/.woodpecker/build.yml
+++ b/.woodpecker/build.yml
@@ -6,7 +6,9 @@ steps:
build:
image: hugomods/hugo
commands:
- - hugo
+ - apk add --no-cache bash git make
+ - git clone --bare --depth 1 --branch master https://gitea.osgeo.org/postgis/postgis.git /tmp/postgis.git
+ - make build-site PUBLISH_ROOT=/tmp/postgis-publisher POSTGIS_REFRESH=0 POSTGIS_GIT=/tmp/postgis.git SITE_OUTPUT=public
check-md5:
image: badouralix/curl-jq
diff --git a/Makefile b/Makefile
index a11c07f..1af7d9c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,23 @@
-.PHONY: help
+.PHONY: help check check-releases refresh-postgis build-site publish \
+ publish-site publish-ci-status
+
+PUBLISH_ROOT ?= /var/www/postgis-publisher
+POSTGIS_REPOSITORY ?= https://gitea.osgeo.org/postgis/postgis.git
+POSTGIS_GIT ?= $(PUBLISH_ROOT)/sources/postgis.git
+POSTGIS_REF ?= refs/heads/master
+POSTGIS_REFRESH ?= 1
+PUBLISH_KEEP ?= 3
+SITE_SOURCE ?= $(CURDIR)
+SITE_OUTPUT ?= $(CURDIR)/public
+SITE_LIVE ?= /var/www/postgis_website/public
+CI_LIVE ?= $(PUBLISH_ROOT)/current/ci
+HUGO ?= hugo
+PYTHON ?= python3
+
+export PUBLISH_ROOT POSTGIS_REPOSITORY POSTGIS_GIT POSTGIS_REF
+export POSTGIS_REFRESH PUBLISH_KEEP SITE_SOURCE SITE_OUTPUT SITE_LIVE CI_LIVE
+export HUGO PYTHON
+
help:
@echo "Available targets:"
@grep -E '^[$$() a-zA-Z_0-9-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
@@ -12,3 +31,26 @@ check: check-releases ## Run all tests
check-releases: ## Check MD5 of releases
utils/check_releases_md5.sh
+
+refresh-postgis: ## Refresh the local mirror used for developer docs and CI status
+ @install -d -m 0755 "$(PUBLISH_ROOT)/locks"
+ @if [ "$(POSTGIS_REFRESH)" = 1 ]; then \
+ flock "$(PUBLISH_ROOT)/locks/source.lock" \
+ utils/site-publisher refresh-postgis; \
+ else \
+ utils/site-publisher check-postgis; \
+ fi
+
+build-site: refresh-postgis ## Build Hugo with developer docs from the PostGIS repository
+ utils/site-publisher build-site
+
+publish: publish-site publish-ci-status ## Publish the site and CI status (use make -j2)
+
+publish-site: refresh-postgis ## Atomically publish Hugo and PostGIS developer docs
+ @install -d -m 0755 "$(PUBLISH_ROOT)/locks"
+ flock "$(PUBLISH_ROOT)/locks/site.lock" utils/site-publisher publish-site
+
+publish-ci-status: refresh-postgis ## Atomically refresh the generated PostGIS CI status page
+ @install -d -m 0755 "$(PUBLISH_ROOT)/locks"
+ flock "$(PUBLISH_ROOT)/locks/ci-status.lock" \
+ utils/site-publisher publish-ci-status
diff --git a/README.md b/README.md
index 8f55fc7..5dbdda3 100644
--- a/README.md
+++ b/README.md
@@ -43,3 +43,48 @@ This will create a public folder:
Run `make check` after changes, before push
+## Production publishing
+
+The production publisher composes the site from two repositories without
+committing generated files to either one:
+
+* this repository supplies the Hugo site;
+* `postgis/postgis:doc/development` is overlaid at
+ `/development/devdocs/` during the Hugo build;
+* `postgis/postgis:utils/ci-status.py` generates `/ci/` independently of the
+ website source revision.
+
+`make -j2 publish` refreshes one local mirror of the PostGIS repository and
+then runs the site and CI-status publishers in parallel. Each publisher builds
+in its own staging directory and atomically switches a symlink only after
+validation succeeds. A failed build therefore leaves the previous release
+served. Each publisher retains its current release and two rollback releases by
+default; set `PUBLISH_KEEP` to change that count. Doxygen and release-manual
+generation remain owned by their existing Jenkins jobs and are not part of
+these targets.
+
+The defaults are for Debbie:
+
+```sh
+make -j2 publish
+```
+
+For an isolated build or test, override the paths and use an existing PostGIS
+Git directory without refreshing it:
+
+```sh
+make build-site \
+ PUBLISH_ROOT=/tmp/postgis-publisher \
+ POSTGIS_REFRESH=0 \
+ POSTGIS_GIT=/path/to/postgis/.git \
+ POSTGIS_REF=refs/remotes/upstream/master \
+ SITE_OUTPUT=/tmp/postgis-site
+```
+
+The first production activation is deliberately not automatic. The existing
+`/var/www/postgis_website/public` directory must be preserved and moved aside
+before `publish-site` can replace that path with an atomic symlink. The
+publisher refuses to overwrite a real directory. Publish `/ci/` successfully
+before switching the site pointer, then verify both URLs and only then replace
+the old cron entry. Restoring the preserved directory and the previous cron
+entry is the rollback path.
diff --git a/content/development/developer_docs.md b/content/development/developer_docs.md
index 293f711..49666dc 100644
--- a/content/development/developer_docs.md
+++ b/content/development/developer_docs.md
@@ -6,6 +6,8 @@ geekdocHidden: false
layout: toplevel
---
+* [PostGIS Developer Documentation](/development/devdocs/)
+* [Current CI Status](/ci/)
* [Style Guide](https://git.osgeo.org/gitea/postgis/postgis/src/branch/master/STYLE)
* [Testing with Docker](https://trac.osgeo.org/postgis/wiki/DevWikiDockerTesting)
* [Backtracing Crashes](https://trac.osgeo.org/postgis/wiki/DevWikiGettingABackTrace)
diff --git a/utils/site-publisher b/utils/site-publisher
new file mode 100755
index 0000000..2badeb9
--- /dev/null
+++ b/utils/site-publisher
@@ -0,0 +1,302 @@
+#!/usr/bin/env bash
+
+set -Eeuo pipefail
+
+PUBLISH_ROOT=${PUBLISH_ROOT:-/var/www/postgis-publisher}
+POSTGIS_REPOSITORY=${POSTGIS_REPOSITORY:-https://gitea.osgeo.org/postgis/postgis.git}
+POSTGIS_GIT=${POSTGIS_GIT:-$PUBLISH_ROOT/sources/postgis.git}
+POSTGIS_REF=${POSTGIS_REF:-refs/heads/master}
+PUBLISH_KEEP=${PUBLISH_KEEP:-3}
+SITE_SOURCE=${SITE_SOURCE:-$(pwd)}
+SITE_OUTPUT=${SITE_OUTPUT:-$SITE_SOURCE/public}
+SITE_LIVE=${SITE_LIVE:-/var/www/postgis_website/public}
+CI_LIVE=${CI_LIVE:-$PUBLISH_ROOT/current/ci}
+HUGO=${HUGO:-hugo}
+PYTHON=${PYTHON:-python3}
+PUBLISHER_SCHEMA=1
+
+stage=
+pointer_tmp=
+
+cleanup() {
+ if [[ -n $pointer_tmp && -L $pointer_tmp ]]; then
+ rm -- "$pointer_tmp"
+ fi
+ if [[ -n $stage && -d $stage ]]; then
+ rm -rf -- "$stage"
+ fi
+}
+trap cleanup EXIT
+
+die() {
+ echo "site-publisher: $*" >&2
+ exit 1
+}
+
+require_command() {
+ command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
+}
+
+check_root() {
+ [[ $PUBLISH_ROOT = /* && $PUBLISH_ROOT != / ]] ||
+ die "PUBLISH_ROOT must be an absolute path other than /"
+}
+
+core_git() {
+ git --git-dir="$POSTGIS_GIT" "$@"
+}
+
+check_postgis() {
+ [[ -d $POSTGIS_GIT ]] || die "PostGIS Git directory not found: $POSTGIS_GIT"
+ core_git rev-parse --git-dir >/dev/null
+ core_git rev-parse --verify "$POSTGIS_REF^{commit}" >/dev/null
+}
+
+refresh_postgis() {
+ check_root
+ require_command git
+ install -d -m 0755 "$(dirname "$POSTGIS_GIT")"
+ if [[ ! -d $POSTGIS_GIT ]]; then
+ git clone --mirror "$POSTGIS_REPOSITORY" "$POSTGIS_GIT"
+ else
+ core_git rev-parse --is-bare-repository | grep -qx true ||
+ die "managed PostGIS source is not a bare mirror: $POSTGIS_GIT"
+ [[ $(core_git remote get-url origin) == "$POSTGIS_REPOSITORY" ]] ||
+ die "PostGIS mirror origin does not match POSTGIS_REPOSITORY"
+ fi
+ core_git remote update --prune
+ check_postgis
+ echo "PostGIS source: $(core_git rev-parse "$POSTGIS_REF^{commit}")"
+}
+
+new_stage() {
+ check_root
+ install -d -m 0755 "$PUBLISH_ROOT/staging"
+ stage=$(mktemp -d "$PUBLISH_ROOT/staging/publish.XXXXXXXX")
+}
+
+site_head() {
+ git -C "$SITE_SOURCE" rev-parse --verify 'HEAD^{commit}'
+}
+
+postgis_head() {
+ core_git rev-parse --verify "$POSTGIS_REF^{commit}"
+}
+
+write_site_manifest() {
+ local destination=$1
+ local website_sha=$2
+ local postgis_sha=$3
+ local hugo_version=$4
+ {
+ printf 'schema=%s\n' "$PUBLISHER_SCHEMA"
+ printf 'website_sha=%s\n' "$website_sha"
+ printf 'postgis_sha=%s\n' "$postgis_sha"
+ printf 'hugo_version=%s\n' "$hugo_version"
+ } >"$destination/.postgis-publisher"
+}
+
+build_site_to() {
+ local destination=$1
+ local website_sha=$2
+ local postgis_sha=$3
+ local hugo_version=$4
+ local source=$stage/site-source
+ local core=$stage/postgis-source
+
+ install -d -m 0755 "$source" "$core" "$destination"
+ git -C "$SITE_SOURCE" archive "$website_sha" | tar -x -C "$source"
+ core_git archive "$postgis_sha" doc/development | tar -x -C "$core"
+
+ install -d -m 0755 "$source/content/development/devdocs"
+ cp -a "$core/doc/development/." "$source/content/development/devdocs/"
+ rm -- "$source/content/development/devdocs/SKILL.md"
+ mv -- "$source/content/development/devdocs/README.md" \
+ "$source/content/development/devdocs/_index.md"
+
+ "$HUGO" --source "$source" --destination "$destination"
+ write_site_manifest "$destination" "$website_sha" "$postgis_sha" "$hugo_version"
+ [[ -s $destination/index.html ]] || die "Hugo did not generate index.html"
+ [[ -s $destination/development/devdocs/index.html ]] ||
+ die "Hugo did not generate the developer documentation index"
+ [[ ! -e $destination/development/devdocs/SKILL/index.html ]] ||
+ die "SKILL.md was unexpectedly published"
+}
+
+check_pointer_target() {
+ local pointer=$1
+ [[ $pointer = /* && $pointer != / ]] ||
+ die "live pointer must be an absolute path other than /: $pointer"
+ if [[ -e $pointer && ! -L $pointer ]]; then
+ die "$pointer exists and is not a symlink; preserve or move it before bootstrap"
+ fi
+}
+
+switch_pointer() {
+ local pointer=$1
+ local release=$2
+ local parent
+ local name
+
+ check_pointer_target "$pointer"
+ parent=$(dirname "$pointer")
+ name=$(basename "$pointer")
+ install -d -m 0755 "$parent"
+ pointer_tmp=$parent/.$name.new.$$
+ [[ ! -e $pointer_tmp && ! -L $pointer_tmp ]] ||
+ die "temporary pointer already exists: $pointer_tmp"
+ ln -s "$release" "$pointer_tmp"
+ mv -Tf -- "$pointer_tmp" "$pointer"
+ pointer_tmp=
+}
+
+prune_releases() {
+ local release_root=$1
+ local pointer=$2
+ local current
+ local keep_previous
+ local kept=0
+ local name
+ local path
+
+ [[ $PUBLISH_KEEP =~ ^[1-9][0-9]*$ ]] ||
+ die "PUBLISH_KEEP must be a positive integer"
+ [[ $release_root == "$PUBLISH_ROOT"/releases/* ]] ||
+ die "refusing to prune unexpected release root: $release_root"
+ current=$(readlink -f "$pointer") || return 1
+ keep_previous=$((PUBLISH_KEEP - 1))
+ while IFS= read -r name; do
+ [[ $name =~ ^[0-9]{8}T[0-9]{6}Z-[0-9a-f]{12}(-[0-9a-f]{12})?-[0-9]+$ ]] ||
+ continue
+ path=$release_root/$name
+ [[ $(readlink -f "$path") != "$current" ]] || continue
+ if ((kept < keep_previous)); then
+ ((kept += 1))
+ continue
+ fi
+ rm -rf -- "$path" || return 1
+ done < <(find "$release_root" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' |
+ LC_ALL=C sort -r)
+}
+
+publish_site() {
+ local website_sha
+ local postgis_sha
+ local hugo_version
+ local release
+ local release_name
+
+ check_pointer_target "$SITE_LIVE"
+ new_stage
+ website_sha=$(site_head)
+ postgis_sha=$(postgis_head)
+ hugo_version=$("$HUGO" version)
+ install -d -m 0755 "$stage/expected"
+ write_site_manifest "$stage/expected" "$website_sha" "$postgis_sha" "$hugo_version"
+ if [[ -L $SITE_LIVE ]] &&
+ cmp -s "$stage/expected/.postgis-publisher" "$SITE_LIVE/.postgis-publisher"; then
+ echo "Site already matches website $website_sha and PostGIS $postgis_sha"
+ return
+ fi
+
+ build_site_to "$stage/output" "$website_sha" "$postgis_sha" "$hugo_version"
+ chmod -R a+rX "$stage/output"
+ [[ ! -e $stage/output/ci && ! -L $stage/output/ci ]] ||
+ die "Hugo output already contains the reserved /ci path"
+ ln -s "$CI_LIVE" "$stage/output/ci"
+ install -d -m 0755 "$PUBLISH_ROOT/releases/site"
+ release_name=$(date -u +%Y%m%dT%H%M%SZ)-${website_sha:0:12}-${postgis_sha:0:12}-$$
+ release=$PUBLISH_ROOT/releases/site/$release_name
+ mv -- "$stage/output" "$release"
+ switch_pointer "$SITE_LIVE" "$release"
+ if ! prune_releases "$PUBLISH_ROOT/releases/site" "$SITE_LIVE"; then
+ echo "site-publisher: warning: could not prune old site releases" >&2
+ fi
+ echo "Published site release $release"
+}
+
+build_site() {
+ local website_sha
+ local postgis_sha
+ local hugo_version
+
+ [[ $SITE_OUTPUT = /* && $SITE_OUTPUT != / ]] ||
+ die "SITE_OUTPUT must be an absolute path other than /"
+ new_stage
+ website_sha=$(site_head)
+ postgis_sha=$(postgis_head)
+ hugo_version=$("$HUGO" version)
+ build_site_to "$SITE_OUTPUT" "$website_sha" "$postgis_sha" "$hugo_version"
+ echo "Built site at $SITE_OUTPUT"
+}
+
+publish_ci_status() {
+ local postgis_sha
+ local release
+ local release_name
+
+ check_pointer_target "$CI_LIVE"
+ new_stage
+ postgis_sha=$(postgis_head)
+ install -d -m 0755 "$stage/source" "$stage/output"
+ core_git archive "$postgis_sha" utils/ci-status.py utils/ci-status.json |
+ tar -x -C "$stage/source"
+ (
+ cd "$POSTGIS_GIT"
+ "$PYTHON" "$stage/source/utils/ci-status.py" \
+ --config "$stage/source/utils/ci-status.json" \
+ --format html --output-dir "$stage/output"
+ )
+ [[ -s $stage/output/index.html ]] || die "CI status generator did not write index.html"
+ [[ -s $stage/output/status.json ]] || die "CI status generator did not write status.json"
+ "$PYTHON" -m json.tool "$stage/output/status.json" >/dev/null
+ {
+ printf 'schema=%s\n' "$PUBLISHER_SCHEMA"
+ printf 'postgis_sha=%s\n' "$postgis_sha"
+ printf 'generated_at=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
+ } >"$stage/output/.postgis-publisher"
+ chmod -R a+rX "$stage/output"
+ install -d -m 0755 "$PUBLISH_ROOT/releases/ci-status"
+ release_name=$(date -u +%Y%m%dT%H%M%SZ)-${postgis_sha:0:12}-$$
+ release=$PUBLISH_ROOT/releases/ci-status/$release_name
+ mv -- "$stage/output" "$release"
+ switch_pointer "$CI_LIVE" "$release"
+ if ! prune_releases "$PUBLISH_ROOT/releases/ci-status" "$CI_LIVE"; then
+ echo "site-publisher: warning: could not prune old CI status releases" >&2
+ fi
+ echo "Published CI status release $release"
+}
+
+usage() {
+ echo "usage: $0 {check-postgis|refresh-postgis|build-site|publish-site|publish-ci-status}" >&2
+ exit 2
+}
+
+require_command git
+require_command tar
+case ${1:-} in
+ check-postgis)
+ check_postgis
+ ;;
+ refresh-postgis)
+ refresh_postgis
+ ;;
+ build-site)
+ require_command "$HUGO"
+ check_postgis
+ build_site
+ ;;
+ publish-site)
+ require_command "$HUGO"
+ check_postgis
+ publish_site
+ ;;
+ publish-ci-status)
+ require_command "$PYTHON"
+ check_postgis
+ publish_ci_status
+ ;;
+ *)
+ usage
+ ;;
+esac
-----------------------------------------------------------------------
Summary of changes:
.gitignore | 1 +
.woodpecker/build.yml | 4 +++-
Makefile | 39 ++++++++++++++++++++++++++---------
README.md | 28 +++++++++++++++++++++++++
content/development/developer_docs.md | 2 ++
5 files changed, 63 insertions(+), 11 deletions(-)
hooks/post-receive
--
postgis.net
More information about the postgis-tickets
mailing list