diff --git a/docs/README.md b/docs/README.md index cfc6d9d..9bbfaf2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,6 +9,7 @@ | [Getting Started](getting-started.md) | Install and run your first extract → publish cycle in 10 minutes | | [Command Reference](commands/) | Detailed docs for [extract](commands/extract.md), [publish](commands/publish.md), [init](commands/init.md) | | [CI/CD Integration](ci-cd/) | Set up [GitHub Actions](ci-cd/github-actions.md) or [Azure DevOps](ci-cd/azure-devops.md) pipelines | +| [Walkthroughs](walkthrough/) | Step-by-step guides: [Air-gapped GitHub Actions](walkthrough/air-gapped-github-actions.md) (local registry or offline tarball), [Air-gapped Azure DevOps](walkthrough/air-gapped-azure-devops.md) (local registry or offline tarball) | ## How It Works @@ -78,6 +79,9 @@ docs/ ├── architecture/ │ ├── overview.md — System design overview │ └── design-principles.md — Architecture principles +├── walkthrough/ +│ ├── air-gapped-github-actions.md — Using `apiops` in air-gapped CI/CD (GitHub Actions) +│ ├── air-gapped-azure-devops.md — Using `apiops` in air-gapped CI/CD (Azure DevOps) └── troubleshooting/ ├── common-errors.md — Error messages and solutions ├── debugging-guide.md — Debugging with --log-level diff --git a/docs/walkthrough/air-gapped-azure-devops-local-registry.md b/docs/walkthrough/air-gapped-azure-devops-local-registry.md new file mode 100644 index 0000000..a21e39d --- /dev/null +++ b/docs/walkthrough/air-gapped-azure-devops-local-registry.md @@ -0,0 +1,192 @@ +# Air-Gapped Setup: Azure DevOps — Local npm Registry + +Deploy APIM configuration using apiops-cli on [self-hosted Azure Pipelines agents](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops#self-hosted-agents) with **no internet access** at runtime. This walkthrough uses an Azure Artifacts npm feed as the package source so agents never reach the public npm registry. + +> Looking for the alternative that doesn't require a registry? See [Offline Tarball walkthrough](air-gapped-azure-devops-offline-tarball.md). + +--- + +## When to Use This Guide + +- Self-hosted agents in a private network with no outbound internet +- You can host (or already have) an Azure Artifacts npm feed reachable from the agent +- Corporate networks that block access to the public npm registry + +--- + +## Architecture Overview + +```mermaid +flowchart LR + subgraph Connected Zone + A[npm install from public registry] --> B[Azure Artifacts feed] + end + subgraph Air-Gapped Network + B -->|Controlled sync window| C[Local feed replica] + C --> D[Self-hosted Agent] + D --> E[npm ci] + E --> F[apiops extract / publish] + end +``` + +--- + +## Prerequisites + +| Requirement | Details | +|-------------|---------| +| **Connected workstation** | A machine with internet access to seed the feed | +| **Node.js 22.x** | Installed on both the workstation and the agent (includes npm) | +| **[Self-hosted Azure Pipelines agent](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops#self-hosted-agents)** | Registered in your agent pool, running in the air-gapped network | +| **Azure connectivity from agent** | The agent must reach your APIM instance's ARM endpoint (network-level, not npm) | +| **Local npm registry** | An [Azure Artifacts npm feed](https://learn.microsoft.com/en-us/azure/devops/artifacts/npm/npmrc?view=azure-devops) accessible from the air-gapped network | + +> **On-premises Azure DevOps:** If you run [Azure DevOps Server](https://learn.microsoft.com/en-us/azure/devops/server/install/get-started?view=azure-devops-2022) (formerly TFS), the same approach applies — Azure Artifacts is included in the server installation. + +--- + +## Step 1 — Configure the Azure Artifacts npm Feed + +Set up an Azure Artifacts npm feed that serves packages to your air-gapped agents without requiring internet access at install time. + +1. **[Create a new feed](https://learn.microsoft.com/en-us/azure/devops/artifacts/get-started-npm?view=azure-devops#create-a-feed)** in your Azure DevOps organization. +2. **[Configure an upstream source](https://learn.microsoft.com/en-us/azure/devops/artifacts/how-to/set-up-upstream-sources?view=azure-devops)** pointing to `https://registry.npmjs.org`. The upstream is only used during controlled sync windows; once `@peterhauge/apiops-cli` and its dependencies are cached, the feed serves them locally. +3. **[Populate the feed](https://learn.microsoft.com/en-us/azure/devops/artifacts/npm/npmrc?view=azure-devops)** from a connected workstation by running `npm install @peterhauge/apiops-cli` against the feed registry URL. This pulls the package and its transitive dependencies into the feed cache. +4. **[Add a project `.npmrc`](https://learn.microsoft.com/en-us/azure/devops/artifacts/npm/npmrc?view=azure-devops)** that points `registry=` at your feed URL and sets `always-auth=true`. Commit this file so pipelines and developers resolve against the local feed. + +> **Tip:** Use the **Connect to feed** button in the Azure Artifacts UI to get the exact registry URL and a ready-to-copy `.npmrc` snippet for your feed. + +--- + +## Step 2 — Initialize the Repository + +```bash +apiops init \ + --ci azure-devops \ + --environments dev,prod \ + --non-interactive +``` + +This generates: + +| File | Purpose | +|------|---------| +| `package.json` | Declares the CLI as a dependency | +| `pipelines/run-extractor.yaml` | Extract pipeline | +| `pipelines/run-publisher.yaml` | Publish pipeline | +| `configuration.*.yaml` | Override templates | + +--- + +## Step 3 — Generate the Lock File + +```bash +npm install +``` + +This creates `package-lock.json`. Commit it — the lock file is **required** for `npm ci` to work. + +--- + +## Step 4 — Configure the Self-Hosted Agent + +Install and register the agent in the air-gapped network per the [self-hosted agent documentation](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/linux-agent?view=azure-devops): + +Ensure: + +1. **Node.js 22.x** is installed and on `PATH` +2. **Network access to the Azure Artifacts feed** — the agent can resolve packages from the local feed +3. **Network access to Azure ARM** — the agent must reach `management.azure.com` (or [sovereign cloud equivalent](https://learn.microsoft.com/en-us/azure/developer/identity/national-cloud)) +4. **Network access to Azure DevOps** — the agent must reach your Azure DevOps org for job dispatch +5. **Git** is installed (required by the `checkout` step) + +> **Agent pool:** Add your air-gapped agents to a [dedicated agent pool](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/pools-queues?view=azure-devops) (e.g., `air-gapped-pool`) so pipelines target them explicitly. + +--- + +## Step 5 — Modify Pipelines for Air-Gapped Operation + +The generated pipelines (`pipelines/run-extractor.yaml` and `pipelines/run-publisher.yaml`) need minimal edits: + +| Edit | What to Change | +|------|----------------| +| **Agent pool** | Replace `pool: vmImage: ubuntu-latest` with `pool: name: air-gapped-pool` ([pool YAML schema](https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/pool?view=azure-pipelines)) | +| **Remove NodeTool task** | Delete the `NodeTool@0` step (Node.js is pre-installed on the agent) | +| **Add feed auth** | Insert [`npmAuthenticate@0`](https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/npm-authenticate-v0?view=azure-pipelines) before the `npm ci` step (see below) | + +### Feed Authentication + +Add this task before any `npm ci` step in both pipelines: + +```yaml +- task: npmAuthenticate@0 + inputs: + workingFile: .npmrc +``` + +`npmAuthenticate@0` reads the committed `.npmrc` and injects credentials for the Azure Artifacts feed using the pipeline's built-in build service identity — no service connection needed for feeds in the same organization. The `npm ci` step then works as-is. + +> **Azure authentication:** The `AzureCLI@2` task handles Azure authentication separately, via the service connection. The service connection injects tokens for `DefaultAzureCredential`. + +--- + +## Step 6 — Configure Variable Groups and Service Connections + +Follow the standard [Azure DevOps integration guide](../ci-cd/azure-devops.md#variable-groups-configuration) to set up: + +1. **Variable group `apim-common`** — variables shared across all environments (e.g., subscription ID, tenant ID, common tags) +2. **Variable groups `apim-dev`, `apim-prod`** — environment-specific variables (e.g., APIM instance name, resource group) that override values from `apim-common` +3. **Service connections** — Azure Resource Manager connections scoped to your APIM instances; they can be referenced from either variable group + +These are configured in Azure DevOps and are injected at pipeline runtime. + +--- + +## Step 7 — Commit and Validate + +```bash +git add . +git commit -m "feat: air-gapped apiops setup with local registry" +git push +``` + +Trigger the extract pipeline manually from **Pipelines → Run pipeline** and verify: + +1. `npm ci` resolves all packages from the local Azure Artifacts feed (no calls to npmjs.org) +2. `apiops extract` authenticates via the service connection and runs successfully + +> **✅ Setup complete.** Your air-gapped apiops pipelines are now operational. The remaining sections cover ongoing maintenance and reference material — read them as needed. + +--- + +## Upgrading the CLI Version + +Sync the feed during a connectivity window to pull the new version, then update `package.json` and regenerate `package-lock.json`. Commit both. + +--- + +## Troubleshooting + +| Problem | Cause | Fix | +|---------|-------|-----| +| `npm ci` fails with `E404` | Package not in local feed | Sync the feed during a connectivity window | +| `npm ci` fails with "lockfile mismatch" | `package-lock.json` out of sync with `package.json` | Re-run `npm install` on connected workstation, commit updated lock file | +| `npx apiops` not found | `npm ci` didn't complete or `.bin` not in PATH | Verify `node_modules/.bin/apiops` exists after install | +| Azure auth fails | Agent can't reach Entra ID or ARM endpoint | Verify network allows traffic to `login.microsoftonline.com` and `management.azure.com` (or sovereign equivalents) | +| `AzureCLI@2` service connection error | Service connection not linked or misconfigured | Verify variable group is linked to pipeline and connection name matches | +| Agent not picking up jobs | Pool name mismatch or agent offline | Confirm pool name in YAML matches the registered agent pool | +| `npmAuthenticate@0` fails | Feed permissions or `.npmrc` path wrong | Ensure the build service identity has Reader access to the feed | + +--- + +## Further Reading + +- [Offline Tarball walkthrough](air-gapped-azure-devops-offline-tarball.md) — alternative for environments without a registry +- [apiops init reference](../commands/init.md) +- [Azure DevOps integration](../ci-cd/azure-devops.md) — standard (connected) setup +- [Authentication guide](../guides/authentication.md) — service principal and managed identity options +- [Azure Artifacts npm feeds](https://learn.microsoft.com/en-us/azure/devops/artifacts/npm/npmrc?view=azure-devops) — official feed setup docs +- [Self-hosted agents](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops#self-hosted-agents) — agent installation and configuration +- [Azure DevOps Server](https://learn.microsoft.com/en-us/azure/devops/server/install/get-started?view=azure-devops-2022) — on-premises installation +- [National cloud endpoints](https://learn.microsoft.com/en-us/azure/developer/identity/national-cloud) — sovereign cloud identity configuration +- [Entra ID authentication endpoints](https://learn.microsoft.com/en-us/azure/developer/identity/national-cloud#azure-ad-authentication-endpoints) — per-cloud token acquisition endpoints diff --git a/docs/walkthrough/air-gapped-azure-devops-offline-tarball.md b/docs/walkthrough/air-gapped-azure-devops-offline-tarball.md new file mode 100644 index 0000000..4f16bcf --- /dev/null +++ b/docs/walkthrough/air-gapped-azure-devops-offline-tarball.md @@ -0,0 +1,216 @@ +# Air-Gapped Setup: Azure DevOps — Offline Tarball + +Deploy APIM configuration using apiops-cli on [self-hosted Azure Pipelines agents](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops#self-hosted-agents) with **no internet access** and **no internal npm registry**. The CLI is packaged as a `.tgz` and the npm cache is pre-staged on the agent so `npm ci --offline` resolves every dependency from disk. + +> If you can host an internal npm feed instead, prefer the [Local npm Registry walkthrough](air-gapped-azure-devops-local-registry.md) — it requires far less manual artifact transfer. + +--- + +## When to Use This Guide + +- Self-hosted agents in a private network with no outbound internet +- No Azure Artifacts feed (or any other internal npm registry) reachable from the agent +- You accept the operational cost of re-transferring the tarball and npm cache whenever dependencies change + +--- + +## Architecture Overview + +```mermaid +flowchart LR + subgraph Connected Workstation + A[npm pack apiops-cli] --> B[apiops-cli.tgz] + B --> C[npm ci] + C --> D[~/.npm/_cacache/] + end + subgraph Air-Gapped Network + B -->|Commit to repo| E[.apiops/*.tgz] + D -->|File copy| F[Agent ~/.npm/_cacache/] + E --> G[Self-hosted Agent] + F --> G + G --> H[npm ci --offline] + H --> I[apiops extract / publish] + end +``` + +--- + +## Prerequisites + +| Requirement | Details | +|-------------|---------| +| **Connected workstation** | A machine with internet access to download the CLI and its dependencies | +| **Node.js 22.x** | Installed on both the workstation and the agent (includes npm) | +| **[Self-hosted Azure Pipelines agent](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops#self-hosted-agents)** | Registered in your agent pool, running in the air-gapped network | +| **Azure connectivity from agent** | The agent must reach your APIM instance's ARM endpoint (network-level only) | +| **File transfer mechanism** | A way to copy the npm cache directory into the air-gapped network | + +> **On-premises Azure DevOps:** If you run [Azure DevOps Server](https://learn.microsoft.com/en-us/azure/devops/server/install/get-started?view=azure-devops-2022), the same approach applies. + +--- + +## Step 1 — Pack the CLI + +On the connected workstation: + +```bash +npm pack @peterhauge/apiops-cli +``` + +This produces `peterhauge-apiops-cli-.tgz` in the current directory. + +Commit the tarball into your repository (e.g., under `.apiops/`) so the pipeline can reference it by path: + +```bash +mkdir -p .apiops +mv peterhauge-apiops-cli-*.tgz .apiops/ +git add .apiops/peterhauge-apiops-cli-*.tgz +``` + +--- + +## Step 2 — Initialize the Repository + +Pass `--cli-package` so the generated `package.json` references the local tarball instead of the public registry: + +```bash +apiops init \ + --ci azure-devops \ + --environments dev,prod \ + --cli-package ./.apiops/peterhauge-apiops-cli-.tgz \ + --non-interactive +``` + +This command generates: + +| File | Purpose | +|------|---------| +| `package.json` | Declares the CLI as a `file:` dependency pointing at the tarball | +| `pipelines/run-extractor.yaml` | Extract pipeline | +| `pipelines/run-publisher.yaml` | Publish pipeline | +| `configuration.*.yaml` | Override templates | + +--- + +## Step 3 — Generate the Lock File and Pre-Stage the npm Cache + +On the **connected workstation**, run: + +```bash +npm install # creates package-lock.json +npm ci # populates ~/.npm/_cacache/ with every package the lock file references +``` + +Commit `package-lock.json` to the repository. The command `npm ci --offline` requires it. + +Then transfer the npm cache to the agent: + +```bash +# On the workstation +tar -czf npm-cacache.tar.gz -C ~/.npm _cacache + +# Transfer npm-cacache.tar.gz into the air-gapped network, then on the agent: +mkdir -p ~/.npm +tar -xzf npm-cacache.tar.gz -C ~/.npm +``` + +> Repeat this cache transfer every time dependencies change (CLI upgrade, new package, etc.). + +--- + +## Step 4 — Configure the Self-Hosted Agent + +Install and register the agent in the air-gapped network per the [self-hosted agent documentation](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/linux-agent?view=azure-devops). + +Verify the following: + +1. **Node.js 22.x** is installed and on `PATH` +2. **npm cache is pre-staged** at the agent service account's `~/.npm/_cacache/` (see Step 3) +3. **Network access to Azure ARM** — the agent must reach `management.azure.com` (or [sovereign cloud equivalent](https://learn.microsoft.com/en-us/azure/developer/identity/national-cloud)) +4. **Network access to Azure DevOps** — the agent must reach your Azure DevOps org for job dispatch +5. **Git** is installed (required by the `checkout` step) + +> **Agent pool:** Add your air-gapped agents to a [dedicated agent pool](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/pools-queues?view=azure-devops) (e.g., `air-gapped-pool`) so pipelines target them explicitly. + +--- + +## Step 5 — Modify Pipelines for Air-Gapped Operation + +The generated pipelines (`pipelines/run-extractor.yaml` and `pipelines/run-publisher.yaml`) need three edits: + +| Edit | What to Change | +|------|----------------| +| **Agent pool** | Replace `pool: vmImage: ubuntu-latest` with `pool: name: air-gapped-pool` ([pool YAML schema](https://learn.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/pool?view=azure-pipelines)) | +| **Remove NodeTool task** | Delete the `NodeTool@0` step (Node.js is pre-installed on the agent) | +| **Use offline `npm ci`** | Change `npm ci` to `npm ci --offline` so npm resolves entirely from the pre-staged cache | + +No `npmAuthenticate@0` task is needed — there is no registry to authenticate against. The `--offline` flag makes npm fail fast if any package is missing from the cache instead of attempting a network call. + +> **Azure authentication:** The `AzureCLI@2` task handles Azure authentication via the service connection. The service connection injects tokens for `DefaultAzureCredential`. + +--- + +## Step 6 — Configure Variable Groups and Service Connections + +Follow the standard [Azure DevOps integration guide](../ci-cd/azure-devops.md#variable-groups-configuration) to set up: + +1. **Variable group `apim-common`** — variables shared across all environments (e.g., subscription ID, tenant ID, common tags) +2. **Variable groups `apim-dev`, `apim-prod`** — environment-specific variables (e.g., APIM instance name, resource group) that override values from `apim-common` +3. **Service connections** — Azure Resource Manager connections scoped to your APIM instances; they can be referenced from either variable group + +These are configured in Azure DevOps and are injected at pipeline runtime. + +--- + +## Step 7 — Commit and Validate + +```bash +git add . +git commit -m "feat: air-gapped apiops setup with offline tarball" +git push +``` + +Trigger the extract pipeline manually from **Pipelines → Run pipeline** and verify: + +1. `npm ci --offline` completes with no network calls +2. `apiops extract` authenticates via the service connection and runs successfully + +> **✅ Setup complete.** Your air-gapped apiops pipelines are now operational. The remaining sections cover ongoing maintenance and reference material — read them as needed. + +--- + +## Upgrading the CLI Version + +1. On a connected workstation, run `npm pack @peterhauge/apiops-cli` for the new version +2. Replace `.apiops/peterhauge-apiops-cli-*.tgz` with the new tarball and update the `file:` path in `package.json` +3. Regenerate `package-lock.json` (`npm install`) +4. Re-populate and re-transfer the npm cache (`npm ci` on the workstation, then copy `~/.npm/_cacache/`) +5. Commit the tarball and updated lock file + +--- + +## Troubleshooting + +| Problem | Cause | Fix | +|---------|-------|-----| +| `npm ci` fails with `ENOTCACHED` | npm cache missing one or more packages | Re-run `npm ci` on the connected workstation and re-transfer `~/.npm/_cacache/` | +| `npm ci` fails with "lockfile mismatch" | `package-lock.json` out of sync with `package.json` | Re-run `npm install` on connected workstation, commit updated lock file, refresh cache | +| `npm install` complains about missing tarball | Path in `package.json` doesn't match the file on disk | Verify the `file:` reference matches the committed `.tgz` filename exactly | +| `npx apiops` not found | `npm ci --offline` didn't complete or `.bin` not in PATH | Verify `node_modules/.bin/apiops` exists after install | +| Azure auth fails | Agent can't reach Entra ID or ARM endpoint | Verify network allows traffic to `login.microsoftonline.com` and `management.azure.com` (or sovereign equivalents) | +| `AzureCLI@2` service connection error | Service connection not linked or misconfigured | Verify variable group is linked to pipeline and connection name matches | +| Agent not picking up jobs | Pool name mismatch or agent offline | Confirm pool name in YAML matches the registered agent pool | + +--- + +## Further Reading + +- [Local npm Registry walkthrough](air-gapped-azure-devops-local-registry.md) — recommended when an internal feed is available +- [apiops init reference](../commands/init.md) — full `--cli-package` documentation +- [Azure DevOps integration](../ci-cd/azure-devops.md) — standard (connected) setup +- [Authentication guide](../guides/authentication.md) — service principal and managed identity options +- [Air-gapped setup: GitHub Actions](air-gapped-github-actions.md) +- [Self-hosted agents](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops#self-hosted-agents) — agent installation and configuration +- [Azure DevOps Server](https://learn.microsoft.com/en-us/azure/devops/server/install/get-started?view=azure-devops-2022) — on-premises installation +- [National cloud endpoints](https://learn.microsoft.com/en-us/azure/developer/identity/national-cloud) — sovereign cloud identity configuration +- [Entra ID authentication endpoints](https://learn.microsoft.com/en-us/azure/developer/identity/national-cloud#azure-ad-authentication-endpoints) — per-cloud token acquisition endpoints diff --git a/docs/walkthrough/air-gapped-azure-devops.md b/docs/walkthrough/air-gapped-azure-devops.md new file mode 100644 index 0000000..0b1cf57 --- /dev/null +++ b/docs/walkthrough/air-gapped-azure-devops.md @@ -0,0 +1,21 @@ +# Air-Gapped Setup: Azure DevOps + +Deploy APIM configuration using apiops-cli on [self-hosted Azure Pipelines agents](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops#self-hosted-agents) with **no internet access** at runtime. There are two supported approaches — pick the one that matches your environment and follow that walkthrough end-to-end. + +--- + +## Choose Your Approach + +| Approach | Best For | Walkthrough | +|----------|----------|-------------| +| **Local npm Registry** (recommended) | You have (or can host) an Azure Artifacts npm feed reachable from the agent. Packages flow through the feed; only the feed needs controlled connectivity to refresh. | [Local npm Registry walkthrough](air-gapped-azure-devops-local-registry.md) | +| **Offline Tarball** | No internal npm registry is available. The CLI ships as a `.tgz` in your repo and the npm cache is transferred manually to the agent. Requires re-staging on every dependency change. | [Offline Tarball walkthrough](air-gapped-azure-devops-offline-tarball.md) | + +--- + +## Related + +- [Air-gapped setup: GitHub Actions](air-gapped-github-actions.md) +- [Azure DevOps integration](../ci-cd/azure-devops.md) — standard (connected) setup +- [apiops init reference](../commands/init.md) +- [Authentication guide](../guides/authentication.md) diff --git a/docs/walkthrough/air-gapped-github-actions-local-registry.md b/docs/walkthrough/air-gapped-github-actions-local-registry.md new file mode 100644 index 0000000..68ccc6c --- /dev/null +++ b/docs/walkthrough/air-gapped-github-actions-local-registry.md @@ -0,0 +1,227 @@ +# Air-Gapped Setup: GitHub Actions — Local npm Registry + +Deploy APIM configuration using apiops-cli on [self-hosted GitHub Actions runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners) with **no internet access** at runtime. This walkthrough uses [GitHub Packages on GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@latest/packages/working-with-a-github-packages-registry/working-with-the-npm-registry) as the npm registry so runners never reach the public npm registry. + +> Looking for the alternative that doesn't require a registry? See [Offline Tarball walkthrough](air-gapped-github-actions-offline-tarball.md). + +> **GitHub.com vs. GitHub Enterprise Server:** GitHub.com's hosted GitHub Packages registry (`npm.pkg.github.com`) is an internet service and cannot serve as an air-gapped registry. This walkthrough assumes [GitHub Enterprise Server (GHES)](https://docs.github.com/en/enterprise-server@latest/admin/overview/about-github-enterprise-server) running inside your network, which can host [GitHub Packages](https://docs.github.com/en/enterprise-server@latest/admin/packages/getting-started-with-github-packages-for-your-enterprise) including the npm registry. + +--- + +## When to Use This Guide + +- Self-hosted runners in a private network with no outbound internet +- You run GHES with GitHub Packages enabled +- Corporate network that blocks access to the public npm registry + +--- + +## Architecture Overview + +```mermaid +flowchart LR + subgraph Connected Zone + A[npm install from public registry] --> B[GHES Packages npm registry] + end + subgraph Air-Gapped Network + B -->|Controlled sync window| C[Local registry replica] + C --> D[Self-hosted Runner] + D --> E[npm ci] + E --> F[apiops extract / publish] + end +``` + +--- + +## Prerequisites + +| Requirement | Details | +|-------------|---------| +| **Connected workstation** | A machine with internet access to seed the registry | +| **Node.js 22.x** | Installed on both the workstation and the runner (includes npm) | +| **[Self-hosted GitHub Actions runner](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners)** | Registered in your repository or organization, running in the air-gapped network | +| **Azure connectivity from runner** | The runner must reach your APIM instance's ARM endpoint (network-level, not npm) | +| **Internal npm registry** | A [GitHub Packages npm registry](https://docs.github.com/en/enterprise-server@latest/packages/working-with-a-github-packages-registry/working-with-the-npm-registry) on your GHES instance, reachable from the air-gapped network. | + +> **GHES on-premises:** If you run [GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@latest/admin/overview/about-github-enterprise-server), the runner, the npm registry, and job dispatch can all live entirely inside your network with no internet egress. GHES is a self-contained appliance "governed by access and security controls that you define, such as firewalls, network policies, IAM, monitoring, and VPNs" ([About GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@latest/admin/overview/about-github-enterprise-server)), and outbound connectivity to GitHub.com is only required if you opt in to [GitHub Connect](https://docs.github.com/en/enterprise-server@latest/admin/configuring-settings/configuring-github-connect/enabling-github-connect-for-githubcom). + +--- + +## Step 1 — Configure the GHES Packages npm Registry + +Set up the [GitHub Packages](https://docs.github.com/en/enterprise-server@latest/admin/packages/getting-started-with-github-packages-for-your-enterprise) npm registry on your GHES instance so it serves packages to your air-gapped runners without requiring internet access at install time. + +1. **[Enable GitHub Packages on GHES](https://docs.github.com/en/enterprise-server@latest/admin/packages/getting-started-with-github-packages-for-your-enterprise)** — turn on the Packages service for your enterprise and configure the storage backend. The npm registry endpoint is `https://npm./`. +2. **Populate the registry** from a connected workstation by running `npm install @peterhauge/apiops-cli` against the GHES npm registry URL. This pulls the package and its transitive dependencies into the registry cache. +3. **Add a project `.npmrc`** that points `registry=` at your GHES npm endpoint and sets `//npm./:_authToken=${NODE_AUTH_TOKEN}` so authentication is read from an environment variable injected at workflow runtime. Commit `.npmrc` so workflows and developers resolve against the local registry. + +> **Tip:** Follow [Authenticating to GitHub Packages](https://docs.github.com/en/enterprise-server@latest/packages/working-with-a-github-packages-registry/working-with-the-npm-registry#authenticating-to-github-packages) for the exact `.npmrc` format your GHES version expects. + +--- + +## Step 2 — Initialize the Repository + +```bash +apiops init \ + --ci github-actions \ + --environments dev,prod \ + --non-interactive +``` + +This generates: + +| File | Purpose | +|------|---------| +| `package.json` | Declares the CLI as a dependency | +| `.github/workflows/run-extractor.yaml` | Extract workflow | +| `.github/workflows/run-publisher.yaml` | Publish workflow | +| `configuration.*.yaml` | Override templates | + +--- + +## Step 3 — Generate the Lock File + +```bash +npm install +``` + +This creates `package-lock.json`. Commit it — the lock file is **required** for `npm ci` to work and pins every transitive dependency to a registry-resolved tarball URL. + +--- + +## Step 4 — Configure the Self-Hosted Runner + +Install and register the runner in the air-gapped network per the [self-hosted runner documentation](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners). + +Verify the following: + +1. **Node.js 22.x** is installed and on `PATH` +2. **Network access to the GHES Packages npm registry** — the runner can resolve packages from `https://npm./` +3. **Network access to Azure ARM** — the runner must reach `management.azure.com` (or [sovereign cloud equivalent](https://learn.microsoft.com/en-us/azure/developer/identity/national-cloud)) +4. **Network access to GHES** — the runner must reach your GHES instance for job dispatch, `actions/checkout`, and secret injection +5. **Git** is installed (required by `actions/checkout`) + +> **Runner labels:** Add a custom label (e.g., `air-gapped`) when registering the runner so workflows can target it via `runs-on: [self-hosted, air-gapped]`. + +--- + +## Step 5 — Modify Workflows for Air-Gapped Operation + +The generated workflows need a few edits per job: + +| Edit | What to Change | +|------|----------------| +| **Runner target** | Replace `runs-on: ubuntu-latest` with `runs-on: [self-hosted, air-gapped]` | +| **`setup-node` registry** | Configure `actions/setup-node@v4` with `registry-url:` pointing at your GHES npm endpoint, and pass `NODE_AUTH_TOKEN` as an env var on the `npm ci` step (see below) | +| **Keep `npm ci`** | `.npmrc` already points at the GHES registry; no `--offline` flag is needed | + +### Registry Authentication + +```yaml +jobs: + extract: + runs-on: [self-hosted, air-gapped] + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + registry-url: 'https://npm./' # your GHES Packages npm endpoint + + - name: Install dependencies + env: + NODE_AUTH_TOKEN: ${{ secrets.GHES_PACKAGES_TOKEN }} + run: npm ci + + - name: Run extract + env: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + run: | + npx apiops extract \ + --resource-group ${{ secrets.APIM_RESOURCE_GROUP }} \ + --service-name ${{ secrets.APIM_SERVICE_NAME }} \ + --subscription-id ${{ secrets.AZURE_SUBSCRIPTION_ID }} \ + --output ./apim-artifacts +``` + +`actions/setup-node@v4` writes the `registry-url` and an auth-token placeholder into a runner-local `.npmrc`; `NODE_AUTH_TOKEN` is substituted in at install time. + +> **Authentication to Azure:** +> +> 1. **Managed identity (preferred)** — if your self-hosted runner is an Azure VM, attach a [user-assigned or system-assigned managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview), grant it APIM roles, and call `azure/login@v3` with `auth-type: IDENTITY`. No secrets to store, no internet egress required, and the token request stays inside the Azure backbone. See [Login With User-assigned Managed Identity](https://github.com/Azure/login#login-with-user-assigned-managed-identity) in the `azure/login` README. +> 2. **OIDC federation** — requires the runner to reach the GitHub Actions OIDC issuer at `token.actions.githubusercontent.com` to request the JWT that Entra ID validates ([GitHub OIDC overview](https://docs.github.com/en/actions/concepts/security/openid-connect#understanding-the-oidc-token), [Configuring OIDC in Azure](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-azure)). Use this when the runner is not on Azure compute but can still reach that endpoint. +> 3. **Service principal secret (fallback)** — when neither managed identity nor OIDC is viable, supply `AZURE_CLIENT_ID`/`AZURE_CLIENT_SECRET`/`AZURE_TENANT_ID` from repository secrets. +> +> See the [authentication guide](../guides/authentication.md) for full details. + +--- + +## Step 6 — Configure Repository Secrets + +Configure the secrets your workflows reference under **Settings → Secrets and variables → Actions**: + +| Secret | Purpose | +|--------|---------| +| `GHES_PACKAGES_TOKEN` | Personal access token / service token with `read:packages` scope on your GHES Packages registry | +| `AZURE_SUBSCRIPTION_ID` | Target subscription | +| `AZURE_TENANT_ID` | Entra ID tenant | +| `AZURE_CLIENT_ID` | Service principal app ID (if not using OIDC) | +| `AZURE_CLIENT_SECRET` | Service principal secret (if not using OIDC) | +| `APIM_RESOURCE_GROUP`, `APIM_SERVICE_NAME` | Per-environment APIM identifiers | + +Use [environment-scoped secrets](https://docs.github.com/en/actions/deployment/targeting-different-environments/managing-environments-for-deployment) for per-environment values (`dev`, `prod`). + +--- + +## Step 7 — Commit and Validate + +```bash +git add . +git commit -m "feat: air-gapped apiops setup with local registry" +git push +``` + +Trigger the extract workflow manually from **Actions → Run workflow** and verify: + +1. `npm ci` resolves all packages from the GHES Packages registry (no calls to `registry.npmjs.org`) +2. `apiops extract` authenticates and runs successfully + +> **✅ Setup complete.** Your air-gapped apiops workflows are now operational. The remaining sections cover ongoing maintenance and reference material — read them as needed. + +--- + +## Upgrading the CLI Version + +Sync the GHES Packages registry during a connectivity window to pull the new version, then update `package.json` and regenerate `package-lock.json`. Commit both. + +--- + +## Troubleshooting + +| Problem | Cause | Fix | +|---------|-------|-----| +| `npm ci` fails with `E404` | Package not in the GHES Packages registry | Sync the registry during a connectivity window | +| `npm ci` fails with `E401` / `E403` | `NODE_AUTH_TOKEN` missing, expired, or lacks `read:packages` | Re-create the PAT / service token and update the repository secret | +| `npm ci` fails with "lockfile mismatch" | `package-lock.json` out of sync with `package.json` | Re-run `npm install` on connected workstation, commit updated lock file | +| `npx apiops` not found | `npm ci` didn't complete or `.bin` not in PATH | Verify `node_modules/.bin/apiops` exists after install | +| Azure auth fails | Runner can't reach Entra ID or ARM endpoint | Verify network allows traffic to `login.microsoftonline.com` and `management.azure.com` (or sovereign equivalents) | +| OIDC token request fails | Runner blocked from `token.actions.githubusercontent.com` | Switch to service principal credentials in repository secrets | +| `actions/checkout` fails | Runner can't reach GHES API | Ensure runner has network path to your GHES instance | +| Runner not picking up jobs | Label mismatch or runner offline | Confirm `runs-on` labels match the registered runner | + +--- + +## Further Reading + +- [Offline Tarball walkthrough](air-gapped-github-actions-offline-tarball.md) — alternative for environments without a registry +- [apiops init reference](../commands/init.md) +- [GitHub Actions integration](../ci-cd/github-actions.md) — standard (connected) setup +- [Authentication guide](../guides/authentication.md) — service principal and managed identity options +- [GitHub Packages npm registry](https://docs.github.com/en/enterprise-server@latest/packages/working-with-a-github-packages-registry/working-with-the-npm-registry) — official GHES npm registry docs +- [Self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners) — runner installation and configuration +- [GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@latest/admin/overview/about-github-enterprise-server) — on-premises GitHub +- [National cloud endpoints](https://learn.microsoft.com/en-us/azure/developer/identity/national-cloud) — sovereign cloud identity configuration +- [Entra ID authentication endpoints](https://learn.microsoft.com/en-us/azure/developer/identity/national-cloud#azure-ad-authentication-endpoints) — per-cloud token acquisition endpoints diff --git a/docs/walkthrough/air-gapped-github-actions-offline-tarball.md b/docs/walkthrough/air-gapped-github-actions-offline-tarball.md new file mode 100644 index 0000000..a70fbca --- /dev/null +++ b/docs/walkthrough/air-gapped-github-actions-offline-tarball.md @@ -0,0 +1,251 @@ +# Air-Gapped Setup: GitHub Actions — Offline Tarball + +Deploy APIM configuration using apiops-cli on [self-hosted GitHub Actions runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners) with **no internet access** and **no internal npm registry**. The CLI is packaged as a `.tgz` and the npm cache is pre-staged on the runner so `npm ci --offline` resolves every dependency from disk. + +> If you can host an internal npm feed instead (e.g., [GitHub Packages on GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@latest/packages/working-with-a-github-packages-registry/working-with-the-npm-registry)), prefer the [Local npm Registry walkthrough](air-gapped-github-actions-local-registry.md) — it requires far less manual artifact transfer. + +--- + +## When to Use This Guide + +- Self-hosted runners in a private network with no outbound internet +- No GitHub Packages / GitHub Enterprise Server / other internal npm registry reachable from the runner +- You accept the operational cost of re-transferring the tarball and npm cache whenever dependencies change + +--- + +## Architecture Overview + +```mermaid +flowchart LR + subgraph Connected Workstation + A[npm pack apiops-cli] --> B[apiops-cli.tgz] + B --> C[npm ci] + C --> D[~/.npm/_cacache/] + end + subgraph Air-Gapped Network + B -->|Commit to repo| E[.apiops/*.tgz] + D -->|File copy| F[Runner ~/.npm/_cacache/] + E --> G[Self-hosted Runner] + F --> G + G --> H[npm ci --offline] + H --> I[apiops extract / publish] + end +``` + +--- + +## Prerequisites + +| Requirement | Details | +|-------------|---------| +| **Connected workstation** | A machine with internet access to download the CLI and its dependencies | +| **Node.js 22.x** | Installed on both the workstation and the runner (includes npm) | +| **[Self-hosted GitHub Actions runner](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners)** | Registered in your repository or organization, running in the air-gapped network | +| **Azure connectivity from runner** | The runner must reach your APIM instance's ARM endpoint (network-level only) | +| **GitHub connectivity from runner** | The runner must reach `github.com` (or your [GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@latest/admin/overview/about-github-enterprise-server)) for job dispatch, checkout, and secret injection | +| **File transfer mechanism** | A way to copy the npm cache directory into the air-gapped network | + +--- + +## Step 1 — Pack the CLI + +On the connected workstation: + +```bash +npm pack @peterhauge/apiops-cli +``` + +This produces `peterhauge-apiops-cli-.tgz` in the current directory. + +Commit the tarball into your repository (e.g., under `.apiops/`) so the workflow can reference it by path: + +```bash +mkdir -p .apiops +mv peterhauge-apiops-cli-*.tgz .apiops/ +git add .apiops/peterhauge-apiops-cli-*.tgz +``` + +--- + +## Step 2 — Initialize the Repository + +Pass `--cli-package` so the generated `package.json` references the local tarball instead of the public registry: + +```bash +apiops init \ + --ci github-actions \ + --environments dev,prod \ + --cli-package ./.apiops/peterhauge-apiops-cli-.tgz \ + --non-interactive +``` + +This command generates: + +| File | Purpose | +|------|---------| +| `package.json` | Declares the CLI as a `file:` dependency pointing at the tarball | +| `.github/workflows/run-extractor.yaml` | Extract workflow | +| `.github/workflows/run-publisher.yaml` | Publish workflow | +| `configuration.*.yaml` | Override templates | + +--- + +## Step 3 — Generate the Lock File and Pre-Stage the npm Cache + +On the **connected workstation**, run: + +```bash +npm install # creates package-lock.json +npm ci # populates ~/.npm/_cacache/ with every package the lock file references +``` + +Commit `package-lock.json` to the repository. The command `npm ci --offline` requires it. + +Then transfer the npm cache to the runner: + +```bash +# On the workstation +tar -czf npm-cacache.tar.gz -C ~/.npm _cacache + +# Transfer npm-cacache.tar.gz into the air-gapped network, then on the runner: +mkdir -p ~/.npm +tar -xzf npm-cacache.tar.gz -C ~/.npm +``` + +> Repeat this cache transfer every time dependencies change (CLI upgrade, new package, etc.). + +--- + +## Step 4 — Configure the Self-Hosted Runner + +Install and register the runner in the air-gapped network per the [self-hosted runner documentation](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners). + +Verify the following: + +1. **Node.js 22.x** is installed and on `PATH` +2. **npm cache is pre-staged** at the runner service account's `~/.npm/_cacache/` (see Step 3) +3. **Network access to Azure ARM** — the runner must reach `management.azure.com` (or [sovereign cloud equivalent](https://learn.microsoft.com/en-us/azure/developer/identity/national-cloud)) +4. **Network access to GitHub** — the runner must reach `github.com` (or your GHES instance) for job dispatch, `actions/checkout`, and secret injection +5. **Git** is installed (required by `actions/checkout`) + +> **Runner labels:** Add a custom label (e.g., `air-gapped`) when registering the runner so workflows can target it via `runs-on: [self-hosted, air-gapped]`. + +--- + +## Step 5 — Modify Workflows for Air-Gapped Operation + +The generated workflows need three edits per job: + +| Edit | What to Change | +|------|----------------| +| **Runner target** | Replace `runs-on: ubuntu-latest` with `runs-on: [self-hosted, air-gapped]` | +| **Remove `setup-node`** | Delete the `actions/setup-node@v4` step (Node.js is pre-installed on the runner) | +| **Use offline `npm ci`** | Change `npm ci` to `npm ci --offline` so npm resolves entirely from the pre-staged cache | + +Example `.github/workflows/run-extractor.yaml`: + +```yaml +jobs: + extract: + runs-on: [self-hosted, air-gapped] + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies (offline) + run: npm ci --offline + + - name: Run extract + env: + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + run: | + npx apiops extract \ + --resource-group ${{ secrets.APIM_RESOURCE_GROUP }} \ + --service-name ${{ secrets.APIM_SERVICE_NAME }} \ + --subscription-id ${{ secrets.AZURE_SUBSCRIPTION_ID }} \ + --output ./apim-artifacts +``` + +The `--offline` flag makes npm fail fast if any package is missing from the cache instead of attempting a network call. + +> **Authentication to Azure (recommended order):** +> +> 1. **Managed identity (preferred)** — if your self-hosted runner is an Azure VM, attach a [user-assigned or system-assigned managed identity](https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview), grant it APIM roles, and call `azure/login@v3` with `auth-type: IDENTITY`. No secrets to store, no internet egress required. See [Login With User-assigned Managed Identity](https://github.com/Azure/login#login-with-user-assigned-managed-identity) in the `azure/login` README. +> 2. **OIDC federation** — requires the runner to reach `token.actions.githubusercontent.com`. Use this when the runner is not on Azure compute but can still reach that endpoint. +> 3. **Service principal secret (fallback)** — when neither managed identity nor OIDC is viable, supply `AZURE_CLIENT_ID`/`AZURE_CLIENT_SECRET`/`AZURE_TENANT_ID` from repository secrets. +> +> See the [authentication guide](../guides/authentication.md) for full details. + +--- + +## Step 6 — Configure Repository Secrets + +Configure the secrets your workflows reference under **Settings → Secrets and variables → Actions**: + +| Secret | Purpose | +|--------|---------| +| `AZURE_SUBSCRIPTION_ID` | Target subscription | +| `AZURE_TENANT_ID` | Entra ID tenant | +| `AZURE_CLIENT_ID` | Service principal app ID (if not using OIDC) | +| `AZURE_CLIENT_SECRET` | Service principal secret (if not using OIDC) | +| `APIM_RESOURCE_GROUP`, `APIM_SERVICE_NAME` | Per-environment APIM identifiers | + +Use [environment-scoped secrets](https://docs.github.com/en/actions/deployment/targeting-different-environments/managing-environments-for-deployment) for per-environment values (`dev`, `prod`). + +--- + +## Step 7 — Commit and Validate + +```bash +git add . +git commit -m "feat: air-gapped apiops setup with offline tarball" +git push +``` + +Trigger the extract workflow manually from **Actions → Run workflow** and verify: + +1. `npm ci --offline` completes with no network calls +2. `apiops extract` authenticates and runs successfully + +> **✅ Setup complete.** Your air-gapped apiops workflows are now operational. The remaining sections cover ongoing maintenance and reference material — read them as needed. + +--- + +## Upgrading the CLI Version + +1. On a connected workstation, run `npm pack @peterhauge/apiops-cli` for the new version +2. Replace `.apiops/peterhauge-apiops-cli-*.tgz` with the new tarball and update the `file:` path in `package.json` +3. Regenerate `package-lock.json` (`npm install`) +4. Re-populate and re-transfer the npm cache (`npm ci` on the workstation, then copy `~/.npm/_cacache/`) +5. Commit the tarball and updated lock file + +--- + +## Troubleshooting + +| Problem | Cause | Fix | +|---------|-------|-----| +| `npm ci` fails with `ENOTCACHED` | npm cache missing one or more packages | Re-run `npm ci` on the connected workstation and re-transfer `~/.npm/_cacache/` | +| `npm ci` fails with "lockfile mismatch" | `package-lock.json` out of sync with `package.json` | Re-run `npm install` on connected workstation, commit updated lock file, refresh cache | +| `npm install` complains about missing tarball | Path in `package.json` doesn't match the file on disk | Verify the `file:` reference matches the committed `.tgz` filename exactly | +| `npx apiops` not found | `npm ci --offline` didn't complete or `.bin` not in PATH | Verify `node_modules/.bin/apiops` exists after install | +| Azure auth fails | Runner can't reach Entra ID or ARM endpoint | Verify network allows traffic to `login.microsoftonline.com` and `management.azure.com` (or sovereign equivalents) | +| `actions/checkout` fails | Runner can't reach GitHub API | Ensure runner has network path to `github.com` (or your GHES instance) | +| OIDC token request fails | Runner blocked from `token.actions.githubusercontent.com` | Switch to service principal credentials in repository secrets | +| Runner not picking up jobs | Label mismatch or runner offline | Confirm `runs-on` labels match the registered runner | + +--- + +## Further Reading + +- [Local npm Registry walkthrough](air-gapped-github-actions-local-registry.md) — recommended when GitHub Packages on GHES (or another internal feed) is available +- [apiops init reference](../commands/init.md) — full `--cli-package` documentation +- [GitHub Actions integration](../ci-cd/github-actions.md) — standard (connected) setup +- [Authentication guide](../guides/authentication.md) — service principal and managed identity options +- [Air-gapped setup: Azure DevOps](air-gapped-azure-devops.md) +- [Self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners) — runner installation and configuration +- [GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@latest/admin/overview/about-github-enterprise-server) — on-premises GitHub +- [National cloud endpoints](https://learn.microsoft.com/en-us/azure/developer/identity/national-cloud) — sovereign cloud identity configuration +- [Entra ID authentication endpoints](https://learn.microsoft.com/en-us/azure/developer/identity/national-cloud#azure-ad-authentication-endpoints) — per-cloud token acquisition endpoints diff --git a/docs/walkthrough/air-gapped-github-actions.md b/docs/walkthrough/air-gapped-github-actions.md new file mode 100644 index 0000000..e04a0a6 --- /dev/null +++ b/docs/walkthrough/air-gapped-github-actions.md @@ -0,0 +1,23 @@ +# Air-Gapped Setup: GitHub Actions + +Deploy APIM configuration using apiops-cli on [self-hosted GitHub Actions runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners) with **no internet access** at runtime. There are two supported approaches — pick the one that matches your environment and follow that walkthrough end-to-end. + +--- + +## Choose Your Approach + +| Approach | Best For | Walkthrough | +|----------|----------|-------------| +| **Local npm Registry** (recommended) | You run [GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@latest/admin/overview/about-github-enterprise-server) with [GitHub Packages](https://docs.github.com/en/enterprise-server@latest/packages/working-with-a-github-packages-registry/working-with-the-npm-registry), or any other internal npm registry reachable from the runner (Verdaccio, Artifactory, Nexus). Packages flow through the registry; only the registry needs controlled connectivity to refresh. | [Local npm Registry walkthrough](air-gapped-github-actions-local-registry.md) | +| **Offline Tarball** | No internal npm registry is available. The CLI ships as a `.tgz` in your repo and the npm cache is transferred manually to the runner. Requires re-staging on every dependency change. | [Offline Tarball walkthrough](air-gapped-github-actions-offline-tarball.md) | + +> **GitHub.com SaaS is not an air-gapped registry.** `npm.pkg.github.com` is an internet service. The Local npm Registry approach assumes [GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@latest/admin/overview/about-github-enterprise-server) running inside your network (or another self-hosted npm registry). + +--- + +## Related + +- [Air-gapped setup: Azure DevOps](air-gapped-azure-devops.md) +- [GitHub Actions integration](../ci-cd/github-actions.md) — standard (connected) setup +- [apiops init reference](../commands/init.md) +- [Authentication guide](../guides/authentication.md)