Skip to content

feat: make install.sh configurable for air-gapped environments#660

Merged
danielmeppiel merged 16 commits intomicrosoft:mainfrom
chkp-roniz:feat/install-dir-param
Apr 27, 2026
Merged

feat: make install.sh configurable for air-gapped environments#660
danielmeppiel merged 16 commits intomicrosoft:mainfrom
chkp-roniz:feat/install-dir-param

Conversation

@chkp-roniz
Copy link
Copy Markdown
Contributor

@chkp-roniz chkp-roniz commented Apr 10, 2026

Summary

  • APM_INSTALL_DIR env var to override install path (default /usr/local/bin)
  • GITHUB_URL env var to override GitHub base URL for mirrors/GHE
  • REPO env var to override repository (default microsoft/apm)
  • VERSION env var or @v1.2.3 arg to pin a specific version — skips GitHub API entirely when set

Usage

# Specific version (no GitHub API call needed)
curl -sSL https://aka.ms/apm-unix | sh -s -- @v0.8.10

# Custom install directory
curl -sSL https://aka.ms/apm-unix | APM_INSTALL_DIR=~/.local/bin sh

# Air-gapped / internal mirror
GITHUB_URL=https://artifactory.corp.com/artifactory/github VERSION=v0.8.10 sh install.sh

Test plan

  • Tested with custom GITHUB_URL + VERSION=v0.8.10 — downloads from mirror, skips API
  • Tested with unreachable API — confirmed API is skipped when VERSION is set
  • Tested with APM_INSTALL_DIR=$HOME/.local/bin — installs to custom dir without sudo
  • bash -n install.sh syntax check passes
  • No hardcoded https://github.com outside Configuration defaults

🤖 Generated with Claude Code

chkp-roniz and others added 2 commits April 10, 2026 10:12
Defaults to /usr/local/bin when not set.
Usage: curl -sSL https://aka.ms/apm-unix | INSTALL_DIR=~/.local/bin sh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ents

- APM_INSTALL_DIR: override install path (default /usr/local/bin)
- GITHUB_URL: override GitHub base URL for mirrors/GHE
- REPO: override repository (default microsoft/apm)
- VERSION: pin a specific version (@v1.2.3 arg or env var),
  skips GitHub API entirely when set

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 10, 2026 07:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the install.sh bootstrap installer to support air-gapped and enterprise environments by allowing callers to override the GitHub host/repo, install location, and (optionally) pin a version to avoid the GitHub API.

Changes:

  • Added configurable environment variables: APM_INSTALL_DIR, GITHUB_URL, REPO, and VERSION (plus @vX.Y.Z positional argument support).
  • When VERSION is provided, the installer skips the GitHub API and constructs the release download URL directly.
  • Updated user-facing URLs/messages to use the configurable GitHub base URL.
Comments suppressed due to low confidence (3)

install.sh:224

  • GITHUB_URL is configurable, but the script still hardcodes https://api.github.com/... for the latest release lookup. This will break GitHub Enterprise / mirrored setups when VERSION is not provided, and it also conflicts with the PR description mentioning GITHUB_API_URL. Consider adding a GITHUB_API_URL (defaulting to https://api.github.com, or derived from GITHUB_URL) and using it for both unauthenticated and authenticated API calls.
if [ -z "$TAG_NAME" ]; then
# Get latest release info
echo -e "${YELLOW}Fetching latest release information...${NC}"

# Try to fetch release info without authentication first (for public repos)
LATEST_RELEASE=$(curl -s "https://api.github.com/repos/$REPO/releases/latest")
CURL_EXIT_CODE=$?

install.sh:518

  • Output strings in this modified block include non-ASCII characters (e.g., , , 🎉). The repo encoding guideline requires printable ASCII only for source/CLI output; please replace these with the project's ASCII status symbols (e.g. [+], [!]) to avoid Windows cp1252 UnicodeEncodeError issues.
    echo -e "${GREEN}✓ APM installed successfully!${NC}"
    echo -e "${BLUE}Version: $INSTALLED_VERSION${NC}"
    echo -e "${BLUE}Location: $APM_INSTALL_DIR/$BINARY_NAME -> $APM_LIB_DIR/$BINARY_NAME${NC}"
