Skip to content

Instantly share code, notes, and snippets.

@ephrin
Last active July 26, 2025 09:34
Show Gist options
  • Save ephrin/b055228e9fb016d1daa60c2904cdb832 to your computer and use it in GitHub Desktop.
Save ephrin/b055228e9fb016d1daa60c2904cdb832 to your computer and use it in GitHub Desktop.
GitHub Deployment User Setup Script - Idempotent user and SSH key management
#!/bin/bash
# GitHub Deployment User Setup Script - Idempotent user and SSH key management
#
# This script sets up a deployment user for GitHub workflow deployments:
# 1. Creates/ensures a deployment user exists
# 2. Generates SSH key pair for GitHub authentication
# 3. Configures SSH for GitHub access (git clone/pull operations)
# 4. Sets up authorized_keys for GitHub workflow SSH access
# 5. Gives user sudo privileges for deployment directory management
#
# Must be run with sudo
set -euo pipefail
# Default values
DEFAULT_USER="deployer"
DEFAULT_HOST="github.com"
DEFAULT_DEPLOY_DIR="/var/www"
DEFAULT_KEY_TYPE="ed25519"
ENV_SUFFIX=""
TARGET_HOST=""
USERNAME=""
DEPLOY_DIR=""
KEY_TYPE=""
# Function to display usage
usage() {
cat << EOF
Usage: $0 [OPTIONS]
This script sets up a deployment user with SSH keys for GitHub workflow access.
The user will be able to:
- Receive SSH connections from GitHub workflows (via authorized_keys)
- Connect to GitHub to pull source code updates
- Manage deployment directories (sudo access for chown operations)
OPTIONS:
-u, --user USER Username to create/configure (default: $DEFAULT_USER)
-e, --env ENVIRONMENT Environment suffix for SSH key (test, staging, prod, etc.)
-h, --host HOST Target host for SSH config (default: $DEFAULT_HOST)
-d, --deploy-dir DIR Deployment directory path (default: $DEFAULT_DEPLOY_DIR)
-t, --key-type TYPE SSH key type: ed25519, rsa, ecdsa (default: $DEFAULT_KEY_TYPE)
--help Show this help message
Examples:
sudo $0 -u deployer -e prod
sudo $0 -u deploy-staging -e staging -d /var/www/staging
sudo $0 -u deploy-test -e test -h gitlab.com -d /opt/apps -t rsa
Key Types:
ed25519 - Modern, fast, short keys (recommended, default)
rsa - Traditional RSA keys (2048-bit minimum)
ecdsa - Elliptic curve keys
Note: This script must be run with sudo privileges.
EOF
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-u|--user)
USERNAME="$2"
shift 2
;;
-e|--env)
ENV_SUFFIX="$2"
shift 2
;;
-h|--host)
TARGET_HOST="$2"
shift 2
;;
-d|--deploy-dir)
DEPLOY_DIR="$2"
shift 2
;;
-t|--key-type)
KEY_TYPE="$2"
shift 2
;;
--help)
usage
if [[ $EUID -ne 0 ]]; then
echo ""
echo "⚠️ WARNING: This script must be run with sudo for actual execution."
echo " Example: sudo $0 -u deployer -e prod"
fi
exit 0
;;
*)
echo "Unknown option: $1"
usage
exit 1
;;
esac
done
# Check if running with sudo (after parsing arguments so --help works)
if [[ $EUID -ne 0 ]]; then
echo "Error: This script must be run with sudo"
echo "Usage: sudo $0 [OPTIONS]"
echo "Use --help for more information"
exit 1
fi
# Set defaults if not provided
USERNAME="${USERNAME:-$DEFAULT_USER}"
TARGET_HOST="${TARGET_HOST:-$DEFAULT_HOST}"
DEPLOY_DIR="${DEPLOY_DIR:-$DEFAULT_DEPLOY_DIR}"
KEY_TYPE="${KEY_TYPE:-$DEFAULT_KEY_TYPE}"
# Validate key type
case "$KEY_TYPE" in
ed25519|rsa|ecdsa)
;;
*)
echo "Error: Invalid key type '$KEY_TYPE'. Use: ed25519, rsa, or ecdsa"
exit 1
;;
esac
# Build key name with environment suffix and key type
if [[ -n "$ENV_SUFFIX" ]]; then
KEY_NAME="id_${KEY_TYPE}_${ENV_SUFFIX}"
echo "⚠️ WARNING: Using environment-specific key suffix '${ENV_SUFFIX}'"
echo " It's recommended to use different keys/secrets for different environments"
echo " (dev, test, staging, prod) for better security isolation."
else
KEY_NAME="id_${KEY_TYPE}"
fi
# User home directory
USER_HOME="/home/$USERNAME"
SSH_DIR="$USER_HOME/.ssh"
PRIVATE_KEY="$SSH_DIR/$KEY_NAME"
PUBLIC_KEY="$SSH_DIR/${KEY_NAME}.pub"
SSH_CONFIG="$SSH_DIR/config"
AUTHORIZED_KEYS="$SSH_DIR/authorized_keys"
KNOWN_HOSTS="$SSH_DIR/known_hosts"
echo "=== GitHub Deployment User Setup ==="
echo "User: $USERNAME"
echo "Environment: ${ENV_SUFFIX:-default}"
echo "Key type: $KEY_TYPE"
echo "Key name: $KEY_NAME"
echo "Target host: $TARGET_HOST"
echo "Deployment directory: $DEPLOY_DIR"
echo "======================================"
# Function to create user if it doesn't exist
create_user() {
if id "$USERNAME" &>/dev/null; then
echo "✓ User '$USERNAME' already exists"
else
echo "→ Creating user '$USERNAME'"
useradd -m -s /bin/bash "$USERNAME"
echo "✓ User '$USERNAME' created"
fi
# Add user to sudo group for deployment operations
if groups "$USERNAME" | grep -q sudo; then
echo "✓ User '$USERNAME' already has sudo privileges"
else
echo "→ Adding user '$USERNAME' to sudo group"
usermod -aG sudo "$USERNAME"
echo "✓ User '$USERNAME' added to sudo group"
fi
# Create sudoers rule for chown operations without password
SUDOERS_FILE="/etc/sudoers.d/deploy-$USERNAME"
if [[ ! -f "$SUDOERS_FILE" ]]; then
echo "→ Creating sudoers rule for deployment operations"
cat > "$SUDOERS_FILE" << EOF
# Allow $USERNAME to chown deployment directories without password
$USERNAME ALL=(ALL) NOPASSWD: /bin/chown, /bin/chmod
EOF
chmod 440 "$SUDOERS_FILE"
echo "✓ Sudoers rule created for deployment operations"
else
echo "✓ Sudoers rule already exists"
fi
}
# Function to setup SSH directory and permissions
setup_ssh_directory() {
echo "→ Setting up SSH directory"
sudo -u "$USERNAME" mkdir -p "$SSH_DIR"
chmod 700 "$SSH_DIR"
chown "$USERNAME:$USERNAME" "$SSH_DIR"
echo "✓ SSH directory configured"
}
# Function to generate SSH key if it doesn't exist
generate_ssh_key() {
if [[ -f "$PRIVATE_KEY" ]]; then
echo "✓ SSH key '$KEY_NAME' already exists"
else
echo "→ Generating SSH key '$KEY_NAME' (type: $KEY_TYPE)"
# Set key generation parameters based on type
case "$KEY_TYPE" in
ed25519)
sudo -u "$USERNAME" ssh-keygen -t ed25519 -f "$PRIVATE_KEY" -N "" -C "${USERNAME}@$(hostname)-${ENV_SUFFIX:-default}"
;;
rsa)
sudo -u "$USERNAME" ssh-keygen -t rsa -b 4096 -f "$PRIVATE_KEY" -N "" -C "${USERNAME}@$(hostname)-${ENV_SUFFIX:-default}"
;;
ecdsa)
sudo -u "$USERNAME" ssh-keygen -t ecdsa -b 521 -f "$PRIVATE_KEY" -N "" -C "${USERNAME}@$(hostname)-${ENV_SUFFIX:-default}"
;;
esac
chmod 600 "$PRIVATE_KEY"
chmod 644 "$PUBLIC_KEY"
echo "✓ SSH key generated ($KEY_TYPE)"
fi
}
# Function to add public key to authorized_keys
setup_authorized_keys() {
echo "→ Setting up authorized_keys"
sudo -u "$USERNAME" touch "$AUTHORIZED_KEYS"
chmod 600 "$AUTHORIZED_KEYS"
# Add public key to authorized_keys if not already present
if ! grep -Fxq "$(cat "$PUBLIC_KEY")" "$AUTHORIZED_KEYS" 2>/dev/null; then
cat "$PUBLIC_KEY" >> "$AUTHORIZED_KEYS"
echo "✓ Public key added to authorized_keys"
else
echo "✓ Public key already in authorized_keys"
fi
}
# Function to add host to known_hosts
setup_known_hosts() {
echo "→ Setting up known_hosts for $TARGET_HOST"
sudo -u "$USERNAME" touch "$KNOWN_HOSTS"
chmod 644 "$KNOWN_HOSTS"
# Get host key and add to known_hosts if not present
if ! grep -q "^$TARGET_HOST" "$KNOWN_HOSTS" 2>/dev/null; then
echo "→ Fetching host key for $TARGET_HOST"
sudo -u "$USERNAME" ssh-keyscan -H "$TARGET_HOST" >> "$KNOWN_HOSTS" 2>/dev/null || {
echo "⚠️ Warning: Could not fetch host key for $TARGET_HOST"
}
echo "✓ Host key added to known_hosts"
else
echo "✓ Host key already in known_hosts"
fi
}
# Function to setup SSH config
setup_ssh_config() {
echo "→ Setting up SSH config for GitHub deployment"
sudo -u "$USERNAME" touch "$SSH_CONFIG"
chmod 600 "$SSH_CONFIG"
# SSH config entry for GitHub
if [[ "$TARGET_HOST" == "github.com" ]]; then
CONFIG_ENTRY="Host github.com
HostName github.com
User git
IdentityFile $PRIVATE_KEY
IdentitiesOnly yes
StrictHostKeyChecking no
UserKnownHostsFile /dev/null"
else
CONFIG_ENTRY="Host $TARGET_HOST
HostName $TARGET_HOST
User git
IdentityFile $PRIVATE_KEY
IdentitiesOnly yes"
fi
# Add config entry if not present
if ! grep -q "Host $TARGET_HOST" "$SSH_CONFIG" 2>/dev/null; then
echo -e "\n$CONFIG_ENTRY" >> "$SSH_CONFIG"
echo "✓ SSH config entry added for $TARGET_HOST"
else
echo "✓ SSH config entry already exists for $TARGET_HOST"
fi
}
# Function to add key to SSH agent
setup_ssh_agent() {
echo "→ Setting up SSH agent"
# Start SSH agent if not running
if ! pgrep -u "$USERNAME" ssh-agent > /dev/null; then
sudo -u "$USERNAME" bash -c 'eval $(ssh-agent -s) && echo $SSH_AGENT_PID > ~/.ssh/agent.pid && echo $SSH_AUTH_SOCK > ~/.ssh/agent.sock'
fi
# Add key to agent
sudo -u "$USERNAME" bash -c "
export SSH_AUTH_SOCK=\$(cat ~/.ssh/agent.sock 2>/dev/null || echo '')
if [[ -n \"\$SSH_AUTH_SOCK\" ]] && ssh-add -l &>/dev/null; then
if ! ssh-add -l | grep -q '$PRIVATE_KEY'; then
ssh-add '$PRIVATE_KEY' 2>/dev/null || echo '⚠️ Could not add key to SSH agent'
else
echo '✓ Key already in SSH agent'
fi
else
echo '⚠️ SSH agent not available or not responding'
fi
"
}
# Function to print SSH keys
print_keys() {
echo ""
echo "=== SSH KEYS ==="
echo ""
echo "📋 PUBLIC KEY ($PUBLIC_KEY):"
echo "----------------------------------------"
cat "$PUBLIC_KEY"
echo "----------------------------------------"
echo ""
echo "🔒 PRIVATE KEY ($PRIVATE_KEY):"
echo "----------------------------------------"
cat "$PRIVATE_KEY"
echo "----------------------------------------"
echo ""
}
# Function to provide deployment instructions
show_deployment_instructions() {
echo ""
echo "=== DEPLOYMENT INSTRUCTIONS ==="
echo ""
echo "📋 Public key for GitHub Deploy Key:"
echo " Add this to your GitHub repository Settings > Deploy keys:"
echo " $(cat "$PUBLIC_KEY")"
echo ""
echo "🔧 To prepare deployment directory:"
echo " sudo chown -R $USERNAME:$USERNAME $DEPLOY_DIR"
echo " sudo chmod -R 755 $DEPLOY_DIR"
echo ""
echo "🚀 Test GitHub connection:"
echo " sudo -u $USERNAME ssh -T git@$TARGET_HOST"
echo ""
echo "📥 Clone repository example:"
echo " sudo -u $USERNAME git clone git@$TARGET_HOST:user/repo.git $DEPLOY_DIR/repo"
echo ""
echo "================================="
}
# Main execution
main() {
create_user
setup_ssh_directory
generate_ssh_key
setup_authorized_keys
setup_known_hosts
setup_ssh_config
setup_ssh_agent
print_keys
show_deployment_instructions
echo "=== SETUP COMPLETE ==="
echo "✓ Deployment User: $USERNAME"
echo "✓ SSH Key Type: $KEY_TYPE"
echo "✓ SSH Key: $KEY_NAME"
echo "✓ Target Host: $TARGET_HOST"
echo "✓ Environment: ${ENV_SUFFIX:-default}"
echo "✓ Deployment Directory: $DEPLOY_DIR"
echo "✓ User has sudo privileges for chown/chmod operations"
echo ""
echo "🔗 Add the public key above to your GitHub repository's Deploy Keys"
echo "🚀 User $USERNAME is ready for GitHub workflow deployments!"
echo "======================="
}
# Run main function
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment