Quick Devops Tool: gha-secrets-setup
Here's a small script I wrote a few days ago, gha-secrets-setup
.
This script accepts a GitHub repository URL and checks its GitHub Actions workflows for secrets
references. Then, using the gh
CLI tool, it checks which secrets haven't yet been configure. It interactively prompts you for each secret's value and sets it up on the repo, again using gh
.
This has already saved me so much time compared to setting secrets via the GitHub web UI!
The full script is copied below:
#!/bin/bash
set -euo pipefail
readonly SCRIPT_NAME="$(basename "$0")"
readonly TEMP_DIR="$(mktemp -d)"
usage() {
cat << EOF
Usage: $SCRIPT_NAME <repository-url>
Sets up GitHub Actions secrets for a repository by:
1. Cloning the repository to a temporary directory
2. Scanning GitHub Actions workflows for secret references
3. Checking which secrets already exist
4. Prompting for missing secrets and creating them
Arguments:
repository-url GitHub repository URL (https://github.com/owner/repo or owner/repo)
Requirements:
- gh CLI tool must be installed and authenticated
- git must be installed
EOF
}
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" >&2
}
error() {
log "ERROR: $*"
exit 1
}
cleanup() {
if [[ -d "$TEMP_DIR" ]]; then
log "Cleaning up temporary directory: $TEMP_DIR"
rm -rf "$TEMP_DIR"
fi
}
trap cleanup EXIT
check_requirements() {
log "Checking requirements..."
if ! command -v gh >/dev/null 2>&1; then
error "gh CLI tool is not installed. Please install it first."
fi
if ! command -v git >/dev/null 2>&1; then
error "git is not installed. Please install it first."
fi
if ! gh auth status >/dev/null 2>&1; then
error "gh CLI is not authenticated. Please run 'gh auth login' first."
fi
log "Requirements check passed"
}
normalize_repo_url() {
local repo_input="$1"
if [[ "$repo_input" =~ ^https://github\.com/([^/]+/[^/]+)/?$ ]]; then
echo "${BASH_REMATCH[1]}"
elif [[ "$repo_input" =~ ^([^/]+/[^/]+)$ ]]; then
echo "$repo_input"
else
error "Invalid repository format. Use 'owner/repo' or 'https://github.com/owner/repo'"
fi
}
clone_repository() {
local repo_url="$1"
local clone_dir="$2"
log "Cloning repository $repo_url to $clone_dir..."
if ! git clone --depth 1 "https://github.com/$repo_url.git" "$clone_dir" >/dev/null 2>&1; then
error "Failed to clone repository $repo_url"
fi
log "Repository cloned successfully"
}
find_secrets_in_workflows() {
local repo_dir="$1"
local workflows_dir="$repo_dir/.github/workflows"
if [[ ! -d "$workflows_dir" ]]; then
log "No GitHub Actions workflows found in $workflows_dir"
return 0
fi
log "Scanning workflows for secret references..."
# Find all secret references in workflow files
# Look for patterns like: secrets.SECRET_NAME, ${{ secrets.SECRET_NAME }}
# Exclude GITHUB_ secrets as they are provided by GitHub automatically
find "$workflows_dir" -name "*.yml" -o -name "*.yaml" | while read -r workflow_file; do
grep -oE '\$\{\{\s*secrets\.[A-Z_][A-Z0-9_]*\s*\}\}|secrets\.[A-Z_][A-Z0-9_]*' "$workflow_file" 2>/dev/null || true
done | sed -E 's/.*secrets\.([A-Z_][A-Z0-9_]*).*/\1/' | grep -v '^GITHUB_' | sort -u
}
get_existing_secrets() {
local repo="$1"
log "Checking existing secrets for repository $repo..."
# Get list of existing secrets (names only)
gh secret list --repo "$repo" --json name --jq '.[].name' 2>/dev/null || true
}
prompt_for_secret() {
local secret_name="$1"
local repo="$2"
echo
echo "Setting up secret: $secret_name"
echo -n "Enter value for $secret_name (input will be hidden): "
# Read secret value without echoing to terminal
read -rs secret_value < /dev/tty
echo
if [[ -z "$secret_value" ]]; then
echo "Skipping empty secret value for $secret_name"
return 0
fi
log "Creating secret $secret_name..."
if echo "$secret_value" | gh secret set "$secret_name" --repo "$repo"; then
log "Successfully created secret: $secret_name"
else
error "Failed to create secret: $secret_name"
fi
}
main() {
if [[ $# -ne 1 ]]; then
usage
exit 1
fi
local repo_input="$1"
if [[ "$repo_input" == "-h" || "$repo_input" == "--help" ]]; then
usage
exit 0
fi
check_requirements
local repo
repo="$(normalize_repo_url "$repo_input")"
local clone_dir="$TEMP_DIR/repo"
clone_repository "$repo" "$clone_dir"
local secrets_found
secrets_found="$(find_secrets_in_workflows "$clone_dir")"
if [[ -z "$secrets_found" ]]; then
log "No secrets found in GitHub Actions workflows"
exit 0
fi
log "Found secrets in workflows:"
echo "$secrets_found" | while read -r secret; do
echo " - $secret"
done
local existing_secrets
existing_secrets="$(get_existing_secrets "$repo")"
if [[ -n "$existing_secrets" ]]; then
log "Existing secrets:"
echo "$existing_secrets" | while read -r secret; do
echo " - $secret"
done
else
log "No existing secrets found"
fi
echo
log "Processing secrets..."
local missing_count=0
# Process secrets using a for loop to avoid stdin conflicts
while IFS= read -r secret_name; do
if [[ -z "$secret_name" ]]; then
continue
fi
if echo "$existing_secrets" | grep -q "^$secret_name$"; then
log "Secret $secret_name already exists, skipping"
else
prompt_for_secret "$secret_name" "$repo" < /dev/tty
((missing_count++))
fi
done <<< "$secrets_found"
if [[ $missing_count -eq 0 ]]; then
log "All secrets already exist. No action needed."
else
log "Successfully processed $missing_count missing secrets"
fi
log "Secret setup complete!"
}
main "$@"