else
    echo -e "${YELLOW}⚠ APM installed but not found in PATH${NC}"
    echo "You may need to add $APM_INSTALL_DIR to your PATH environment variable."
    echo "Add this line to your shell profile (.bashrc, .zshrc, etc.):"
    echo "  export PATH=\"$APM_INSTALL_DIR:\$PATH\""
fi

echo ""
echo -e "${GREEN}🎉 Installation complete!${NC}"

install.sh:12

  • The private-repo usage example still hardcodes https://raw.githubusercontent.com/microsoft/apm/... even though REPO/GITHUB_URL are now configurable for GHE/mirrors. Consider either updating these instructions to match the new configuration knobs (or explicitly documenting that these curl examples apply only to github.com).
# For private repositories, use with authentication:
#   curl -sSL -H "Authorization: token $GITHUB_APM_PAT" \
#     https://raw.githubusercontent.com/microsoft/apm/main/install.sh | \
#     GITHUB_APM_PAT=$GITHUB_APM_PAT sh

Comment thread install.sh
Comment thread install.sh
Comment thread install.sh
Comment thread install.sh Outdated
Comment thread install.sh
chkp-roniz and others added 2 commits April 10, 2026 12:05
- Use $HOME instead of ~ in usage example (dash/sh compatibility)
- Set AUTH_HEADER_VALUE before download section so private repo
  auth works even when VERSION skips the API path
- mkdir -p APM_INSTALL_DIR before symlink to avoid unnecessary sudo
- Update stale comment referencing /usr/local/bin

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update installation docs and apm-guide skill to cover
APM_INSTALL_DIR, GITHUB_URL, REPO, VERSION, and @Version syntax.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@sergio-sisternes-epam sergio-sisternes-epam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great contribution, @chkp-roniz! Air-gapped and GHE support for the installer is a real need. A few things to address before we can merge:

Must-fix

  1. GitHub API URL is hardcodedhttps://api.github.com/repos/$REPO/releases/latest doesn't change when GITHUB_URL points to GHE (API lives at $GITHUB_URL/api/v3/...). Either derive a GITHUB_API_URL or document that VERSION is mandatory for GHE setups.

  2. REPO env var is too generic — Rename to APM_REPO for consistency with APM_INSTALL_DIR and to avoid collisions with the user's environment.

  3. Missing CHANGELOG entry — Please add a line under ## [Unreleased] / ### Added per our contributing guide.

Should-fix

  1. Document APM_LIB_DIR derivation — The bundle goes to $(dirname "$APM_INSTALL_DIR")/lib/apm, which is reasonable but undiscoverable. Add it to the docs table so users know where the full binary directory lands.

  2. Non-ASCII characters (, , 🎉) — These pre-date your PR, but since you're touching those lines anyway, would you mind swapping them for ASCII equivalents ([+], [!], etc.)? We keep output within printable ASCII for Windows cp1252 compatibility.

