Skip to content
Closed
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
20 changes: 20 additions & 0 deletions .github/scripts/assert-aws-account.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
set -euo pipefail

expected_account_id="${1:-${AWS_ACCOUNT_ID:-}}"
stage="${2:-${SST_STAGE:-unknown}}"
operation="${3:-deploy}"

if [ -z "$expected_account_id" ]; then
echo "Missing expected AWS account ID. Pass it as the first argument or set AWS_ACCOUNT_ID." >&2
exit 1
fi

actual_account_id="$(aws sts get-caller-identity --query Account --output text)"

if [ "$actual_account_id" != "$expected_account_id" ]; then
echo "stage ${stage} must ${operation} in AWS account ${expected_account_id}, but the current credentials resolved to ${actual_account_id}." >&2
exit 1
fi

echo "Using AWS account ${actual_account_id} for stage ${stage}."
30 changes: 30 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: CI

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

jobs:
build:
name: Typecheck and build
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build
69 changes: 69 additions & 0 deletions .github/workflows/deploy-site.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Deploy Site (SST)

on:
push:
branches:
- main
paths:
- site/**
- sst.config.ts
- package.json
- package-lock.json
- .github/workflows/deploy-site.yml
- .github/scripts/assert-aws-account.sh
workflow_dispatch:

concurrency:
group: sst-pear-production
cancel-in-progress: false

permissions:
contents: read
id-token: write

jobs:
deploy-production:
name: Deploy production
runs-on: ubuntu-latest
environment: prod
env:
AWS_ACCOUNT_ID: ${{ vars.AWS_ACCOUNT_ID }}
AWS_REGION: ${{ vars.AWS_REGION }}
SST_STAGE: production
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm

- name: Install dependencies
run: npm ci

- name: Validate Cloudflare token
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
if [ -z "${CLOUDFLARE_API_TOKEN}" ]; then
echo "Missing required secret: CLOUDFLARE_API_TOKEN"
exit 1
fi

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v5.1.1
with:
role-to-assume: ${{ vars.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ env.AWS_REGION }}
role-session-name: gha-pear-prod-${{ github.run_id }}

- name: Assert AWS account
run: bash .github/scripts/assert-aws-account.sh "$AWS_ACCOUNT_ID" "$SST_STAGE" deploy

- name: Deploy production SST app
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_DEFAULT_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
run: npx sst deploy --stage "$SST_STAGE"
104 changes: 104 additions & 0 deletions .github/workflows/preview-site.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: Preview Site (SST)

on:
workflow_dispatch:
inputs:
pr_number:
description: Pull request number to deploy as pr-<number>
required: true
type: number
operation:
description: Deploy or remove the preview
required: true
default: deploy
type: choice
options:
- deploy
- remove

permissions:
contents: read
id-token: write
issues: write
pull-requests: write

concurrency:
group: sst-pear-pr-${{ inputs.pr_number }}
cancel-in-progress: false

jobs:
preview:
name: ${{ inputs.operation == 'remove' && 'Cleanup preview' || 'Deploy preview' }}
runs-on: ubuntu-latest
environment: preview
timeout-minutes: 30
env:
AWS_ACCOUNT_ID: ${{ vars.AWS_ACCOUNT_ID }}
AWS_REGION: ${{ vars.AWS_REGION }}
SST_STAGE: pr-${{ inputs.pr_number }}
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm

- name: Install dependencies
run: npm ci

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v5.1.1
with:
role-to-assume: ${{ vars.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ env.AWS_REGION }}
role-session-name: gha-pear-preview-${{ inputs.operation }}-${{ github.run_id }}

- name: Assert AWS account
run: bash .github/scripts/assert-aws-account.sh "$AWS_ACCOUNT_ID" "$SST_STAGE" "${{ inputs.operation }}"

- name: Deploy SST preview
if: inputs.operation == 'deploy'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_DEFAULT_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
run: npx sst deploy --stage "$SST_STAGE"

- name: Remove SST preview
if: inputs.operation == 'remove'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_DEFAULT_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
run: npx sst remove --stage "$SST_STAGE"

- name: Comment on PR
if: inputs.operation == 'deploy'
continue-on-error: true
uses: actions/github-script@v7
with:
script: |
const prNum = Number('${{ inputs.pr_number }}');
const url = `https://pr-${prNum}.pear.agentrelay.com`;
const body = `**Pear preview deployed**\n\n` +
`| Environment | URL |\n` +
`|-------------|-----|\n` +
`| Site | ${url} |\n\n` +
`This preview was deployed manually. Run this workflow with operation \`remove\` to clean it up.`;

const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNum,
});
const existing = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('Pear preview deployed')
);

const params = { owner: context.repo.owner, repo: context.repo.repo, body };
if (existing) {
await github.rest.issues.updateComment({ ...params, comment_id: existing.id });
} else {
await github.rest.issues.createComment({ ...params, issue_number: prNum });
}
56 changes: 56 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Release Installers

on:
push:
tags:
- 'v*'
workflow_dispatch:

permissions:
contents: write

jobs:
build:
name: Build ${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: macOS universal
os: macos-latest
command: npm run release -- --mac --universal
csc_link_secret: MACOS_CSC_LINK
csc_key_password_secret: MACOS_CSC_KEY_PASSWORD
- name: Windows x64
os: windows-latest
command: npm run release -- --win --x64
csc_link_secret: WINDOWS_CSC_LINK
csc_key_password_secret: WINDOWS_CSC_KEY_PASSWORD
- name: Linux x64
os: ubuntu-latest
command: npm run release -- --linux --x64
csc_link_secret: ''
csc_key_password_secret: ''
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: npm

- name: Install dependencies
run: npm ci

- name: Build and publish installers
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: ${{ matrix.csc_link_secret != '' && secrets[matrix.csc_link_secret] || '' }}
CSC_KEY_PASSWORD: ${{ matrix.csc_key_password_secret != '' && secrets[matrix.csc_key_password_secret] || '' }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: ${{ matrix.command }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ node_modules/
out/
dist/
.npm-cache/
.sst
*.tsbuildinfo
*.env
.DS_Store
/.agent-relay
70 changes: 70 additions & 0 deletions docs/release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Pear Release Runbook

Pear ships as signed Electron installers published to GitHub Releases. The
install page and bootstrap scripts are deployed with SST at:

```sh
https://pear.agentrelay.com
```

## Install Commands

macOS and Linux:

```sh
curl -fsSL https://pear.agentrelay.com/install.sh | sh
```

or:

```sh
wget -qO- https://pear.agentrelay.com/install.sh | sh
```

Windows PowerShell:

```powershell
iwr https://pear.agentrelay.com/install.ps1 -UseB | iex
```

## Release A Version

1. Update `package.json` version.
2. Merge to `main`.
3. Push a matching tag:

```sh
git tag v1.0.1
git push origin v1.0.1
```

The `Release Installers` workflow builds and publishes:

- `Pear-mac-universal.dmg`
- `Pear-mac-universal.zip`
- `Pear-win-x64.exe`
- `Pear-linux-x64.AppImage`
- `Pear-linux-x64.deb`
- electron-updater metadata files

Installed apps check GitHub Releases for updates on launch and every six hours.

## Required Repository Secrets

The release workflow can build unsigned artifacts, but production auto-update
requires signed macOS and Windows builds.

- `MACOS_CSC_LINK`
- `MACOS_CSC_KEY_PASSWORD`
- `APPLE_ID`
- `APPLE_APP_SPECIFIC_PASSWORD`
- `APPLE_TEAM_ID`
- `WINDOWS_CSC_LINK`
- `WINDOWS_CSC_KEY_PASSWORD`

The SST deploy workflows use the same environment variables and secrets as the
Agent Relay web deploy:

- Repository/environment variables: `AWS_ACCOUNT_ID`, `AWS_REGION`,
`AWS_ROLE_TO_ASSUME`, `CLOUDFLARE_ACCOUNT_ID`
- Secret: `CLOUDFLARE_API_TOKEN`
Loading