feat: Create VM bootstrap script for repository setup#797
Conversation
Create minimal shell script to bootstrap Ubuntu VMs with tools needed to work on the ai-agents repository, matching copilot-setup-steps.yml. Installs: Node.js LTS, PowerShell Core, GitHub CLI, uv, markdownlint-cli2, Pester 5.7.1, powershell-yaml. Configures git hooks and SKIP_AUTOFIX env.
PR Validation ReportTip ✅ Status: PASS Description Validation
QA Validation
Powered by PR Validation workflow |
Spec-to-Implementation ValidationTip ✅ Final Verdict: PASS What is Spec Validation?This validation ensures your implementation matches the specifications:
Validation Summary
Spec References
Requirements Traceability DetailsBased on my analysis of the PR description, diff, and copilot-setup-steps.yml, I can now perform the requirements traceability check. Requirements Coverage Matrix
Summary
Gaps
VERDICT: PARTIAL Implementation Completeness DetailsAcceptance Criteria ChecklistBased on the PR description "matching copilot-setup-steps.yml", I extracted implicit acceptance criteria:
Missing Functionality
Edge Cases Not Covered
Implementation Quality
VERDICT: PASS Run Details
Powered by AI Spec Validator workflow |
AI Quality Gate ReviewCaution ❌ Final Verdict: CRITICAL_FAIL WalkthroughThis PR was reviewed by six AI agents in parallel, analyzing different aspects of the changes:
Review Summary
💡 Quick Access: Click on individual agent jobs (e.g., "🔒 security Review", "🧪 qa Review") in the workflow run to see detailed findings and step summaries. DevOps Review DetailsDevOps Review:
|
| Area | Impact | Notes |
|---|---|---|
| Build | None | No build pipeline changes |
| Test | None | No test pipeline changes |
| Deploy | None | Bootstrap script for local/VM setup |
| Cost | None | No CI/CD cost impact |
CI/CD Quality Checks
| Check | Status | Location |
|---|---|---|
| YAML syntax valid | N/A | No workflow changes |
| Actions pinned | N/A | No workflow changes |
| Secrets secure | ✅ | No secrets hardcoded |
| Permissions minimal | N/A | No workflow changes |
| Shell scripts robust | ✅ | set -euo pipefail at line 4 |
Findings
| Severity | Category | Finding | Location | Fix |
|---|---|---|---|---|
| Low | Documentation | Usage comment shows GH_TOKEN but script checks GITHUB_TOKEN first |
Line 3, 38 | Clarify both GH_TOKEN and GITHUB_TOKEN work |
| Low | Idempotency | grep -q 'local/bin' is too broad; could match substrings |
Line 45 | Use more specific pattern like export PATH=.*local/bin |
| Low | Idempotency | grep -q 'SKIP_AUTOFIX' could match comments |
Line 63 | Use more specific pattern like ^export SKIP_AUTOFIX= |
| Low | Missing | powershell-yaml module not installed in copilot-setup-steps.yml but installed here | Line 57 | Consider adding to workflow for parity |
| Low | Missing | No final success/failure summary | Line 66 | Consider adding exit code verification |
Comparison with copilot-setup-steps.yml
| Tool | bootstrap-vm.sh | copilot-setup-steps.yml | Match |
|---|---|---|---|
| Node.js LTS | ✅ | ✅ | Yes |
| PowerShell Core | ✅ | ✅ (pre-installed) | Yes |
| GitHub CLI | ✅ | ✅ (pre-installed) | Yes |
| uv | ✅ | ✅ | Yes |
| markdownlint-cli2 | ✅ | ✅ | Yes |
| Pester 5.7.1 | ✅ | ✅ | Yes |
| powershell-yaml | ✅ | ❌ (not installed) | Mismatch |
| Git hooks | ✅ | ✅ | Yes |
| SKIP_AUTOFIX | ✅ | ✅ | Yes |
| Forgetful MCP | ❌ | ✅ | Mismatch |
Note: The workflow starts Forgetful MCP server which is not replicated in bootstrap script. This may be intentional for VM environments.
Shell Script Quality Assessment
Strengths:
- Uses
set -euo pipefailfor strict error handling - Idempotent checks before installing (checks if command exists)
- Quiet apt-get with
-qqflag - Non-interactive mode with
DEBIAN_FRONTEND=noninteractive - Suppresses unnecessary output appropriately
Acceptable Patterns:
- Piping curl to bash is standard for NodeSource, uv, and is from trusted sources
- Using
sudoappropriately for system-level installations - Error handling with
2>/dev/null || truefor optional powershell-yaml
Template Assessment
N/A - No template changes.
Automation Opportunities
| Opportunity | Type | Benefit | Effort |
|---|---|---|---|
| Add Forgetful MCP to bootstrap | Script update | Full parity with CI | Low |
| Create composite action for PowerShell module installs | Action | Reuse across workflows | Medium |
Recommendations
- Add comment documenting that both
GH_TOKENandGITHUB_TOKENare accepted (line 3) - Consider adding Forgetful MCP server setup for full CI parity (optional based on use case)
- The powershell-yaml difference is acceptable as it may be needed only for VM environments
Verdict
VERDICT: PASS
MESSAGE: Script follows shell best practices with strict error handling. Minor documentation improvements suggested but no blocking issues.
Security Review Details
Security Review: bootstrap-vm.sh
PR Type: CODE (Shell Script)
This is a bash script requiring full OWASP review with focus on shell injection and remote code execution vectors.
Findings
| Severity | Category | Finding | Location | CWE |
|---|---|---|---|---|
| Medium | Remote Code Execution | Piping curl output directly to shell (`curl ... | |
Roadmap Review Details
Strategic Alignment Assessment
| Criterion | Rating | Notes |
|---|---|---|
| Aligns with project goals | High | Master objective: "enable development teams to adopt coordinated multi-agent AI workflows with minimal friction." VM bootstrap enables new developer onboarding. |
| Priority appropriate | Medium | Infrastructure tooling. Not on explicit roadmap but supports contributor onboarding (<30 min target metric). |
| User value clear | High | Developers setting up new VMs can match the copilot-setup-steps.yml environment locally. |
| Investment justified | High | 66-line shell script with minimal maintenance burden. Directly mirrors existing CI workflow. |
Feature Completeness
- Scope Assessment: Right-sized
- Ship Ready: Yes
- MVP Complete: Yes
- Enhancement Opportunities:
- Missing: Forgetful MCP server setup (present in copilot-setup-steps.yml lines 92-135)
- Missing: powershell-yaml success verification
- Consider: Version pinning for uv, gh, markdownlint-cli2 for reproducibility
Impact Analysis
| Dimension | Assessment | Notes |
|---|---|---|
| User Value | Medium | Targets VM setup users (subset of contributors) |
| Business Impact | Low | Infrastructure enablement, not core product |
| Technical Leverage | Medium | Reusable for DROID/Factory.ai environments per comment |
| Competitive Position | Neutral | Developer experience parity with CI |
Concerns
| Priority | Concern | Recommendation |
|---|---|---|
| Low | Missing Forgetful MCP server setup | Document as known gap or add in follow-up |
| Low | No version pinning for external tools | Acceptable for bootstrap script; CI workflow also lacks this |
| Low | No verification step like CI has | Consider adding summary verification similar to copilot-setup-steps.yml lines 152-203 |
Recommendations
- Script aligns with the stated contributor onboarding metric (< 30 min) by eliminating manual setup steps.
- The gap between this script and copilot-setup-steps.yml (Forgetful MCP) is acceptable for initial version since Forgetful is optional for local development.
- Consider documenting the script's existence in README.md or CONTRIBUTING.md for discoverability.
Verdict
VERDICT: PASS
MESSAGE: Infrastructure script correctly mirrors CI environment. Enables contributor onboarding with minimal maintenance burden. Low-priority gaps (Forgetful MCP, verification step) are acceptable for v1.
Analyst Review Details
Now I have sufficient context to provide the analysis.
Analysis: PR bootstrap-vm.sh
Code Quality Score
| Criterion | Score (1-5) | Notes |
|---|---|---|
| Readability | 4 | Clear section headers, logical flow, idempotent checks |
| Maintainability | 3 | 66 lines, no tests, version pinning missing for Node.js |
| Consistency | 1 | Violates ADR-005 (PowerShell-only scripting) |
| Simplicity | 4 | Direct approach, minimal complexity |
Overall: 3/5
Impact Assessment
- Scope: Isolated (new file, development tooling only)
- Risk Level: Medium (ADR violation, security script executes remote URLs)
- Affected Components: Developer VM setup, onboarding workflow
Findings
| Priority | Category | Finding | Location |
|---|---|---|---|
| High | Architectural | Creates .sh file violating ADR-005 (PowerShell-only) | scripts/bootstrap-vm.sh |
| High | Security | Pipes curl to shell without verification (3 instances) | Lines 14, 42, 88 |
| Medium | Completeness | Missing Forgetful MCP server setup present in workflow | copilot-setup-steps.yml:92-135 |
| Medium | Completeness | Missing powershell-yaml module (partial - has but silent fail) | Line 57 |
| Low | Maintainability | Node.js version not pinned (uses LTS but may drift from workflow) | Line 14 |
| Low | Documentation | PR description template not completed | PR body |
Evidence
ADR-005 Violation (PROJECT-CONSTRAINTS.md lines 17-28):
"MUST NOT create bash scripts (.sh)" with verification via "Pre-commit hook, code review"
"Exceptions: None. If PowerShell is genuinely insufficient, document why and get explicit approval."
Security Concern:
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - # Line 14
curl -LsSf https://astral.sh/uv/install.sh | sh # Line 42These patterns execute remote code without checksum verification.
Missing Forgetful MCP: The workflow includes lines 92-135 for starting Forgetful MCP server. The bootstrap script omits this entirely.
Recommendations
- Convert to PowerShell: Rewrite as
bootstrap-vm.ps1per ADR-005. PowerShell Core runs on Ubuntu. - Add checksum verification: Download scripts, verify SHA256, then execute.
- Add Forgetful MCP: Include the MCP server startup logic from the workflow.
- Complete PR template: Fill in specification references and change type checkboxes.
- Request exception: If bash is required for specific bootstrapping reasons, document rationale and request ADR-005 exception.
Verdict
VERDICT: CRITICAL_FAIL
MESSAGE: Creates bash script violating ADR-005 (PowerShell-only constraint). No documented exception. Requires rewrite or formal exception approval.
Architect Review Details
Design Quality Assessment
| Aspect | Rating (1-5) | Notes |
|---|---|---|
| Pattern Adherence | 2 | Violates ADR-005: PowerShell-only scripting standard |
| Boundary Respect | 4 | Script placed in correct directory (scripts/) |
| Coupling | 4 | Minimal dependencies, idempotent checks |
| Cohesion | 4 | Single responsibility: VM bootstrap |
| Extensibility | 3 | Linear script structure limits selective execution |
Overall Design Score: 3/5
Architectural Concerns
| Severity | Concern | Location | Recommendation |
|---|---|---|---|
| Critical | Bash script violates ADR-005 PowerShell-only standard | scripts/bootstrap-vm.sh |
Rewrite as scripts/bootstrap-vm.ps1 |
| Low | Piping curl to shell is a security pattern concern | Lines 14, 42 | Consider checksum verification |
| Low | Hardcoded version (Pester 5.7.1) creates maintenance burden | Line 52 | Consider parameterizing or documenting update process |
Breaking Change Assessment
- Breaking Changes: No
- Impact Scope: None (new file)
- Migration Required: No
- Migration Path: N/A
Technical Debt Analysis
- Debt Added: High (introduces bash script in PowerShell-only codebase)
- Debt Reduced: Low
- Net Impact: Degraded
ADR Assessment
- ADR Required: No (no new architectural decision needed)
- Decisions Identified: ADR-005 explicitly prohibits bash scripts
- Existing ADR: ADR-005-powershell-only-scripting.md
- Recommendation: Comply with existing ADR or request formal exception
Recommendations
- Rewrite
bootstrap-vm.shasbootstrap-vm.ps1using PowerShell Core, which runs on Ubuntu per ADR-005 justification - If bash is required for the bootstrap chicken-egg problem (PowerShell not yet installed), document a formal ADR-005 exception request with justification that "PowerShell installation requires a non-PowerShell bootstrapper"
- Consider splitting into minimal bash installer for PowerShell only, then PowerShell script for remaining tools
Verdict
VERDICT: CRITICAL_FAIL
MESSAGE: Script violates ADR-005 PowerShell-only scripting standard. The ADR acknowledges PowerShell Core runs on Linux and explicitly rejects bash scripts. Either rewrite in PowerShell or amend ADR-005 with documented exception.
QA Review Details
Based on my analysis of the PR, here is my QA review:
QA Review: Bootstrap VM Script
VERDICT: PASS
MESSAGE: Shell infrastructure script with defensive coding patterns; no tests required per project conventions.
PR TYPE: CODE (shell script)
FILES: scripts/bootstrap-vm.sh (1 new file, 66 lines)
Evidence
Tests found: 0 for 1 new script
Edge cases: N/A - infrastructure script, not application logic
Error handling: Present via set -euo pipefail and conditional checks
Blocking issues: 0
Analysis
| Area | Status | Evidence |
|---|---|---|
| Fail-fast enabled | [PASS] | Line 4: set -euo pipefail |
| Idempotent design | [PASS] | Lines 13, 20, 29, 41, 48: command -v checks before install |
| Error handling | [PASS] | Script exits on any command failure via set -e |
| Input validation | [PASS] | Line 38: Safe ${GITHUB_TOKEN:-} with default |
| No hardcoded secrets | [PASS] | Token passed via environment variable |
Test Requirements Assessment
This is an infrastructure bootstrap script that:
- Installs system packages via apt
- Configures development environment
- Must run with sudo on real Ubuntu VMs
Testing rationale: Infrastructure scripts that require root privileges and system package installation are not unit-testable in isolation. The project test suite contains PowerShell .Tests.ps1 files only. Shell scripts in this repository follow a pattern of syntax validation rather than unit tests.
Quality Concerns
| Severity | Issue | Location | Evidence | Required Fix |
|---|---|---|---|---|
| LOW | No version pinning for Node.js | Line 14 | setup_lts.x installs latest LTS |
Consider pinning major version |
| LOW | Relies on external URLs | Lines 14, 22, 30, 42 | External download scripts | Expected for bootstrap scripts |
Fail-Safe Pattern Verification
| Pattern | Status | Evidence |
|---|---|---|
| Exit on error | [PASS] | set -e on line 4 |
| Undefined variable check | [PASS] | set -u on line 4 |
| Pipe failure detection | [PASS] | set -o pipefail on line 4 |
| Idempotent operations | [PASS] | All installs check command -v first |
| Non-interactive mode | [PASS] | DEBIAN_FRONTEND=noninteractive on line 5 |
Regression Risk Assessment
- Risk Level: Low
- Affected Components: New script only, no existing code modified
- Breaking Changes: None
- Required Testing: Manual execution on Ubuntu VM
Verdict Rationale
The script follows shell best practices with defensive coding patterns. It is a new infrastructure file that does not modify existing functionality. The project has no test framework for shell scripts, and this pattern matches other install scripts in the repository (install-*.ps1 files also lack shell-level unit tests for system operations).
Run Details
| Property | Value |
|---|---|
| Run ID | 20726890526 |
| Triggered by | pull_request on 797/merge |
| Commit | 827097fe22ae193306234a57c5fd9b7dea93b3ce |
Powered by AI Quality Gate workflow
There was a problem hiding this comment.
Code Review
This pull request introduces a new bootstrap script, scripts/bootstrap-vm.sh, to automate the setup of a development environment on an Ubuntu VM. The script is well-structured and covers the installation of all necessary dependencies for the repository.
My review focuses on improving the security and robustness of the script. I've identified a critical security vulnerability related to the use of a predictable temporary filename, which could allow for privilege escalation. I've also found a high-severity issue where installation errors for a PowerShell module are suppressed, which could lead to an incomplete and broken development environment without the user's knowledge. I have provided detailed explanations and suggestions for how to address these points.
| source /etc/os-release | ||
| wget -q "https://packages.microsoft.com/config/ubuntu/${VERSION_ID}/packages-microsoft-prod.deb" -O /tmp/ms.deb | ||
| sudo dpkg -i /tmp/ms.deb && rm /tmp/ms.deb |
There was a problem hiding this comment.
Using a predictable filename like /tmp/ms.deb in a world-writable directory is a security risk. A malicious local user could potentially exploit a race condition (TOCTOU - Time-of-check to time-of-use) by replacing this file between the wget and sudo dpkg commands. This could lead to arbitrary code execution with root privileges.
To mitigate this, you should use mktemp to create a temporary file with a random, unpredictable name. For robustness, it's also best practice to use a trap to ensure the temporary file is cleaned up, even if the script fails.
Here's a more secure approach using a subshell to scope the temporary file and cleanup trap. This block would replace lines 21-23:
(
set -euo pipefail # Ensure strict mode in subshell
source /etc/os-release
ms_deb_file=$(mktemp --suffix=.deb)
trap 'rm -f -- "$ms_deb_file"' EXIT
wget -q "https://packages.microsoft.com/config/ubuntu/${VERSION_ID}/packages-microsoft-prod.deb" -O "$ms_deb_file"
sudo dpkg -i "$ms_deb_file"
)| ' | ||
|
|
||
| echo "=== powershell-yaml ===" | ||
| pwsh -NoProfile -Command 'Install-Module -Name powershell-yaml -Force -Scope CurrentUser -EA SilentlyContinue' 2>/dev/null || true |
There was a problem hiding this comment.
The use of 2>/dev/null || true completely silences any errors from the powershell-yaml module installation. If this module fails to install for any reason (e.g., network issue, PSGallery problem), the script will continue without any indication of failure, leaving the development environment in an incomplete state. This can lead to confusing errors later on.
The -EA SilentlyContinue within the PowerShell command already prevents terminating errors but will still write to the error stream. By redirecting stderr and using || true, you are hiding important feedback from the user. It's better to let the error be displayed so the user is aware of the installation failure.
| pwsh -NoProfile -Command 'Install-Module -Name powershell-yaml -Force -Scope CurrentUser -EA SilentlyContinue' 2>/dev/null || true | |
| pwsh -NoProfile -Command 'Install-Module -Name powershell-yaml -Force -Scope CurrentUser -EA SilentlyContinue' |
| pwsh -NoProfile -Command 'Install-Module -Name powershell-yaml -Force -Scope CurrentUser -EA SilentlyContinue' 2>/dev/null || true | ||
|
|
||
| echo "=== Git Hooks ===" | ||
| [[ -d ".githooks" ]] && git config core.hooksPath .githooks |
There was a problem hiding this comment.
Git hooks configuration silently skipped in wrong directory
The .githooks directory check uses a relative path from the current working directory rather than the repository root. If the script is run from within the scripts/ directory (cd scripts && ./bootstrap-vm.sh) or copied to another location, the check [[ -d ".githooks" ]] will fail and git hooks won't be configured. The PR description states the script "Configures git hooks" but this step silently completes without any warning when the directory isn't found, leaving hooks unconfigured.
| curl -LsSf https://astral.sh/uv/install.sh | sh | ||
| fi | ||
| export PATH="$HOME/.local/bin:$PATH" | ||
| grep -q 'local/bin' "$HOME/.bashrc" 2>/dev/null || echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.bashrc" |
There was a problem hiding this comment.
Overly broad grep pattern may skip PATH addition
The grep pattern 'local/bin' is too broad and will match any occurrence of that substring, including /usr/local/bin which is a common PATH entry. If .bashrc contains any reference to /usr/local/bin (or a comment mentioning it), the grep succeeds and the $HOME/.local/bin PATH export will not be added. This causes uv and other tools installed to ~/.local/bin to be unavailable in new shell sessions, even though the script appears to complete successfully.
|
Caution Review failedFailed to post review comments 📝 WalkthroughWalkthroughAdds a new Bash script that bootstraps an Ubuntu VM by installing system prerequisites, development tools (Node.js, PowerShell, GitHub CLI, Python uv), testing frameworks (Pester, markdownlint-cli2), and configuring environment variables. Changes
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~5 minutes Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
|
Caution Review failedFailed to post review comments Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughNew Bash script added to bootstrap a Ubuntu VM with required tools and dependencies for the ai-agents repository. Script performs sequential installations of system packages, runtimes, CLIs, and PowerShell modules with conditional checks to avoid redundant installs. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
Review Triage RequiredNote Priority: NORMAL - Human approval required before bot responds Review Summary
Next Steps
Powered by PR Maintenance workflow - Add triage:approved label |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ff8910e230
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| #!/usr/bin/env bash | ||
| # Bootstrap Ubuntu VM for ai-agents repository (DROID/Factory.ai) | ||
| # Usage: GH_TOKEN=<pat> ./bootstrap-vm.sh |
There was a problem hiding this comment.
Replace bash script with PowerShell per ADR-005
The repository root AGENTS.md explicitly states “Use PowerShell (.ps1/.psm1) for all scripts (ADR-005).” Adding a new bash script under scripts/ violates this requirement, which is a project-level constraint for script contributions and will make this change non-compliant with the documented standards. Please reimplement this bootstrapper as a PowerShell script (or relocate it outside the governed scripts/ scope).
Useful? React with 👍 / 👎.
| echo "=== Git Hooks ===" | ||
| [[ -d ".githooks" ]] && git config core.hooksPath .githooks |
There was a problem hiding this comment.
Resolve repo root before configuring hooks
The hook setup relies on the current working directory: [[ -d ".githooks" ]] will only succeed when the script is run from the repo root. The usage comment suggests running ./bootstrap-vm.sh from the script’s directory, which means .githooks won’t be found and hooks won’t be configured. If the script is executed from scripts/ (or any non-root path), hooks remain disabled; resolve the repo root first (e.g., via git rev-parse --show-toplevel) before checking .githooks.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
This PR adds a bash shell script to bootstrap Ubuntu VMs for the ai-agents repository, designed for DROID/Factory.ai workspace initialization. However, this directly violates the project's mandatory PowerShell-only scripting standard (ADR-005), which requires all scripts to be written in PowerShell (.ps1/.psm1) with only a narrow exception for SkillForge developer tools.
Key Changes:
- Adds
scripts/bootstrap-vm.shwith system dependency installation (Node.js, PowerShell Core, GitHub CLI, uv, markdownlint-cli2) - Installs PowerShell modules (Pester 5.7.1, powershell-yaml)
- Configures git hooks and sets environment variables
| #!/usr/bin/env bash | ||
| # Bootstrap Ubuntu VM for ai-agents repository (DROID/Factory.ai) | ||
| # Usage: GH_TOKEN=<pat> ./bootstrap-vm.sh | ||
| set -euo pipefail | ||
| export DEBIAN_FRONTEND=noninteractive | ||
|
|
||
| echo "=== System Prerequisites ===" | ||
| sudo apt-get update -qq | ||
| sudo apt-get install -y -qq curl wget git jq unzip apt-transport-https \ | ||
| ca-certificates gnupg software-properties-common build-essential | ||
|
|
||
| echo "=== Node.js LTS ===" | ||
| if ! command -v node &>/dev/null; then | ||
| curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - | ||
| sudo apt-get install -y -qq nodejs | ||
| fi | ||
| node --version && npm --version | ||
|
|
||
| echo "=== PowerShell Core ===" | ||
| if ! command -v pwsh &>/dev/null; then | ||
| source /etc/os-release | ||
| wget -q "https://packages.microsoft.com/config/ubuntu/${VERSION_ID}/packages-microsoft-prod.deb" -O /tmp/ms.deb | ||
| sudo dpkg -i /tmp/ms.deb && rm /tmp/ms.deb | ||
| sudo apt-get update -qq && sudo apt-get install -y -qq powershell | ||
| fi | ||
| pwsh --version | ||
|
|
||
| echo "=== GitHub CLI ===" | ||
| if ! command -v gh &>/dev/null; then | ||
| curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | \ | ||
| sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg 2>/dev/null | ||
| echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | \ | ||
| sudo tee /etc/apt/sources.list.d/github-cli.list >/dev/null | ||
| sudo apt-get update -qq && sudo apt-get install -y -qq gh | ||
| fi | ||
| gh --version | ||
|
|
||
| [[ -n "${GITHUB_TOKEN:-}" ]] && export GH_TOKEN="$GITHUB_TOKEN" | ||
|
|
||
| echo "=== Python uv ===" | ||
| if ! command -v uv &>/dev/null && [[ ! -f "$HOME/.local/bin/uv" ]]; then | ||
| curl -LsSf https://astral.sh/uv/install.sh | sh | ||
| fi | ||
| export PATH="$HOME/.local/bin:$PATH" | ||
| grep -q 'local/bin' "$HOME/.bashrc" 2>/dev/null || echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.bashrc" | ||
|
|
||
| echo "=== markdownlint-cli2 ===" | ||
| command -v markdownlint-cli2 &>/dev/null || sudo npm install -g markdownlint-cli2 | ||
|
|
||
| echo "=== Pester ===" | ||
| pwsh -NoProfile -Command ' | ||
| Set-PSRepository -Name PSGallery -InstallationPolicy Trusted | ||
| Install-Module -Name Pester -RequiredVersion 5.7.1 -Force -Scope CurrentUser | ||
| ' | ||
|
|
||
| echo "=== powershell-yaml ===" | ||
| pwsh -NoProfile -Command 'Install-Module -Name powershell-yaml -Force -Scope CurrentUser -EA SilentlyContinue' 2>/dev/null || true | ||
|
|
||
| echo "=== Git Hooks ===" | ||
| [[ -d ".githooks" ]] && git config core.hooksPath .githooks | ||
|
|
||
| echo "=== Environment ===" | ||
| grep -q 'SKIP_AUTOFIX' "$HOME/.bashrc" 2>/dev/null || echo 'export SKIP_AUTOFIX=0' >> "$HOME/.bashrc" | ||
| export SKIP_AUTOFIX=0 | ||
|
|
||
| echo "=== Done ===" |
There was a problem hiding this comment.
This shell script violates the project's PowerShell-only scripting standard defined in ADR-005. All scripts in this repository must be written in PowerShell (.ps1 or .psm1), not bash (.sh).
According to ADR-005, the project has standardized on PowerShell for all scripting because:
- 100% of existing infrastructure is PowerShell
- Single testing framework (Pester)
- Cross-platform support via PowerShell Core
- Token efficiency for AI agents
While the PR description mentions this is for DROID/Factory.ai workspace initialization, ADR-005 provides limited exceptions (only SkillForge developer tools), and VM bootstrap scripts are not included in those exceptions.
PowerShell Core can run on Ubuntu and provides all the capabilities needed for this bootstrap script. The script should be rewritten as a .ps1 file using PowerShell syntax.
| ' | ||
|
|
||
| echo "=== powershell-yaml ===" | ||
| pwsh -NoProfile -Command 'Install-Module -Name powershell-yaml -Force -Scope CurrentUser -EA SilentlyContinue' 2>/dev/null || true |
There was a problem hiding this comment.
The powershell-yaml module installation has error suppression that might hide real issues. The command uses -EA SilentlyContinue and redirects stderr to /dev/null, then forces success with || true. This triple-layer of error suppression makes it impossible to diagnose installation problems.
If the module installation is truly optional, add a comment explaining why. If it's required (as suggested by its inclusion in the bootstrap script), the error handling should allow visibility into failures while still being recoverable.
| pwsh -NoProfile -Command 'Install-Module -Name powershell-yaml -Force -Scope CurrentUser -EA SilentlyContinue' 2>/dev/null || true | |
| # Install powershell-yaml for PowerShell tooling; failure is non-fatal but reported. | |
| if ! pwsh -NoProfile -Command 'Install-Module -Name powershell-yaml -Force -Scope CurrentUser -ErrorAction Stop'; then | |
| echo "WARNING: Failed to install powershell-yaml module. Some PowerShell tooling may not work. Please install it manually in PowerShell:" >&2 | |
| echo " Install-Module -Name powershell-yaml -Force -Scope CurrentUser" >&2 | |
| fi |
| pwsh -NoProfile -Command 'Install-Module -Name powershell-yaml -Force -Scope CurrentUser -EA SilentlyContinue' 2>/dev/null || true | ||
|
|
||
| echo "=== Git Hooks ===" | ||
| [[ -d ".githooks" ]] && git config core.hooksPath .githooks |
There was a problem hiding this comment.
The git hooks configuration check at line 60 assumes the script is run from the repository root. If the script is executed from a different directory, the .githooks directory check will fail and hooks won't be configured. Consider adding a repository root check or cd to the repository root at the start of the script, or make the path check more robust.
| [[ -d ".githooks" ]] && git config core.hooksPath .githooks | |
| if git_root=$(git rev-parse --show-toplevel 2>/dev/null); then | |
| if [[ -d "$git_root/.githooks" ]]; then | |
| git config core.hooksPath "$git_root/.githooks" | |
| fi | |
| fi |
| curl -LsSf https://astral.sh/uv/install.sh | sh | ||
| fi | ||
| export PATH="$HOME/.local/bin:$PATH" | ||
| grep -q 'local/bin' "$HOME/.bashrc" 2>/dev/null || echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$HOME/.bashrc" |
There was a problem hiding this comment.
The PATH modification at line 45 appends to .bashrc without checking if the entry already exists. Running this script multiple times will add duplicate PATH entries to .bashrc, which can lead to PATH bloat and potential issues with command resolution order.
| [[ -d ".githooks" ]] && git config core.hooksPath .githooks | ||
|
|
||
| echo "=== Environment ===" | ||
| grep -q 'SKIP_AUTOFIX' "$HOME/.bashrc" 2>/dev/null || echo 'export SKIP_AUTOFIX=0' >> "$HOME/.bashrc" |
There was a problem hiding this comment.
The SKIP_AUTOFIX environment variable is appended to .bashrc without checking if it already exists. Running this script multiple times will add duplicate export SKIP_AUTOFIX=0 lines to .bashrc. The same idempotency issue exists here as with the PATH modification on line 45.
| curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - | ||
| sudo apt-get install -y -qq nodejs |
There was a problem hiding this comment.
The command curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - executes a remote script as root without any integrity or authenticity verification. Because the documented usage passes GH_TOKEN via the environment and sudo -E preserves that environment, a compromise of deb.nodesource.com or its TLS could yield arbitrary code execution with access to GH_TOKEN/GITHUB_TOKEN and full system control. To reduce supply-chain risk, avoid piping remote scripts directly into a root shell, rely on signed OS packages or downloads with checksum/signature verification, and do not propagate secrets into such installation steps.
| wget -q "https://packages.microsoft.com/config/ubuntu/${VERSION_ID}/packages-microsoft-prod.deb" -O /tmp/ms.deb | ||
| sudo dpkg -i /tmp/ms.deb && rm /tmp/ms.deb | ||
| sudo apt-get update -qq && sudo apt-get install -y -qq powershell |
There was a problem hiding this comment.
The wget/dpkg sequence that downloads https://packages.microsoft.com/.../packages-microsoft-prod.deb and installs it with sudo dpkg -i /tmp/ms.deb pulls a binary package from the network and executes its maintainer scripts as root without any independent integrity check beyond TLS. If an attacker can tamper with this download path, they gain arbitrary code execution on the VM and can persist malicious repositories or tooling. Prefer adding official repositories via signed release channels or verifying checksums/signatures of downloaded .deb files before installing them.
| curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | \ | ||
| sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg 2>/dev/null | ||
| echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | \ | ||
| sudo tee /etc/apt/sources.list.d/github-cli.list >/dev/null | ||
| sudo apt-get update -qq && sudo apt-get install -y -qq gh |
There was a problem hiding this comment.
The GitHub CLI setup downloads a GPG key with curl ... | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg and directly configures an external APT repository using that key, but never verifies the key’s fingerprint or origin beyond HTTPS. If an attacker can substitute their own key or tamper with the download, APT will trust attacker-signed packages, enabling installation of arbitrary code as root. To limit supply-chain risk, pin and verify the key fingerprint (or use a distribution-provided package) before trusting it for package verification.
| curl -LsSf https://astral.sh/uv/install.sh | sh | ||
| fi |
There was a problem hiding this comment.
The curl -LsSf https://astral.sh/uv/install.sh | sh pattern executes a remote installer script directly in a shell without any checksum or signature verification. If astral.sh or its TLS channel is compromised, the attacker’s script will run with the current user’s permissions and can exfiltrate GH_TOKEN/GITHUB_TOKEN from the environment or install backdoors on the development VM. Downloading installers to disk and verifying integrity (or using a signed package from a trusted repository) significantly reduces this supply-chain attack surface.
| fi | ||
| gh --version | ||
|
|
||
| [[ -n "${GITHUB_TOKEN:-}" ]] && export GH_TOKEN="$GITHUB_TOKEN" |
There was a problem hiding this comment.
Token override ignores user-provided GH_TOKEN value
Medium Severity
The usage comment on line 3 says to run with GH_TOKEN=<pat> ./bootstrap-vm.sh, but line 38 unconditionally overwrites GH_TOKEN if GITHUB_TOKEN is also set in the environment. This means a user's explicit token choice is silently ignored when GITHUB_TOKEN is present (common in CI environments). The condition needs to check if GH_TOKEN is already set before overwriting it with GITHUB_TOKEN.
| pwsh -NoProfile -Command ' | ||
| Set-PSRepository -Name PSGallery -InstallationPolicy Trusted | ||
| Install-Module -Name Pester -RequiredVersion 5.7.1 -Force -Scope CurrentUser | ||
| ' |
There was a problem hiding this comment.
Missing Pester installation verification unlike reference implementation
Low Severity
The reference implementation in copilot-setup-steps.yml calls Import-Module Pester -RequiredVersion 5.7.1 after Install-Module to verify the module is actually usable. This bootstrap script is missing that verification step. PowerShell's Install-Module can fail silently in certain conditions (PSGallery connectivity issues, partial downloads), and without the Import-Module check, the script could complete "successfully" while Pester is not actually installed, leading to confusing test failures later.
Summary
Add a minimal shell script to bootstrap Ubuntu VMs for working on the ai-agents repository. Designed for DROID/Factory.ai workspace initialization, matching the setup in
copilot-setup-steps.yml.Specification References
.github/workflows/copilot-setup-steps.ymlChanges
scripts/bootstrap-vm.sh(2585 bytes, under 10KB limit).githooks/SKIP_AUTOFIX=0environment variableGH_TOKEN/GITHUB_TOKENfor authenticated gh CLIType of Change
Testing
Agent Review
Security Review
Script uses standard package managers (apt, npm, pwsh modules) and trusted sources.
Checklist