diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 079a458a75b..5bd7753191a 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -13,12 +13,12 @@ jobs: permissions: contents: read steps: - - name: Install gh-aw extension - run: curl -fsSL https://raw.githubusercontent.com/github/gh-aw/refs/heads/main/install-gh-aw.sh | bash - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + - name: Install gh-aw extension + run: bash install-gh-aw.sh - name: Set up Node.js uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f with: diff --git a/.github/workflows/daily-byok-ollama-test.lock.yml b/.github/workflows/daily-byok-ollama-test.lock.yml index 5e866e46657..896adfe2f32 100644 --- a/.github/workflows/daily-byok-ollama-test.lock.yml +++ b/.github/workflows/daily-byok-ollama-test.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"eee569f9fae2a93ae15390081598db67ecce6bb925746a1efcc2137e5d82fdde","body_hash":"bd80ca99e3f4cd56715c7a73bd9f5c56165dc64beee430b859670700764082f0","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.68"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"92a633df2d6b6105c26057412bc3df6371091bbf0c1018517abcd8015e274086","body_hash":"bd80ca99e3f4cd56715c7a73bd9f5c56165dc64beee430b859670700764082f0","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.68"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.26","digest":"sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.26@sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.26","digest":"sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.26@sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.26","digest":"sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.26@sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.33","digest":"sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.33@sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -451,9 +451,16 @@ jobs: with: name: activation path: /tmp/gh-aw - - name: Install Ollama + - env: + OLLAMA_INSTALL_SHA256: 25f64b810b947145095956533e1bdf56eacea2673c55a7e586be4515fc882c9f + OLLAMA_VERSION: 0.31.1 + name: Install Ollama run: | - curl -fsSL https://ollama.com/install.sh | sh + echo "Downloading Ollama v${OLLAMA_VERSION} install script..." + mkdir -p /tmp/gh-aw + curl -fsSL "https://github.com/ollama/ollama/releases/download/v${OLLAMA_VERSION}/install.sh" -o /tmp/gh-aw/ollama-install.sh + echo "${OLLAMA_INSTALL_SHA256} /tmp/gh-aw/ollama-install.sh" | sha256sum -c - + bash /tmp/gh-aw/ollama-install.sh - name: Generate Ollama API key run: | OLLAMA_API_KEY="$(openssl rand -hex 16)" diff --git a/.github/workflows/daily-byok-ollama-test.md b/.github/workflows/daily-byok-ollama-test.md index 7e446a1ab30..e0a68013d2b 100644 --- a/.github/workflows/daily-byok-ollama-test.md +++ b/.github/workflows/daily-byok-ollama-test.md @@ -20,8 +20,17 @@ strict: true timeout-minutes: 20 steps: - name: Install Ollama + env: + OLLAMA_VERSION: "0.31.1" + # SHA256 of install.sh from https://github.com/ollama/ollama/releases/download/v${OLLAMA_VERSION}/install.sh + # To update: curl -fsSL https://github.com/ollama/ollama/releases/download/vNEW_VERSION/install.sh | sha256sum + OLLAMA_INSTALL_SHA256: "25f64b810b947145095956533e1bdf56eacea2673c55a7e586be4515fc882c9f" run: | - curl -fsSL https://ollama.com/install.sh | sh + echo "Downloading Ollama v${OLLAMA_VERSION} install script..." + mkdir -p /tmp/gh-aw + curl -fsSL "https://github.com/ollama/ollama/releases/download/v${OLLAMA_VERSION}/install.sh" -o /tmp/gh-aw/ollama-install.sh + echo "${OLLAMA_INSTALL_SHA256} /tmp/gh-aw/ollama-install.sh" | sha256sum -c - + bash /tmp/gh-aw/ollama-install.sh - name: Generate Ollama API key run: | OLLAMA_API_KEY="$(openssl rand -hex 16)" diff --git a/.github/workflows/shared/trufflehog.md b/.github/workflows/shared/trufflehog.md index 633a1deaaf7..db69a43ae19 100644 --- a/.github/workflows/shared/trufflehog.md +++ b/.github/workflows/shared/trufflehog.md @@ -38,9 +38,22 @@ jobs: id: install-trufflehog env: TRUFFLEHOG_VERSION: "3.88.27" + # SHA256 of the linux_amd64 .tar.gz from the pinned release. Only GitHub-hosted ubuntu-latest runners + # (always amd64) are supported; self-hosted ARM runners will need a separate per-arch hash. + # To update: curl -fsSL https://github.com/trufflesecurity/trufflehog/releases/download/vNEW/trufflehog_NEW_linux_amd64.tar.gz | sha256sum + TRUFFLEHOG_SHA256: "e3b2647b7a7bc1591f316da91fd33fc7397f8e3c21e2feed791c171f0c406bc7" run: | - echo "Installing TruffleHog v${TRUFFLEHOG_VERSION}..." - curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin "v${TRUFFLEHOG_VERSION}" + ARCH=$(uname -m) + if [ "$ARCH" != "x86_64" ]; then + echo "::error::TruffleHog installation only supports x86_64 (linux_amd64); detected $ARCH. Self-hosted ARM runners must install TruffleHog separately." + exit 1 + fi + echo "Downloading TruffleHog v${TRUFFLEHOG_VERSION}..." + mkdir -p /tmp/gh-aw + curl -fsSL "https://github.com/trufflesecurity/trufflehog/releases/download/v${TRUFFLEHOG_VERSION}/trufflehog_${TRUFFLEHOG_VERSION}_linux_amd64.tar.gz" -o /tmp/gh-aw/trufflehog.tar.gz + # SHA256 covers the .tar.gz archive; the binary is extracted from the verified archive + echo "${TRUFFLEHOG_SHA256} /tmp/gh-aw/trufflehog.tar.gz" | sha256sum -c - + sudo tar -xzf /tmp/gh-aw/trufflehog.tar.gz --no-same-owner -C /usr/local/bin trufflehog trufflehog --version - name: Scan agent output for secrets diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index bf08d059eac..a0955b6a474 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"f4c25dc4e3ebaa00201eec60464e37b5d059d7c2c14344ad95baab84851c84c8","body_hash":"fae212c4ff39a682e3254fae440084a2152282e383467ff3f0c15fb9d2d69411","strict":true,"agent_id":"codex","engine_versions":{"codex":"0.142.5"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"b1584c2862c71b94968843ff97149d2dfd8b6d9e66c14bd7dbfbe0c1af4c124b","body_hash":"fae212c4ff39a682e3254fae440084a2152282e383467ff3f0c15fb9d2d69411","strict":true,"agent_id":"codex","engine_versions":{"codex":"0.142.5"}} # gh-aw-manifest: {"version":1,"secrets":["CODEX_API_KEY","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN","OPENAI_API_KEY"],"actions":[{"repo":"actions-ecosystem/action-add-labels","sha":"c96b68fec76a0987cd93957189e9abd0b9a72ff1","version":"v1.1.3"},{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"924ae3a1cded613372ab5595356fb5720e22ba16","version":"v6.5.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.26","digest":"sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.26@sha256:b283b1f037d2e068532fe178a06f2944696c3933ba11604979134e7896ac6f8c"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.26","digest":"sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.26@sha256:1743dbac9bf4225f3acfdcbce4f77f5a3e61e22b2e929305525f00196693c015"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.26","digest":"sha256:6006b1c579ca550e023697b5dfd832ae03361328a4c0f2eb49fb181a6b8d7a4b","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.26@sha256:6006b1c579ca550e023697b5dfd832ae03361328a4c0f2eb49fb181a6b8d7a4b"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.26","digest":"sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.26@sha256:d4647c3bfaf80889eb1dbd3d4e2063340cbf94c0ca6c5747bbfc8507b12f3485"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.33","digest":"sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.33@sha256:f0e1f1d562f01e737f3ef125cb4d7057ff87a68a97927f5ea9fd6d3f5091da06"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"},{"image":"ghcr.io/github/serena-mcp-server:latest","digest":"sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5","pinned_image":"ghcr.io/github/serena-mcp-server:latest@sha256:bf343399e3725c45528f531a230f3a04521d4cdef29f9a5af6282ff0d3c393c5"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -2423,10 +2423,20 @@ jobs: - name: Install TruffleHog id: install-trufflehog run: | - echo "Installing TruffleHog v${TRUFFLEHOG_VERSION}..." - curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin "v${TRUFFLEHOG_VERSION}" + ARCH=$(uname -m) + if [ "$ARCH" != "x86_64" ]; then + echo "::error::TruffleHog installation only supports x86_64 (linux_amd64); detected $ARCH. Self-hosted ARM runners must install TruffleHog separately." + exit 1 + fi + echo "Downloading TruffleHog v${TRUFFLEHOG_VERSION}..." + mkdir -p /tmp/gh-aw + curl -fsSL "https://github.com/trufflesecurity/trufflehog/releases/download/v${TRUFFLEHOG_VERSION}/trufflehog_${TRUFFLEHOG_VERSION}_linux_amd64.tar.gz" -o /tmp/gh-aw/trufflehog.tar.gz + # SHA256 covers the .tar.gz archive; the binary is extracted from the verified archive + echo "${TRUFFLEHOG_SHA256} /tmp/gh-aw/trufflehog.tar.gz" | sha256sum -c - + sudo tar -xzf /tmp/gh-aw/trufflehog.tar.gz --no-same-owner -C /usr/local/bin trufflehog trufflehog --version env: + TRUFFLEHOG_SHA256: e3b2647b7a7bc1591f316da91fd33fc7397f8e3c21e2feed791c171f0c406bc7 TRUFFLEHOG_VERSION: 3.88.27 - name: Scan agent output for secrets id: scan-agent-output diff --git a/Makefile b/Makefile index 88a141ab5ec..da1bdbe9628 100644 --- a/Makefile +++ b/Makefile @@ -1013,6 +1013,13 @@ sync-action-scripts: @chmod +x actions/setup-cli/install.sh @echo "✓ Action scripts synced successfully" +# Sync install-gh-aw.sh SHA/hash constants in pkg/cli/copilot_setup.go +.PHONY: sync-install-script-hashes +sync-install-script-hashes: + @echo "Updating install-gh-aw.sh SHA and SHA256 constants..." + @bash scripts/update-install-script-hashes.sh + @echo "✓ Install script hashes synced successfully" + # Recompile all workflow files .PHONY: recompile recompile: build @@ -1052,6 +1059,7 @@ dependabot: build update: build ./$(BINARY_NAME) update $(MAKE) sync-action-pins + $(MAKE) sync-install-script-hashes $(MAKE) build # Run development server @@ -1190,6 +1198,7 @@ help: @echo " install - Install binary locally" @echo " sync-action-pins - Sync actions-lock.json from .github/aw to pkg/actionpins/data and pkg/workflow/data (runs automatically during build)" @echo " sync-action-scripts - Sync install-gh-aw.sh to actions/setup-cli/install.sh (runs automatically during build)" + @echo " sync-install-script-hashes - Update install-gh-aw.sh SHA and SHA256 constants in pkg/cli/copilot_setup.go (runs automatically during update)" @echo " update - Update GitHub Actions and workflows, sync action pins, and rebuild binary" @echo " fix - Apply automatic codemod-style fixes to workflow files (depends on build)" @echo " recompile - Recompile all workflow files (runs init, depends on build)" diff --git a/pkg/cli/copilot_setup.go b/pkg/cli/copilot_setup.go index 5c91ecf731d..2926fd92309 100644 --- a/pkg/cli/copilot_setup.go +++ b/pkg/cli/copilot_setup.go @@ -3,6 +3,8 @@ package cli import ( "bytes" "context" + "crypto/sha256" + "encoding/hex" "fmt" "os" "path/filepath" @@ -12,13 +14,61 @@ import ( "github.com/github/gh-aw/pkg/console" "github.com/github/gh-aw/pkg/constants" "github.com/github/gh-aw/pkg/fileutil" + "github.com/github/gh-aw/pkg/gitutil" "github.com/github/gh-aw/pkg/logger" "github.com/github/gh-aw/pkg/workflow" ) var copilotSetupLog = logger.New("cli:copilot_setup") -// getActionRef returns the action reference string based on action mode and version. +// installScriptTempPath is the temporary file path used for the downloaded gh-aw install script. +const installScriptTempPath = "/tmp/gh-aw/install-gh-aw.sh" + +// copilotSetupStepsStaticSHA is the pinned commit SHA of install-gh-aw.sh used in the static +// YAML test template and as the fallback when ResolveGhAwRef is unavailable. +// Run scripts/update-install-script-hashes.sh to refresh both this value and copilotSetupStepsStaticSHA256. +const copilotSetupStepsStaticSHA = "21a6827c430f89d3b7443074cfc8bd25b84d278f" + +// copilotSetupStepsStaticSHA256 is the SHA256 hex digest of install-gh-aw.sh at copilotSetupStepsStaticSHA. +// Run scripts/update-install-script-hashes.sh to refresh both this value and copilotSetupStepsStaticSHA. +const copilotSetupStepsStaticSHA256 = "248ccebcb998c6a506548156e1bf9f02429cbbaec407d5adbdfd316ab0f866a0" + +// sha256HexRegex matches a valid lowercase SHA256 hex digest (exactly 64 hex chars). +var sha256HexRegex = regexp.MustCompile(`^[0-9a-f]{64}$`) + +// resolveInstallScriptSHA256 fetches install-gh-aw.sh at the given immutable commit SHA +// and returns its SHA256 hex digest for use in a sha256sum integrity check. +// Returns an empty string and logs a warning if the fetch or computation fails. +func resolveInstallScriptSHA256(ctx context.Context, commitSHA string) string { + if !gitutil.IsValidFullSHA(commitSHA) { + copilotSetupLog.Printf("resolveInstallScriptSHA256: commitSHA %q is not a valid full SHA, skipping", commitSHA) + return "" + } + scriptURL := fmt.Sprintf("https://raw.githubusercontent.com/github/gh-aw/%s/install-gh-aw.sh", commitSHA) + res, err := FetchImportURL(ctx, scriptURL, FetchOptions{}) + if err != nil { + copilotSetupLog.Printf("Could not fetch install-gh-aw.sh from %s to compute SHA256: %v", scriptURL, err) + return "" + } + h := sha256.Sum256(res.Body) + return hex.EncodeToString(h[:]) +} + +// sha256CheckLine returns a YAML-indented shell command (with trailing newline) that verifies +// digest against path using sha256sum. Returns an empty string if either parameter is invalid. +// digest must be a 64-char lowercase hex string (sha256HexRegex); path must contain only +// safe filesystem characters (no spaces, quotes, or shell metacharacters). +func sha256CheckLine(digest, path string) string { + if !sha256HexRegex.MatchString(digest) { + return "" + } + if path == "" || strings.ContainsAny(path, " \t\n\"'\\$`;&|<>(){}*?") { + copilotSetupLog.Printf("sha256CheckLine: unsafe path %q, skipping", path) + return "" + } + return fmt.Sprintf(` echo "%s %s" | sha256sum -c -`+"\n", digest, path) +} + // If a resolver is provided and mode is release or action, attempts to resolve the SHA for a SHA-pinned reference. // Falls back to a version tag reference if SHA resolution fails or resolver is nil. func getActionRef(ctx context.Context, actionMode workflow.ActionMode, version string, resolver workflow.SHAResolver) string { @@ -82,8 +132,19 @@ jobs: `, actionRepo, actionRef, version) } - // Default (dev/script mode): use curl to download install script - return `name: "Copilot Setup Steps" + // Default (dev/script mode): try to resolve the main branch to a pinned SHA so the + // downloaded script is immutable; fall back to the mutable branch ref if unavailable. + installRef := "refs/heads/main" + installSHA256 := "" + if sha, err := workflow.ResolveGhAwRef(ctx, "main"); err == nil && sha != "" { + installRef = sha + // Fetch the script to compute an explicit SHA256 integrity check line. + installSHA256 = resolveInstallScriptSHA256(ctx, sha) + } else { + copilotSetupLog.Printf("Could not resolve github/gh-aw main SHA for dev-mode template, falling back to mutable ref: %v", err) + } + sha256Cmd := sha256CheckLine(installSHA256, installScriptTempPath) + return fmt.Sprintf(`name: "Copilot Setup Steps" # This workflow configures the environment for GitHub Copilot Agent with gh-aw MCP server on: @@ -105,11 +166,17 @@ jobs: steps: - name: Install gh-aw extension run: | - curl -fsSL https://raw.githubusercontent.com/github/gh-aw/refs/heads/main/install-gh-aw.sh | bash -` + mkdir -p /tmp/gh-aw + curl -fsSL https://raw.githubusercontent.com/github/gh-aw/%s/install-gh-aw.sh -o %s +%s bash %s +`, installRef, installScriptTempPath, sha256Cmd, installScriptTempPath) } -const copilotSetupStepsYAML = `name: "Copilot Setup Steps" +// copilotSetupStepsYAML is a static dev-mode template used only for YAML validity tests. +// It is built from copilotSetupStepsStaticSHA and copilotSetupStepsStaticSHA256 so that +// scripts/update-install-script-hashes.sh can refresh both values in a single place. +// The runtime function generateCopilotSetupStepsYAML resolves the ref dynamically via ResolveGhAwRef. +var copilotSetupStepsYAML = fmt.Sprintf(`name: "Copilot Setup Steps" # This workflow configures the environment for GitHub Copilot Agent with gh-aw MCP server on: @@ -131,8 +198,11 @@ jobs: steps: - name: Install gh-aw extension run: | - curl -fsSL https://raw.githubusercontent.com/github/gh-aw/refs/heads/main/install-gh-aw.sh | bash -` + mkdir -p /tmp/gh-aw + curl -fsSL https://raw.githubusercontent.com/github/gh-aw/%s/install-gh-aw.sh -o %s + echo "%s %s" | sha256sum -c - + bash %s +`, copilotSetupStepsStaticSHA, installScriptTempPath, copilotSetupStepsStaticSHA256, installScriptTempPath, installScriptTempPath) // CopilotWorkflowStep represents a GitHub Actions workflow step for Copilot setup scaffolding type CopilotWorkflowStep struct { @@ -278,9 +348,22 @@ func renderCopilotSetupUpdateInstructions(ctx context.Context, filePath string, fmt.Fprintln(os.Stderr, " with:") fmt.Fprintln(os.Stderr, " version: "+version) } else { + // Dev/script mode: try to resolve main to a pinned SHA so the instructions emit an + // immutable URL; fall back to the mutable branch ref if resolution is unavailable. + installRef := "refs/heads/main" + installSHA256 := "" + if sha, err := workflow.ResolveGhAwRef(ctx, "main"); err == nil && sha != "" { + installRef = sha + installSHA256 = resolveInstallScriptSHA256(ctx, sha) + } fmt.Fprintln(os.Stderr, " - name: Install gh-aw extension") fmt.Fprintln(os.Stderr, " run: |") - fmt.Fprintln(os.Stderr, " curl -fsSL https://raw.githubusercontent.com/github/gh-aw/refs/heads/main/install-gh-aw.sh | bash") + fmt.Fprintln(os.Stderr, " mkdir -p /tmp/gh-aw") + fmt.Fprintln(os.Stderr, " curl -fsSL https://raw.githubusercontent.com/github/gh-aw/"+installRef+"/install-gh-aw.sh -o "+installScriptTempPath) + if line := sha256CheckLine(installSHA256, installScriptTempPath); line != "" { + fmt.Fprint(os.Stderr, line) // sha256CheckLine includes trailing newline + } + fmt.Fprintln(os.Stderr, " bash "+installScriptTempPath) } fmt.Fprintln(os.Stderr) } diff --git a/scripts/update-install-script-hashes.sh b/scripts/update-install-script-hashes.sh new file mode 100755 index 00000000000..3fdb44f8a1c --- /dev/null +++ b/scripts/update-install-script-hashes.sh @@ -0,0 +1,65 @@ +#!/bin/bash +set +o histexpand + +# Updates copilotSetupStepsStaticSHA and copilotSetupStepsStaticSHA256 in +# pkg/cli/copilot_setup.go to the current HEAD commit of the main branch. +# +# Usage: +# scripts/update-install-script-hashes.sh +# +# Requirements: gh (GitHub CLI, authenticated), sha256sum (coreutils), curl, sed +# +# Environment Variables: +# GITHUB_TOKEN - Optional. GitHub token for authentication (used by gh CLI). +set -euo pipefail + +REPO="github/gh-aw" +FILE="pkg/cli/copilot_setup.go" + +# Accept a GITHUB_TOKEN forwarded from CI +if [ -n "${GITHUB_TOKEN:-}" ]; then + export GH_TOKEN="$GITHUB_TOKEN" +fi + +if ! command -v gh &>/dev/null; then + echo "error: GitHub CLI (gh) not found. Install it from https://cli.github.com" >&2 + exit 1 +fi + +echo "Resolving HEAD SHA of ${REPO} main branch..." +NEW_SHA=$(gh api "repos/${REPO}/commits/main" --jq '.sha') +if [ -z "$NEW_SHA" ] || [ "${#NEW_SHA}" -ne 40 ]; then + echo "error: could not resolve main HEAD SHA (got: '${NEW_SHA}')" >&2 + exit 1 +fi +echo " HEAD SHA: ${NEW_SHA}" + +SCRIPT_URL="https://raw.githubusercontent.com/${REPO}/${NEW_SHA}/install-gh-aw.sh" +echo "Downloading install-gh-aw.sh at ${NEW_SHA}..." +TMPFILE=$(mktemp /tmp/install-gh-aw-XXXXXX.sh) +trap 'rm -f "$TMPFILE"' EXIT +curl -fsSL "$SCRIPT_URL" -o "$TMPFILE" + +NEW_SHA256=$(sha256sum "$TMPFILE" | awk '{print $1}') +if [ -z "$NEW_SHA256" ] || [ "${#NEW_SHA256}" -ne 64 ]; then + echo "error: could not compute SHA256 (got: '${NEW_SHA256}')" >&2 + exit 1 +fi +echo " SHA256: ${NEW_SHA256}" + +OLD_SHA=$(grep 'copilotSetupStepsStaticSHA = ' "$FILE" | sed 's/.*"\([^"]*\)".*/\1/') +OLD_SHA256=$(grep 'copilotSetupStepsStaticSHA256 = ' "$FILE" | sed 's/.*"\([^"]*\)".*/\1/') + +if [ "$OLD_SHA" = "$NEW_SHA" ] && [ "$OLD_SHA256" = "$NEW_SHA256" ]; then + echo "No changes needed — values are already up to date." + exit 0 +fi + +echo "Updating ${FILE}..." +sed -i \ + -e "s|copilotSetupStepsStaticSHA = \"[0-9a-f]*\"|copilotSetupStepsStaticSHA = \"${NEW_SHA}\"|" \ + -e "s|copilotSetupStepsStaticSHA256 = \"[0-9a-f]*\"|copilotSetupStepsStaticSHA256 = \"${NEW_SHA256}\"|" \ + "$FILE" + +echo "Done. Review the diff and commit:" +echo " git diff ${FILE}"