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
27 changes: 27 additions & 0 deletions .claude/commands/build-backstage-server.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
description: Build, lint, test, and run the backstage-server locally
allowed-tools: Bash, Read
model: sonnet
---

# Build Backstage Server

Build, validate, and run the `backstage-server` subproject locally for inspection. Stop immediately on any failure and report the error.

## Workflow

Run each step sequentially from the `backstage-server/` directory. If any step fails, stop and report the failure clearly.

1. **Install dependencies**: `cd backstage-server && bun install`
2. **Lint**: `cd backstage-server && bun run lint`
3. **Typecheck**: `cd backstage-server && bun run typecheck`
4. **Test**: `cd backstage-server && bun test`
5. **Build**: `cd backstage-server && bun run build` (builds frontend, embeds assets, produces standalone binary at `dist/backstage-server`)
6. **Verify binary**: `ls -lh backstage-server/dist/backstage-server` (confirm binary exists and report its size)
7. **Run dev server**: `cd backstage-server && bun run dev` (run in background with hot-reload on port 7007)
8. **Report**: Confirm the server is running at http://localhost:7007. Note that it proxies to the Operator API at :7008.

## Notes

- If port 7007 is already in use, report the conflict and suggest killing the existing process.
- To stop the dev server later, kill the background Bun process.
23 changes: 23 additions & 0 deletions .claude/commands/build-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
description: Build and serve the docs site locally with Jekyll
allowed-tools: Bash, Read
model: sonnet
---

# Build Docs

Build and serve the `docs/` subproject locally for inspection. Stop immediately on any failure and report the error.

## Workflow

Run each step sequentially from the `docs/` directory. If any step fails, stop and report the failure clearly.

1. **Install dependencies**: `cd docs && bundle install`
2. **Build site**: `cd docs && bundle exec jekyll build`
3. **Serve locally**: `cd docs && bundle exec jekyll serve` (run in background so the session remains interactive; serves on port 4000)
4. **Report**: Confirm the site is running at http://localhost:4000. Let the user know it auto-rebuilds on file changes.

## Notes

- If port 4000 is already in use, report the conflict and suggest killing the existing process or using `--port` to pick a different one.
- To stop the server later, kill the background Jekyll process.
28 changes: 28 additions & 0 deletions .claude/commands/build-vscode-extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
description: Build, lint, test, package, and install the VS Code extension locally
allowed-tools: Bash, Read
model: sonnet
---

# Build VS Code Extension

Build, validate, package, and install the `vscode-extension` subproject for local inspection. Stop immediately on any failure and report the error.

## Workflow

Run each step sequentially from the `vscode-extension/` directory. If any step fails, stop and report the failure clearly.

1. **Install dependencies**: `cd vscode-extension && npm install`
2. **Lint**: `cd vscode-extension && npm run lint`
3. **Compile**: `cd vscode-extension && npm run compile`
4. **Test**: `cd vscode-extension && npm test` (note: requires a display environment for @vscode/test-electron; if tests fail due to missing display, report it and continue)
5. **Package**: `cd vscode-extension && npm run package` (creates a `.vsix` file via vsce)
6. **Detect version**: `cd vscode-extension && node -p "require('./package.json').version"`
7. **Install extension**: `cd vscode-extension && code --install-extension ./operator-terminals-VERSION.vsix` (substitute the detected version)
8. **Report**: Confirm the extension was installed successfully. Remind the user they must reload their VS Code window (`Developer: Reload Window` from the command palette) for changes to take effect.

## Notes

- The `npm run compile` step runs `copy-types` then `tsc`.
- The `.vsix` filename follows the pattern `operator-terminals-VERSION.vsix`.
- If `code` CLI is not on PATH, suggest the user install it via VS Code command palette: "Shell Command: Install 'code' command in PATH".
69 changes: 68 additions & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,37 @@ jobs:
cp target/${{ matrix.target }}/release/operator ${{ matrix.artifact_name }}
fi

# Apple code signing + notarization (macOS only)
- name: Codesign operator binary
if: matrix.os == 'macos-14'
env:
APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: scripts/codesign.sh "${{ matrix.artifact_name }}"

- name: Notarize operator binary
if: matrix.os == 'macos-14'
env:
APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
run: scripts/notarize.sh "${{ matrix.artifact_name }}"