Follow-ups (we'll open issues for these)

  1. apm config integrationinstall.sh is the bootstrap, so env vars are the right call here. But once installed, users will expect apm update to remember their custom install dir and mirror URL without re-exporting env vars. We should persist these settings via apm config (e.g., install-dir, mirror-url) and have the self-update path read them. Env vars would still override for CI/unattended scenarios.

  2. Windows parityinstall.ps1 should support the same configurability (APM_INSTALL_DIR, GITHUB_URL, APM_REPO, VERSION) so Windows users in air-gapped/GHE environments get the same experience.

Copy link
Copy Markdown
Collaborator

@sergio-sisternes-epam sergio-sisternes-epam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, my previous feedback was not fixed yet. Please review my it.

chkp-roniz and others added 2 commits April 12, 2026 20:37
- Rename REPO -> APM_REPO for consistency with APM_INSTALL_DIR
- Replace non-ASCII chars (✓ ⚠ 🎉) with ASCII equivalents ([+] [!])
- Add APM_LIB_DIR to docs table and GHE note (VERSION required for air-gapped)
- Add CHANGELOG entry under [Unreleased] / Added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@danielmeppiel danielmeppiel added CI/CD Deprecated: use area/ci-cd. Kept for issue history; will be removed in milestone 0.10.0. and removed CI/CD Deprecated: use area/ci-cd. Kept for issue history; will be removed in milestone 0.10.0. labels Apr 19, 2026
Copy link
Copy Markdown
Collaborator

@danielmeppiel danielmeppiel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @chkp-roniz — this is a real user need and the diff is in good shape overall. CI is green and most of @sergio-sisternes-epam's earlier feedback is addressed (APM_REPO rename, CHANGELOG entry, docs table). Three small items to fix before merge:

Must-fix

1. APM_LIB_DIR is documented as overridable but the script ignores user-supplied values.

The docs table in installation.md lists APM_LIB_DIR with a default of $(dirname APM_INSTALL_DIR)/lib/apm, implying users can override it. But in install.sh:

APM_LIB_DIR="$(dirname "$APM_INSTALL_DIR")/lib/apm"

This unconditionally overwrites any value the user sets. Either:

  • Honour the env var: APM_LIB_DIR="${APM_LIB_DIR:-$(dirname "$APM_INSTALL_DIR")/lib/apm}", or
  • Change the doc row to mark it derived/read-only.

The first option is more useful — a user who sets APM_INSTALL_DIR=$HOME/bin today gets the bundle at $HOME/lib/apm, which is surprising.

2. Status symbols are missing the trailing space.

Per the codebase convention (STATUS_SYMBOLS in src/apm_cli/utils/console.py), bracket symbols are always followed by a single space. Current output reads:

[+]APM installed successfully!
[!]APM installed but not found in PATH
[!]Compatibility Issue Detected
[!]glibc version incompatibility detected
[!]Container/Dev Container environment detected
[+]Download successful
[+]Extraction successful
[+]Binary test successful

Should be [+] APM installed successfully!, etc. ~10 sites in install.sh.

3. Spurious "will need sudo" notice for non-existent custom dirs.

The writability check runs before mkdir -p:

if [ ! -w "$APM_INSTALL_DIR" ]; then
    echo -e "${YELLOW}Note: Will need sudo permissions to install to $APM_INSTALL_DIR${NC}"
fi

If a user sets APM_INSTALL_DIR=$HOME/.local/bin and that dir doesn't exist yet, [ ! -w ... ] is true and they see a misleading sudo warning even though mkdir -p later succeeds without it. Suggest:

if [ -e "$APM_INSTALL_DIR" ] && [ ! -w "$APM_INSTALL_DIR" ]; then
    ...
fi

Acceptable as-is

  • Hardcoded api.github.com for latest-release lookup. The doc note explicitly tells GHE/air-gapped users to set VERSION=, which is a reasonable workaround for a bootstrap installer. A proper GITHUB_API_URL (or derivation from GITHUB_URL) would be cleaner but is fine as a follow-up.

Nits (optional)

  • The header comment block (lines 4–6) documents APM_INSTALL_DIR, GITHUB_URL, and @v1.2.3 but omits APM_REPO and VERSION env var. Quick consistency tidy.

Follow-ups (not for this PR)

Sergio's earlier suggestions still stand for separate issues:

  • apm config integration so apm update remembers custom install dir / mirror without re-exporting env vars.
  • install.ps1 parity for Windows users in air-gapped/GHE environments.

Once items 1–3 land, ping a maintainer to dismiss the stale CHANGES_REQUESTED and approve.

chkp-roniz and others added 4 commits April 26, 2026 18:44
…mbol spacing, writability guard

- Honour user-supplied APM_LIB_DIR (was unconditionally overwritten,
  contradicting the docs table that lists it as overridable).
- Restore the trailing space after every [+] / [!] bracket symbol
  to match the STATUS_SYMBOLS convention in src/apm_cli/utils/console.py
  (13 sites).
- Skip the "will need sudo" notice when APM_INSTALL_DIR doesn't exist
  yet — mkdir -p handles that case without elevation, so the warning
  was misleading for the common $HOME/.local/bin override.
- Header comment block now mentions VERSION and APM_REPO env vars to
  match the docs table.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@danielmeppiel danielmeppiel added the panel-review Trigger the apm-review-panel gh-aw workflow label Apr 27, 2026
@github-actions
Copy link
Copy Markdown

APM Review Panel Verdict

Disposition: APPROVE (two minor optional follow-ups noted below; no required pre-merge actions)


Per-persona findings

Python Architect: This PR is purely procedural bash (install.sh) plus documentation -- no Python classes in scope. Substituting a module-boundary class diagram per the persona contract.

OO / class diagram (module boundaries):

classDiagram
    direction LR
    class install_sh {
        <<IOBoundary>>
        +APM_REPO string
        +APM_INSTALL_DIR string
        +GITHUB_URL string
        +VERSION string
        +APM_LIB_DIR string
        +check_python_requirements()
        +try_pip_installation()
    }
    class GitHubReleasesAPI {
        <<ExternalService>>
        +api.github.com repos releases latest
    }
    class GitHubDownloadHost {
        <<ExternalService>>
        +GITHUB_URL/APM_REPO/releases/download/TAG/BINARY
    }
    class LocalFilesystem {
        <<IOBoundary>>
        +APM_LIB_DIR binary bundle dir
        +APM_INSTALL_DIR symlink dir
    }
    class install_sh:::touched
    classDef touched fill:#fff3b0,stroke:#d47600
    install_sh ..> GitHubReleasesAPI : curl (skipped when VERSION set)
    install_sh ..> GitHubDownloadHost : curl -L download
    install_sh ..> LocalFilesystem : cp + ln -sf
    note for install_sh "Pure procedural bash. No Python classes in scope."
Loading

Execution-flow diagram:

flowchart TD
    A([curl pipe sh install.sh]) --> B{VERSION env set\nor `@v` arg present?}
    B -- yes --> C[VERSION=arg strip @\nTAG_NAME=VERSION\nDOWNLOAD_URL=GITHUB_URL/APM_REPO/releases/download/TAG/BINARY]
    B -- no --> D["[NET] curl api.github.com/repos/APM_REPO/releases/latest"]
    C --> E["[NET] curl -L DOWNLOAD_URL"]
    D --> F{API response valid?}
    F -- Not Found or empty --> G["[NET] curl with Authorization: token AUTH_HEADER_VALUE"]
    F -- yes --> H[grep TAG_NAME\nDOWNLOAD_URL=GITHUB_URL/APM_REPO/releases/download/TAG/BINARY]
    G --> H
    H --> I["[+] Latest version TAG_NAME\nDownload URL DOWNLOAD_URL"]
    I --> E
    E --> J{curl exit 0?}
    J -- no + AUTH_HEADER_VALUE --> K["[NET] curl ASSET_URL with auth or DOWNLOAD_URL with auth"]
    J -- yes --> L["[FS] tar -xzf TMP_DIR/BINARY"]
    K --> L
    L --> M["[EXEC] EXTRACTED_DIR/apm --version"]
    M --> N{Binary test exit 0?}
    N -- no + glibc error --> O["[EXEC] try_pip_installation()"]
    N -- yes --> P["[FS] rm -rf APM_LIB_DIR\nmkdir -p APM_LIB_DIR\ncp -r EXTRACTED/* APM_LIB_DIR/"]
    P --> Q["[FS] mkdir -p APM_INSTALL_DIR\nln -sf APM_LIB_DIR/apm APM_INSTALL_DIR/apm"]
    Q --> R["[+] APM installed successfully!"]
    O --> Z([Done])
    R --> Z
Loading

Design patterns

  • Used in this PR: none -- straight-line procedural bash, appropriate for the scope.
  • Pragmatic suggestion: none -- the current shape is the simplest correct design at this scope.

Structural notes: (1) The name collision between the old APM_INSTALL_DIR (hardcoded lib dir) and the new APM_INSTALL_DIR (env-configurable symlink dir) is correctly resolved by introducing APM_LIB_DIR. (2) AUTH_HEADER_VALUE is now resolved unconditionally at the top AND reset+re-resolved inside the private-repo retry block -- redundant but functionally equivalent, no regression. (3) APM_LIB_DIR default $(dirname "$APM_INSTALL_DIR")/lib/apm places lib at ./lib/apm if APM_INSTALL_DIR has no directory component -- edge case only.


CLI Logging Expert: All emoji-to-ASCII symbol replacements are correct and complete: checkmark to [+] (9 instances), warning to [!] (5 instances). One inconsistency: Installation complete! (2 instances, previously celebration emoji Installation complete!) strips the emoji but adds no status symbol, while every other success message in the file now carries [+]. This breaks the established pattern. Recommend [+] Installation complete! but not a blocker. The new VERSION fast-path feedback messages (Version: $TAG_NAME, Download URL: $DOWNLOAD_URL) correctly lead with the outcome before download begins -- good signal-to-noise.


DevX UX Expert: Strong UX improvement for enterprise and CI users. ENV_VAR=value sh install.sh pattern matches rustup/nvm mental models. @v1.2.3 positional arg is slightly unusual but documented and correctly handled ("${1#@}" works equally for v1.2.3 without @). With VERSION set the installer is fully deterministic -- no API call, reproducible binary URL -- which is excellent for CI-as-code pipelines that get shared in dotfiles and blog posts. Default path (no env vars) is completely unchanged. One UX gap: GITHUB_URL implies full GitHub URL replacement but the release-discovery API call remains hardcoded to api.github.com; the docs correctly note this and prescribe VERSION as the workaround. A future GITHUB_API_URL env var would close the gap. The updated troubleshooting section (APM_INSTALL_DIR=$HOME/.local/bin as the concrete permission-denied fix) is a significant improvement over the old vague guidance.


Supply Chain Security Expert: No new critical attack surfaces opened. Detailed scan:

  • Identity/Integrity: No binary hash verification -- pre-existing limitation, not introduced here. VERSION pinning is a mild improvement (reproducible URL = reproducible artifact).
  • Token handling: AUTH_HEADER_VALUE resolved from GITHUB_APM_PAT or GITHUB_TOKEN; value never echoed to stdout. Early unconditional resolution is safe -- used only in curl -H headers.
  • Containment: APM_LIB_DIR and APM_INSTALL_DIR are used in mkdir -p and cp -r without path validation. Malformed env vars (e.g. APM_LIB_DIR=../../etc/cron.d) could write outside expected directories. Risk requires attacker to control env vars (implying prior code execution); identical risk existed with the previous hardcoded paths. Pattern matches rustup/nvm. Acceptable; recommend a follow-up issue for input validation.
  • GITHUB_URL injection: User-controlled mirror redirect -- same model as RUSTUP_DIST_SERVER. Acceptable.
  • Fail-closed: All existing error paths preserved. No new silent fallback.
  • Determinism: Improved by VERSION pinning.

Binary SHA256 checksum verification remains the strongest available improvement to installer integrity posture -- recommend as a follow-up issue independent of this PR.


Auth Expert: Not activated -- files touched are install.sh (bash bootstrap installer), two docs files, and a skill resource; no Python AuthResolver, token manager, host-classification, or credential-helper code is changed.


OSS Growth Hacker: High-value growth unlock. Air-gapped and GHE mirror support directly removes the top enterprise blocker: "we cannot use it because our network cannot reach github.com at install time." Regulated industries (finance, healthcare, defense) are high-value early adopters with strong community influence. External contributor chkp-roniz is likely from such an environment -- this PR is direct enterprise user feedback shipped as code; merging promptly signals community health.

VERSION pinning makes curl | sh pipelines reproducible, which means they appear in IaC templates, dotfiles repos, and team onboarding docs -- compounding reach passively. The copy-pasteable one-liners in the updated docs are the conversion surface to optimize.

Story angle: "Install APM anywhere -- including behind your firewall. GITHUB_URL=(yourmirror.com/redacted) VERSION=v1.2.3 sh install.sh"

Side-channel to CEO: recommend a release note and short social post leading with the air-gapped one-liner. Credit the external contributor by handle. This is also an opportunity to add an "enterprise install" section to the docs site that aggregates GITHUB_URL, VERSION, and the future GITHUB_API_URL knob when it lands.


CEO arbitration

All five mandatory specialists converge on APPROVE with no disagreements to arbitrate. The change is additive, non-breaking, and correctly documented. The external contributor (chkp-roniz) has shipped a clean PR with test evidence and a proper CHANGELOG entry -- this is exactly the kind of community contribution that deserves fast turnaround. The two flagged items (missing [+] on "Installation complete!" and the hardcoded api.github.com for GHE API discovery) are genuine follow-ups but not merge gates; blocking an external contributor's PR on them would be disproportionate. Binary checksum verification is the right next security investment but belongs in its own issue. Disposition ratified: APPROVE.


Required actions before merge

None.


Optional follow-ups

  • [+] Installation complete!: install.sh lines where Installation complete! is printed (pip path and binary path) should carry a [+] prefix to match every other success message in the file. Trivial one-line fix, can land in a follow-up PR.
  • GITHUB_API_URL env var: GITHUB_URL overrides the download host but api.github.com is hardcoded for release-discovery. True GHE support requires a separate GITHUB_API_URL knob so users do not have to know to always pair GITHUB_URL with VERSION. Track as a follow-up issue.
  • Binary SHA256 checksum verification: The installer downloads binaries without verifying a checksum. Adding a SHA256SUMS file to GitHub releases and verifying it in install.sh would significantly strengthen the integrity posture. Recommend a dedicated follow-up issue.
  • APM_LIB_DIR path validation: User-supplied paths used in mkdir -p and cp -r without guards. Low real-world risk (requires env control), but a basic validation (reject .. components) would follow the defense-in-depth pattern established in src/apm_cli/utils/path_security.py.

Generated by PR Review Panel for issue #660 · ● 690.9K ·

@danielmeppiel danielmeppiel merged commit e08a78e into microsoft:main Apr 27, 2026
17 checks passed
danielmeppiel added a commit that referenced this pull request Apr 27, 2026
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
danielmeppiel added a commit that referenced this pull request Apr 27, 2026
* chore(release): cut 0.9.4

CHANGELOG entry for 0.9.4 covers all 7 PRs merged since v0.9.3:

- #974 SKILL_BUNDLE day-0 install parity (Added)
- #954 automate apm-triage-panel workflow (Added)
- #970 python-architect mermaid classDiagram trap (Changed)
- #911 REQUESTS_CA_BUNDLE TLS validation (Fixed)
- #971 triage-panel project-sync dispatch (Fixed)
- #910 CLI consistency cleanup (Fixed)
- #958 issue templates label taxonomy (Fixed)
- #953 docs auto-deploy after bot-cut releases (Fixed)

Open milestone 0.9.4 issues (41) reassigned to 0.9.5.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(changelog): tighten 0.9.4 entries (so-what for developers)

Refactor per Keep-a-Changelog spirit: lead with developer impact,
trim agent-internals prose, group maintainer-only changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(changelog): add #660 install.sh air-gapped entry to 0.9.4

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@chkp-roniz chkp-roniz deleted the feat/install-dir-param branch April 28, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

panel-review Trigger the apm-review-panel gh-aw workflow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants