Skip to content

Commit 70a7e33

Browse files
committed
fix(release): add missing updater release scripts
Forgot to include release/test bundle scripts in the previous commit.
1 parent 1cc9dde commit 70a7e33

4 files changed

Lines changed: 501 additions & 0 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
usage() {
5+
cat <<EOF
6+
Usage (artifact bundle only):
7+
$0 --tag vX.Y.Z --workdir ./release_work --labels A,B \\
8+
--sig-a /path/to/manifest.json.A.asc --sig-b /path/to/manifest.json.B.asc \\
9+
--artifact-dir /path/to/builder_bundle_dir
10+
11+
Outputs:
12+
<workdir>/<tag>/bundle/
13+
<workdir>/<tag>/out/secluso-<tag>.zip
14+
EOF
15+
exit 1
16+
}
17+
18+
TAG=""
19+
WORKDIR=""
20+
LABELS=""
21+
SIG_A=""
22+
SIG_B=""
23+
ARTIFACT_DIR=""
24+
25+
while [[ $# -gt 0 ]]; do
26+
case "$1" in
27+
--tag) TAG="$2"; shift 2;;
28+
--workdir) WORKDIR="$2"; shift 2;;
29+
--labels) LABELS="$2"; shift 2;;
30+
--sig-a) SIG_A="$2"; shift 2;;
31+
--sig-b) SIG_B="$2"; shift 2;;
32+
--artifact-dir) ARTIFACT_DIR="$2"; shift 2;;
33+
-h|--help) usage;;
34+
*) echo "Unknown arg: $1" >&2; usage;;
35+
esac
36+
done
37+
38+
[[ -n "$TAG" && -n "$WORKDIR" && -n "$LABELS" && -n "$SIG_A" && -n "$SIG_B" && -n "$ARTIFACT_DIR" ]] || usage
39+
40+
IFS=',' read -r LABEL_A LABEL_B <<<"$LABELS"
41+
[[ -n "${LABEL_A:-}" && -n "${LABEL_B:-}" ]] || { echo "Need --labels A,B" >&2; exit 1; }
42+
43+
REL_DIR="$WORKDIR/$TAG"
44+
MANIFEST="$REL_DIR/manifest.json"
45+
46+
[[ -f "$MANIFEST" ]] || { echo "Missing manifest: $MANIFEST" >&2; exit 1; }
47+
[[ -f "$SIG_A" ]] || { echo "Missing sig-a: $SIG_A" >&2; exit 1; }
48+
[[ -f "$SIG_B" ]] || { echo "Missing sig-b: $SIG_B" >&2; exit 1; }
49+
[[ -d "$ARTIFACT_DIR" ]] || { echo "Missing --artifact-dir directory: $ARTIFACT_DIR" >&2; exit 1; }
50+
[[ -f "$ARTIFACT_DIR/manifest.json" ]] || { echo "--artifact-dir must contain manifest.json" >&2; exit 1; }
51+
52+
hash_file() {
53+
local f="$1"
54+
if command -v sha256sum >/dev/null 2>&1; then
55+
sha256sum "$f" | awk '{print $1}'
56+
else
57+
shasum -a 256 "$f" | awk '{print $1}'
58+
fi
59+
}
60+
61+
echo "Verifying signatures locally..."
62+
gpg --verify "$SIG_A" "$MANIFEST" >/dev/null
63+
gpg --verify "$SIG_B" "$MANIFEST" >/dev/null
64+
echo "OK: both signatures verify"
65+
66+
fpr_from_sig() {
67+
local sig="$1"
68+
gpg --status-fd=1 --verify "$sig" "$MANIFEST" 2>/dev/null \
69+
| awk '$1=="[GNUPG:]" && $2=="VALIDSIG" {print $3; exit}'
70+
}
71+
72+
FPR_A="$(fpr_from_sig "$SIG_A")"
73+
FPR_B="$(fpr_from_sig "$SIG_B")"
74+
[[ -n "$FPR_A" && -n "$FPR_B" ]] || { echo "Could not extract signer fingerprints" >&2; exit 1; }
75+
76+
if [[ "$FPR_A" == "$FPR_B" ]]; then
77+
echo "ERROR: both sigs are from the same key fingerprint: $FPR_A" >&2
78+
exit 1
79+
fi
80+
81+
echo "OK: distinct signer fingerprints"
82+
echo " $LABEL_A -> $FPR_A"
83+
echo " $LABEL_B -> $FPR_B"
84+
85+
BUNDLE_DIR="$REL_DIR/bundle"
86+
OUT_DIR="$REL_DIR/out"
87+
rm -rf "$BUNDLE_DIR"
88+
mkdir -p "$BUNDLE_DIR" "$OUT_DIR"
89+
90+
echo "Copying builder artifacts from: $ARTIFACT_DIR"
91+
cp -a "$ARTIFACT_DIR/." "$BUNDLE_DIR/"
92+
93+
# Overwrite manifest.json with the signed one
94+
cp "$MANIFEST" "$BUNDLE_DIR/manifest.json"
95+
96+
# Drop sigs next to manifest with expected names
97+
cp "$SIG_A" "$BUNDLE_DIR/manifest.json.${LABEL_A}.asc"
98+
cp "$SIG_B" "$BUNDLE_DIR/manifest.json.${LABEL_B}.asc"
99+
100+
# Enforce manifest sha256 matches the actual bundle bytes
101+
if command -v jq >/dev/null 2>&1; then
102+
echo "Checking manifest sha256 fields vs bundle contents..."
103+
104+
jq -r '.artifacts[] | @base64' "$BUNDLE_DIR/manifest.json" | while read -r row; do
105+
_jq() { echo "$row" | base64 --decode | jq -r "$1"; }
106+
107+
bin_path="$(_jq '.bin_path')"
108+
sha="$(_jq '.sha256')"
109+
110+
if [[ -z "$bin_path" || "$bin_path" == "null" ]]; then
111+
echo "ERROR: artifact missing bin_path" >&2
112+
exit 1
113+
fi
114+
if [[ -z "$sha" || "$sha" == "null" ]]; then
115+
echo "ERROR: artifact $bin_path missing sha256 field in manifest.json" >&2
116+
exit 1
117+
fi
118+
119+
f="$BUNDLE_DIR/$bin_path"
120+
if [[ ! -f "$f" ]]; then
121+
echo "ERROR: manifest references missing file: $bin_path" >&2
122+
exit 1
123+
fi
124+
125+
got="$(hash_file "$f")"
126+
want="$(echo "$sha" | tr -d ' \t\r\n' | sed 's/^sha256://I' | tr 'A-F' 'a-f')"
127+
128+
if [[ "$got" != "$want" ]]; then
129+
echo "ERROR: sha256 mismatch for $bin_path" >&2
130+
echo " manifest: $want" >&2
131+
echo " actual: $got" >&2
132+
exit 1
133+
fi
134+
done
135+
136+
echo "OK: manifest sha256 matches all artifact files"
137+
else
138+
echo "WARN: jq not found, skipping manifest sha256 verification"
139+
fi
140+
141+
ZIP_PATH="$OUT_DIR/secluso-${TAG}.zip"
142+
rm -f "$ZIP_PATH"
143+
( cd "$BUNDLE_DIR" && zip -qr "$ZIP_PATH" . )
144+
145+
echo
146+
echo "Bundle folder: $BUNDLE_DIR"
147+
echo "Zip asset: $ZIP_PATH"
148+
echo "Upload this zip as the Release asset in GitHub UI."
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
usage() {
5+
cat <<EOF
6+
Usage:
7+
$0 --tag vX.Y.Z --workdir ./release_work --labels L1,L2 [--test-gnupg-home /tmp/secluso_test_gnupg]
8+
9+
Creates a realistic builder-style bundle with dummy binaries:
10+
<workdir>/<tag>/builder_out/manifest.json
11+
<workdir>/<tag>/builder_out/{aarch64-unknown-linux-gnu,x86_64-unknown-linux-gnu}/...
12+
<workdir>/<tag>/manifest.json + manifest.sha256 (manager copy + hash)
13+
<workdir>/<tag>/sigs/ (both .asc)
14+
<workdir>/<tag>/out/secluso-<tag>.zip (to be uploaded as test release)
15+
16+
Reuses the same two test keys across runs by keeping GNUPGHOME stable.
17+
EOF
18+
exit 1
19+
}
20+
21+
TAG=""
22+
WORKDIR=""
23+
LABELS=""
24+
TEST_GNUPG_HOME="/tmp/secluso_test_gnupg"
25+
26+
# Validate args
27+
while [[ $# -gt 0 ]]; do
28+
case "$1" in
29+
--tag) TAG="$2"; shift 2;;
30+
--workdir) WORKDIR="$2"; shift 2;;
31+
--labels) LABELS="$2"; shift 2;;
32+
--test-gnupg-home) TEST_GNUPG_HOME="$2"; shift 2;;
33+
-h|--help) usage;;
34+
*) echo "Unknown arg: $1" >&2; usage;;
35+
esac
36+
done
37+
38+
[[ -n "$TAG" && -n "$WORKDIR" && -n "$LABELS" ]] || usage
39+
IFS=',' read -r L1 L2 <<<"$LABELS"
40+
[[ -n "${L1:-}" && -n "${L2:-}" ]] || { echo "Need --labels L1,L2" >&2; exit 1; }
41+
[[ "$L1" != "$L2" ]] || { echo "Labels must be distinct" >&2; exit 1; }
42+
43+
# Require a v-prefixed tag
44+
if [[ "$TAG" != v* ]]; then
45+
echo "Tag must start with 'v' (example: v0.1.0). Got: $TAG" >&2
46+
exit 1
47+
fi
48+
49+
REL_DIR="$WORKDIR/$TAG"
50+
mkdir -p "$REL_DIR"
51+
52+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
53+
54+
# Stable test keyring
55+
GNUPG_HOME="$TEST_GNUPG_HOME"
56+
mkdir -p "$GNUPG_HOME"
57+
chmod 700 "$GNUPG_HOME"
58+
export GNUPGHOME="$GNUPG_HOME"
59+
60+
# Sha256 helper (mac + linux)
61+
sha256_file() {
62+
local f="$1"
63+
if command -v sha256sum >/dev/null 2>&1; then
64+
sha256sum "$f" | awk '{print $1}'
65+
else
66+
shasum -a 256 "$f" | awk '{print $1}'
67+
fi
68+
}
69+
70+
# Create fake "builder output" directory
71+
ARTIFACT_DIR="$REL_DIR/builder_out"
72+
rm -rf "$ARTIFACT_DIR"
73+
mkdir -p "$ARTIFACT_DIR/aarch64-unknown-linux-gnu" "$ARTIFACT_DIR/x86_64-unknown-linux-gnu"
74+
75+
# Dummy binaries (make them executable, updater will install them)
76+
cat > "$ARTIFACT_DIR/aarch64-unknown-linux-gnu/secluso-config-tool" <<EOF
77+
#!/usr/bin/env sh
78+
echo "dummy secluso-config-tool aarch64 for ${TAG}"
79+
EOF
80+
chmod +x "$ARTIFACT_DIR/aarch64-unknown-linux-gnu/secluso-config-tool"
81+
82+
cat > "$ARTIFACT_DIR/x86_64-unknown-linux-gnu/secluso-config-tool" <<EOF
83+
#!/usr/bin/env sh
84+
echo "dummy secluso-config-tool x86_64 for ${TAG}"
85+
EOF
86+
chmod +x "$ARTIFACT_DIR/x86_64-unknown-linux-gnu/secluso-config-tool"
87+
88+
# Compute sha256s that will be embedded in the signed manifest
89+
SHA_A="$(sha256_file "$ARTIFACT_DIR/aarch64-unknown-linux-gnu/secluso-config-tool")"
90+
SHA_X="$(sha256_file "$ARTIFACT_DIR/x86_64-unknown-linux-gnu/secluso-config-tool")"
91+
92+
# Create builder-style manifest.json w/ per-artifact sha256
93+
cat > "$ARTIFACT_DIR/manifest.json" <<EOF
94+
{
95+
"build": {
96+
"target": "test",
97+
"profile": "release",
98+
"run_id": "1",
99+
"timestamp": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
100+
},
101+
"artifacts": [
102+
{
103+
"package": "config_tool",
104+
"target": "aarch64-unknown-linux-gnu",
105+
"bin": "secluso-config-tool",
106+
"bin_path": "aarch64-unknown-linux-gnu/secluso-config-tool",
107+
"crate": "config_tool",
108+
"version": "${TAG#v}",
109+
"crate_lock_sha256": "test",
110+
"rust_digest": "test",
111+
"sha256": "${SHA_A}"
112+
},
113+
{
114+
"package": "config_tool",
115+
"target": "x86_64-unknown-linux-gnu",
116+
"bin": "secluso-config-tool",
117+
"bin_path": "x86_64-unknown-linux-gnu/secluso-config-tool",
118+
"crate": "config_tool",
119+
"version": "${TAG#v}",
120+
"crate_lock_sha256": "test",
121+
"rust_digest": "test",
122+
"sha256": "${SHA_X}"
123+
}
124+
]
125+
}
126+
EOF
127+
128+
echo "Created fake builder_out at: $ARTIFACT_DIR"
129+
echo " aarch64 sha256: $SHA_A"
130+
echo " x86_64 sha256: $SHA_X"
131+
echo
132+
133+
# Manager step: copy builder manifest into <workdir>/<tag>/manifest.json + write manifest.sha256
134+
"$SCRIPT_DIR/secluso_prepare_release_dir.sh" \
135+
--tag "$TAG" \
136+
--workdir "$WORKDIR" \
137+
--labels "$LABELS" \
138+
--artifact-dir "$ARTIFACT_DIR"
139+
140+
MANIFEST="$REL_DIR/manifest.json"
141+
SHA_FILE="$REL_DIR/manifest.sha256"
142+
SIG_DIR="$REL_DIR/sigs"
143+
mkdir -p "$SIG_DIR"
144+
145+
# Find an existing key by UID fragment, otherwisewe can create it.
146+
ensure_key() {
147+
local uid="$1"
148+
local existing
149+
existing="$(gpg --with-colons --list-keys "$uid" 2>/dev/null | awk -F: '$1=="fpr" {print $10; exit}' || true)"
150+
if [[ -n "$existing" ]]; then
151+
echo "$existing"
152+
return
153+
fi
154+
155+
gpg --batch --pinentry-mode loopback --passphrase "" \
156+
--quick-generate-key "$uid" ed25519 sign 5y >/dev/null
157+
158+
gpg --with-colons --fingerprint "$uid" | awk -F: '$1=="fpr" {print $10; exit}'
159+
}
160+
161+
UID1="secluso-test-${L1} <secluso-test-${L1}@example.invalid>"
162+
UID2="secluso-test-${L2} <secluso-test-${L2}@example.invalid>"
163+
164+
FPR1="$(ensure_key "$UID1")"
165+
FPR2="$(ensure_key "$UID2")"
166+
167+
if [[ "$FPR1" == "$FPR2" ]]; then
168+
echo "ERROR: both labels resolved to the same key fingerprint; aborting" >&2
169+
exit 1
170+
fi
171+
172+
echo "Using test keys from GNUPGHOME=$GNUPG_HOME"
173+
echo " $L1 -> $FPR1"
174+
echo " $L2 -> $FPR2"
175+
echo
176+
177+
# Sign as each label (signer script verifies sha-file etc)
178+
"$SCRIPT_DIR/secluso_sign_manifest.sh" \
179+
--manifest "$MANIFEST" \
180+
--sha-file "$SHA_FILE" \
181+
--label "$L1" \
182+
--key "$FPR1" \
183+
--outdir "$SIG_DIR"
184+
185+
"$SCRIPT_DIR/secluso_sign_manifest.sh" \
186+
--manifest "$MANIFEST" \
187+
--sha-file "$SHA_FILE" \
188+
--label "$L2" \
189+
--key "$FPR2" \
190+
--outdir "$SIG_DIR"
191+
192+
# Build a real bundle (copies builder_out, overlays the signed manifest + signature files)
193+
"$SCRIPT_DIR/secluso_build_bundle.sh" \
194+
--tag "$TAG" \
195+
--workdir "$WORKDIR" \
196+
--labels "$LABELS" \
197+
--sig-a "$SIG_DIR/manifest.json.${L1}.asc" \
198+
--sig-b "$SIG_DIR/manifest.json.${L2}.asc" \
199+
--artifact-dir "$ARTIFACT_DIR"
200+
201+
echo
202+
echo "Test bundle ready at: $REL_DIR/out/secluso-${TAG}.zip"
203+
echo "Keys are persisted at: $GNUPG_HOME"
204+
echo
205+
echo "If you want the updater to accept these via GitHub keys, export + add them to your GitHub account:"
206+
echo " gpg --armor --export $FPR1"
207+
echo " gpg --armor --export $FPR2"

0 commit comments

Comments
 (0)