- name: Codesign backstage-server binary
if: matrix.os == 'macos-14' && steps.backstage.outputs.exists == 'true'
env:
APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: scripts/codesign.sh "target/backstage-server-${{ matrix.bun_target }}"

- name: Notarize backstage-server binary
if: matrix.os == 'macos-14' && steps.backstage.outputs.exists == 'true'
env:
APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
run: scripts/notarize.sh "target/backstage-server-${{ matrix.bun_target }}"

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -314,6 +345,24 @@ jobs:
if: runner.os != 'Windows'
run: strip target/release/opr8r || true

# Apple code signing + notarization (macOS only)
- name: Codesign opr8r binary
if: matrix.os == 'macos-14'
working-directory: .
env:
APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: scripts/codesign.sh opr8r/target/release/opr8r

- name: Notarize opr8r binary
if: matrix.os == 'macos-14'
working-directory: .
env:
APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
run: scripts/notarize.sh opr8r/target/release/opr8r

- name: Rename binary
shell: bash
run: |
Expand All @@ -329,8 +378,26 @@ jobs:
name: ${{ matrix.artifact_name }}
path: ${{ matrix.artifact_name }}

release:
sign-windows:
needs: [build, build-opr8r]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
strategy:
matrix:
artifact:
- operator-windows-x86_64.exe
- opr8r-windows-x86_64.exe
uses: ./.github/workflows/sign-windows.yaml
with:
artifact-name: ${{ matrix.artifact }}
secrets:
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
AZ_PIPELINES_PAT: ${{ secrets.AZ_PIPELINES_PAT }}
AZ_ORG: ${{ secrets.AZ_ORG }}
AZ_PROJECT: ${{ secrets.AZ_PROJECT }}
AZ_PIPELINE_DEFINITION_ID: ${{ secrets.AZ_PIPELINE_DEFINITION_ID }}

release:
needs: [build, build-opr8r, sign-windows]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
33 changes: 32 additions & 1 deletion .github/workflows/opr8r.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,40 @@ jobs:
if: runner.os != 'Windows'
run: strip target/release/opr8r || true

# Apple code signing + notarization (macOS only)
- name: Codesign opr8r binary
if: matrix.os == 'macos-14'
working-directory: .
env:
APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: scripts/codesign.sh opr8r/target/release/opr8r

- name: Notarize opr8r binary
if: matrix.os == 'macos-14'
working-directory: .
env:
APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
run: scripts/notarize.sh opr8r/target/release/opr8r

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: opr8r/target/release/opr8r${{ matrix.extension || '' }}
retention-days: 30
retention-days: 30

sign-windows:
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: ./.github/workflows/sign-windows.yaml
with:
artifact-name: opr8r-windows-x86_64
secrets:
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}
AZ_PIPELINES_PAT: ${{ secrets.AZ_PIPELINES_PAT }}
AZ_ORG: ${{ secrets.AZ_ORG }}
AZ_PROJECT: ${{ secrets.AZ_PROJECT }}
AZ_PIPELINE_DEFINITION_ID: ${{ secrets.AZ_PIPELINE_DEFINITION_ID }}
148 changes: 148 additions & 0 deletions .github/workflows/sign-windows.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
name: Sign Windows Binary

on:
workflow_call:
inputs:
artifact-name:
description: GitHub Actions artifact name containing the unsigned .exe
required: true
type: string
secrets:
AZURE_STORAGE_CONNECTION_STRING:
required: true
AZ_PIPELINES_PAT:
required: true
AZ_ORG:
required: true
AZ_PROJECT:
required: true
AZ_PIPELINE_DEFINITION_ID:
required: true

jobs:
sign:
runs-on: ubuntu-latest
env:
BLOB_NAME: ${{ github.run_id }}-${{ inputs.artifact-name }}
SIGNED_BLOB_NAME: signed-${{ github.run_id }}-${{ inputs.artifact-name }}
steps:
- name: Download unsigned artifact
uses: actions/download-artifact@v4
with:
name: ${{ inputs.artifact-name }}
path: unsigned

- name: Find .exe file
id: find-exe
run: |
EXE_PATH=$(find unsigned -name '*.exe' -type f | head -n 1)
if [ -z "$EXE_PATH" ]; then
echo "::error::No .exe file found in artifact ${{ inputs.artifact-name }}"
exit 1
fi
EXE_NAME=$(basename "$EXE_PATH")
echo "exe_path=$EXE_PATH" >> "$GITHUB_OUTPUT"
echo "exe_name=$EXE_NAME" >> "$GITHUB_OUTPUT"
echo "Found: $EXE_PATH"

