Skip to content
Draft
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3d32570
Fix synced config application
michaelw Apr 15, 2026
6621fa2
Retry config sync on concurrent changes
michaelw Apr 15, 2026
ac4df13
Merge branch 'mw/fix-config-sync-apply' into mw/main
michaelw Apr 20, 2026
6d1258c
test: add self-contained agent login e2e
michaelw Apr 20, 2026
5c093ce
fix(config): guard config push-back sync behind real Thand service
michaelw Apr 20, 2026
86d65f8
temporal: keep shared device registry queues unversioned
michaelw Apr 24, 2026
66846ea
feat(devices): add canonical device_id plumbing and route registry
michaelw Apr 22, 2026
5b09d79
feat(workflows): add execution planning for device-targeted provider …
michaelw Apr 22, 2026
0f55f82
feat(local-sudo): add device-local sudo request and provider flow
michaelw Apr 24, 2026
fee8bbd
localbroker: add gRPC bridge for the macOS privilege broker
michaelw Apr 24, 2026
76d4e18
provider/local: route Darwin timed sudo through broker activities
michaelw Apr 22, 2026
19c7618
macos: add PrivilegeServices broker workspace and broker core
michaelw Apr 24, 2026
d97b8e4
macos: bind notifier subscriptions to the login-item identity
michaelw Apr 24, 2026
61253b1
macos: add packaging, install, CI wiring, and strict signed local docs
michaelw Apr 24, 2026
dbf72eb
feat(workflows): add timeout routing for approvals
michaelw Apr 24, 2026
4443130
feat(providers): add local-presence approval provider
michaelw Apr 24, 2026
1aac00a
feat(local-notifications): add explicit local sudo notifications
michaelw Apr 24, 2026
7ab6235
fix(config): make config push-back sync deterministic
michaelw Apr 25, 2026
a7c1755
Merge branch 'main' into mw/local-sudo-request
hughneale Apr 27, 2026
8bd46f4
chore(deps): tidy test module dependencies
hughneale Apr 28, 2026
7d65f0f
fix(workflows): use constructed revoke request
hughneale Apr 28, 2026
11b65f8
fix(ci): resolve workflowcheck compile blockers
hughneale Apr 28, 2026
fd888a3
fix(workflows): replace reflect.DeepEqual with deterministic comparison
hughneale Apr 30, 2026
ac299ac
fix(temporal): run device registry setup after workers start
hughneale May 1, 2026
6b3847c
fix(workflows): pass pre-built request types to provider child workflows
hughneale May 2, 2026
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
166 changes: 165 additions & 1 deletion .github/workflows/manual-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ permissions:
packages: write

env:
GO_VERSION: '1.26'
MACOS_XCODE_VERSION: 'latest-stable'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

Expand All @@ -39,9 +41,164 @@ jobs:
exit 1
fi

release:
macos-release-sign-notarize:
needs: validate-tag
if: needs.validate-tag.outputs.tag-exists == 'true'
runs-on: macos-latest
outputs:
release-ready: ${{ steps.apple-secrets.outputs.ready }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag_version }}
fetch-depth: 0

- name: Check Apple release secrets
id: apple-secrets
shell: bash
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_DEV_ID_APPLICATION_CERT_BASE64: ${{ secrets.APPLE_DEV_ID_APPLICATION_CERT_BASE64 }}
APPLE_DEV_ID_APPLICATION_CERT_PASSWORD: ${{ secrets.APPLE_DEV_ID_APPLICATION_CERT_PASSWORD }}
APPLE_DEV_ID_INSTALLER_CERT_BASE64: ${{ secrets.APPLE_DEV_ID_INSTALLER_CERT_BASE64 }}
APPLE_DEV_ID_INSTALLER_CERT_PASSWORD: ${{ secrets.APPLE_DEV_ID_INSTALLER_CERT_PASSWORD }}
APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
APPLE_NOTARYTOOL_KEY_ID: ${{ secrets.APPLE_NOTARYTOOL_KEY_ID }}
APPLE_NOTARYTOOL_ISSUER_ID: ${{ secrets.APPLE_NOTARYTOOL_ISSUER_ID }}
APPLE_NOTARYTOOL_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARYTOOL_KEY_P8_BASE64 }}
run: |
missing=()
for var in \
APPLE_TEAM_ID \
APPLE_DEV_ID_APPLICATION_CERT_BASE64 \
APPLE_DEV_ID_APPLICATION_CERT_PASSWORD \
APPLE_DEV_ID_INSTALLER_CERT_BASE64 \
APPLE_DEV_ID_INSTALLER_CERT_PASSWORD \
APPLE_KEYCHAIN_PASSWORD \
APPLE_NOTARYTOOL_KEY_ID \
APPLE_NOTARYTOOL_ISSUER_ID \
APPLE_NOTARYTOOL_KEY_P8_BASE64
do
if [ -z "${!var}" ]; then
missing+=("$var")
fi
done

if [ "${#missing[@]}" -gt 0 ]; then
echo "ready=false" >> "$GITHUB_OUTPUT"
{
echo "### macOS release packaging skipped"
echo
echo "Missing Apple release secrets:"
for item in "${missing[@]}"; do
echo "- $item"
done
} >> "$GITHUB_STEP_SUMMARY"
exit 0
fi

echo "ready=true" >> "$GITHUB_OUTPUT"
echo "### macOS release packaging enabled" >> "$GITHUB_STEP_SUMMARY"

- name: Select Xcode
if: steps.apple-secrets.outputs.ready == 'true'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: ${{ env.MACOS_XCODE_VERSION }}

- name: Capture Xcode version
if: steps.apple-secrets.outputs.ready == 'true'
id: xcode
run: |
VERSION=$(xcodebuild -version | tr '\n' ' ' | sed 's/ */ /g')
echo "version=$VERSION" >> $GITHUB_OUTPUT
xcodebuild -version

- name: Install XcodeGen
if: steps.apple-secrets.outputs.ready == 'true'
run: |
brew list xcodegen >/dev/null 2>&1 || brew install xcodegen
xcodegen version

- name: Set up Go
if: steps.apple-secrets.outputs.ready == 'true'
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true

- name: Cache macOS DerivedData
if: steps.apple-secrets.outputs.ready == 'true'
uses: actions/cache@v4
with:
path: .build/macos/DerivedData
key: macos-release-deriveddata-${{ runner.os }}-${{ steps.xcode.outputs.version }}-${{ hashFiles('proto/localbroker/**/*.proto', 'buf*.yaml', 'platform/macos/PrivilegeServices/project.yml', 'platform/macos/PrivilegeServices/**/*.swift', 'platform/macos/PrivilegeServices/**/*.plist', 'platform/macos/PrivilegeServices/**/*.entitlements', 'platform/macos/PrivilegeServices/**/*.template', 'scripts/*macos-privilege-services*.sh', 'scripts/localbroker-codegen-common.sh', 'Makefile') }}
restore-keys: |
macos-release-deriveddata-${{ runner.os }}-${{ steps.xcode.outputs.version }}-

- name: Import Developer ID certificates
if: steps.apple-secrets.outputs.ready == 'true'
shell: bash
env:
APPLE_KEYCHAIN_PASSWORD: ${{ secrets.APPLE_KEYCHAIN_PASSWORD }}
APPLE_DEV_ID_APPLICATION_CERT_BASE64: ${{ secrets.APPLE_DEV_ID_APPLICATION_CERT_BASE64 }}
APPLE_DEV_ID_APPLICATION_CERT_PASSWORD: ${{ secrets.APPLE_DEV_ID_APPLICATION_CERT_PASSWORD }}
APPLE_DEV_ID_INSTALLER_CERT_BASE64: ${{ secrets.APPLE_DEV_ID_INSTALLER_CERT_BASE64 }}
APPLE_DEV_ID_INSTALLER_CERT_PASSWORD: ${{ secrets.APPLE_DEV_ID_INSTALLER_CERT_PASSWORD }}
run: |
KEYCHAIN_PATH="$RUNNER_TEMP/thand-build.keychain-db"
APP_CERT_PATH="$RUNNER_TEMP/dev-id-application.p12"
INSTALLER_CERT_PATH="$RUNNER_TEMP/dev-id-installer.p12"

echo "$APPLE_DEV_ID_APPLICATION_CERT_BASE64" | base64 --decode > "$APP_CERT_PATH"
echo "$APPLE_DEV_ID_INSTALLER_CERT_BASE64" | base64 --decode > "$INSTALLER_CERT_PATH"

security create-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$APP_CERT_PATH" -k "$KEYCHAIN_PATH" -P "$APPLE_DEV_ID_APPLICATION_CERT_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
security import "$INSTALLER_CERT_PATH" -k "$KEYCHAIN_PATH" -P "$APPLE_DEV_ID_INSTALLER_CERT_PASSWORD" -T /usr/bin/productsign -T /usr/bin/security
security list-keychains -d user -s "$KEYCHAIN_PATH" login.keychain-db
security default-keychain -d user -s "$KEYCHAIN_PATH"
security set-key-partition-list -S apple-tool:,apple: -s -k "$APPLE_KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"

- name: Configure notarytool profile
if: steps.apple-secrets.outputs.ready == 'true'
shell: bash
env:
APPLE_NOTARYTOOL_KEY_ID: ${{ secrets.APPLE_NOTARYTOOL_KEY_ID }}
APPLE_NOTARYTOOL_ISSUER_ID: ${{ secrets.APPLE_NOTARYTOOL_ISSUER_ID }}
APPLE_NOTARYTOOL_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARYTOOL_KEY_P8_BASE64 }}
run: |
KEY_PATH="$RUNNER_TEMP/AuthKey_${APPLE_NOTARYTOOL_KEY_ID}.p8"
echo "$APPLE_NOTARYTOOL_KEY_P8_BASE64" | base64 --decode > "$KEY_PATH"
xcrun notarytool store-credentials thand-ci-notary \
--key "$KEY_PATH" \
--key-id "$APPLE_NOTARYTOOL_KEY_ID" \
--issuer "$APPLE_NOTARYTOOL_ISSUER_ID"
echo "NOTARYTOOL_PROFILE=thand-ci-notary" >> "$GITHUB_ENV"

- name: Package signed macOS installer
if: steps.apple-secrets.outputs.ready == 'true'
env:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
PACKAGE_VERSION="${{ github.event.inputs.tag_version }}"
PACKAGE_VERSION="${PACKAGE_VERSION#v}"
export PACKAGE_VERSION
./scripts/package-macos-privilege-services-release.sh

- name: Upload macOS installer artifact
if: steps.apple-secrets.outputs.ready == 'true'
uses: actions/upload-artifact@v4
with:
name: thand-macos-privilege-services-pkg
path: .build/macos/PrivilegeServices/release/ThandPrivilegeServices.pkg

release:
needs: [validate-tag, macos-release-sign-notarize]
if: needs.validate-tag.outputs.tag-exists == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout code
Expand Down Expand Up @@ -94,6 +251,13 @@ jobs:
repository: ${{ github.repository }}
run-id: ${{ steps.find-run.outputs.run-id }}

- name: Download macOS installer artifact
if: needs.macos-release-sign-notarize.outputs.release-ready == 'true'
uses: actions/download-artifact@v4
with:
name: thand-macos-privilege-services-pkg
path: dist/

- name: Generate changelog
id: changelog
run: |
Expand Down
67 changes: 64 additions & 3 deletions .github/workflows/test-and-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ permissions:
env:
GO_VERSION: '1.26'
GOEXPERIMENT: 'jsonv2'
MACOS_XCODE_VERSION: 'latest-stable'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

Expand All @@ -49,6 +50,7 @@ jobs:

- name: Run unit tests
run: |
make gen-buf
# Run all tests except functional and integration tests
go test -v -short $(go list ./... | grep -v '/test/functional' | grep -v '/test/integration')

Expand All @@ -71,6 +73,7 @@ jobs:

- name: Run functional tests
run: |
make gen-buf
# Run functional tests (these use testcontainers/Docker)
# Docker is available by default on ubuntu-latest runners
cd test && go test -v -timeout 10m ./functional/...
Expand All @@ -93,7 +96,9 @@ jobs:
cache: true

- name: Run services integration tests
run: cd test && go test -v -timeout 5m ./integration/services/...
run: |
make gen-buf
cd test && go test -v -timeout 5m ./integration/services/...

integration-workflows:
runs-on: ubuntu-latest
Expand All @@ -113,7 +118,9 @@ jobs:
cache: true

- name: Run workflows integration tests
run: cd test && go test -v -timeout 15m ./integration/workflows/...
run: |
make gen-buf
cd test && go test -v -timeout 15m ./integration/workflows/...

build-linux-amd64:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -142,6 +149,57 @@ jobs:
path: bin/thand-linux-amd64
retention-days: 1

macos-validation:
runs-on: macos-latest
if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main')

steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: recursive

- name: Select Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: ${{ env.MACOS_XCODE_VERSION }}

- name: Capture Xcode version
id: xcode
run: |
VERSION=$(xcodebuild -version | tr '\n' ' ' | sed 's/ */ /g')
echo "version=$VERSION" >> $GITHUB_OUTPUT
xcodebuild -version

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache: true

- name: Install XcodeGen
run: |
brew list xcodegen >/dev/null 2>&1 || brew install xcodegen
xcodegen version

- name: Cache macOS DerivedData
uses: actions/cache@v4
with:
path: .build/macos/DerivedData
key: macos-deriveddata-${{ runner.os }}-${{ steps.xcode.outputs.version }}-${{ hashFiles('proto/localbroker/**/*.proto', 'buf*.yaml', 'platform/macos/PrivilegeServices/project.yml', 'platform/macos/PrivilegeServices/**/*.swift', 'platform/macos/PrivilegeServices/**/*.plist', 'platform/macos/PrivilegeServices/**/*.entitlements', 'platform/macos/PrivilegeServices/**/*.template', 'scripts/*macos-privilege-services*.sh', 'scripts/localbroker-codegen-common.sh', 'Makefile') }}
restore-keys: |
macos-deriveddata-${{ runner.os }}-${{ steps.xcode.outputs.version }}-

- name: Build
run: make build

- name: Test
run: make test

- name: Verify unsigned package layout
run: THAND_MACOS_SKIP_SIGNING=1 make package-macos-privilege-services-dev

integration-frontend:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
Expand Down Expand Up @@ -175,7 +233,9 @@ jobs:
sudo apt-get install -y chromium-browser

- name: Run frontend E2E integration tests
run: cd test && go test -v -timeout 30m ./integration/frontend/...
run: |
make gen-buf
cd test && go test -v -timeout 30m ./integration/frontend/...
env:
DISPLAY: :99
CHROME_BIN: /usr/bin/chromium-browser
Expand Down Expand Up @@ -272,6 +332,7 @@ jobs:

- name: Build for ${{ matrix.goos }}/${{ matrix.goarch }}
run: |
make gen-buf
VERSION=${{ steps.version.outputs.version }}
COMMIT=${{ steps.version.outputs.commit }}
GOOS=${{ matrix.goos }}
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ go.work.sum
/bin/
/dist/
/build/
/.build/

# Temporary files
*.tmp
Expand Down Expand Up @@ -105,3 +106,8 @@ config/workflows/*.json
config/temporal/*.key
config/temporal/*.pem
config/README.md

# Generated protobuf/gRPC sources
internal/localbroker/proto/localbroker/v1/*.pb.go
platform/macos/PrivilegeServices/Generated/LocalBroker/*.swift
platform/macos/PrivilegeServices/ThandPrivilegeServices.xcodeproj/
Loading
Loading