Compare commits
4 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4861d65a3c | |||
| 5ca29a1899 | |||
| 6f6f64807e | |||
| 0338e73fcc |
3 changed files with 113 additions and 29 deletions
8
.github/workflows/example.yml
vendored
8
.github/workflows/example.yml
vendored
|
|
@ -19,9 +19,13 @@ jobs:
|
|||
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
ssh-host: ${{ secrets.SSH_HOST }}
|
||||
ssh-user: ${{ secrets.SSH_USER }}
|
||||
# Try turning on the shell wrapper debug if the job succeeds but
|
||||
# produces no visible output. This will print the temp script and
|
||||
# its contents to the step log.
|
||||
debug-shell-wrapper: 'true'
|
||||
|
||||
- name: Check system info
|
||||
shell: ssh-remote
|
||||
shell: ssh-remote {0}
|
||||
run: |
|
||||
echo "=== System Information ==="
|
||||
whoami
|
||||
|
|
@ -30,7 +34,7 @@ jobs:
|
|||
pwd
|
||||
|
||||
- name: Run deployment script
|
||||
shell: ssh-remote
|
||||
shell: ssh-remote {0}
|
||||
run: |
|
||||
echo "=== Starting Deployment ==="
|
||||
cd /var/www || cd ~
|
||||
|
|
|
|||
55
README.md
55
README.md
|
|
@ -31,9 +31,12 @@ jobs:
|
|||
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
ssh-host: ${{ secrets.SSH_HOST }}
|
||||
ssh-user: ${{ secrets.SSH_USER }}
|
||||
# Optional: enable debug mode to print the temp script and its
|
||||
# contents on the runner for troubleshooting "no output" cases.
|
||||
debug-shell-wrapper: 'true'
|
||||
|
||||
- name: Run remote commands with custom shell
|
||||
shell: ssh-remote
|
||||
shell: ssh-remote {0}
|
||||
run: |
|
||||
cd /var/www
|
||||
git pull origin main
|
||||
|
|
@ -135,8 +138,9 @@ jobs:
|
|||
| `ssh-port` | SSH port | No | `22` |
|
||||
| `ssh-known-hosts` | Known hosts content (uses ssh-keyscan if not provided) | No | `''` |
|
||||
| `strict-host-key-checking` | Enable strict host key checking (`yes`/`no`/`accept-new`) | No | `accept-new` |
|
||||
| `use-shell-wrapper` | Create shell wrapper for remote execution (enables `shell: ssh-remote`) | No | `true` |
|
||||
| `use-shell-wrapper` | Create shell wrapper for remote execution (enables `shell: ssh-remote {0}`) | No | `true` |
|
||||
| `remote-shell` | Shell to use on remote server (`bash`, `sh`, `zsh`, etc.) | No | `bash` |
|
||||
| `debug-shell-wrapper` | Print script path and contents before executing remote commands (for debugging only) | No | `false` |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
|
@ -152,11 +156,11 @@ This action provides two ways to execute commands remotely:
|
|||
|
||||
### 1. Custom Shell Wrapper (Recommended)
|
||||
|
||||
Use `shell: ssh-remote` in any step to execute the entire script on the remote server:
|
||||
Use `shell: ssh-remote {0}` in any step to execute the entire script on the remote server:
|
||||
|
||||
```yaml
|
||||
- name: Deploy application
|
||||
shell: ssh-remote
|
||||
- name: Deploy application
|
||||
shell: ssh-remote {0}
|
||||
run: |
|
||||
cd /var/www/myapp
|
||||
git pull origin main
|
||||
|
|
@ -166,13 +170,14 @@ Use `shell: ssh-remote` in any step to execute the entire script on the remote s
|
|||
|
||||
**Benefits:**
|
||||
- Natural multi-line script syntax
|
||||
- Automatic error handling with `set -e`
|
||||
shell: ssh-remote {0}
|
||||
- Works like a local shell
|
||||
- No need to wrap commands in SSH
|
||||
|
||||
⚠️ Note: Enabling `debug-shell-wrapper: 'true'` will print the contents of the temporary script and the exit code to the job logs. This can help diagnose "no output" runs, but may leak secrets or other sensitive data — only enable on trusted runs.
|
||||
|
||||
### 2. SSH Host Alias (Direct)
|
||||
|
||||
Use the `github-action-host` alias for direct SSH commands:
|
||||
|
||||
```yaml
|
||||
- name: Run single commands
|
||||
|
|
@ -183,7 +188,6 @@ Use the `github-action-host` alias for direct SSH commands:
|
|||
|
||||
This eliminates the need to specify the host, user, port, and key path in every SSH command.
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### Generating SSH Keys
|
||||
|
||||
|
|
@ -191,8 +195,7 @@ This eliminates the need to specify the host, user, port, and key path in every
|
|||
# Generate a dedicated SSH key pair for GitHub Actions
|
||||
ssh-keygen -t ed25519 -C "github-actions" -f github_actions_key
|
||||
|
||||
# Or use RSA if ed25519 is not supported
|
||||
ssh-keygen -t rsa -b 4096 -C "github-actions" -f github_actions_key
|
||||
shell: ssh-remote {0}
|
||||
```
|
||||
|
||||
### Setting up Secrets
|
||||
|
|
@ -201,7 +204,6 @@ ssh-keygen -t rsa -b 4096 -C "github-actions" -f github_actions_key
|
|||
```bash
|
||||
cat github_actions_key
|
||||
```
|
||||
|
||||
2. Add it to GitHub Secrets:
|
||||
- Go to your repository → Settings → Secrets and variables → Actions
|
||||
- Click "New repository secret"
|
||||
|
|
@ -216,8 +218,7 @@ ssh-keygen -t rsa -b 4096 -C "github-actions" -f github_actions_key
|
|||
### Getting Known Hosts
|
||||
|
||||
To pre-populate known hosts (recommended for security):
|
||||
|
||||
```bash
|
||||
shell: ssh-remote {0}
|
||||
ssh-keyscan -H your-server.com
|
||||
```
|
||||
|
||||
|
|
@ -225,6 +226,34 @@ Add the output to a GitHub secret named `SSH_KNOWN_HOSTS`.
|
|||
|
||||
## Troubleshooting
|
||||
|
||||
### Debugging no output from custom shell
|
||||
|
||||
If a step using `shell: ssh-remote {0}` shows no output but reports success:
|
||||
|
||||
- Enable `debug-shell-wrapper: 'true'` in the `Setup SSH` step. This will tell the wrapper to print the temp script path and the first 200 lines of the script it executes. Example:
|
||||
|
||||
```yaml
|
||||
- name: Setup SSH
|
||||
uses: your-username/setup-ssh-client@v1
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
ssh-host: ${{ secrets.SSH_HOST }}
|
||||
ssh-user: ${{ secrets.SSH_USER }}
|
||||
debug-shell-wrapper: 'true'
|
||||
```
|
||||
|
||||
- Re-run the job — the logs from subsequent steps will include the script path and contents; confirm the commands you expect are present.
|
||||
- If the script looks correct, try a direct SSH command from a `run:` step to verify remote side:
|
||||
|
||||
```yaml
|
||||
- name: Direct SSH smoke test
|
||||
run: ssh github-action-host "echo hello; whoami; pwd"
|
||||
```
|
||||
|
||||
- If the direct test shows output but the wrapper step still shows nothing, examine whether your remote shell sets PATH or environment differently for non-interactive shells — you may need to force a login shell or source shell rc files with `shell: ssh-remote {0}` and `run: | source ~/.bashrc; your commands` or set `remote-shell: 'bash -l'`.
|
||||
|
||||
Note: Don't forget to disable `debug-shell-wrapper` when done; it prints the script contents into logs which may reveal secrets.
|
||||
|
||||
### Connection Timeout
|
||||
|
||||
If the connection test fails with a timeout:
|
||||
|
|
|
|||
79
action.yml
79
action.yml
|
|
@ -35,6 +35,10 @@ inputs:
|
|||
description: 'Shell to use on remote server (bash, sh, zsh, etc.)'
|
||||
required: false
|
||||
default: 'bash'
|
||||
debug-shell-wrapper:
|
||||
description: 'Enable debug logging in the SSH remote wrapper (prints script path and contents)'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
outputs:
|
||||
ssh-config-path:
|
||||
|
|
@ -155,22 +159,53 @@ runs:
|
|||
# the wrapper in the `GITHUB_WORKSPACE` to avoid polluting repo files.
|
||||
# Prefer runner-specific temp folders (RUNNER_TEMP), then /tmp.
|
||||
TEMP_DIR="${RUNNER_TEMP:-/tmp}"
|
||||
WRAPPER_DIR=$(mktemp -d "$TEMP_DIR/setup-ssh-client-XXXXXX")
|
||||
# Create temporary directory. mktemp implementation differs between
|
||||
# Linux (GNU coreutils) and macOS (BSD). Use common-fallback patterns
|
||||
# to be portable across runners.
|
||||
if WRAPPER_DIR=$(mktemp -d "$TEMP_DIR/setup-ssh-client-XXXXXX" 2>/dev/null); then
|
||||
:
|
||||
elif WRAPPER_DIR=$(mktemp -d -p "$TEMP_DIR" setup-ssh-client-XXXXXX 2>/dev/null); then
|
||||
:
|
||||
else
|
||||
WRAPPER_DIR="$TEMP_DIR/setup-ssh-client-$(date +%s)-$$"
|
||||
mkdir -p "$WRAPPER_DIR"
|
||||
fi
|
||||
mkdir -p "$WRAPPER_DIR"
|
||||
WRAPPER_PATH=${mktemp "$WRAPPER_DIR/ssh-remote-shell-XXXXXX.sh"}
|
||||
# Use command substitution $(...) — earlier use of ${...} is invalid and
|
||||
# caused a "bad substitution" error in POSIX shells. `mktemp` accepts a
|
||||
# template including a directory, but options differ between GNU and BSD
|
||||
# implementations. Use template first, and fall back to -p for macOS.
|
||||
if WRAPPER_PATH=$(mktemp "$WRAPPER_DIR/ssh-remote-shell-XXXXXX.sh" 2>/dev/null); then
|
||||
: # created by GNU extension or compatible mktemp
|
||||
elif WRAPPER_PATH=$(mktemp -p "$WRAPPER_DIR" ssh-remote-shell-XXXXXX.sh 2>/dev/null); then
|
||||
: # fallback for BSD mktemp on macOS
|
||||
else
|
||||
# Last resort: generate a safe filename and create it
|
||||
WRAPPER_PATH="$WRAPPER_DIR/ssh-remote-shell-$(date +%s)-$$.sh"
|
||||
: > "$WRAPPER_PATH"
|
||||
fi
|
||||
|
||||
cat << 'WRAPPER_EOF' > "$WRAPPER_PATH"
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Check if input file is provided
|
||||
if [ -z "$1" ]; then
|
||||
set -euo pipefail
|
||||
|
||||
# Runner normally passes a temp script path as the first argument.
|
||||
# If that isn't present, allow script to be piped to stdin.
|
||||
TMPDIR="${TMPDIR:-/tmp}"
|
||||
SCRIPT_FILE=""
|
||||
if [ -n "${1-}" ]; then
|
||||
SCRIPT_FILE="$1"
|
||||
elif ! [ -t 0 ]; then
|
||||
# Write stdin to a temp file
|
||||
TMP_SCRIPT=$(mktemp "$TMPDIR/ssh-remote-stdin-XXXXXX.sh" 2>/dev/null || mktemp -t ssh-remote-stdin-XXXXXX)
|
||||
cat - > "$TMP_SCRIPT"
|
||||
chmod +x "$TMP_SCRIPT"
|
||||
SCRIPT_FILE="$TMP_SCRIPT"
|
||||
else
|
||||
echo "Error: No script file provided" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT_FILE="$1"
|
||||
|
||||
# Check if script file exists
|
||||
if [ ! -f "$SCRIPT_FILE" ]; then
|
||||
echo "Error: Script file '$SCRIPT_FILE' not found" >&2
|
||||
|
|
@ -181,10 +216,21 @@ runs:
|
|||
# Use BatchMode to prevent interactive prompts
|
||||
# ConnectTimeout to fail fast if connection issues
|
||||
echo "Executing script on remote server '${{ inputs.ssh-host }}' via SSH..."
|
||||
# Print some debug information if requested. See 'debug-shell-wrapper' input.
|
||||
if [ "${SSH_REMOTE_DEBUG-}" = "true" ]; then
|
||||
echo "[ssh-remote wrapper] Script file: $SCRIPT_FILE"
|
||||
echo "[ssh-remote wrapper] Script contents:" >&2
|
||||
sed -n '1,200p' "$SCRIPT_FILE"
|
||||
echo "[ssh-remote wrapper] ---------- end script ----------"
|
||||
fi
|
||||
ssh -o BatchMode=yes \
|
||||
-o ConnectTimeout=10 \
|
||||
github-action-host \
|
||||
"${{ inputs.remote-shell }} -s" < "$SCRIPT_FILE"
|
||||
SSH_EXIT_CODE=$?
|
||||
if [ "${SSH_REMOTE_DEBUG-}" = "true" ]; then
|
||||
echo "[ssh-remote wrapper] SSH exit code: $SSH_EXIT_CODE"
|
||||
fi
|
||||
WRAPPER_EOF
|
||||
|
||||
chmod +x "$WRAPPER_PATH"
|
||||
|
|
@ -192,12 +238,12 @@ runs:
|
|||
# We also create symlink without the .sh extension to provide a
|
||||
# short executable name inside the wrapper dir (e.g. $WRAPPER_DIR/ssh-remote)
|
||||
ln -s "$WRAPPER_PATH" "$WRAPPER_DIR/ssh-remote"
|
||||
if [ "${{ inputs.use-shell-wrapper }}" = "true" ]; then
|
||||
echo ""
|
||||
echo "To use the remote shell in subsequent steps, add:"
|
||||
echo " shell: ssh-remote"
|
||||
echo ""
|
||||
echo "The 'ssh-remote' shell is now available for use."
|
||||
if [ "${{ inputs.debug-shell-wrapper }}" = "true" ]; then
|
||||
# Persist debug flag to the job environment; the wrapper can pick
|
||||
# this up later when the job runs. This allows debugging without
|
||||
# changing the wrapper script at runtime.
|
||||
echo "SSH_REMOTE_DEBUG=true" >> $GITHUB_ENV
|
||||
echo "⚠️ SSH remote shell wrapper debug is enabled (SSH_REMOTE_DEBUG=true)"
|
||||
fi
|
||||
echo "shell-wrapper-dir=$WRAPPER_DIR" >> $GITHUB_OUTPUT
|
||||
echo "shell-wrapper-path=$WRAPPER_PATH" >> $GITHUB_OUTPUT
|
||||
|
|
@ -212,3 +258,8 @@ runs:
|
|||
echo "Adding custom shell 'ssh-remote' to PATH"
|
||||
echo "PATH=$SHELL_WRAPPER_DIR:$PATH" >> $GITHUB_ENV
|
||||
echo "✅ Custom shell 'ssh-remote' registered for this job"
|
||||
echo ""
|
||||
echo "To use the remote shell in subsequent steps, add:"
|
||||
echo " shell: ssh-remote {0}"
|
||||
echo ""
|
||||
echo "The 'ssh-remote' shell is now available for use."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue