kitfly 0.1.2
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/CHANGELOG.md +60 -0
- package/LICENSE +21 -0
- package/README.md +136 -0
- package/VERSION +1 -0
- package/package.json +63 -0
- package/schemas/README.md +32 -0
- package/schemas/site.schema.json +5 -0
- package/schemas/theme.schema.json +5 -0
- package/schemas/v0/site.schema.json +172 -0
- package/schemas/v0/theme.schema.json +210 -0
- package/scripts/build-all.ts +121 -0
- package/scripts/build.ts +601 -0
- package/scripts/bundle.ts +781 -0
- package/scripts/dev.ts +777 -0
- package/scripts/generate-checksums.sh +78 -0
- package/scripts/release/export-release-key.sh +28 -0
- package/scripts/release/release-guard-tag-version.sh +79 -0
- package/scripts/release/sign-release-assets.sh +123 -0
- package/scripts/release/upload-release-assets.sh +76 -0
- package/scripts/release/upload-release-provenance.sh +52 -0
- package/scripts/release/verify-public-key.sh +48 -0
- package/scripts/release/verify-signatures.sh +117 -0
- package/scripts/version-sync.ts +82 -0
- package/src/__tests__/build.test.ts +240 -0
- package/src/__tests__/bundle.test.ts +786 -0
- package/src/__tests__/cli.test.ts +706 -0
- package/src/__tests__/crucible.test.ts +1043 -0
- package/src/__tests__/engine.test.ts +157 -0
- package/src/__tests__/init.test.ts +450 -0
- package/src/__tests__/pipeline.test.ts +1087 -0
- package/src/__tests__/productbook.test.ts +1206 -0
- package/src/__tests__/runbook.test.ts +974 -0
- package/src/__tests__/server-registry.test.ts +1251 -0
- package/src/__tests__/servicebook.test.ts +1248 -0
- package/src/__tests__/shared.test.ts +2005 -0
- package/src/__tests__/styles.test.ts +14 -0
- package/src/__tests__/theme-schema.test.ts +47 -0
- package/src/__tests__/theme.test.ts +554 -0
- package/src/cli.ts +582 -0
- package/src/commands/init.ts +92 -0
- package/src/commands/update.ts +444 -0
- package/src/engine.ts +20 -0
- package/src/logger.ts +15 -0
- package/src/migrations/0000_schema_versioning.ts +67 -0
- package/src/migrations/0001_server_port.ts +52 -0
- package/src/migrations/0002_brand_logo.ts +49 -0
- package/src/migrations/index.ts +26 -0
- package/src/migrations/schema.ts +24 -0
- package/src/server-registry.ts +405 -0
- package/src/shared.ts +1239 -0
- package/src/site/styles.css +931 -0
- package/src/site/template.html +193 -0
- package/src/templates/crucible.ts +1163 -0
- package/src/templates/driver.ts +876 -0
- package/src/templates/handbook.ts +339 -0
- package/src/templates/minimal.ts +139 -0
- package/src/templates/pipeline.ts +966 -0
- package/src/templates/productbook.ts +1032 -0
- package/src/templates/runbook.ts +829 -0
- package/src/templates/schema.ts +119 -0
- package/src/templates/servicebook.ts +1242 -0
- package/src/theme.ts +245 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
# Generate SHA256SUMS and SHA512SUMS for release artifacts
|
|
6
|
+
#
|
|
7
|
+
# Usage: generate-checksums.sh [dir] <binary_name>
|
|
8
|
+
#
|
|
9
|
+
# Checksums:
|
|
10
|
+
# - npm tarball (if present)
|
|
11
|
+
# - source archives (if present)
|
|
12
|
+
# - platform binaries (bun build --compile output)
|
|
13
|
+
|
|
14
|
+
DIR=${1:-dist/release}
|
|
15
|
+
BINARY_NAME=${2:-}
|
|
16
|
+
|
|
17
|
+
if [ -z "${BINARY_NAME}" ]; then
|
|
18
|
+
echo "usage: $0 [dir] <binary_name>" >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
if [ ! -d "${DIR}" ]; then
|
|
23
|
+
echo "error: directory ${DIR} not found" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
cd "${DIR}"
|
|
28
|
+
|
|
29
|
+
rm -f SHA256SUMS SHA256SUMS.* SHA512SUMS SHA512SUMS.*
|
|
30
|
+
|
|
31
|
+
# Find release artifacts
|
|
32
|
+
artifacts=()
|
|
33
|
+
|
|
34
|
+
# npm tarball pattern
|
|
35
|
+
for f in "${BINARY_NAME}-"*.tgz; do
|
|
36
|
+
[ -f "$f" ] && artifacts+=("$f")
|
|
37
|
+
done
|
|
38
|
+
|
|
39
|
+
# Source archive patterns
|
|
40
|
+
for f in "${BINARY_NAME}-"*.tar.gz "${BINARY_NAME}-"*.zip; do
|
|
41
|
+
[ -f "$f" ] && artifacts+=("$f")
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
# Platform binary patterns (bun build --compile output)
|
|
45
|
+
for f in "${BINARY_NAME}-"*; do
|
|
46
|
+
# Skip archives already handled above
|
|
47
|
+
case "$f" in *.tgz|*.tar.gz|*.zip) continue ;; esac
|
|
48
|
+
# Skip checksum/signature files
|
|
49
|
+
case "$f" in SHA*|*.sig|*.minisig|*.asc) continue ;; esac
|
|
50
|
+
[ -f "$f" ] && artifacts+=("$f")
|
|
51
|
+
done
|
|
52
|
+
|
|
53
|
+
if [ ${#artifacts[@]} -eq 0 ]; then
|
|
54
|
+
echo "error: no artifacts found matching ${BINARY_NAME}-* in ${DIR}" >&2
|
|
55
|
+
echo "Expected: npm tarball (*.tgz), source archive (*.tar.gz, *.zip), or platform bundles" >&2
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
echo "Generating checksums for ${#artifacts[@]} artifact(s)..."
|
|
60
|
+
|
|
61
|
+
if command -v sha256sum > /dev/null 2>&1; then
|
|
62
|
+
sha256sum "${artifacts[@]}" > SHA256SUMS
|
|
63
|
+
else
|
|
64
|
+
shasum -a 256 "${artifacts[@]}" > SHA256SUMS
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
if command -v sha512sum > /dev/null 2>&1; then
|
|
68
|
+
sha512sum "${artifacts[@]}" > SHA512SUMS
|
|
69
|
+
else
|
|
70
|
+
shasum -a 512 "${artifacts[@]}" > SHA512SUMS
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
echo "Wrote SHA256SUMS and SHA512SUMS"
|
|
74
|
+
echo ""
|
|
75
|
+
echo "Artifacts checksummed:"
|
|
76
|
+
for f in "${artifacts[@]}"; do
|
|
77
|
+
echo " - $f"
|
|
78
|
+
done
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Export PGP public key for release signing verification
|
|
5
|
+
#
|
|
6
|
+
# Usage: export-release-key.sh <key-id> [dest_dir]
|
|
7
|
+
#
|
|
8
|
+
# Environment variables:
|
|
9
|
+
# KITFLY_GPG_HOMEDIR - Custom GPG homedir (optional, defaults to ~/.gnupg)
|
|
10
|
+
|
|
11
|
+
KEY_ID=${1:?"usage: export-release-key.sh <key-id> [dest_dir]"}
|
|
12
|
+
DIR=${2:-dist/release}
|
|
13
|
+
KITFLY_GPG_HOMEDIR=${KITFLY_GPG_HOMEDIR:-}
|
|
14
|
+
|
|
15
|
+
if ! command -v gpg >/dev/null 2>&1; then
|
|
16
|
+
echo "gpg is required" >&2
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
mkdir -p "$DIR"
|
|
20
|
+
OUTPUT="$DIR/kitfly-release-signing-key.asc"
|
|
21
|
+
|
|
22
|
+
if [ -n "$KITFLY_GPG_HOMEDIR" ]; then
|
|
23
|
+
env GNUPGHOME="$KITFLY_GPG_HOMEDIR" gpg --armor --export "$KEY_ID" >"$OUTPUT"
|
|
24
|
+
else
|
|
25
|
+
gpg --armor --export "$KEY_ID" >"$OUTPUT"
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
echo "[ok] Exported $KEY_ID to $OUTPUT"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Release guard: ensure git tag matches VERSION file
|
|
5
|
+
#
|
|
6
|
+
# Environment variables:
|
|
7
|
+
# KITFLY_RELEASE_TAG - Override tag to check (e.g., v0.1.2)
|
|
8
|
+
# KITFLY_REQUIRE_TAG - Set to 1 to enforce tag presence in CI
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# ./scripts/release/release-guard-tag-version.sh
|
|
12
|
+
# KITFLY_RELEASE_TAG=v0.1.2 ./scripts/release/release-guard-tag-version.sh
|
|
13
|
+
# KITFLY_REQUIRE_TAG=1 ./scripts/release/release-guard-tag-version.sh
|
|
14
|
+
|
|
15
|
+
repo_root() {
|
|
16
|
+
git rev-parse --show-toplevel
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
read_version() {
|
|
20
|
+
if [ ! -f VERSION ]; then
|
|
21
|
+
echo "error: VERSION file not found" >&2
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
tr -d ' \t\r\n' <VERSION
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
normalize_tag() {
|
|
28
|
+
local raw="${1:-}"
|
|
29
|
+
if [ -z "$raw" ]; then
|
|
30
|
+
printf '%s' ""
|
|
31
|
+
return 0
|
|
32
|
+
fi
|
|
33
|
+
if [[ "$raw" == v* ]]; then
|
|
34
|
+
printf '%s' "$raw"
|
|
35
|
+
else
|
|
36
|
+
printf 'v%s' "$raw"
|
|
37
|
+
fi
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
detect_tag() {
|
|
41
|
+
if [ -n "${KITFLY_RELEASE_TAG:-}" ]; then
|
|
42
|
+
normalize_tag "${KITFLY_RELEASE_TAG}"
|
|
43
|
+
return 0
|
|
44
|
+
fi
|
|
45
|
+
git describe --tags --exact-match 2>/dev/null || true
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
main() {
|
|
49
|
+
local root
|
|
50
|
+
root="$(repo_root)"
|
|
51
|
+
cd "$root"
|
|
52
|
+
|
|
53
|
+
local version
|
|
54
|
+
version="$(read_version)"
|
|
55
|
+
|
|
56
|
+
local expected="v${version}"
|
|
57
|
+
local tag
|
|
58
|
+
tag="$(detect_tag)"
|
|
59
|
+
|
|
60
|
+
if [ -z "$tag" ]; then
|
|
61
|
+
if [ "${KITFLY_REQUIRE_TAG:-}" = "1" ]; then
|
|
62
|
+
echo "error: no exact tag found for HEAD and no KITFLY_RELEASE_TAG provided" >&2
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
echo "→ release guard: no tag detected (set KITFLY_REQUIRE_TAG=1 to enforce in CI)"
|
|
66
|
+
exit 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
if [ "$tag" != "$expected" ]; then
|
|
70
|
+
echo "error: release tag/version mismatch" >&2
|
|
71
|
+
echo " tag: $tag" >&2
|
|
72
|
+
echo " VERSION: $version (expected tag: $expected)" >&2
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
echo "✅ release guard: tag matches VERSION ($tag)"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
main "$@"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Dual-format release signing: minisign (.minisig) + PGP (.asc)
|
|
5
|
+
#
|
|
6
|
+
# Usage: sign-release-assets.sh <tag> [dir]
|
|
7
|
+
#
|
|
8
|
+
# Environment variables:
|
|
9
|
+
# KITFLY_MINISIGN_KEY - Path to minisign secret key file. Primary format.
|
|
10
|
+
# KITFLY_PGP_KEY_ID - GPG key ID for PGP signing. Optional secondary format.
|
|
11
|
+
# KITFLY_GPG_HOMEDIR - Custom GPG homedir (optional, defaults to ~/.gnupg)
|
|
12
|
+
#
|
|
13
|
+
# Minisign was chosen over raw ed25519 because:
|
|
14
|
+
# - Created by Frank Denis (libsodium author), well-audited
|
|
15
|
+
# - Trusted comments provide signed metadata (version, timestamp)
|
|
16
|
+
# - Password-protected keys by default
|
|
17
|
+
# - Compatible with OpenBSD signify
|
|
18
|
+
#
|
|
19
|
+
# Only SHA256SUMS is signed (not individual files). This is the standard pattern:
|
|
20
|
+
# verify signature on checksum file, then verify file checksums against that.
|
|
21
|
+
# This means one password prompt instead of N.
|
|
22
|
+
|
|
23
|
+
TAG=${1:?"usage: sign-release-assets.sh <tag> [dir]"}
|
|
24
|
+
DIR=${2:-dist/release}
|
|
25
|
+
|
|
26
|
+
KITFLY_MINISIGN_KEY=${KITFLY_MINISIGN_KEY:-}
|
|
27
|
+
KITFLY_PGP_KEY_ID=${KITFLY_PGP_KEY_ID:-}
|
|
28
|
+
KITFLY_GPG_HOMEDIR=${KITFLY_GPG_HOMEDIR:-}
|
|
29
|
+
|
|
30
|
+
# Validation
|
|
31
|
+
if [ ! -d "$DIR" ]; then
|
|
32
|
+
echo "error: directory $DIR not found" >&2
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
checksum_files=()
|
|
37
|
+
for file in SHA256SUMS SHA512SUMS; do
|
|
38
|
+
if [ -f "$DIR/$file" ]; then
|
|
39
|
+
checksum_files+=("$file")
|
|
40
|
+
fi
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
if [ ${#checksum_files[@]} -eq 0 ]; then
|
|
44
|
+
echo "error: no checksum files found (run make release-checksums first)" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
has_minisign=false
|
|
49
|
+
has_pgp=false
|
|
50
|
+
|
|
51
|
+
if [ -n "$KITFLY_MINISIGN_KEY" ]; then
|
|
52
|
+
if [ ! -f "$KITFLY_MINISIGN_KEY" ]; then
|
|
53
|
+
echo "error: KITFLY_MINISIGN_KEY=$KITFLY_MINISIGN_KEY not found" >&2
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
if ! command -v minisign >/dev/null 2>&1; then
|
|
57
|
+
echo "error: minisign not found in PATH" >&2
|
|
58
|
+
echo " Install: brew install minisign (macOS) or see https://jedisct1.github.io/minisign/" >&2
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
has_minisign=true
|
|
62
|
+
echo "minisign signing enabled (key: $KITFLY_MINISIGN_KEY)"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Only enable PGP if explicitly requested via KITFLY_PGP_KEY_ID
|
|
66
|
+
if [ -n "$KITFLY_PGP_KEY_ID" ]; then
|
|
67
|
+
if ! command -v gpg >/dev/null 2>&1; then
|
|
68
|
+
echo "error: KITFLY_PGP_KEY_ID set but gpg not found in PATH" >&2
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
has_pgp=true
|
|
72
|
+
echo "PGP signing enabled (key: $KITFLY_PGP_KEY_ID)"
|
|
73
|
+
if [ -n "$KITFLY_GPG_HOMEDIR" ]; then
|
|
74
|
+
echo "GPG homedir: $KITFLY_GPG_HOMEDIR"
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
if [ "$has_minisign" = false ] && [ "$has_pgp" = false ]; then
|
|
79
|
+
echo "error: no signing method available" >&2
|
|
80
|
+
echo " Set KITFLY_MINISIGN_KEY for minisign signing" >&2
|
|
81
|
+
echo " Set KITFLY_PGP_KEY_ID for PGP signing" >&2
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Sign checksum manifests (preferred workflow)
|
|
86
|
+
# Users verify: 1) signature on checksum file, 2) file checksums against it
|
|
87
|
+
#
|
|
88
|
+
# Signing is grouped by tool (all minisign first, then all PGP) to minimize
|
|
89
|
+
# password prompt switching during manual signing workflows.
|
|
90
|
+
|
|
91
|
+
if [ "$has_minisign" = true ]; then
|
|
92
|
+
echo ""
|
|
93
|
+
echo "=== Minisign signatures ==="
|
|
94
|
+
for file in "${checksum_files[@]}"; do
|
|
95
|
+
echo "[minisign] Signing $file"
|
|
96
|
+
rm -f "$DIR/$file.minisig"
|
|
97
|
+
minisign -S -s "$KITFLY_MINISIGN_KEY" -t "kitfly $TAG $(date -u +%Y-%m-%dT%H:%M:%SZ)" -m "$DIR/$file"
|
|
98
|
+
done
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
if [ "$has_pgp" = true ]; then
|
|
102
|
+
echo ""
|
|
103
|
+
echo "=== PGP signatures ==="
|
|
104
|
+
for file in "${checksum_files[@]}"; do
|
|
105
|
+
echo "[PGP] Signing $file"
|
|
106
|
+
if [ -n "$KITFLY_GPG_HOMEDIR" ]; then
|
|
107
|
+
env GNUPGHOME="$KITFLY_GPG_HOMEDIR" gpg --batch --yes --armor --local-user "$KITFLY_PGP_KEY_ID" --detach-sign -o "$DIR/$file.asc" "$DIR/$file"
|
|
108
|
+
else
|
|
109
|
+
gpg --batch --yes --armor --local-user "$KITFLY_PGP_KEY_ID" --detach-sign -o "$DIR/$file.asc" "$DIR/$file"
|
|
110
|
+
fi
|
|
111
|
+
done
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
echo ""
|
|
115
|
+
echo "[ok] Signing complete for $TAG"
|
|
116
|
+
for file in "${checksum_files[@]}"; do
|
|
117
|
+
if [ "$has_minisign" = true ]; then
|
|
118
|
+
echo " $file.minisig"
|
|
119
|
+
fi
|
|
120
|
+
if [ "$has_pgp" = true ]; then
|
|
121
|
+
echo " $file.asc"
|
|
122
|
+
fi
|
|
123
|
+
done
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Upload ALL release assets (binaries, tarballs, and provenance) to GitHub.
|
|
5
|
+
#
|
|
6
|
+
# CAUTION: Only use when a release was built locally and all assets need
|
|
7
|
+
# transfer. Each file is a separate API call — uploading many assets in
|
|
8
|
+
# rapid succession can trigger GitHub's secondary rate limits (HTTP 429).
|
|
9
|
+
#
|
|
10
|
+
# For the normal workflow use upload-release-provenance.sh instead — CI
|
|
11
|
+
# uploads binaries and tarballs, provenance-only upload keeps API calls minimal.
|
|
12
|
+
#
|
|
13
|
+
# Usage: upload-release-assets.sh <tag> [dir]
|
|
14
|
+
|
|
15
|
+
TAG=${1:?"usage: upload-release-assets.sh <tag> [dir]"}
|
|
16
|
+
DIR=${2:-dist/release}
|
|
17
|
+
|
|
18
|
+
if ! command -v gh >/dev/null 2>&1; then
|
|
19
|
+
echo "gh CLI is required" >&2
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
if [ ! -d "$DIR" ]; then
|
|
24
|
+
echo "directory $DIR not found" >&2
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Collect all release assets into a single upload
|
|
29
|
+
shopt -s nullglob
|
|
30
|
+
|
|
31
|
+
assets=()
|
|
32
|
+
|
|
33
|
+
# Platform binaries (kitfly-linux-amd64, kitfly-windows-amd64.exe, etc.)
|
|
34
|
+
for f in "$DIR"/kitfly-*; do
|
|
35
|
+
# Skip npm tarballs, checksums, signatures, keys — handled below
|
|
36
|
+
case "$f" in *.tgz|SHA*|*.sig|*.minisig|*.asc|*.pub|*.md) continue ;; esac
|
|
37
|
+
[ -f "$f" ] && assets+=("$f")
|
|
38
|
+
done
|
|
39
|
+
|
|
40
|
+
# Tarballs and checksums
|
|
41
|
+
assets+=("$DIR"/*.tgz)
|
|
42
|
+
assets+=("$DIR"/SHA256SUMS "$DIR"/SHA512SUMS)
|
|
43
|
+
|
|
44
|
+
# Signatures
|
|
45
|
+
assets+=("$DIR"/SHA256SUMS.* "$DIR"/SHA512SUMS.*)
|
|
46
|
+
|
|
47
|
+
# Public keys
|
|
48
|
+
assets+=("$DIR"/*-minisign.pub "$DIR"/*-signing-key.asc)
|
|
49
|
+
|
|
50
|
+
# Release notes
|
|
51
|
+
assets+=("$DIR"/release-notes-*.md)
|
|
52
|
+
|
|
53
|
+
# Filter to existing files
|
|
54
|
+
final_assets=()
|
|
55
|
+
for f in "${assets[@]}"; do
|
|
56
|
+
if [ -f "$f" ]; then
|
|
57
|
+
final_assets+=("$f")
|
|
58
|
+
fi
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
if [ ${#final_assets[@]} -eq 0 ]; then
|
|
62
|
+
echo "no assets found to upload from $DIR" >&2
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
echo "[..] Uploading ${#final_assets[@]} asset(s) to ${TAG} (clobber)"
|
|
67
|
+
gh release upload "$TAG" "${final_assets[@]}" --clobber
|
|
68
|
+
|
|
69
|
+
# Update release body with notes if present
|
|
70
|
+
NOTES_FILE="$DIR/release-notes-${TAG}.md"
|
|
71
|
+
if [ -f "$NOTES_FILE" ]; then
|
|
72
|
+
echo "[..] Updating release body from $NOTES_FILE"
|
|
73
|
+
gh release edit "$TAG" --notes-file "$NOTES_FILE"
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
echo "[ok] Release assets uploaded for $TAG"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Upload only provenance assets (checksums, signatures, keys, notes) to a
|
|
5
|
+
# GitHub release. Does NOT upload binaries or tarballs — CI handles those.
|
|
6
|
+
#
|
|
7
|
+
# Usage: upload-release-provenance.sh <tag> [dir]
|
|
8
|
+
|
|
9
|
+
TAG="${1:?"usage: upload-release-provenance.sh <tag> [dir]"}"
|
|
10
|
+
DIR="${2:-dist/release}"
|
|
11
|
+
|
|
12
|
+
if ! command -v gh >/dev/null 2>&1; then
|
|
13
|
+
echo "gh CLI is required" >&2
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
if [ ! -d "$DIR" ]; then
|
|
18
|
+
echo "directory $DIR not found" >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
shopt -s nullglob
|
|
23
|
+
|
|
24
|
+
assets=()
|
|
25
|
+
assets+=("$DIR"/SHA256SUMS "$DIR"/SHA512SUMS)
|
|
26
|
+
assets+=("$DIR"/SHA256SUMS.* "$DIR"/SHA512SUMS.*)
|
|
27
|
+
assets+=("$DIR"/*-minisign.pub "$DIR"/*-signing-key.asc)
|
|
28
|
+
assets+=("$DIR"/release-notes-*.md)
|
|
29
|
+
|
|
30
|
+
final_assets=()
|
|
31
|
+
for f in "${assets[@]}"; do
|
|
32
|
+
if [ -f "$f" ]; then
|
|
33
|
+
final_assets+=("$f")
|
|
34
|
+
fi
|
|
35
|
+
done
|
|
36
|
+
|
|
37
|
+
if [ ${#final_assets[@]} -eq 0 ]; then
|
|
38
|
+
echo "no provenance assets found to upload from $DIR" >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
echo "[..] Uploading ${#final_assets[@]} provenance asset(s) to ${TAG} (clobber)"
|
|
43
|
+
gh release upload "$TAG" "${final_assets[@]}" --clobber
|
|
44
|
+
|
|
45
|
+
# Update release body with notes if present
|
|
46
|
+
NOTES_FILE="$DIR/release-notes-${TAG}.md"
|
|
47
|
+
if [ -f "$NOTES_FILE" ]; then
|
|
48
|
+
echo "[..] Updating release body from $NOTES_FILE"
|
|
49
|
+
gh release edit "$TAG" --notes-file "$NOTES_FILE"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
echo "[ok] Provenance assets uploaded for $TAG"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Verify that an exported GPG key file contains only public key material
|
|
5
|
+
#
|
|
6
|
+
# Usage: verify-public-key.sh <keyfile>
|
|
7
|
+
#
|
|
8
|
+
# This is a safety check before uploading keys to ensure we never
|
|
9
|
+
# accidentally publish secret key material.
|
|
10
|
+
|
|
11
|
+
KEY_FILE=${1:?"usage: verify-public-key.sh <keyfile>"}
|
|
12
|
+
|
|
13
|
+
if [ ! -f "$KEY_FILE" ]; then
|
|
14
|
+
echo "error: file $KEY_FILE not found" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
if ! command -v gpg >/dev/null 2>&1; then
|
|
19
|
+
echo "gpg is required" >&2
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Check if file contains "BEGIN PGP PRIVATE KEY" or similar
|
|
24
|
+
if grep -q "PRIVATE KEY" "$KEY_FILE"; then
|
|
25
|
+
echo "[!!] DANGER: $KEY_FILE appears to contain private key material!" >&2
|
|
26
|
+
echo "[!!] DO NOT upload this file!" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Verify it's a valid public key
|
|
31
|
+
if ! gpg --show-keys "$KEY_FILE" >/dev/null 2>&1; then
|
|
32
|
+
echo "[!!] $KEY_FILE is not a valid GPG key file" >&2
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Check that gpg reports it as public only
|
|
37
|
+
KEY_INFO=$(gpg --show-keys --with-colons "$KEY_FILE" 2>/dev/null)
|
|
38
|
+
if echo "$KEY_INFO" | grep -q "^sec:"; then
|
|
39
|
+
echo "[!!] DANGER: $KEY_FILE contains secret key material!" >&2
|
|
40
|
+
exit 1
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if ! echo "$KEY_INFO" | grep -q "^pub:"; then
|
|
44
|
+
echo "[!!] $KEY_FILE does not contain a public key" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
echo "[ok] $KEY_FILE is a valid public-only GPG key"
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Verify release signatures (minisign and optional PGP) on checksum manifests.
|
|
5
|
+
#
|
|
6
|
+
# Usage: verify-signatures.sh [dir]
|
|
7
|
+
#
|
|
8
|
+
# Environment variables:
|
|
9
|
+
# KITFLY_MINISIGN_PUB - path to minisign public key (required for minisign)
|
|
10
|
+
# KITFLY_GPG_HOMEDIR - isolated gpg homedir for PGP verification (optional)
|
|
11
|
+
|
|
12
|
+
DIR=${1:-dist/release}
|
|
13
|
+
|
|
14
|
+
if [ ! -d "$DIR" ]; then
|
|
15
|
+
echo "error: directory $DIR not found" >&2
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
KITFLY_MINISIGN_PUB=${KITFLY_MINISIGN_PUB:-}
|
|
20
|
+
KITFLY_GPG_HOMEDIR=${KITFLY_GPG_HOMEDIR:-}
|
|
21
|
+
|
|
22
|
+
verified=0
|
|
23
|
+
failed=0
|
|
24
|
+
|
|
25
|
+
verify_minisign() {
|
|
26
|
+
local manifest="$1"
|
|
27
|
+
local base="${DIR}/${manifest}"
|
|
28
|
+
local sig="${base}.minisig"
|
|
29
|
+
|
|
30
|
+
if [ ! -f "${sig}" ]; then
|
|
31
|
+
echo "[--] No minisign signature for ${manifest} (skipping)"
|
|
32
|
+
return 0
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
if [ -z "${KITFLY_MINISIGN_PUB}" ]; then
|
|
36
|
+
echo "[!!] KITFLY_MINISIGN_PUB not set, cannot verify ${manifest}.minisig"
|
|
37
|
+
failed=$((failed + 1))
|
|
38
|
+
return 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
if [ ! -f "${KITFLY_MINISIGN_PUB}" ]; then
|
|
42
|
+
echo "error: KITFLY_MINISIGN_PUB=${KITFLY_MINISIGN_PUB} not found" >&2
|
|
43
|
+
failed=$((failed + 1))
|
|
44
|
+
return 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
if ! command -v minisign >/dev/null 2>&1; then
|
|
48
|
+
echo "error: minisign not found in PATH" >&2
|
|
49
|
+
failed=$((failed + 1))
|
|
50
|
+
return 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
echo "[..] [minisign] Verifying ${manifest}"
|
|
54
|
+
if minisign -V -p "${KITFLY_MINISIGN_PUB}" -m "${base}"; then
|
|
55
|
+
echo "[ok] ${manifest}.minisig verified"
|
|
56
|
+
verified=$((verified + 1))
|
|
57
|
+
else
|
|
58
|
+
echo "[!!] ${manifest}.minisig verification FAILED"
|
|
59
|
+
failed=$((failed + 1))
|
|
60
|
+
fi
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
verify_pgp() {
|
|
64
|
+
local manifest="$1"
|
|
65
|
+
local base="${DIR}/${manifest}"
|
|
66
|
+
local sig="${base}.asc"
|
|
67
|
+
|
|
68
|
+
if [ ! -f "${sig}" ]; then
|
|
69
|
+
echo "[--] No PGP signature for ${manifest} (skipping)"
|
|
70
|
+
return 0
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
if ! command -v gpg >/dev/null 2>&1; then
|
|
74
|
+
echo "[!!] gpg not found, cannot verify ${manifest}.asc"
|
|
75
|
+
failed=$((failed + 1))
|
|
76
|
+
return 1
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
echo "[..] [PGP] Verifying ${manifest}"
|
|
80
|
+
local rc=0
|
|
81
|
+
if [ -n "${KITFLY_GPG_HOMEDIR}" ] && [ -d "${KITFLY_GPG_HOMEDIR}" ]; then
|
|
82
|
+
env GNUPGHOME="${KITFLY_GPG_HOMEDIR}" gpg --verify "${sig}" "${base}" 2>&1 || rc=$?
|
|
83
|
+
else
|
|
84
|
+
gpg --verify "${sig}" "${base}" 2>&1 || rc=$?
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
if [ $rc -eq 0 ]; then
|
|
88
|
+
echo "[ok] ${manifest}.asc verified"
|
|
89
|
+
verified=$((verified + 1))
|
|
90
|
+
else
|
|
91
|
+
echo "[!!] ${manifest}.asc verification FAILED"
|
|
92
|
+
failed=$((failed + 1))
|
|
93
|
+
fi
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
echo "Verifying release signatures in ${DIR}..."
|
|
97
|
+
echo ""
|
|
98
|
+
|
|
99
|
+
verify_minisign "SHA256SUMS"
|
|
100
|
+
verify_minisign "SHA512SUMS"
|
|
101
|
+
|
|
102
|
+
echo ""
|
|
103
|
+
|
|
104
|
+
verify_pgp "SHA256SUMS"
|
|
105
|
+
verify_pgp "SHA512SUMS"
|
|
106
|
+
|
|
107
|
+
echo ""
|
|
108
|
+
echo "----------------------------------------------------------------------"
|
|
109
|
+
if [ $failed -gt 0 ]; then
|
|
110
|
+
echo "[!!] Signature verification: ${verified} passed, ${failed} FAILED"
|
|
111
|
+
exit 1
|
|
112
|
+
elif [ $verified -eq 0 ]; then
|
|
113
|
+
echo "[!!] No signatures found to verify"
|
|
114
|
+
exit 1
|
|
115
|
+
else
|
|
116
|
+
echo "[ok] Signature verification: ${verified} passed"
|
|
117
|
+
fi
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* version-sync.ts - Sync VERSION file to package.json
|
|
4
|
+
*
|
|
5
|
+
* VERSION file is the Single Source of Truth (SSOT) for kitfly versioning.
|
|
6
|
+
* This script propagates the version to package.json.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* bun run scripts/version-sync.ts # Sync and display result
|
|
10
|
+
* bun run scripts/version-sync.ts --check # Verify they match (CI)
|
|
11
|
+
* bun run scripts/version-sync.ts --set X.Y.Z # Set VERSION and sync
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
|
|
17
|
+
const ROOT = join(import.meta.dir, "..");
|
|
18
|
+
const VERSION_PATH = join(ROOT, "VERSION");
|
|
19
|
+
const PACKAGE_PATH = join(ROOT, "package.json");
|
|
20
|
+
|
|
21
|
+
function getVersionFromFile(): string {
|
|
22
|
+
return readFileSync(VERSION_PATH, "utf-8").trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getPackageVersion(): string {
|
|
26
|
+
const pkg = JSON.parse(readFileSync(PACKAGE_PATH, "utf-8"));
|
|
27
|
+
return pkg.version;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function setVersionFile(version: string): void {
|
|
31
|
+
writeFileSync(VERSION_PATH, `${version}\n`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function syncPackageJson(version: string): void {
|
|
35
|
+
const pkg = JSON.parse(readFileSync(PACKAGE_PATH, "utf-8"));
|
|
36
|
+
pkg.version = version;
|
|
37
|
+
writeFileSync(PACKAGE_PATH, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function main() {
|
|
41
|
+
const args = process.argv.slice(2);
|
|
42
|
+
|
|
43
|
+
// --check: Verify versions match (for CI)
|
|
44
|
+
if (args.includes("--check")) {
|
|
45
|
+
const fileVersion = getVersionFromFile();
|
|
46
|
+
const pkgVersion = getPackageVersion();
|
|
47
|
+
if (fileVersion !== pkgVersion) {
|
|
48
|
+
console.error(`Version mismatch: VERSION=${fileVersion}, package.json=${pkgVersion}`);
|
|
49
|
+
console.error("Run: bun run scripts/version-sync.ts");
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
console.log(`Versions match: ${fileVersion}`);
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// --set X.Y.Z: Set VERSION file and sync
|
|
57
|
+
const setIndex = args.indexOf("--set");
|
|
58
|
+
if (setIndex !== -1) {
|
|
59
|
+
const newVersion = args[setIndex + 1];
|
|
60
|
+
if (!newVersion || !/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(newVersion)) {
|
|
61
|
+
console.error("Usage: bun run scripts/version-sync.ts --set X.Y.Z");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
setVersionFile(newVersion);
|
|
65
|
+
syncPackageJson(newVersion);
|
|
66
|
+
console.log(`Version set to ${newVersion}`);
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Default: Sync VERSION → package.json
|
|
71
|
+
const version = getVersionFromFile();
|
|
72
|
+
const pkgVersion = getPackageVersion();
|
|
73
|
+
|
|
74
|
+
if (version === pkgVersion) {
|
|
75
|
+
console.log(`Already in sync: ${version}`);
|
|
76
|
+
} else {
|
|
77
|
+
syncPackageJson(version);
|
|
78
|
+
console.log(`Synced package.json: ${pkgVersion} → ${version}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
main();
|