Skip to content

Commit 20852be

Browse files
committed
build: add build scripts
1 parent 741f454 commit 20852be

2 files changed

Lines changed: 230 additions & 0 deletions

File tree

justfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ ci: test build check-dist
2121
ci-setup:
2222
bash scripts/ci-setup.sh
2323

24+
# Sign the dist/index.js artifact locally (creates dist/index.js.auths.json)
25+
sign-dist:
26+
auths artifact sign dist/index.js
27+
2428
# Cut a release: bump version (if needed), commit, then tag+push via release script
2529
# Usage: just release 1.0.0
2630
release VERSION: test build

scripts/ci-setup.sh

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
#!/usr/bin/env bash
2+
#
3+
# CI Release Signing Setup (One-Time)
4+
#
5+
# Creates a limited-capability device key for GitHub Actions to sign
6+
# release artifacts. Your root identity stays on your machine.
7+
#
8+
# Prerequisites:
9+
# - auths CLI installed and initialized (auths init)
10+
# - gh CLI installed and authenticated (gh auth login)
11+
#
12+
# Usage:
13+
# bash scripts/ci-setup.sh # interactive setup
14+
# just ci-setup # same thing via justfile
15+
#
16+
set -euo pipefail
17+
18+
CYAN='\033[0;36m'
19+
GREEN='\033[0;32m'
20+
BOLD='\033[1m'
21+
DIM='\033[2m'
22+
YELLOW='\033[1;33m'
23+
RESET='\033[0m'
24+
25+
echo ""
26+
echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${RESET}"
27+
echo -e "${CYAN}${RESET}${BOLD} CI Release Signing Setup (One-Time) ${RESET}${CYAN}${RESET}"
28+
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${RESET}"
29+
echo ""
30+
echo "This creates a limited-capability device for GitHub Actions to sign"
31+
echo "release artifacts. Your root identity stays on your machine."
32+
echo ""
33+
34+
# --- Step 1: Verify identity exists ---
35+
if ! auths status > /dev/null 2>&1; then
36+
echo "ERROR: No auths identity found. Run 'auths init' first." >&2
37+
exit 1
38+
fi
39+
40+
# --- Step 2: Read identity info ---
41+
ID_OUTPUT=$(auths id show)
42+
IDENTITY_DID=$(echo "$ID_OUTPUT" | grep "Controller DID:" | awk '{print $3}')
43+
if [ -z "$IDENTITY_DID" ]; then
44+
echo "ERROR: Could not parse Controller DID from 'auths id show'" >&2
45+
exit 1
46+
fi
47+
48+
KEY_OUTPUT=$(auths key list)
49+
IDENTITY_KEY_ALIAS=$(echo "$KEY_OUTPUT" | grep '^-' | head -1 | awk '{print $2}')
50+
if [ -z "$IDENTITY_KEY_ALIAS" ]; then
51+
echo "ERROR: Could not parse key alias from 'auths key list'" >&2
52+
exit 1
53+
fi
54+
55+
echo -e "${BOLD}Identity:${RESET} ${CYAN}${IDENTITY_DID}${RESET}"
56+
echo -e "${BOLD}Key alias:${RESET} ${CYAN}${IDENTITY_KEY_ALIAS}${RESET}"
57+
echo ""
58+
59+
# --- Step 3: Check for existing CI device key ---
60+
REUSE=0
61+
if echo "$KEY_OUTPUT" | grep -q "ci-release-device"; then
62+
echo -e "${DIM}Found existing ci-release-device key — will reuse it.${RESET}"
63+
REUSE=1
64+
fi
65+
66+
# --- Step 4: Prompt for passphrase ---
67+
echo -e "${BOLD}Choose a passphrase for the CI device key.${RESET}"
68+
echo -e "${DIM}This will be stored as AUTHS_CI_PASSPHRASE in GitHub Secrets.${RESET}"
69+
echo ""
70+
71+
read -rsp "CI device passphrase: " CI_PASS
72+
echo ""
73+
read -rsp "Confirm passphrase: " CI_PASS_CONFIRM
74+
echo ""
75+
76+
if [ "$CI_PASS" != "$CI_PASS_CONFIRM" ]; then
77+
echo "ERROR: Passphrases do not match" >&2
78+
exit 1
79+
fi
80+
81+
# --- Step 5: Generate seed + import key (or reuse existing) ---
82+
TMPDIR_WORK=$(mktemp -d)
83+
trap 'rm -rf "$TMPDIR_WORK"' EXIT
84+
85+
if [ "$REUSE" -eq 0 ]; then
86+
echo ""
87+
echo -e "${DIM}Generating CI device key...${RESET}"
88+
89+
# Generate a fresh 32-byte Ed25519 seed
90+
SEED_PATH="$TMPDIR_WORK/ci-device-seed.bin"
91+
dd if=/dev/urandom of="$SEED_PATH" bs=32 count=1 2>/dev/null
92+
chmod 600 "$SEED_PATH"
93+
94+
# Import the seed into platform keychain
95+
echo -e "${DIM}Importing key into platform keychain:${RESET}"
96+
auths key import \
97+
--alias ci-release-device \
98+
--seed-file "$SEED_PATH" \
99+
--controller-did "$IDENTITY_DID"
100+
101+
echo -e "${GREEN}${RESET} CI device key imported into platform keychain"
102+
fi
103+
104+
# Create CI file keychain
105+
KEYCHAIN_PATH="$TMPDIR_WORK/ci-keychain.enc"
106+
if [ "$REUSE" -eq 0 ]; then
107+
echo -e "${DIM}Creating CI file keychain...${RESET}"
108+
else
109+
echo -e "${DIM}Reusing existing ci-release-device key — regenerating CI file keychain...${RESET}"
110+
fi
111+
112+
AUTHS_PASSPHRASE="$CI_PASS" auths key copy-backend \
113+
--alias ci-release-device \
114+
--dst-backend file \
115+
--dst-file "$KEYCHAIN_PATH"
116+
117+
KEYCHAIN_B64=$(base64 < "$KEYCHAIN_PATH" | tr -d '\n')
118+
echo -e "${GREEN}${RESET} CI file keychain created"
119+
120+
# --- Step 6: Derive device DID ---
121+
DEVICE_PUB=$(auths key export \
122+
--alias ci-release-device \
123+
--passphrase "$CI_PASS" \
124+
--format pub)
125+
126+
DEVICE_DID=$(auths debug util pubkey-to-did "$DEVICE_PUB")
127+
echo -e "${GREEN}${RESET} Device DID: ${CYAN}${DEVICE_DID}${RESET}"
128+
129+
# --- Step 7: Link device (if not already linked) ---
130+
DEVICES_OUTPUT=$(auths device list 2>/dev/null || echo "")
131+
if echo "$DEVICES_OUTPUT" | grep -q "$DEVICE_DID"; then
132+
echo -e "${GREEN}${RESET} CI device already linked — skipping"
133+
else
134+
echo ""
135+
echo -e "${DIM}Linking CI device to identity...${RESET}"
136+
AUTHS_PASSPHRASE="$CI_PASS" auths device link \
137+
--key "$IDENTITY_KEY_ALIAS" \
138+
--device-key ci-release-device \
139+
--device-did "$DEVICE_DID" \
140+
--note "GitHub Actions release signer" \
141+
--capabilities sign_release
142+
echo -e "${GREEN}${RESET} CI device linked"
143+
fi
144+
145+
# --- Step 8: Package identity repo (for release signing) ---
146+
AUTHS_DIR="${HOME}/.auths"
147+
echo -e "${DIM}Packaging identity repo...${RESET}"
148+
149+
if ! git -C "$AUTHS_DIR" rev-parse --git-dir > /dev/null 2>&1; then
150+
echo "ERROR: ~/.auths does not appear to be a git repository. Run 'auths init' first." >&2
151+
exit 1
152+
fi
153+
echo -e " ${GREEN}${RESET} ~/.auths is a valid git repo"
154+
155+
# Build tar.gz of ~/.auths, excluding *.sock files
156+
BUNDLE_PATH="$TMPDIR_WORK/identity-bundle.tar.gz"
157+
tar -czf "$BUNDLE_PATH" \
158+
--exclude='*.sock' \
159+
-C "$(dirname "$AUTHS_DIR")" \
160+
"$(basename "$AUTHS_DIR")"
161+
162+
IDENTITY_BUNDLE_B64=$(base64 < "$BUNDLE_PATH" | tr -d '\n')
163+
164+
# --- Step 8b: Export identity bundle JSON (for CI artifact verification) ---
165+
echo -e "${DIM}Exporting identity bundle JSON (1-year TTL)...${RESET}"
166+
BUNDLE_JSON_PATH="$TMPDIR_WORK/identity-bundle.json"
167+
auths id export-bundle \
168+
--alias ci-release-device \
169+
--output "$BUNDLE_JSON_PATH" \
170+
--max-age-secs 31536000
171+
172+
IDENTITY_BUNDLE_JSON=$(cat "$BUNDLE_JSON_PATH")
173+
echo -e "${GREEN}${RESET} Identity bundle JSON exported (expires in 1 year)"
174+
175+
# --- Step 9: Set GitHub secrets ---
176+
echo ""
177+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
178+
echo -e "${BOLD} Setting GitHub Secrets:${RESET}"
179+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
180+
echo ""
181+
182+
REPO=$(git remote get-url origin | sed 's/.*github\.com[:/]//' | sed 's/\.git$//')
183+
if [ -z "$REPO" ] || ! echo "$REPO" | grep -q '/'; then
184+
echo "ERROR: Could not extract owner/repo from git remote URL" >&2
185+
exit 1
186+
fi
187+
188+
GH_OK=1
189+
190+
if ! gh auth status > /dev/null 2>&1; then
191+
GH_OK=0
192+
fi
193+
194+
if [ "$GH_OK" -eq 1 ]; then
195+
echo -e "${DIM}Setting secrets via gh CLI...${RESET}"
196+
197+
echo -n "$CI_PASS" | gh secret set AUTHS_CI_PASSPHRASE --repo "$REPO" || GH_OK=0
198+
echo -n "$KEYCHAIN_B64" | gh secret set AUTHS_CI_KEYCHAIN --repo "$REPO" || GH_OK=0
199+
echo -n "$IDENTITY_BUNDLE_B64" | gh secret set AUTHS_CI_IDENTITY_BUNDLE --repo "$REPO" || GH_OK=0
200+
echo -n "$IDENTITY_BUNDLE_JSON" | gh secret set AUTHS_CI_IDENTITY_BUNDLE_JSON --repo "$REPO" || GH_OK=0
201+
fi
202+
203+
if [ "$GH_OK" -eq 1 ]; then
204+
echo -e "${GREEN}${RESET} All 4 secrets set on ${CYAN}${REPO}${RESET}"
205+
else
206+
echo -e "${YELLOW}Could not set secrets automatically.${RESET}"
207+
echo -e "${DIM}Try: gh auth login then re-run, or add manually:${RESET}"
208+
echo -e "${DIM} Repository → Settings → Secrets → Actions → New secret${RESET}"
209+
echo ""
210+
echo -e "${BOLD}AUTHS_CI_PASSPHRASE${RESET}"
211+
echo "$CI_PASS"
212+
echo ""
213+
echo -e "${BOLD}AUTHS_CI_KEYCHAIN${RESET}"
214+
echo "$KEYCHAIN_B64"
215+
echo ""
216+
echo -e "${BOLD}AUTHS_CI_IDENTITY_BUNDLE${RESET}"
217+
echo "$IDENTITY_BUNDLE_B64"
218+
echo ""
219+
echo -e "${BOLD}AUTHS_CI_IDENTITY_BUNDLE_JSON${RESET}"
220+
echo "$IDENTITY_BUNDLE_JSON"
221+
fi
222+
223+
echo ""
224+
echo -e "${BOLD}To revoke CI access at any time:${RESET}"
225+
echo -e " ${CYAN}auths device revoke --device-did ${DEVICE_DID} --key ${IDENTITY_KEY_ALIAS}${RESET}"
226+
echo ""

0 commit comments

Comments
 (0)