Skip to content

Add GitHub API caching to prevent rate limiting #448

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ RUN sh -c "$(wget -O- https://github.com/deluan/zsh-in-docker/releases/download/
# Install Claude
RUN npm install -g @anthropic-ai/claude-code

# Copy and set up firewall script
COPY init-firewall.sh /usr/local/bin/
# Copy and set up scripts
COPY init-firewall.sh cache-github-api.sh /usr/local/bin/
USER root
RUN chmod +x /usr/local/bin/init-firewall.sh && \
RUN chmod +x /usr/local/bin/init-firewall.sh /usr/local/bin/cache-github-api.sh && \
echo "node ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" > /etc/sudoers.d/node-firewall && \
chmod 0440 /etc/sudoers.d/node-firewall
USER node
109 changes: 109 additions & 0 deletions .devcontainer/cache-github-api.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/bin/bash
set -e

# Script to cache GitHub API data
# Used to prevent rate limiting during container builds

# Configuration
# Store cache in the home directory
CACHE_DIR="${HOME}/.github-meta-cache"
CACHE_FILE="${CACHE_DIR}/meta.json"
TIMESTAMP_FILE="${CACHE_DIR}/meta-timestamp.txt"
MAX_AGE_SECONDS=3600 # Cache expires after 1 hour

# Create cache directory if it doesn't exist
mkdir -p "${CACHE_DIR}"

# Function to get current timestamp
get_timestamp() {
date +%s
}

# Function to check if cache is valid
is_cache_valid() {
if [[ ! -f "${CACHE_FILE}" || ! -f "${TIMESTAMP_FILE}" ]]; then
return 1
fi

local cache_time=$(cat "${TIMESTAMP_FILE}")
local current_time=$(get_timestamp)
local age=$((current_time - cache_time))

if [[ ${age} -gt ${MAX_AGE_SECONDS} ]]; then
echo "Cache is expired (${age} seconds old)"
return 1
fi

echo "Using cached GitHub API data (${age} seconds old)"
return 0
}

# Function to fetch data using authenticated gh cli
fetch_with_gh() {
echo "Attempting to fetch GitHub API data using authenticated gh CLI..."
if gh auth status &>/dev/null; then
gh api meta > "${CACHE_FILE}" &&
get_timestamp > "${TIMESTAMP_FILE}" &&
echo "Successfully fetched and cached GitHub API data using gh CLI"
return $?
else
echo "gh CLI not authenticated"
return 1
fi
}

# Function to fetch data using curl
fetch_with_curl() {
echo "Attempting to fetch GitHub API data using curl..."
# First try with GITHUB_TOKEN if available
if [[ -n "${GITHUB_TOKEN}" ]]; then
echo "Using GITHUB_TOKEN for authentication"
curl -s -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/meta > "${CACHE_FILE}" &&
get_timestamp > "${TIMESTAMP_FILE}" &&
echo "Successfully fetched and cached GitHub API data using curl with token"
return $?
else
# Fall back to unauthenticated request
echo "No GITHUB_TOKEN found, making unauthenticated request (may be rate limited)"
curl -s https://api.github.com/meta > "${CACHE_FILE}"

# Check if the response indicates rate limiting
if grep -q "API rate limit exceeded" "${CACHE_FILE}"; then
echo "Rate limit exceeded for unauthenticated request"
return 1
else
get_timestamp > "${TIMESTAMP_FILE}"
echo "Successfully fetched and cached GitHub API data using curl without auth"
return 0
fi
fi
}

# Main logic
if is_cache_valid; then
echo "Using existing cache from $(cat ${TIMESTAMP_FILE})"
exit 0
fi

# Try with gh CLI first
if ! fetch_with_gh; then
# Fall back to curl
if ! fetch_with_curl; then
# Both methods failed, check if we have an existing cache file
if [[ -f "${CACHE_FILE}" ]]; then
echo "Warning: Failed to update cache, using existing cached data (which may be expired)"
exit 0
else
echo "Error: Failed to fetch GitHub API data and no cache exists"
exit 1
fi
fi
fi

# Display a summary of the cached data
echo "GitHub API meta data cached successfully. Summary:"
jq -r '.domains.actions | length' "${CACHE_FILE}" > /dev/null 2>&1 &&
echo "- Actions domains: $(jq -r '.domains.actions | length' "${CACHE_FILE}")" ||
echo "- Could not parse actions domains from cache file"

exit 0
6 changes: 5 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
"dockerfile": "Dockerfile",
"args": {
"TZ": "${localEnv:TZ:America/Los_Angeles}"
},
"prebuild": {
"command": "bash -c '${localWorkspaceFolder}/.devcontainer/cache-github-api.sh || echo \"Warning: Failed to cache GitHub API data\"'"
}
},
"runArgs": [
Expand Down Expand Up @@ -39,7 +42,8 @@
"remoteUser": "node",
"mounts": [
"source=claude-code-bashhistory,target=/commandhistory,type=volume",
"source=claude-code-config,target=/home/node/.claude,type=volume"
"source=claude-code-config,target=/home/node/.claude,type=volume",
"type=bind,source=${localEnv:HOME}/.github-meta-cache,target=/github-meta-cache,consistency=cached"
],
"remoteEnv": {
"NODE_OPTIONS": "--max-old-space-size=4096",
Expand Down
22 changes: 13 additions & 9 deletions .devcontainer/init-firewall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,20 @@ iptables -A OUTPUT -o lo -j ACCEPT
# Create ipset with CIDR support
ipset create allowed-domains hash:net

# Fetch GitHub meta information and aggregate + add their IP ranges
echo "Fetching GitHub IP ranges..."
gh_ranges=$(curl -s https://api.github.com/meta)
if [ -z "$gh_ranges" ]; then
echo "ERROR: Failed to fetch GitHub IP ranges"
exit 1
fi
# Use cached GitHub meta information from mounted volume
CACHE_FILE="/github-meta-cache/meta.json"

if ! echo "$gh_ranges" | jq -e '.web and .api and .git' >/dev/null; then
echo "ERROR: GitHub API response missing required fields"
echo "Using cached GitHub IP ranges..."
if [ -f "${CACHE_FILE}" ]; then
gh_ranges=$(cat "${CACHE_FILE}")

# Verify the cached data is valid
if ! echo "$gh_ranges" | jq -e '.web and .api and .git' >/dev/null; then
echo "ERROR: Cached GitHub API data is invalid"
exit 1
fi
else
echo "ERROR: No cached GitHub IP ranges found"
exit 1
fi

Expand Down