- name: Upload unsigned binary to Azure Blob
run: |
az storage blob upload \
--connection-string "${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}" \
--container-name signing-stage \
--name "$BLOB_NAME" \
--file "${{ steps.find-exe.outputs.exe_path }}" \
--overwrite

- name: Trigger Azure Pipeline
id: trigger
run: |
RESPONSE=$(curl -s -w "\n%{http_code}" \
-u ":${{ secrets.AZ_PIPELINES_PAT }}" \
-X POST \
-H "Content-Type: application/json" \
-d '{
"templateParameters": {
"blobName": "'"$BLOB_NAME"'",
"signedBlobName": "'"$SIGNED_BLOB_NAME"'"
}
}' \
"https://dev.azure.com/${{ secrets.AZ_ORG }}/${{ secrets.AZ_PROJECT }}/_apis/pipelines/${{ secrets.AZ_PIPELINE_DEFINITION_ID }}/runs?api-version=7.1")

HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
BODY=$(echo "$RESPONSE" | sed '$d')

if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then
echo "::error::Azure Pipeline trigger failed (HTTP $HTTP_CODE): $BODY"
exit 1
fi

BUILD_ID=$(echo "$BODY" | jq -r '.id')
echo "build_id=$BUILD_ID" >> "$GITHUB_OUTPUT"
echo "Triggered Azure Pipeline build $BUILD_ID"

- name: Poll for Azure Pipeline completion
run: |
BUILD_ID=${{ steps.trigger.outputs.build_id }}
MAX_ATTEMPTS=40 # 40 × 15s = 10 minutes
ATTEMPT=0

while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
ATTEMPT=$((ATTEMPT + 1))
sleep 15

RESPONSE=$(curl -s \
-u ":${{ secrets.AZ_PIPELINES_PAT }}" \
"https://dev.azure.com/${{ secrets.AZ_ORG }}/${{ secrets.AZ_PROJECT }}/_apis/build/builds/${BUILD_ID}?api-version=7.1")

STATUS=$(echo "$RESPONSE" | jq -r '.status')
RESULT=$(echo "$RESPONSE" | jq -r '.result')
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS: status=$STATUS result=$RESULT"

if [ "$STATUS" = "completed" ]; then
if [ "$RESULT" = "succeeded" ]; then
echo "Azure Pipeline succeeded"
exit 0
else
echo "::error::Azure Pipeline finished with result: $RESULT"
exit 1
fi
fi
done

echo "::error::Azure Pipeline timed out after 10 minutes"
exit 1

- name: Download signed binary
run: |
mkdir -p signed
az storage blob download \
--connection-string "${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}" \
--container-name signing-stage \
--name "$SIGNED_BLOB_NAME" \
--file "signed/${{ steps.find-exe.outputs.exe_name }}"

- name: Upload signed artifact
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-name }}
path: signed/${{ steps.find-exe.outputs.exe_name }}
overwrite: true

- name: Cleanup unsigned blob
if: always()
run: |
az storage blob delete \
--connection-string "${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}" \
--container-name signing-stage \
--name "$BLOB_NAME" \
--delete-snapshots include 2>/dev/null || true

- name: Cleanup signed blob
if: always()
run: |
az storage blob delete \
--connection-string "${{ secrets.AZURE_STORAGE_CONNECTION_STRING }}" \
--container-name signing-stage \
--name "$SIGNED_BLOB_NAME" \
--delete-snapshots include 2>/dev/null || true
18 changes: 18 additions & 0 deletions .github/workflows/vscode-extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,24 @@ jobs:
if: runner.os != 'Windows'
run: strip target/release/opr8r || true

# Apple code signing + notarization (macOS only)
- name: Codesign opr8r binary
if: matrix.os == 'macos-14'
working-directory: .
env:
APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
run: scripts/codesign.sh opr8r/target/release/opr8r

- name: Notarize opr8r binary
if: matrix.os == 'macos-14'
working-directory: .
env:
APPLE_NOTARY_KEY_BASE64: ${{ secrets.APPLE_NOTARY_KEY_BASE64 }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
run: scripts/notarize.sh opr8r/target/release/opr8r

- name: Upload artifact
uses: actions/upload-artifact@v4
with:
Expand Down
Loading