pgserve 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -8
- package/bin/pgserve-wrapper.cjs +19 -0
- package/package.json +1 -1
- package/scripts/aggregate-manifest.sh +184 -0
- package/scripts/assemble-tarball.sh +191 -0
- package/scripts/build-binary.sh +213 -0
- package/scripts/fetch-postgres-bins.sh +234 -0
- package/scripts/postinstall.cjs +102 -18
- package/scripts/verify-published-artifacts.sh +211 -0
- package/src/cli-install.cjs +65 -3
- package/src/commands/doctor.js +465 -0
- package/src/commands/gc.js +276 -0
- package/src/commands/provision.js +396 -0
- package/src/commands/trust.js +187 -0
- package/src/cosign/trust-list.js +3 -3
- package/src/cosign/trust-store.js +250 -0
- package/src/gc/audit-log.js +150 -0
- package/src/gc/orphan-detection.js +190 -0
- package/src/gc/queries.js +193 -0
- package/src/lib/pg-query.js +145 -0
- package/src/provision/advisory-lock.js +91 -0
- package/src/provision/db-naming.js +130 -0
- package/src/provision/fingerprint.js +144 -0
- package/src/schema/pgserve-meta.js +120 -0
- package/src/security/blocked-versions.js +103 -0
- package/src/upgrade/steps/binary-cache-flush.js +2 -2
package/README.md
CHANGED
|
@@ -101,17 +101,14 @@ psql postgresql://localhost:8432/myapp
|
|
|
101
101
|
## Installation
|
|
102
102
|
|
|
103
103
|
```bash
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
# Global install
|
|
108
|
-
npm install -g pgserve
|
|
104
|
+
# Canonical install — signed binary from GitHub Releases
|
|
105
|
+
curl -fsSL https://raw.githubusercontent.com/namastexlabs/pgserve/main/install.sh | bash
|
|
109
106
|
|
|
110
|
-
#
|
|
111
|
-
|
|
107
|
+
# Pinned version
|
|
108
|
+
PGSERVE_VERSION=v2.6.0 curl -fsSL .../install.sh | bash
|
|
112
109
|
```
|
|
113
110
|
|
|
114
|
-
>
|
|
111
|
+
> `install.sh` fetches the signed tarball from GitHub Releases and verifies it via `gh attestation verify` (Sigstore Rekor public-good). Requires the [`gh` CLI](https://cli.github.com/). pgserve no longer depends on npm — the install + upgrade path is binary tarballs all the way down.
|
|
115
112
|
|
|
116
113
|
### Windows
|
|
117
114
|
|
package/bin/pgserve-wrapper.cjs
CHANGED
|
@@ -47,6 +47,25 @@ const __installSubcommands = new Set([
|
|
|
47
47
|
// `verify` shells out to cosign + writes an HMAC cache token. Pure node
|
|
48
48
|
// (no bun) so it must skip the bun probe like the install surface above.
|
|
49
49
|
'verify',
|
|
50
|
+
// pgserve singleton (v2.4) — wish Group 3, read-only V1. `doctor` runs
|
|
51
|
+
// entirely in node (admin.json + supervisor probes + socket reachability)
|
|
52
|
+
// and must skip the bun probe so it works on any installed binary.
|
|
53
|
+
'doctor',
|
|
54
|
+
// pgserve singleton (v2.4) — wish Group 3. `trust` manages the
|
|
55
|
+
// user-extensible cosign trust store at ~/.pgserve/trust/identities.json.
|
|
56
|
+
// Pure node, must skip bun probe.
|
|
57
|
+
'trust',
|
|
58
|
+
// pgserve singleton (v2.4) — wish Group 3, verb 3. `gc` shells out to
|
|
59
|
+
// psql to scan pgserve_meta + pg_database, classify orphans, and
|
|
60
|
+
// (under --apply) DROP DATABASE. Pure node + child_process, must skip
|
|
61
|
+
// the bun probe.
|
|
62
|
+
'gc',
|
|
63
|
+
// pgserve singleton (v2.4) — wish Group 3, verb 4. `provision` shells
|
|
64
|
+
// out to psql to CREATE ROLE / CREATE DATABASE / GRANT / INSERT INTO
|
|
65
|
+
// pgserve_meta. Idempotency-driven (no advisory lock — see
|
|
66
|
+
// src/commands/provision.js header for why). Pure node + child_process,
|
|
67
|
+
// must skip bun probe.
|
|
68
|
+
'provision',
|
|
50
69
|
]);
|
|
51
70
|
if (__subcommand && __installSubcommands.has(__subcommand)) {
|
|
52
71
|
const cli = require(path.join(__dirname, '..', 'src', 'cli-install.cjs'));
|
package/package.json
CHANGED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# aggregate-manifest.sh — Group 8 of autopg-distribution-cutover.
|
|
4
|
+
#
|
|
5
|
+
# Walks dist/autopg-<version>-<platform>.tar.gz, gathers each tarball's
|
|
6
|
+
# SHA256 + signature URL + provenance URL + platform tuple, and emits
|
|
7
|
+
# dist/manifest.json that consumers (install.sh, autopg update) read to
|
|
8
|
+
# resolve a download for a given (channel, version, platform).
|
|
9
|
+
#
|
|
10
|
+
# This runs after Group 8 signing + provenance generation and before
|
|
11
|
+
# Group 9 (CDN publish).
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# bash scripts/aggregate-manifest.sh --version 2.260503.1
|
|
15
|
+
# bash scripts/aggregate-manifest.sh --version 2.260503.1 --base-url https://cdn.automagik.dev/autopg/stable/2.260503.1
|
|
16
|
+
#
|
|
17
|
+
# Output:
|
|
18
|
+
# dist/manifest.json
|
|
19
|
+
#
|
|
20
|
+
# Exit codes:
|
|
21
|
+
# 0 ok
|
|
22
|
+
# 1 IO failure
|
|
23
|
+
# 2 invalid args / missing inputs
|
|
24
|
+
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
28
|
+
DIST_DIR="${AUTOPG_DIST_DIR:-${REPO_ROOT}/dist}"
|
|
29
|
+
|
|
30
|
+
usage() {
|
|
31
|
+
cat <<EOF
|
|
32
|
+
Usage: $0 --version <v> [--base-url <url>] [--channel <c>] [--cosign-pub-url <url>]
|
|
33
|
+
|
|
34
|
+
--version autopg version, e.g. 2.260503.1 (or read from package.json)
|
|
35
|
+
--base-url absolute base URL prefix for tarball URLs
|
|
36
|
+
(default: relative — consumers resolve against the
|
|
37
|
+
directory the manifest sits in).
|
|
38
|
+
--channel channel hint embedded in the manifest (stable|beta|canary).
|
|
39
|
+
default: stable
|
|
40
|
+
--cosign-pub-url absolute URL to the published cosign public key.
|
|
41
|
+
default: <base-url>/../../keys/cosign.pub for production
|
|
42
|
+
(cdn.automagik.dev layout) or "keys/cosign.pub" relative.
|
|
43
|
+
|
|
44
|
+
Reads:
|
|
45
|
+
dist/autopg-<version>-<platform>.tar.gz
|
|
46
|
+
dist/autopg-<version>-<platform>.tar.gz.sha256
|
|
47
|
+
dist/autopg-<version>-<platform>.tar.gz.sig (optional)
|
|
48
|
+
dist/autopg-<version>-<platform>.tar.gz.intoto.jsonl (optional)
|
|
49
|
+
|
|
50
|
+
Writes:
|
|
51
|
+
dist/manifest.json
|
|
52
|
+
EOF
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
parse_args() {
|
|
56
|
+
VERSION="${AUTOPG_VERSION:-}"
|
|
57
|
+
BASE_URL=""
|
|
58
|
+
CHANNEL="stable"
|
|
59
|
+
COSIGN_PUB_URL=""
|
|
60
|
+
while [[ $# -gt 0 ]]; do
|
|
61
|
+
case "$1" in
|
|
62
|
+
--version) VERSION="$2"; shift 2 ;;
|
|
63
|
+
--base-url) BASE_URL="$2"; shift 2 ;;
|
|
64
|
+
--channel) CHANNEL="$2"; shift 2 ;;
|
|
65
|
+
--cosign-pub-url) COSIGN_PUB_URL="$2"; shift 2 ;;
|
|
66
|
+
-h|--help) usage; exit 0 ;;
|
|
67
|
+
*) echo "unknown arg: $1" >&2; usage; exit 2 ;;
|
|
68
|
+
esac
|
|
69
|
+
done
|
|
70
|
+
if [[ -z "$VERSION" ]]; then
|
|
71
|
+
VERSION=$(node -p "require('${REPO_ROOT}/package.json').version" 2>/dev/null || echo "")
|
|
72
|
+
fi
|
|
73
|
+
if [[ -z "$VERSION" ]]; then
|
|
74
|
+
echo "error: --version required (or set in package.json)" >&2; exit 2
|
|
75
|
+
fi
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Portable SHA256 — sha256sum on linux, shasum -a 256 on macOS.
|
|
79
|
+
sha256_of() {
|
|
80
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
81
|
+
sha256sum "$1" | awk '{print $1}'
|
|
82
|
+
else
|
|
83
|
+
shasum -a 256 "$1" | awk '{print $1}'
|
|
84
|
+
fi
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
prefix_url() {
|
|
88
|
+
local rel="$1"
|
|
89
|
+
if [[ -n "$BASE_URL" ]]; then
|
|
90
|
+
printf '%s/%s' "${BASE_URL%/}" "$rel"
|
|
91
|
+
else
|
|
92
|
+
printf '%s' "$rel"
|
|
93
|
+
fi
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
emit_entry() {
|
|
97
|
+
local tarball="$1"
|
|
98
|
+
local first="$2"
|
|
99
|
+
local base platform sha sz
|
|
100
|
+
base=$(basename "$tarball")
|
|
101
|
+
# Strip "autopg-<version>-" prefix and ".tar.gz" suffix to get platform.
|
|
102
|
+
platform="${base#autopg-${VERSION}-}"
|
|
103
|
+
platform="${platform%.tar.gz}"
|
|
104
|
+
|
|
105
|
+
if [[ ! -f "${tarball}.sha256" ]]; then
|
|
106
|
+
echo "error: ${tarball}.sha256 missing — run assemble-tarball.sh first" >&2
|
|
107
|
+
return 1
|
|
108
|
+
fi
|
|
109
|
+
sha=$(awk '{print $1}' "${tarball}.sha256")
|
|
110
|
+
sz=$(stat -c %s "$tarball" 2>/dev/null || stat -f %z "$tarball")
|
|
111
|
+
|
|
112
|
+
local sig_url="" prov_url=""
|
|
113
|
+
if [[ -f "${tarball}.sig" ]]; then
|
|
114
|
+
sig_url=$(prefix_url "${base}.sig")
|
|
115
|
+
fi
|
|
116
|
+
if [[ -f "${tarball}.intoto.jsonl" ]]; then
|
|
117
|
+
prov_url=$(prefix_url "${base}.intoto.jsonl")
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
if [[ "$first" -eq 0 ]]; then printf ',\n'; fi
|
|
121
|
+
printf ' {\n'
|
|
122
|
+
printf ' "platform": "%s",\n' "$platform"
|
|
123
|
+
printf ' "file": "%s",\n' "$base"
|
|
124
|
+
printf ' "url": "%s",\n' "$(prefix_url "$base")"
|
|
125
|
+
printf ' "sha256": "%s",\n' "$sha"
|
|
126
|
+
printf ' "size": %d,\n' "$sz"
|
|
127
|
+
printf ' "signature_url": "%s",\n' "$sig_url"
|
|
128
|
+
printf ' "provenance_url": "%s"\n' "$prov_url"
|
|
129
|
+
printf ' }'
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
main() {
|
|
133
|
+
parse_args "$@"
|
|
134
|
+
[[ -d "$DIST_DIR" ]] || { echo "error: $DIST_DIR not a directory" >&2; exit 2; }
|
|
135
|
+
|
|
136
|
+
local tarballs=()
|
|
137
|
+
while IFS= read -r line; do
|
|
138
|
+
tarballs+=("$line")
|
|
139
|
+
done < <(find "$DIST_DIR" -maxdepth 1 -name "autopg-${VERSION}-*.tar.gz" -type f | LC_ALL=C sort)
|
|
140
|
+
|
|
141
|
+
if [[ ${#tarballs[@]} -eq 0 ]]; then
|
|
142
|
+
echo "error: no autopg-${VERSION}-*.tar.gz files in ${DIST_DIR}" >&2
|
|
143
|
+
exit 2
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
local generated_at
|
|
147
|
+
generated_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
148
|
+
|
|
149
|
+
local out="${DIST_DIR}/manifest.json"
|
|
150
|
+
{
|
|
151
|
+
printf '{\n'
|
|
152
|
+
printf ' "name": "autopg",\n'
|
|
153
|
+
printf ' "version": "%s",\n' "$VERSION"
|
|
154
|
+
printf ' "channel": "%s",\n' "$CHANNEL"
|
|
155
|
+
printf ' "schemaVersion": 1,\n'
|
|
156
|
+
printf ' "generated_at": "%s",\n' "$generated_at"
|
|
157
|
+
local cpub
|
|
158
|
+
if [[ -n "$COSIGN_PUB_URL" ]]; then
|
|
159
|
+
cpub="$COSIGN_PUB_URL"
|
|
160
|
+
elif [[ -n "$BASE_URL" ]]; then
|
|
161
|
+
# CDN layout: <base>/autopg/<channel>/<version>/manifest.json
|
|
162
|
+
# Public key lives at: <base>/autopg/keys/cosign.pub
|
|
163
|
+
# If caller passes the full <base>/autopg/<channel>/<version> as
|
|
164
|
+
# base-url, walk two levels up to reach the keys/ sibling.
|
|
165
|
+
cpub="${BASE_URL%/*}"
|
|
166
|
+
cpub="${cpub%/*}/keys/cosign.pub"
|
|
167
|
+
else
|
|
168
|
+
cpub="keys/cosign.pub"
|
|
169
|
+
fi
|
|
170
|
+
printf ' "cosign_pub_url": "%s",\n' "$cpub"
|
|
171
|
+
printf ' "platforms": [\n'
|
|
172
|
+
local first=1
|
|
173
|
+
for t in "${tarballs[@]}"; do
|
|
174
|
+
emit_entry "$t" "$first"
|
|
175
|
+
first=0
|
|
176
|
+
done
|
|
177
|
+
printf '\n ]\n'
|
|
178
|
+
printf '}\n'
|
|
179
|
+
} > "$out"
|
|
180
|
+
|
|
181
|
+
echo "==> manifest: $out ($(wc -l < "$out") lines, ${#tarballs[@]} platforms)"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
main "$@"
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# assemble-tarball.sh — Group 7 of autopg-distribution-cutover.
|
|
4
|
+
#
|
|
5
|
+
# Assembles a single platform tarball with the locked shape:
|
|
6
|
+
#
|
|
7
|
+
# autopg/
|
|
8
|
+
# autopg # static binary (from build-binary.sh)
|
|
9
|
+
# postgres/
|
|
10
|
+
# bin/* # postgres + initdb + libpq + ...
|
|
11
|
+
# share/* # timezone data, locale, etc.
|
|
12
|
+
# manifest.json # per-file SHA256 + size
|
|
13
|
+
#
|
|
14
|
+
# The tarball lives at:
|
|
15
|
+
# dist/autopg-<version>-<platform>.tar.gz
|
|
16
|
+
# and a sibling .sha256 file holds the outer hash for Group 8 (cosign sign)
|
|
17
|
+
# and Group 9 (CDN publish) to consume.
|
|
18
|
+
#
|
|
19
|
+
# Inputs come from dist/<platform>/autopg/{autopg, postgres/}, populated by
|
|
20
|
+
# build-binary.sh + fetch-postgres-bins.sh.
|
|
21
|
+
#
|
|
22
|
+
# Usage:
|
|
23
|
+
# scripts/assemble-tarball.sh --platform linux-x64-glibc
|
|
24
|
+
# scripts/assemble-tarball.sh --all --version 2.260503.1
|
|
25
|
+
|
|
26
|
+
set -euo pipefail
|
|
27
|
+
|
|
28
|
+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
29
|
+
DIST_DIR="${AUTOPG_DIST_DIR:-${REPO_ROOT}/dist}"
|
|
30
|
+
|
|
31
|
+
PLATFORMS=(linux-x64-glibc linux-x64-musl linux-arm64 darwin-x64 darwin-arm64)
|
|
32
|
+
|
|
33
|
+
usage() {
|
|
34
|
+
cat <<EOF
|
|
35
|
+
Usage: $0 (--platform <p> | --all) [--version <v>]
|
|
36
|
+
|
|
37
|
+
Platforms: ${PLATFORMS[*]}
|
|
38
|
+
|
|
39
|
+
Inputs (must already exist):
|
|
40
|
+
dist/<platform>/autopg/autopg (build-binary.sh)
|
|
41
|
+
dist/<platform>/autopg/postgres/bin/* (fetch-postgres-bins.sh)
|
|
42
|
+
dist/<platform>/autopg/postgres/share/* (fetch-postgres-bins.sh)
|
|
43
|
+
|
|
44
|
+
Outputs:
|
|
45
|
+
dist/autopg-<version>-<platform>.tar.gz
|
|
46
|
+
dist/autopg-<version>-<platform>.tar.gz.sha256
|
|
47
|
+
EOF
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
parse_args() {
|
|
51
|
+
TARGET_PLATFORM=""
|
|
52
|
+
ASSEMBLE_ALL=0
|
|
53
|
+
VERSION="${AUTOPG_VERSION:-}"
|
|
54
|
+
|
|
55
|
+
while [[ $# -gt 0 ]]; do
|
|
56
|
+
case "$1" in
|
|
57
|
+
--platform) TARGET_PLATFORM="$2"; shift 2 ;;
|
|
58
|
+
--all) ASSEMBLE_ALL=1; shift ;;
|
|
59
|
+
--version) VERSION="$2"; shift 2 ;;
|
|
60
|
+
-h|--help) usage; exit 0 ;;
|
|
61
|
+
*) echo "unknown arg: $1" >&2; usage; exit 2 ;;
|
|
62
|
+
esac
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
if [[ "$ASSEMBLE_ALL" -eq 0 && -z "$TARGET_PLATFORM" ]]; then
|
|
66
|
+
echo "error: pass --platform <p> or --all" >&2; usage; exit 2
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
if [[ -z "$VERSION" ]]; then
|
|
70
|
+
VERSION=$(node -p "require('${REPO_ROOT}/package.json').version" 2>/dev/null || echo "0.0.0")
|
|
71
|
+
fi
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Portable SHA256 — use sha256sum on linux, shasum -a 256 on macOS.
|
|
75
|
+
sha256_of() {
|
|
76
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
77
|
+
sha256sum "$1" | awk '{print $1}'
|
|
78
|
+
else
|
|
79
|
+
shasum -a 256 "$1" | awk '{print $1}'
|
|
80
|
+
fi
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Emit manifest.json for the given platform's staged tree.
|
|
84
|
+
# Walks autopg/ relative to <root>/, skipping manifest.json itself.
|
|
85
|
+
emit_manifest() {
|
|
86
|
+
local root="$1" platform="$2" out="$3"
|
|
87
|
+
|
|
88
|
+
pushd "$root" >/dev/null
|
|
89
|
+
{
|
|
90
|
+
printf '{\n'
|
|
91
|
+
printf ' "name": "autopg",\n'
|
|
92
|
+
printf ' "version": "%s",\n' "$VERSION"
|
|
93
|
+
printf ' "platform": "%s",\n' "$platform"
|
|
94
|
+
printf ' "schemaVersion": 1,\n'
|
|
95
|
+
printf ' "files": [\n'
|
|
96
|
+
|
|
97
|
+
local first=1
|
|
98
|
+
while IFS= read -r f; do
|
|
99
|
+
[[ "$f" == "autopg/manifest.json" ]] && continue
|
|
100
|
+
local h sz
|
|
101
|
+
h=$(sha256_of "$f")
|
|
102
|
+
sz=$(stat -c %s "$f" 2>/dev/null || stat -f %z "$f")
|
|
103
|
+
if [[ $first -eq 1 ]]; then
|
|
104
|
+
first=0
|
|
105
|
+
else
|
|
106
|
+
printf ',\n'
|
|
107
|
+
fi
|
|
108
|
+
printf ' { "path": "%s", "sha256": "%s", "size": %d }' "$f" "$h" "$sz"
|
|
109
|
+
done < <(find autopg -type f | LC_ALL=C sort)
|
|
110
|
+
|
|
111
|
+
printf '\n ]\n'
|
|
112
|
+
printf '}\n'
|
|
113
|
+
} > "$out"
|
|
114
|
+
popd >/dev/null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# Verify staged inputs are present + executable.
|
|
118
|
+
verify_inputs() {
|
|
119
|
+
local stage="$1" platform="$2"
|
|
120
|
+
local missing=0
|
|
121
|
+
for required in autopg/autopg autopg/postgres/bin/postgres; do
|
|
122
|
+
if [[ ! -f "${stage}/${required}" ]]; then
|
|
123
|
+
echo "error: ${platform}: missing ${required}" >&2
|
|
124
|
+
missing=1
|
|
125
|
+
fi
|
|
126
|
+
done
|
|
127
|
+
return $missing
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
assemble_one() {
|
|
131
|
+
local platform="$1"
|
|
132
|
+
local stage="${DIST_DIR}/${platform}"
|
|
133
|
+
local tarball="${DIST_DIR}/autopg-${VERSION}-${platform}.tar.gz"
|
|
134
|
+
local outer_sha="${tarball}.sha256"
|
|
135
|
+
|
|
136
|
+
if [[ ! -d "${stage}/autopg" ]]; then
|
|
137
|
+
echo "error: ${stage}/autopg/ does not exist (run build-binary.sh + fetch-postgres-bins.sh first)" >&2
|
|
138
|
+
return 1
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
echo "==> [${platform}] assemble tarball"
|
|
142
|
+
# `assemble_one` is invoked from main as `assemble_one ... || rc=$?` which
|
|
143
|
+
# disables `set -e` for the duration of this function. Each potentially-
|
|
144
|
+
# failing command needs explicit `|| return 1` to halt early instead of
|
|
145
|
+
# silently producing a corrupt tarball (gemini bot review HIGH on PR #84).
|
|
146
|
+
verify_inputs "$stage" "$platform" || return 1
|
|
147
|
+
|
|
148
|
+
# 1) emit per-file manifest BEFORE the tarball is rolled — manifest is
|
|
149
|
+
# bundled inside.
|
|
150
|
+
emit_manifest "$stage" "$platform" "${stage}/autopg/manifest.json" || return 1
|
|
151
|
+
|
|
152
|
+
# 2) ensure binaries are executable inside the tar.
|
|
153
|
+
chmod +x "${stage}/autopg/autopg" || true
|
|
154
|
+
find "${stage}/autopg/postgres/bin" -type f -exec chmod +x {} +
|
|
155
|
+
|
|
156
|
+
# 3) build deterministic tarball: sorted entries, locked mtime.
|
|
157
|
+
local tar_flags=()
|
|
158
|
+
if tar --help 2>&1 | grep -q -- '--sort=name'; then
|
|
159
|
+
tar_flags+=(--sort=name)
|
|
160
|
+
fi
|
|
161
|
+
if tar --help 2>&1 | grep -q -- '--mtime='; then
|
|
162
|
+
tar_flags+=(--mtime=2026-01-01)
|
|
163
|
+
fi
|
|
164
|
+
if tar --help 2>&1 | grep -q -- '--owner='; then
|
|
165
|
+
tar_flags+=(--owner=0 --group=0 --numeric-owner)
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
tar -C "$stage" -czf "$tarball" "${tar_flags[@]}" autopg/ || return 1
|
|
169
|
+
echo " ✓ tarball: $tarball ($(du -h "$tarball" | cut -f1))"
|
|
170
|
+
|
|
171
|
+
# 4) outer SHA256 — Group 8 cosign-signs this; Group 9 publishes both.
|
|
172
|
+
sha256_of "$tarball" > "$outer_sha" || return 1
|
|
173
|
+
echo " ✓ sha256: $(cat "$outer_sha") $(basename "$tarball")"
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
main() {
|
|
177
|
+
parse_args "$@"
|
|
178
|
+
mkdir -p "$DIST_DIR"
|
|
179
|
+
|
|
180
|
+
local rc=0
|
|
181
|
+
if [[ "$ASSEMBLE_ALL" -eq 1 ]]; then
|
|
182
|
+
for p in "${PLATFORMS[@]}"; do
|
|
183
|
+
assemble_one "$p" || rc=$?
|
|
184
|
+
done
|
|
185
|
+
else
|
|
186
|
+
assemble_one "$TARGET_PLATFORM" || rc=$?
|
|
187
|
+
fi
|
|
188
|
+
exit $rc
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
main "$@"
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# build-binary.sh — Group 7 of autopg-distribution-cutover.
|
|
4
|
+
#
|
|
5
|
+
# Compiles the autopg CLI to a static binary using `bun build --compile`
|
|
6
|
+
# for one or all of the 5 supported platforms:
|
|
7
|
+
#
|
|
8
|
+
# linux-x64-glibc, linux-x64-musl, linux-arm64,
|
|
9
|
+
# darwin-x64, darwin-arm64
|
|
10
|
+
#
|
|
11
|
+
# Outputs land at: dist/<platform>/autopg/autopg
|
|
12
|
+
#
|
|
13
|
+
# Per the wish G7 fallback contract (distribution-exodus G1): if
|
|
14
|
+
# `bun build --compile` fails for a target, retry with `pkg`/`nexe`
|
|
15
|
+
# when AUTOPG_BUILD_FALLBACK=1. The fallback is recorded in the build
|
|
16
|
+
# log so Group 9's CDN publish can surface it.
|
|
17
|
+
#
|
|
18
|
+
# Usage:
|
|
19
|
+
# scripts/build-binary.sh --platform linux-x64-glibc
|
|
20
|
+
# scripts/build-binary.sh --all
|
|
21
|
+
# scripts/build-binary.sh --platform darwin-arm64 --version 2.260503.1
|
|
22
|
+
#
|
|
23
|
+
# Exit codes:
|
|
24
|
+
# 0 success
|
|
25
|
+
# 1 bun build failed AND fallback disabled or also failed
|
|
26
|
+
# 2 invalid arguments / unsupported platform
|
|
27
|
+
|
|
28
|
+
set -euo pipefail
|
|
29
|
+
|
|
30
|
+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
31
|
+
ENTRY_POINT="${AUTOPG_ENTRY_POINT:-bin/postgres-server.js}"
|
|
32
|
+
DIST_DIR="${AUTOPG_DIST_DIR:-${REPO_ROOT}/dist}"
|
|
33
|
+
FALLBACK_ENABLED="${AUTOPG_BUILD_FALLBACK:-0}"
|
|
34
|
+
|
|
35
|
+
PLATFORMS=(linux-x64-glibc linux-x64-musl linux-arm64 darwin-x64 darwin-arm64)
|
|
36
|
+
|
|
37
|
+
# Map autopg platform tag → bun --target value.
|
|
38
|
+
bun_target_for() {
|
|
39
|
+
case "$1" in
|
|
40
|
+
linux-x64-glibc) echo "bun-linux-x64" ;;
|
|
41
|
+
linux-x64-musl) echo "bun-linux-x64-musl" ;;
|
|
42
|
+
linux-arm64) echo "bun-linux-arm64" ;;
|
|
43
|
+
darwin-x64) echo "bun-darwin-x64" ;;
|
|
44
|
+
darwin-arm64) echo "bun-darwin-arm64" ;;
|
|
45
|
+
*) return 1 ;;
|
|
46
|
+
esac
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
usage() {
|
|
50
|
+
cat <<EOF
|
|
51
|
+
Usage: $0 (--platform <p> | --all) [--version <v>] [--entry <path>]
|
|
52
|
+
|
|
53
|
+
Platforms: ${PLATFORMS[*]}
|
|
54
|
+
|
|
55
|
+
Environment:
|
|
56
|
+
AUTOPG_ENTRY_POINT Override entry file (default: bin/postgres-server.js)
|
|
57
|
+
AUTOPG_DIST_DIR Override output root (default: \$REPO/dist)
|
|
58
|
+
AUTOPG_BUILD_FALLBACK Set to 1 to retry failed bun builds via pkg/nexe
|
|
59
|
+
EOF
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
parse_args() {
|
|
63
|
+
TARGET_PLATFORM=""
|
|
64
|
+
BUILD_ALL=0
|
|
65
|
+
VERSION="${AUTOPG_VERSION:-}"
|
|
66
|
+
|
|
67
|
+
while [[ $# -gt 0 ]]; do
|
|
68
|
+
case "$1" in
|
|
69
|
+
--platform) TARGET_PLATFORM="$2"; shift 2 ;;
|
|
70
|
+
--all) BUILD_ALL=1; shift ;;
|
|
71
|
+
--version) VERSION="$2"; shift 2 ;;
|
|
72
|
+
--entry) ENTRY_POINT="$2"; shift 2 ;;
|
|
73
|
+
-h|--help) usage; exit 0 ;;
|
|
74
|
+
*) echo "unknown arg: $1" >&2; usage; exit 2 ;;
|
|
75
|
+
esac
|
|
76
|
+
done
|
|
77
|
+
|
|
78
|
+
if [[ "$BUILD_ALL" -eq 0 && -z "$TARGET_PLATFORM" ]]; then
|
|
79
|
+
echo "error: pass --platform <p> or --all" >&2
|
|
80
|
+
usage; exit 2
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
if [[ -z "$VERSION" ]]; then
|
|
84
|
+
VERSION=$(node -p "require('${REPO_ROOT}/package.json').version" 2>/dev/null || echo "0.0.0")
|
|
85
|
+
fi
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
build_one() {
|
|
89
|
+
local platform="$1"
|
|
90
|
+
local target
|
|
91
|
+
target="$(bun_target_for "$platform")" || {
|
|
92
|
+
echo "error: unsupported platform: $platform" >&2
|
|
93
|
+
return 2
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
local out_dir="${DIST_DIR}/${platform}/autopg"
|
|
97
|
+
# `build_one` runs without `set -e` (called via `|| rc=$?` in main), so
|
|
98
|
+
# mkdir failures must propagate explicitly (gemini PR #84 HIGH review).
|
|
99
|
+
mkdir -p "$out_dir" || return 1
|
|
100
|
+
local outfile="${out_dir}/autopg"
|
|
101
|
+
|
|
102
|
+
echo "==> [${platform}] bun build --compile --target=${target}"
|
|
103
|
+
if bun build --compile \
|
|
104
|
+
--target="${target}" \
|
|
105
|
+
--define BUILD_VERSION="'${VERSION}'" \
|
|
106
|
+
"${REPO_ROOT}/${ENTRY_POINT}" \
|
|
107
|
+
--outfile "${outfile}" 2>&1 | tee -a "${DIST_DIR}/build.log"; then
|
|
108
|
+
echo " ✓ built: ${outfile}"
|
|
109
|
+
record_build "$platform" "bun" "ok"
|
|
110
|
+
return 0
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
echo " ✗ bun build failed for ${platform}" >&2
|
|
114
|
+
|
|
115
|
+
if [[ "$FALLBACK_ENABLED" -eq 1 ]]; then
|
|
116
|
+
echo "==> [${platform}] retry via pkg/nexe (AUTOPG_BUILD_FALLBACK=1)"
|
|
117
|
+
if try_fallback "$platform" "$outfile"; then
|
|
118
|
+
record_build "$platform" "fallback" "ok"
|
|
119
|
+
return 0
|
|
120
|
+
fi
|
|
121
|
+
record_build "$platform" "fallback" "fail"
|
|
122
|
+
else
|
|
123
|
+
record_build "$platform" "bun" "fail"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
return 1
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Fallback: try pkg first, then nexe. Both consume the same entrypoint and
|
|
130
|
+
# emit a single executable. We only attempt the fallback when bun fails;
|
|
131
|
+
# this is per the distribution-exodus G1 contract.
|
|
132
|
+
try_fallback() {
|
|
133
|
+
local platform="$1"
|
|
134
|
+
local outfile="$2"
|
|
135
|
+
|
|
136
|
+
if command -v pkg >/dev/null 2>&1; then
|
|
137
|
+
echo " -> trying pkg"
|
|
138
|
+
local pkg_target
|
|
139
|
+
pkg_target="$(pkg_target_for "$platform")" || return 1
|
|
140
|
+
if pkg --target "$pkg_target" \
|
|
141
|
+
--output "$outfile" \
|
|
142
|
+
"${REPO_ROOT}/${ENTRY_POINT}" 2>&1 | tee -a "${DIST_DIR}/build.log"; then
|
|
143
|
+
echo " ✓ pkg succeeded"
|
|
144
|
+
return 0
|
|
145
|
+
fi
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
if command -v nexe >/dev/null 2>&1; then
|
|
149
|
+
echo " -> trying nexe"
|
|
150
|
+
local nexe_target
|
|
151
|
+
nexe_target="$(nexe_target_for "$platform")" || return 1
|
|
152
|
+
if nexe --target "$nexe_target" \
|
|
153
|
+
--output "$outfile" \
|
|
154
|
+
"${REPO_ROOT}/${ENTRY_POINT}" 2>&1 | tee -a "${DIST_DIR}/build.log"; then
|
|
155
|
+
echo " ✓ nexe succeeded"
|
|
156
|
+
return 0
|
|
157
|
+
fi
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
echo " ✗ no fallback worked (install pkg or nexe to enable)" >&2
|
|
161
|
+
return 1
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
pkg_target_for() {
|
|
165
|
+
case "$1" in
|
|
166
|
+
linux-x64-glibc) echo "node20-linux-x64" ;;
|
|
167
|
+
linux-x64-musl) echo "node20-linuxstatic-x64" ;;
|
|
168
|
+
linux-arm64) echo "node20-linux-arm64" ;;
|
|
169
|
+
darwin-x64) echo "node20-macos-x64" ;;
|
|
170
|
+
darwin-arm64) echo "node20-macos-arm64" ;;
|
|
171
|
+
*) return 1 ;;
|
|
172
|
+
esac
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
nexe_target_for() {
|
|
176
|
+
case "$1" in
|
|
177
|
+
linux-x64-glibc) echo "linux-x64-20.0.0" ;;
|
|
178
|
+
linux-x64-musl) echo "alpine-x64-20.0.0" ;;
|
|
179
|
+
linux-arm64) echo "linux-arm64-20.0.0" ;;
|
|
180
|
+
darwin-x64) echo "mac-x64-20.0.0" ;;
|
|
181
|
+
darwin-arm64) echo "mac-arm64-20.0.0" ;;
|
|
182
|
+
*) return 1 ;;
|
|
183
|
+
esac
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
record_build() {
|
|
187
|
+
local platform="$1" tool="$2" status="$3"
|
|
188
|
+
local rec="${DIST_DIR}/build-record.tsv"
|
|
189
|
+
mkdir -p "$DIST_DIR"
|
|
190
|
+
printf '%s\t%s\t%s\t%s\t%s\n' "$(date -u +%FT%TZ)" "$platform" "$tool" "$status" "$VERSION" >> "$rec"
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
main() {
|
|
194
|
+
parse_args "$@"
|
|
195
|
+
mkdir -p "$DIST_DIR"
|
|
196
|
+
: > "${DIST_DIR}/build.log"
|
|
197
|
+
|
|
198
|
+
local rc=0
|
|
199
|
+
if [[ "$BUILD_ALL" -eq 1 ]]; then
|
|
200
|
+
for p in "${PLATFORMS[@]}"; do
|
|
201
|
+
build_one "$p" || rc=$?
|
|
202
|
+
done
|
|
203
|
+
else
|
|
204
|
+
build_one "$TARGET_PLATFORM" || rc=$?
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
if [[ $rc -ne 0 ]]; then
|
|
208
|
+
echo "error: at least one build target failed (see ${DIST_DIR}/build.log)" >&2
|
|
209
|
+
fi
|
|
210
|
+
exit $rc
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
main "$@"
|