Skip to content
This repository was archived by the owner on Oct 17, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,32 @@ remote="$1"
default="$(git symbolic-ref --quiet "refs/remotes/$remote/HEAD" 2>/dev/null | sed "s|^refs/remotes/$remote/||")" || default=""
[ -n "$default" ] || default="$(git config init.defaultBranch || echo main)"

# scripts/agent records the commit it last checked in full, with a clean tree.
recorded="$(cat "$(git rev-parse --git-dir)/agent-checked" 2>/dev/null || :)"
branches=0
while read -r _local_ref _local_sha remote_ref _remote_sha; do
unchecked=0
while read -r _local_ref local_sha remote_ref _remote_sha; do
if [ "$remote_ref" = "refs/heads/$default" ] && git ls-remote --exit-code --heads "$remote" "$default" >/dev/null 2>&1; then
echo "pre-push: refusing to push to $default; open a pull request (scripts/agent pr)" >&2
exit 1
fi
case "$remote_ref" in refs/heads/*) branches=$((branches + 1)) ;; esac
case "$remote_ref" in
refs/heads/*)
branches=$((branches + 1))
[ "$local_sha" = "$recorded" ] || unchecked=$((unchecked + 1))
;;
esac
done

# A tag points at a merge commit CI already checked; checking the working tree again would test
# whatever happens to be checked out, not what's tagged.
[ "$branches" -gt 0 ] || exit 0

# The same check already passed on exactly this commit; running it twice only doubles the wait.
if [ "$unchecked" -eq 0 ]; then
echo "pre-push: $recorded was checked by scripts/agent"
exit 0
fi

echo "pre-push: full check"
exec scripts/task check
13 changes: 11 additions & 2 deletions scripts/agent
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ default_branch() {

current_branch() { git rev-parse --abbrev-ref HEAD; }

# The full check, recorded against HEAD when the tree is clean, so the pre-push hook can skip running
# the same check again on exactly the commit being pushed.
check_and_record() {
"$root/scripts/task" check
if [ -z "$(git status --porcelain)" ]; then
git rev-parse HEAD >"$(git rev-parse --git-dir)/agent-checked"
fi
}

cmd="${1:-}"
[ $# -gt 0 ] && shift

Expand Down Expand Up @@ -82,7 +91,7 @@ pr)
branch="$(current_branch)"
[ "$branch" != "$(default_branch)" ] || die "on the default branch; start a branch with 'scripts/agent start'"
echo "==> checking"
"$root/scripts/task" check
check_and_record
merged="the pull request for $branch already merged; put new commits on a new branch ('scripts/agent start'), then run 'scripts/agent done' here"
state="$(gh pr view "$branch" --json state --jq .state 2>/dev/null || :)"
case "$state" in
Expand Down Expand Up @@ -147,7 +156,7 @@ ship)
# Test against what it will land on, not what it branched from.
echo "==> $base moved; rebasing and checking again"
git rebase --quiet "origin/$base" || die "conflicts with $base; resolve them, then run 'scripts/agent ship' again"
"$root/scripts/task" check
check_and_record
# Auto-merge may have landed the pull request while the check ran, deleting the branch.
merged() { [ "$(gh pr view "$branch" --json state --jq .state)" = "MERGED" ]; }
merged && continue
Expand Down
64 changes: 41 additions & 23 deletions scripts/dev-cert
Original file line number Diff line number Diff line change
@@ -1,35 +1,53 @@
#!/bin/sh
set -eu
# Creates a self-signed code signing certificate in the login keychain, once, shared by every
# Common Tools app.
# Creates a self-signed code signing certificate in the login keychain, trusted for code signing,
# shared by every Common Tools app. Run it once per Mac; running it again repairs trust and resets
# this app's permission grants.
#
# macOS ties permissions such as Accessibility, and login items, to an app's signature. Ad-hoc
# signatures change with every build, so every rebuild loses them. A certificate that stays put
# keeps them. scripts/task build signs with it whenever it's there.
# macOS ties permissions such as Accessibility and Screen Recording, and login items, to an app's
# signature. Ad-hoc signatures change with every build, so every rebuild loses them. A certificate
# that stays put keeps them. scripts/task build signs with it whenever it's valid.

name="Common Tools Development"
keychain="$HOME/Library/Keychains/login.keychain-db"

if security find-certificate -c "$name" "$keychain" >/dev/null 2>&1; then
echo "==> '$name' is already in the login keychain; nothing to do"
exit 0
fi
bundle_id="com.oddurs.commoncommand"
# The privacy permissions this app asks for, as tccutil names them.
permissions=""

work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT

echo "==> generating '$name' (valid ten years)"
# The system LibreSSL writes PKCS#12 files that security import can read; OpenSSL 3 needs -legacy.
/usr/bin/openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
-subj "/CN=$name" \
-addext "basicConstraints=critical,CA:false" \
-addext "keyUsage=critical,digitalSignature" \
-addext "extendedKeyUsage=critical,codeSigning" \
-keyout "$work/key.pem" -out "$work/cert.pem"
/usr/bin/openssl pkcs12 -export -inkey "$work/key.pem" -in "$work/cert.pem" \
-name "$name" -out "$work/identity.p12" -passout pass:common-tools
if security find-certificate -c "$name" "$keychain" >/dev/null 2>&1; then
echo "==> '$name' is already in the login keychain"
security find-certificate -c "$name" -p "$keychain" >"$work/cert.pem"
else
echo "==> generating '$name' (valid ten years)"
# The system LibreSSL writes PKCS#12 files that security import can read; OpenSSL 3 needs -legacy.
/usr/bin/openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
-subj "/CN=$name" \
-addext "basicConstraints=critical,CA:false" \
-addext "keyUsage=critical,digitalSignature" \
-addext "extendedKeyUsage=critical,codeSigning" \
-keyout "$work/key.pem" -out "$work/cert.pem"
/usr/bin/openssl pkcs12 -export -inkey "$work/key.pem" -in "$work/cert.pem" \
-name "$name" -out "$work/identity.p12" -passout pass:common-tools
echo "==> importing into the login keychain, usable by codesign"
security import "$work/identity.p12" -k "$keychain" -P common-tools -T /usr/bin/codesign >/dev/null
fi

# An untrusted self-signed identity isn't valid for signing; trust it for code signing only.
if security find-identity -v -p codesigning | grep -q "\"$name\""; then
echo "==> '$name' is trusted for code signing"
else
echo "==> trusting '$name' for code signing (macOS asks for your password)"
security add-trusted-cert -r trustRoot -p codeSign -k "$keychain" "$work/cert.pem"
fi

echo "==> importing into the login keychain, usable by codesign"
security import "$work/identity.p12" -k "$keychain" -P common-tools -T /usr/bin/codesign >/dev/null
# A permission granted to an earlier signature stays switched on in System Settings but no longer
# applies. Clear it, so the app asks again and the new grant sticks.
for service in $permissions; do
echo "==> resetting the $service permission for $bundle_id; grant it again when the app asks"
tccutil reset "$service" "$bundle_id"
done

echo "==> done. Rebuild with scripts/task build, then grant any permission an app asks for once more."
echo "==> done. Rebuild with scripts/task build or scripts/task run."
6 changes: 4 additions & 2 deletions scripts/make-icon.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Renders Resources/AppIcon.icns and docs/icon.png. Run: swift scripts/make-icon.swift
//
// The Common Tools icon family: a blue squircle with a frosted glass glyph. Only the glyph differs
// between Common Stats, Common Keys, and Common Bar; keep the rest identical across the three repos.
// The Common Tools icon family: a blue squircle with a frosted glass glyph. Only the glyph is this
// app's own; the rest is the same in every app and comes from the standard in oddurs/common-tools.
// After changing the glyph, copy a 128 px render into the standard's Resources/CommonTools/ too, so
// every app's Settings shows the new icon.
import AppKit

// Shared look: Apple's 824 pt squircle on a 1024 canvas, a blue gradient body, and a frosted
Expand Down
3 changes: 2 additions & 1 deletion scripts/task
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ test_flags() {
# makes one, shared by every Common Tools app.
sign() {
identity="Common Tools Development"
if security find-certificate -c "$identity" >/dev/null 2>&1; then
# Only a valid identity: one that's present but not trusted for code signing makes codesign fail.
if security find-identity -v -p codesigning | grep -q "\"$identity\""; then
echo "==> signing $1 with '$identity'"
codesign --force --sign "$identity" --identifier "$2" --options runtime "$1"
else
Expand Down
Loading