Skip to content

Commit f873364

Browse files
CopilotMossaka
andauthored
feat: add GitHub Action for installing awf (#184)
* Initial plan * feat: add GitHub Action for installing awf Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com> * fix: address code review comments for action.yml Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com> * ci: add workflow to test setup action Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com> * feat: add image pinning support to setup action Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com>
1 parent 59d1d9c commit f873364

5 files changed

Lines changed: 473 additions & 16 deletions

File tree

.github/workflows/test-action.yml

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
name: Test Setup Action
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
workflow_dispatch:
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
test-action-latest:
15+
name: Test Action (Latest Version)
16+
runs-on: ubuntu-latest
17+
timeout-minutes: 5
18+
19+
steps:
20+
- name: Checkout repository
21+
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
22+
23+
- name: Setup awf using action
24+
id: setup-awf
25+
uses: ./
26+
27+
- name: Verify awf is installed
28+
run: |
29+
echo "Installed version: ${{ steps.setup-awf.outputs.version }}"
30+
which awf
31+
awf --version
32+
awf --help
33+
34+
test-action-specific-version:
35+
name: Test Action (Specific Version)
36+
runs-on: ubuntu-latest
37+
timeout-minutes: 5
38+
39+
steps:
40+
- name: Checkout repository
41+
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
42+
43+
- name: Setup awf using action with specific version
44+
id: setup-awf
45+
uses: ./
46+
with:
47+
version: 'v0.7.0'
48+
49+
- name: Verify awf is installed with correct version
50+
run: |
51+
echo "Installed version: ${{ steps.setup-awf.outputs.version }}"
52+
echo "Image tag: ${{ steps.setup-awf.outputs.image-tag }}"
53+
which awf
54+
awf --version
55+
# Verify the version matches
56+
if [[ "${{ steps.setup-awf.outputs.version }}" != "v0.7.0" ]]; then
57+
echo "::error::Version mismatch! Expected v0.7.0, got ${{ steps.setup-awf.outputs.version }}"
58+
exit 1
59+
fi
60+
# Verify image tag is set correctly (without 'v' prefix)
61+
if [[ "${{ steps.setup-awf.outputs.image-tag }}" != "0.7.0" ]]; then
62+
echo "::error::Image tag mismatch! Expected 0.7.0, got ${{ steps.setup-awf.outputs.image-tag }}"
63+
exit 1
64+
fi
65+
66+
test-action-with-images:
67+
name: Test Action (With Image Pull)
68+
runs-on: ubuntu-latest
69+
timeout-minutes: 10
70+
71+
steps:
72+
- name: Checkout repository
73+
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
74+
75+
- name: Setup awf with image pull
76+
id: setup-awf
77+
uses: ./
78+
with:
79+
version: 'v0.7.0'
80+
pull-images: 'true'
81+
82+
- name: Verify awf and images are available
83+
run: |
84+
echo "Installed version: ${{ steps.setup-awf.outputs.version }}"
85+
echo "Image tag: ${{ steps.setup-awf.outputs.image-tag }}"
86+
which awf
87+
awf --version
88+
89+
# Verify Docker images are pulled
90+
echo "Checking for pulled images..."
91+
docker images ghcr.io/githubnext/gh-aw-firewall/squid
92+
docker images ghcr.io/githubnext/gh-aw-firewall/agent
93+
94+
test-action-invalid-version:
95+
name: Test Action (Invalid Version - Should Fail)
96+
runs-on: ubuntu-latest
97+
timeout-minutes: 5
98+
99+
steps:
100+
- name: Checkout repository
101+
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
102+
103+
- name: Setup awf with invalid version (should fail)
104+
id: setup-awf
105+
uses: ./
106+
with:
107+
version: 'invalid-version'
108+
continue-on-error: true
109+
110+
- name: Verify action failed as expected
111+
run: |
112+
if [[ "${{ steps.setup-awf.outcome }}" == "success" ]]; then
113+
echo "::error::Action should have failed with invalid version"
114+
exit 1
115+
fi
116+
echo "Action correctly rejected invalid version format"

README.md

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,81 @@ sudo -E awf \
3434

3535
For checksum verification, version pinning, and manual installation steps, see [Quick start](docs/quickstart.md).
3636

37-
All published container images are cryptographically signed with cosign. See [docs/image-verification.md](docs/image-verification.md) for verification instructions.
37+
#### GitHub Action (recommended for CI/CD)
38+
39+
Use the setup action in your workflows:
40+
41+
```yaml
42+
steps:
43+
- name: Setup awf
44+
uses: githubnext/gh-aw-firewall@main
45+
with:
46+
# version: 'v1.0.0' # Optional: defaults to latest
47+
# pull-images: 'true' # Optional: pre-pull Docker images for the version
48+
49+
- name: Run command with firewall
50+
run: sudo awf --allow-domains github.com -- curl https://api.github.com
51+
```
52+
53+
To pin Docker images to match the installed version, use `pull-images: 'true'` and pass the image tag to awf:
54+
55+
```yaml
56+
steps:
57+
- name: Setup awf
58+
id: setup-awf
59+
uses: githubnext/gh-aw-firewall@main
60+
with:
61+
version: 'v0.7.0'
62+
pull-images: 'true'
63+
64+
- name: Run with pinned images
65+
run: |
66+
sudo awf --allow-domains github.com \
67+
--image-tag ${{ steps.setup-awf.outputs.image-tag }} \
68+
-- curl https://api.github.com
69+
```
70+
71+
#### Shell script
72+
73+
```bash
74+
# Install latest version
75+
curl -sSL https://raw.githubusercontent.com/githubnext/gh-aw-firewall/main/install.sh | sudo bash
76+
77+
# Install a specific version
78+
curl -sSL https://raw.githubusercontent.com/githubnext/gh-aw-firewall/main/install.sh | sudo bash -s -- v1.0.0
79+
80+
# Or using environment variable
81+
curl -sSL https://raw.githubusercontent.com/githubnext/gh-aw-firewall/main/install.sh | sudo AWF_VERSION=v1.0.0 bash
82+
```
83+
84+
The shell installer automatically:
85+
- Downloads the latest release binary (or a specified version)
86+
- Verifies SHA256 checksum to detect corruption or tampering
87+
- Validates the file is a valid Linux executable
88+
- Protects against 404 error pages being saved as binaries
89+
- Installs to `/usr/local/bin/awf`
90+
91+
**Alternative: Manual installation**
92+
93+
```bash
94+
# Download the latest release binary
95+
curl -fL https://github.com/githubnext/gh-aw-firewall/releases/latest/download/awf-linux-x64 -o awf
96+
97+
# Download checksums for verification
98+
curl -fL https://github.com/githubnext/gh-aw-firewall/releases/latest/download/checksums.txt -o checksums.txt
99+
100+
# Verify SHA256 checksum
101+
sha256sum -c checksums.txt --ignore-missing
102+
103+
# Install
104+
chmod +x awf
105+
sudo mv awf /usr/local/bin/
106+
107+
# Verify installation
108+
sudo awf --help
109+
```
110+
111+
**Docker Image Verification:** All published container images are cryptographically signed with cosign. See [docs/image-verification.md](docs/image-verification.md) for verification instructions.
38112

39113
## Explore the docs
40114

action.yml

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
name: 'Setup AWF'
2+
description: 'Install the Agentic Workflow Firewall (awf) CLI tool'
3+
author: 'GitHub'
4+
branding:
5+
icon: 'shield'
6+
color: 'blue'
7+
8+
inputs:
9+
version:
10+
description: 'Version to install (e.g., v1.0.0). Defaults to latest release.'
11+
required: false
12+
default: 'latest'
13+
pull-images:
14+
description: 'Pull Docker images for the installed version. Set to "true" to pre-pull squid and agent images.'
15+
required: false
16+
default: 'false'
17+
18+
outputs:
19+
version:
20+
description: 'The version of awf that was installed'
21+
value: ${{ steps.install.outputs.version }}
22+
image-tag:
23+
description: 'The image tag that matches the installed version (without the v prefix)'
24+
value: ${{ steps.install.outputs.image_tag }}
25+
26+
runs:
27+
using: 'composite'
28+
steps:
29+
- name: Validate runner OS and architecture
30+
shell: bash
31+
run: |
32+
if [ "$RUNNER_OS" != "Linux" ]; then
33+
echo "::error::This action only supports Linux runners. Current OS: $RUNNER_OS"
34+
exit 1
35+
fi
36+
37+
# Validate architecture (only x64 is supported)
38+
ARCH=$(uname -m)
39+
if [ "$ARCH" != "x86_64" ] && [ "$ARCH" != "amd64" ]; then
40+
echo "::error::This action only supports x64 architecture. Current architecture: $ARCH"
41+
exit 1
42+
fi
43+
44+
- name: Install awf
45+
id: install
46+
shell: bash
47+
env:
48+
INPUT_VERSION: ${{ inputs.version }}
49+
run: |
50+
set -euo pipefail
51+
52+
REPO="githubnext/gh-aw-firewall"
53+
BINARY_NAME="awf-linux-x64"
54+
INSTALL_DIR="${RUNNER_TEMP}/awf-bin"
55+
56+
# Create install directory
57+
mkdir -p "$INSTALL_DIR"
58+
59+
# Determine version
60+
if [ "$INPUT_VERSION" = "latest" ] || [ -z "$INPUT_VERSION" ]; then
61+
echo "Fetching latest release version..."
62+
# Use jq if available, fallback to grep/sed
63+
if command -v jq &> /dev/null; then
64+
VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" | jq -r '.tag_name')
65+
else
66+
VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
67+
fi
68+
if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then
69+
echo "::error::Failed to fetch latest version from GitHub API"
70+
exit 1
71+
fi
72+
echo "Latest version: $VERSION"
73+
else
74+
VERSION="$INPUT_VERSION"
75+
# Validate version format (supports v1.0.0, v1.0.0-beta.1, v1.0.0-rc.1, etc.)
76+
if ! echo "$VERSION" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then
77+
echo "::error::Invalid version format: $VERSION. Expected format: v1.0.0 or v1.0.0-beta.1"
78+
exit 1
79+
fi
80+
fi
81+
82+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
83+
84+
# Extract image tag (version without 'v' prefix)
85+
IMAGE_TAG="${VERSION#v}"
86+
echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT"
87+
88+
# Download URLs
89+
BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
90+
BINARY_URL="${BASE_URL}/${BINARY_NAME}"
91+
CHECKSUMS_URL="${BASE_URL}/checksums.txt"
92+
93+
# Download binary
94+
echo "Downloading awf ${VERSION}..."
95+
if ! curl -fsSL "$BINARY_URL" -o "$INSTALL_DIR/awf"; then
96+
echo "::error::Failed to download binary from $BINARY_URL"
97+
exit 1
98+
fi
99+
100+
# Download checksums
101+
echo "Downloading checksums..."
102+
if ! curl -fsSL "$CHECKSUMS_URL" -o "$INSTALL_DIR/checksums.txt"; then
103+
echo "::error::Failed to download checksums from $CHECKSUMS_URL"
104+
exit 1
105+
fi
106+
107+
# Verify checksum
108+
echo "Verifying SHA256 checksum..."
109+
110+
# Validate checksums.txt format (should have "checksum filename" format)
111+
if ! grep -qE '^[a-fA-F0-9]{64} ' "$INSTALL_DIR/checksums.txt"; then
112+
echo "::error::checksums.txt has unexpected format"
113+
exit 1
114+
fi
115+
116+
EXPECTED_SUM=$(awk -v fname="$BINARY_NAME" '$2 == fname {print $1; exit}' "$INSTALL_DIR/checksums.txt")
117+
118+
if [ -z "$EXPECTED_SUM" ]; then
119+
echo "::error::Could not find checksum for $BINARY_NAME in checksums.txt"
120+
exit 1
121+
fi
122+
123+
# Validate checksum format (64 hex characters)
124+
if ! echo "$EXPECTED_SUM" | grep -qE '^[a-fA-F0-9]{64}$'; then
125+
echo "::error::Invalid checksum format: $EXPECTED_SUM"
126+
exit 1
127+
fi
128+
129+
# Normalize to lowercase for comparison
130+
EXPECTED_SUM=$(echo "$EXPECTED_SUM" | tr '[:upper:]' '[:lower:]')
131+
ACTUAL_SUM=$(sha256sum "$INSTALL_DIR/awf" | awk '{print $1}' | tr '[:upper:]' '[:lower:]')
132+
133+
if [ "$EXPECTED_SUM" != "$ACTUAL_SUM" ]; then
134+
echo "::error::Checksum verification failed!"
135+
echo "Expected: $EXPECTED_SUM"
136+
echo "Got: $ACTUAL_SUM"
137+
exit 1
138+
fi
139+
140+
echo "Checksum verification passed ✓"
141+
142+
# Verify it's a valid ELF executable
143+
if ! file "$INSTALL_DIR/awf" | grep -q "ELF.*executable"; then
144+
echo "::error::Downloaded file is not a valid Linux executable"
145+
exit 1
146+
fi
147+
148+
# Make executable
149+
chmod +x "$INSTALL_DIR/awf"
150+
151+
# Clean up checksums file
152+
rm -f "$INSTALL_DIR/checksums.txt"
153+
154+
# Add to PATH
155+
echo "$INSTALL_DIR" >> "$GITHUB_PATH"
156+
157+
echo "Successfully installed awf ${VERSION} to $INSTALL_DIR"
158+
echo "awf is now available in PATH for subsequent steps"
159+
160+
- name: Pull Docker images
161+
if: ${{ inputs.pull-images == 'true' }}
162+
shell: bash
163+
env:
164+
IMAGE_TAG: ${{ steps.install.outputs.image_tag }}
165+
run: |
166+
set -euo pipefail
167+
168+
REGISTRY="ghcr.io/githubnext/gh-aw-firewall"
169+
170+
echo "Pulling awf Docker images with tag: ${IMAGE_TAG}"
171+
172+
# Pull squid image
173+
echo "Pulling ${REGISTRY}/squid:${IMAGE_TAG}..."
174+
if ! docker pull "${REGISTRY}/squid:${IMAGE_TAG}"; then
175+
echo "::warning::Failed to pull squid image with tag ${IMAGE_TAG}, trying 'latest'"
176+
docker pull "${REGISTRY}/squid:latest"
177+
fi
178+
179+
# Pull agent image
180+
echo "Pulling ${REGISTRY}/agent:${IMAGE_TAG}..."
181+
if ! docker pull "${REGISTRY}/agent:${IMAGE_TAG}"; then
182+
echo "::warning::Failed to pull agent image with tag ${IMAGE_TAG}, trying 'latest'"
183+
docker pull "${REGISTRY}/agent:latest"
184+
fi
185+
186+
echo "Docker images pulled successfully ✓"

0 commit comments

Comments
 (0)