diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 5c1b5d1e..26bc2afa 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -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 diff --git a/.devcontainer/cache-github-api.sh b/.devcontainer/cache-github-api.sh new file mode 100755 index 00000000..ce753681 --- /dev/null +++ b/.devcontainer/cache-github-api.sh @@ -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 \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 58513062..53aca3cb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -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": [ @@ -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", diff --git a/.devcontainer/init-firewall.sh b/.devcontainer/init-firewall.sh index e45908c5..ea890f55 100644 --- a/.devcontainer/init-firewall.sh +++ b/.devcontainer/init-firewall.sh @@ -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