Skip to content
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
15 changes: 12 additions & 3 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,21 @@ export MAPLE_HOOK_STAGED="$STAGED_FILE"
export MAPLE_HOOK_SELECTED="$SELECTED"
export MAPLE_HOOK_FULL="${MAPLE_HOOK_FULL:-0}"

# run_component NAME DIR FLAKE_REF SCRIPT [ENV=VALUE ...]
# run_component NAME DIR FLAKE_REF SCRIPT [--bash] [ENV=VALUE ...]
# DIR is where the script and `nix develop` run; FLAKE_REF is relative to DIR.
# CI entrypoints may opt into Bash; component hooks use POSIX sh by default.
# ENV=VALUE pairs are set for `nix develop` itself, not only for the script:
# a flake's shellHook runs before the `-c` command, and the opensecret shell
# starts PostgreSQL and writes .env unless OPENSECRET_DEV_* tell it not to.
# `nix develop` passes its environment through, so the script sees them too.
run_component() {
name=$1; dir=$2; flake_ref=$3; script=$4
shift 4
component_shell=sh
if [ "${1:-}" = "--bash" ]; then
component_shell=bash
shift
fi
hook_log "=== $name ==="
started=$(date +%s)
# Git exports GIT_INDEX_FILE, GIT_DIR, and friends to hooks. They must not
Expand All @@ -94,10 +100,10 @@ run_component() {
# shellcheck disable=SC2086 # GIT_ENV_UNSET is a deliberate word list
if [ -n "$NIX_BIN" ]; then
(cd "$dir" && env $GIT_ENV_UNSET "$@" "$NIX_BIN" develop --no-update-lock-file "$flake_ref" \
-c env MAPLE_HOOK_IN_NIX=1 sh "$script")
-c env MAPLE_HOOK_IN_NIX=1 "$component_shell" "$script")
else
# shellcheck disable=SC2086
(cd "$dir" && env $GIT_ENV_UNSET MAPLE_HOOK_IN_NIX=0 "$@" sh "$script")
(cd "$dir" && env $GIT_ENV_UNSET MAPLE_HOOK_IN_NIX=0 "$@" "$component_shell" "$script")
fi
status=$?
hook_log "=== $name finished in $(( $(date +%s) - started ))s ==="
Expand All @@ -116,6 +122,9 @@ fi
if hook_selected updates; then
run_component updates "$REPO_ROOT" ".#ci" "$REPO_ROOT/services/updates/.githooks/pre-commit"
fi
if hook_selected agent_updates; then
run_component agent-updates "$REPO_ROOT" ".#ci" "$REPO_ROOT/scripts/ci/agent-updates.sh" --bash
fi
if hook_selected sdk_rust || hook_selected sdk_ts; then
run_component sdk "$REPO_ROOT/sdk" "." "$REPO_ROOT/sdk/.githooks/pre-commit"
fi
Expand Down
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
/apps/maple-agent/release_profile.rs @AnthonyRonning
/apps/maple-agent/release-profiles.json @AnthonyRonning
/apps/maple-agent/scripts/ @AnthonyRonning
/apps/maple-agent/crates/maple-agent-updater/ @AnthonyRonning
/apps/maple-agent/app/packaging/ @AnthonyRonning
/apps/maple-agent/app/macos/ @AnthonyRonning
/apps/maple-research/frontend/package.json @AnthonyRonning
Expand Down Expand Up @@ -75,4 +76,5 @@

# Retain the existing owner boundary for these distribution components.
/services/updates/ @AnthonyRonning
/services/agent-updates/ @AnthonyRonning
/proxy/ @AnthonyRonning
248 changes: 248 additions & 0 deletions .github/workflows/agent-updates-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
name: Publish Agent Updates
run-name: Publish Agent ${{ inputs.version }} from verified build ${{ inputs.build_run_id }}

on:
workflow_dispatch:
inputs:
build_run_id:
description: Exact completed trusted master run with verified Prod packages
required: true
type: string
version:
description: Stable Agent version matching the selected Prod packages
required: true
type: string
notes:
description: Plain-text release notes (must remain identical on retries)
required: true
type: string
include_linux:
description: Include Linux only after its packaged update launch gate passes
required: true
default: false
type: boolean

permissions:
contents: read
actions: read
attestations: read

concurrency:
# Operator pointer changes must use this same serialization boundary.
group: agent-updates-production
cancel-in-progress: false

jobs:
verify:
if: ${{ github.repository == 'MaplePrivacyLabs/Maple' && github.ref == 'refs/heads/master' && github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
ref: ${{ github.sha }}
persist-credentials: false
- name: Install pinned Nix
uses: DeterminateSystems/determinate-nix-action@96c5ee93a9dddf2830d31e1da6ec719077336c28 # v3.23.3
with:
source-tag: v3.23.3
github-token: ""
- name: Verify pinned Nix runtime
run: bash scripts/ci/verify-nix-runtime.sh
- name: Require separately approved production setup
env:
ACTIVATED: ${{ vars.AGENT_UPDATES_ENABLED }}
PUBLIC_KEY: ${{ vars.AGENT_UPDATE_PUBLIC_KEY }}
run: |
set -euo pipefail
[ "$ACTIVATED" = true ] || { echo 'Agent update production setup has not been activated by its owner.' >&2; exit 1; }
[ -n "$PUBLIC_KEY" ] || { echo 'Agent public update key is not configured.' >&2; exit 1; }
printf '%s\n' "$PUBLIC_KEY" > public-key.pub
- name: Verify exact build, download final packages and prepare without secrets
env:
GH_TOKEN: ${{ github.token }}
BUILD_RUN_ID: ${{ inputs.build_run_id }}
RELEASE_VERSION: ${{ inputs.version }}
RELEASE_NOTES: ${{ inputs.notes }}
INCLUDE_LINUX: ${{ inputs.include_linux }}
run: |
nix develop --no-update-lock-file .#ci -c bash -euo pipefail <<'PREPARE'
linux_args=()
if [ "$INCLUDE_LINUX" = true ]; then linux_args=(--linux); fi
python3 services/agent-updates/publisher/publisher.py trust \
--run-id "$BUILD_RUN_ID" "${linux_args[@]}" --output trust.json
gh run download "$BUILD_RUN_ID" --repo "$GITHUB_REPOSITORY" \
--name "$(jq -er .artifacts.macos trust.json)" --dir packages/macos
linux_args=()
if [ "$INCLUDE_LINUX" = true ]; then
gh run download "$BUILD_RUN_ID" --repo "$GITHUB_REPOSITORY" \
--name "$(jq -er .artifacts.linux trust.json)" --dir packages/linux
linux_args=(--linux packages/linux)
fi
printf '%s' "$RELEASE_NOTES" > notes.txt
python3 services/agent-updates/publisher/publisher.py prepare \
--run-id "$BUILD_RUN_ID" --version "$RELEASE_VERSION" --build "$(jq -er .build trust.json)" \
--macos packages/macos "${linux_args[@]}" --notes-file notes.txt --public-key public-key.pub --output bundle
PREPARE
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: agent-update-prepared-${{ github.run_id }}
path: bundle/
overwrite: true
if-no-files-found: error
retention-days: 7

reserve:
needs: verify
if: ${{ github.repository == 'MaplePrivacyLabs/Maple' && github.ref == 'refs/heads/master' && github.event_name == 'workflow_dispatch' }}
environment: updates-production
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
ref: ${{ github.sha }}
persist-credentials: false
- name: Install pinned Nix
uses: DeterminateSystems/determinate-nix-action@96c5ee93a9dddf2830d31e1da6ec719077336c28 # v3.23.3
with:
source-tag: v3.23.3
github-token: ""
- name: Verify pinned Nix runtime
run: bash scripts/ci/verify-nix-runtime.sh
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: agent-update-prepared-${{ github.run_id }}
path: bundle
- name: Realize pinned publisher tools before retrieving credentials
run: nix develop --no-update-lock-file .#ci -c true
- name: Resolve Agent bucket credentials from Bitwarden Secrets Manager
id: r2-credentials
uses: bitwarden/sm-action@1238aae8fc64b212641190a9227c8a734ab1a793 # v3.0.1
with:
access_token: ${{ secrets.AGENT_UPDATES_BWS_ACCESS_TOKEN }}
secrets: |
${{ vars.AGENT_R2_ACCESS_KEY_ID_SECRET_ID }} > AWS_ACCESS_KEY_ID
${{ vars.AGENT_R2_SECRET_ACCESS_KEY_SECRET_ID }} > AWS_SECRET_ACCESS_KEY
set_env: false
- name: Reserve immutable exact manifest and reuse prior signed envelope
env:
AWS_ACCESS_KEY_ID: ${{ steps.r2-credentials.outputs.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ steps.r2-credentials.outputs.AWS_SECRET_ACCESS_KEY }}
R2_ACCOUNT: ${{ vars.AGENT_R2_ACCOUNT_ID }}
R2_BUCKET: ${{ vars.AGENT_R2_BUCKET }}
run: |
set -euo pipefail
[[ "$R2_ACCOUNT" =~ ^[0-9a-f]{32}$ ]]
[ -n "$R2_BUCKET" ]
nix develop --no-update-lock-file .#ci -c python3 services/agent-updates/publisher/publisher.py reserve \
--bundle bundle --public-key bundle/public-key.pub \
--endpoint "https://${R2_ACCOUNT}.r2.cloudflarestorage.com" --bucket "$R2_BUCKET"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: agent-update-reserved-${{ github.run_id }}
path: bundle/
overwrite: true
if-no-files-found: error
retention-days: 7

sign:
needs: reserve
if: ${{ github.repository == 'MaplePrivacyLabs/Maple' && github.ref == 'refs/heads/master' && github.event_name == 'workflow_dispatch' }}
environment: updates-production
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
ref: ${{ github.sha }}
persist-credentials: false
- name: Install pinned Nix
uses: DeterminateSystems/determinate-nix-action@96c5ee93a9dddf2830d31e1da6ec719077336c28 # v3.23.3
with:
source-tag: v3.23.3
github-token: ""
- name: Verify pinned Nix runtime
run: bash scripts/ci/verify-nix-runtime.sh
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: agent-update-reserved-${{ github.run_id }}
path: bundle
- name: Realize pinned publisher tools before retrieving credentials
run: nix develop --no-update-lock-file .#ci -c true
- name: Resolve Agent update key from Bitwarden Secrets Manager
id: signing-key
uses: bitwarden/sm-action@1238aae8fc64b212641190a9227c8a734ab1a793 # v3.0.1
with:
access_token: ${{ secrets.AGENT_UPDATES_BWS_ACCESS_TOKEN }}
secrets: |
${{ vars.AGENT_UPDATE_SIGNING_KEY_ID }} > AGENT_UPDATE_SIGNING_KEY
set_env: false
- name: Sign exact reserved manifest with Agent-only key
env:
AGENT_UPDATE_SIGNING_KEY: ${{ steps.signing-key.outputs.AGENT_UPDATE_SIGNING_KEY }}
run: |
set -euo pipefail
umask 077
key_file="$(mktemp)"
trap 'rm -f "$key_file"' EXIT
[ -n "$AGENT_UPDATE_SIGNING_KEY" ]
printf '%s\n' "$AGENT_UPDATE_SIGNING_KEY" > "$key_file"
unset AGENT_UPDATE_SIGNING_KEY
nix develop --no-update-lock-file .#ci -c python3 services/agent-updates/publisher/publisher.py sign \
--bundle bundle --public-key bundle/public-key.pub --secret-key "$key_file"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: agent-update-signed-${{ github.run_id }}
path: bundle/
overwrite: true
if-no-files-found: error
retention-days: 7

publish:
needs: sign
if: ${{ github.repository == 'MaplePrivacyLabs/Maple' && github.ref == 'refs/heads/master' && github.event_name == 'workflow_dispatch' }}
environment: updates-production
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
ref: ${{ github.sha }}
persist-credentials: false
- name: Install pinned Nix
uses: DeterminateSystems/determinate-nix-action@96c5ee93a9dddf2830d31e1da6ec719077336c28 # v3.23.3
with:
source-tag: v3.23.3
github-token: ""
- name: Verify pinned Nix runtime
run: bash scripts/ci/verify-nix-runtime.sh
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
with:
name: agent-update-signed-${{ github.run_id }}
path: bundle
- name: Realize pinned publisher tools before retrieving credentials
run: nix develop --no-update-lock-file .#ci -c true
- name: Resolve Agent bucket credentials from Bitwarden Secrets Manager
id: r2-credentials
uses: bitwarden/sm-action@1238aae8fc64b212641190a9227c8a734ab1a793 # v3.0.1
with:
access_token: ${{ secrets.AGENT_UPDATES_BWS_ACCESS_TOKEN }}
secrets: |
${{ vars.AGENT_R2_ACCESS_KEY_ID_SECRET_ID }} > AWS_ACCESS_KEY_ID
${{ vars.AGENT_R2_SECRET_ACCESS_KEY_SECRET_ID }} > AWS_SECRET_ACCESS_KEY
set_env: false
- name: Upload immutably, conditionally promote, and verify public delivery
env:
AWS_ACCESS_KEY_ID: ${{ steps.r2-credentials.outputs.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ steps.r2-credentials.outputs.AWS_SECRET_ACCESS_KEY }}
R2_ACCOUNT: ${{ vars.AGENT_R2_ACCOUNT_ID }}
R2_BUCKET: ${{ vars.AGENT_R2_BUCKET }}
run: |
set -euo pipefail
[[ "$R2_ACCOUNT" =~ ^[0-9a-f]{32}$ ]]
[ -n "$R2_BUCKET" ]
nix develop --no-update-lock-file .#ci -c python3 services/agent-updates/publisher/publisher.py publish \
--bundle bundle --public-key bundle/public-key.pub \
--endpoint "https://${R2_ACCOUNT}.r2.cloudflarestorage.com" --bucket "$R2_BUCKET" \
--base-url https://updates.trymaple.ai
47 changes: 47 additions & 0 deletions .github/workflows/agent-updates-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Agent Update Delivery Tests

permissions:
contents: read

on:
push:
branches: [master]
paths:
- ".github/workflows/agent-updates-*.yml"
- "services/agent-updates/**"
- "scripts/ci/agent-updates.sh"
- "scripts/ci/test_agent_updates_workflows.py"
- "scripts/ci/_common.sh"
- "apps/maple-agent/scripts/release-info.py"
- "apps/maple-agent/release-profiles.json"
- "flake.nix"
- "flake.lock"
pull_request:
paths:
- ".github/workflows/agent-updates-*.yml"
- "services/agent-updates/**"
- "scripts/ci/agent-updates.sh"
- "scripts/ci/test_agent_updates_workflows.py"
- "scripts/ci/_common.sh"
- "apps/maple-agent/scripts/release-info.py"
- "apps/maple-agent/release-profiles.json"
- "flake.nix"
- "flake.lock"

jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
persist-credentials: false
- name: Install pinned Nix
uses: DeterminateSystems/determinate-nix-action@96c5ee93a9dddf2830d31e1da6ec719077336c28 # v3.23.3
with:
source-tag: v3.23.3
github-token: ""
- name: Verify pinned Nix runtime
run: bash scripts/ci/verify-nix-runtime.sh
- name: Check isolated Agent delivery
run: nix develop --no-update-lock-file .#ci -c ./scripts/ci/agent-updates.sh
Loading
Loading