Skip to content
Merged
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
7 changes: 6 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This document provides comprehensive guidance for AI coding agents and contribut
- Support for multiple fetch methods (`raw`, `api`, `ghuser`).
- Logging and error handling.
- Configuration loading from `users.conf`.
- A helper function `fetch_key_file` to handle key retrieval logic.
- A helper function `fetch_key_file` to handle key retrieval logic with retries for failed operations.

### Configuration
- **`users.conf`**: Defines users and their key sources. Example structure:
Expand Down Expand Up @@ -97,3 +97,8 @@ This document provides comprehensive guidance for AI coding agents and contribut
- **`raw`**: Fetches keys from a public URL.
- **`api`**: Fetches keys from a private GitHub repository using the GitHub API.
- **`ghuser`**: Fetches public keys from a GitHub user's profile.

### Enhanced Error Handling
- The `fetch_key_file` function includes a retry mechanism for failed fetch operations.
- By default, it retries up to 3 times with a 2-second delay between attempts.
- Logs detailed error messages for each failed attempt and skips the user if all retries fail.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This Bash script pulls `authorized_keys` files from remote URLs and updates SSH
- ✅ GitHub user public keys (method: `ghuser`)
- Safe: Only updates keys if they’ve changed
- Logs activity per user
- Retries failed fetch operations up to 3 times with a delay

## ⚙️ Configuration

Expand Down Expand Up @@ -41,10 +42,11 @@ declare -A USER_KEYS=(
## Usage

1. Edit the `users.conf` file to define users and their key URLs or GitHub usernames.
2. If using the `api` method, either export your GitHub token or set `CONF_GITHUB_TOKEN` in `users.conf`:
2. If using the `api` method, either export your GitHub token:
```bash
export GITHUB_TOKEN=your_token_here
```
or set `CONF_GITHUB_TOKEN` in `users.conf`.
3. Make sure the script is executable:
```bash
chmod +x sync-ssh-keys.sh
Expand All @@ -58,5 +60,18 @@ declare -A USER_KEYS=(

- The script sources `users.conf` for configuration.
- Uses a helper function `fetch_key_file` to fetch keys using the appropriate method.
- Includes a retry mechanism for failed fetch operations (3 attempts with a 2-second delay).
- Only updates a user's `authorized_keys` if the remote file has changed.
- Logs all actions with timestamps.

## Examples

### Adding a New User
1. Add the user to `users.conf`:
```bash
["newuser"]="ghuser:newuser-github-username"
```
2. Run the script to sync keys:
```bash
./sync-ssh-keys.sh
```
66 changes: 40 additions & 26 deletions sync-ssh-keys.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set -euo pipefail

# shellcheck disable=SC2034 # planned to be used in a future release
SCRIPT_VERSION="0.0.7"
SCRIPT_VERSION="0.0.8"

# === Load user configuration ===
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
Expand Down Expand Up @@ -30,24 +30,30 @@ fetch_key_file() {
local METHOD="$1"
local TARGET="$2"
local OUTFILE="$3"
local RETRIES=3
local RETRY_DELAY=2

if [[ "$METHOD" == "raw" ]]; then
curl -fsSL "$TARGET" -o "$OUTFILE"
return $?
elif [[ "$METHOD" == "api" ]]; then
: "${GITHUB_TOKEN:?GITHUB_TOKEN is required for API access}"
curl -fsSL -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3.raw" \
"$TARGET" -o "$OUTFILE"
return $?
elif [[ "$METHOD" == "ghuser" ]]; then
# TARGET is the GitHub username
curl -fsSL "https://github.com/${TARGET}.keys" -o "$OUTFILE"
return $?
else
log_message "Error: Unsupported method '$METHOD' encountered for URL '$TARGET'. Halting execution."
exit 2
fi
for ((i=1; i<=RETRIES; i++)); do
if [[ "$METHOD" == "raw" ]]; then
curl -fsSL "$TARGET" -o "$OUTFILE" && return 0
elif [[ "$METHOD" == "api" ]]; then
: "${GITHUB_TOKEN:?GITHUB_TOKEN is required for API access}"
curl -fsSL -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3.raw" \
"$TARGET" -o "$OUTFILE" && return 0
elif [[ "$METHOD" == "ghuser" ]]; then
curl -fsSL "https://github.com/${TARGET}.keys" -o "$OUTFILE" && return 0
else
log_message "Error: Unsupported method '$METHOD' encountered for URL '$TARGET'. Halting execution."
exit 2
fi

log_message "Attempt $i/$RETRIES failed for method '$METHOD' and URL '$TARGET'. Retrying in $RETRY_DELAY seconds..."
sleep "$RETRY_DELAY"
done

log_message "Error: All $RETRIES attempts failed for method '$METHOD' and URL '$TARGET'. Skipping."
return 1
}

TMP_FILES=()
Expand All @@ -58,16 +64,19 @@ for USER in "${!USER_KEYS[@]}"; do
ENTRY="${USER_KEYS[$USER]}"
METHOD="${ENTRY%%:*}"
URL="${ENTRY#*:}"

# Ensure user exists
if ! id "$USER" &>/dev/null; then
log_message "User '$USER' does not exist. Skipping."
continue
fi

USER_HOME=$(getent passwd "$USER" | cut -d: -f6)
if [ -z "$USER_HOME" ]; then
log_message "Failed to determine home directory for user '$USER'. Skipping."
continue
fi

AUTH_KEYS="$USER_HOME/.ssh/authorized_keys"
SSH_DIR="$(dirname "$AUTH_KEYS")"

Expand All @@ -76,21 +85,26 @@ for USER in "${!USER_KEYS[@]}"; do
mkdir -p "$SSH_DIR"
chown "$USER:$USER" "$SSH_DIR"
chmod 700 "$SSH_DIR"
log_message "Created .ssh directory for user '$USER'"
log_message "Created .ssh directory for user '$USER' at $SSH_DIR."
fi

log_message "Fetching key file for $USER from $URL (method: $METHOD)"
if ! fetch_key_file "$METHOD" "$URL" "$TMP_FILE"; then
log_message "Failed to fetch key file for user '$USER' from $URL. Skipping."
log_message "Failed to fetch key file for user '$USER' from $URL after multiple attempts. Skipping."
continue
fi

if [ ! -f "$AUTH_KEYS" ] || ! cmp -s "$TMP_FILE" "$AUTH_KEYS"; then
cp "$TMP_FILE" "$AUTH_KEYS"
chown "$USER:$USER" "$AUTH_KEYS"
chmod 600 "$AUTH_KEYS"
log_message "Updated authorized_keys for user '$USER'"
if [ ! -f "$AUTH_KEYS" ]; then
log_message "No existing authorized_keys file for user '$USER'. Creating a new one."
elif ! cmp -s "$TMP_FILE" "$AUTH_KEYS"; then
log_message "Changes detected in authorized_keys for user '$USER'. Updating the file."
else
log_message "No changes for user '$USER'"
log_message "No changes detected in authorized_keys for user '$USER'."
continue
fi

cp "$TMP_FILE" "$AUTH_KEYS"
chown "$USER:$USER" "$AUTH_KEYS"
chmod 600 "$AUTH_KEYS"
log_message "Updated authorized_keys for user '$USER' at $AUTH_KEYS."
done