# 🚀 SSH for GitHub Actions English | [įšéĢ”ä¸­æ–‡](./README.zh-tw.md) | [įŽ€äŊ“中文](./README.zh-cn.md) ## Table of Contents - [🚀 SSH for GitHub Actions](#-ssh-for-github-actions) - [Table of Contents](#table-of-contents) - [📖 Introduction](#-introduction) - [🧩 Core Concepts \& Input Parameters](#-core-concepts--input-parameters) - [🔌 Connection Settings](#-connection-settings) - [đŸ› ī¸ SSH Command Settings](#ī¸-ssh-command-settings) - [🌐 Proxy Settings](#-proxy-settings) - [⚡ Quick Start](#-quick-start) - [🔑 SSH Key Setup \& OpenSSH Compatibility](#-ssh-key-setup--openssh-compatibility) - [Setting Up SSH Keys](#setting-up-ssh-keys) - [Generate RSA key](#generate-rsa-key) - [Generate ED25519 key](#generate-ed25519-key) - [OpenSSH Compatibility](#openssh-compatibility) - [đŸ› ī¸ Usage Scenarios \& Advanced Examples](#ī¸-usage-scenarios--advanced-examples) - [Using password authentication](#using-password-authentication) - [Using private key authentication](#using-private-key-authentication) - [Multiple commands](#multiple-commands) - [Run commands from a file](#run-commands-from-a-file) - [Multiple hosts](#multiple-hosts) - [Multiple hosts with different ports](#multiple-hosts-with-different-ports) - [Synchronous execution on multiple hosts](#synchronous-execution-on-multiple-hosts) - [Pass environment variables to shell script](#pass-environment-variables-to-shell-script) - [🌐 Proxy \& Jump Host Usage](#-proxy--jump-host-usage) - [đŸ›Ąī¸ Security Best Practices](#ī¸-security-best-practices) - [Protecting Your Private Key](#protecting-your-private-key) - [Host Fingerprint Verification](#host-fingerprint-verification) - [🚨 Error Handling \& Troubleshooting](#-error-handling--troubleshooting) - [Q\&A](#qa) - [Command not found (npm or other command)](#command-not-found-npm-or-other-command) - [🤝 Contributing](#-contributing) - [📝 License](#-license) --- ## 📖 Introduction **SSH for GitHub Actions** is a powerful [GitHub Action](https://github.com/features/actions) for executing remote SSH commands easily and securely in your CI/CD workflows. Built with [Golang](https://go.dev) and [drone-ssh](https://github.com/appleboy/drone-ssh), it supports a wide range of SSH scenarios, including multi-host, proxy, and advanced authentication. ![ssh workflow](./images/ssh-workflow.png) [![testing main branch](https://github.com/appleboy/ssh-action/actions/workflows/main.yml/badge.svg)](https://github.com/appleboy/ssh-action/actions/workflows/main.yml) --- ## 🧩 Core Concepts & Input Parameters This action provides flexible SSH command execution with a rich set of configuration options. For full details, see [action.yml](./action.yml). ### 🔌 Connection Settings These parameters control how the action connects to your remote host. | Parameter | Description | Default | | ------------------- | ----------------------------------------------------------------- | ------- | | host | SSH host address | | | port | SSH port number | 22 | | username | SSH username | | | password | SSH password | | | protocol | SSH protocol version (`tcp`, `tcp4`, `tcp6`) | tcp | | sync | Run synchronously if multiple hosts are specified | false | | timeout | Timeout for SSH connection to host | 30s | | key | Content of SSH private key (e.g., raw content of `~/.ssh/id_rsa`) | | | key_path | Path to SSH private key | | | passphrase | Passphrase for the SSH private key | | | fingerprint | SHA256 fingerprint of the host public key | | | use_insecure_cipher | Allow additional (less secure) ciphers | false | | cipher | Allowed cipher algorithms. Uses sensible defaults if unspecified | | --- ### đŸ› ī¸ SSH Command Settings These parameters control the commands executed on the remote host and related behaviors. | Parameter | Description | Default | | --------------- | --------------------------------------------------------------------------------- | ------- | | script | Commands to execute remotely | | | script_path | Path to a file containing commands to execute | | | envs | Environment variables to pass to the shell script | | | envs_format | Flexible configuration for environment variable transfer | | | allenvs | Pass all environment variables with `GITHUB_` and `INPUT_` prefixes to the script | false | | command_timeout | Timeout for SSH command execution | 10m | | debug | Enable debug mode | false | | request_pty | Request a pseudo-terminal from the server | false | | curl_insecure | Allow curl to connect to SSL sites without certificates | false | | version | drone-ssh binary version. If not specified, the latest version will be used. | | --- ### 🌐 Proxy Settings These parameters control the use of a proxy (jump host) for connecting to your target host. | Parameter | Description | Default | | ------------------------- | ----------------------------------------------- | ------- | | proxy_host | SSH proxy host | | | proxy_port | SSH proxy port | 22 | | proxy_username | SSH proxy username | | | proxy_password | SSH proxy password | | | proxy_passphrase | SSH proxy key passphrase | | | proxy_protocol | SSH proxy protocol version | tcp | | proxy_timeout | Timeout for SSH connection to proxy host | 30s | | proxy_key | Content of SSH proxy private key | | | proxy_key_path | Path to SSH proxy private key | | | proxy_fingerprint | SHA256 fingerprint of the proxy host public key | | | proxy_cipher | Allowed cipher algorithms for the proxy | | | proxy_use_insecure_cipher | Allow insecure ciphers for the proxy | false | > **Note:** To mimic the removed `script_stop` option, add `set -e` at the top of your shell script. --- ## ⚡ Quick Start Run remote SSH commands in your workflow with minimal configuration: ```yaml name: Remote SSH Command on: [push] jobs: build: name: Build runs-on: ubuntu-latest steps: - name: Execute remote SSH commands using password uses: appleboy/ssh-action@v1 with: host: ${{ secrets.HOST }} username: linuxserver.io password: ${{ secrets.PASSWORD }} port: ${{ secrets.PORT }} script: whoami ``` **Output:** ```sh ======CMD====== whoami ======END====== linuxserver.io =============================================== ✅ Successfully executed commands to all hosts. =============================================== ``` --- ## 🔑 SSH Key Setup & OpenSSH Compatibility ### Setting Up SSH Keys It is best practice to create SSH keys on your local machine (not on a remote server). Log in with the username specified in GitHub Secrets and generate a key pair: #### Generate RSA key ```bash ssh-keygen -t rsa -b 4096 -C "your_email@example.com" ``` #### Generate ED25519 key ```bash ssh-keygen -t ed25519 -a 200 -C "your_email@example.com" ``` Add the new public key to the authorized keys on your server. [Learn more about authorized keys.](https://www.ssh.com/ssh/authorized_keys/) ```bash # Add RSA key cat .ssh/id_rsa.pub | ssh user@host 'cat >> .ssh/authorized_keys' # Add ED25519 key cat .ssh/id_ed25519.pub | ssh user@host 'cat >> .ssh/authorized_keys' ``` Copy the private key content and paste it into GitHub Secrets. ```bash # macOS pbcopy < ~/.ssh/id_rsa # Ubuntu xclip < ~/.ssh/id_rsa ``` > **Tip:** Copy from `-----BEGIN OPENSSH PRIVATE KEY-----` to `-----END OPENSSH PRIVATE KEY-----` (inclusive). For ED25519: ```bash # macOS pbcopy < ~/.ssh/id_ed25519 # Ubuntu xclip < ~/.ssh/id_ed25519 ``` See more: [SSH login without a password](http://www.linuxproblem.org/art_9.html). > **Note:** Depending on your SSH version, you may also need to: > > - Place the public key in `.ssh/authorized_keys2` > - Set `.ssh` permissions to 700 > - Set `.ssh/authorized_keys2` permissions to 640 ### OpenSSH Compatibility If you see this error: ```bash ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey] ``` On Ubuntu 20.04+ you may need to explicitly allow the `ssh-rsa` algorithm. Add this to your OpenSSH daemon config (`/etc/ssh/sshd_config` or a drop-in under `/etc/ssh/sshd_config.d/`): ```bash CASignatureAlgorithms +ssh-rsa ``` Alternatively, use ED25519 keys (supported by default): ```bash ssh-keygen -t ed25519 -a 200 -C "your_email@example.com" ``` --- ## đŸ› ī¸ Usage Scenarios & Advanced Examples This section covers common and advanced usage patterns, including multi-host, proxy, and environment variable passing. ### Using password authentication ```yaml - name: Execute remote SSH commands using password uses: appleboy/ssh-action@v1 with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} password: ${{ secrets.PASSWORD }} port: ${{ secrets.PORT }} script: whoami ``` ### Using private key authentication ```yaml - name: Execute remote SSH commands using SSH key uses: appleboy/ssh-action@v1 with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} port: ${{ secrets.PORT }} script: whoami ``` ### Multiple commands ```yaml - name: Multiple commands uses: appleboy/ssh-action@v1 with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} port: ${{ secrets.PORT }} script: | whoami ls -al ``` ![result](./images/output-result.png) ### Run commands from a file ```yaml - name: File commands uses: appleboy/ssh-action@v1 with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} port: ${{ secrets.PORT }} script_path: scripts/script.sh ``` ### Multiple hosts ```diff - name: Multiple hosts uses: appleboy/ssh-action@v1 with: - host: "foo.com" + host: "foo.com,bar.com" username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} port: ${{ secrets.PORT }} script: | whoami ls -al ``` Default `port` is `22`. ### Multiple hosts with different ports ```diff - name: Multiple hosts uses: appleboy/ssh-action@v1 with: - host: "foo.com" + host: "foo.com:1234,bar.com:5678" username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} script: | whoami ls -al ``` ### Synchronous execution on multiple hosts ```diff - name: Multiple hosts uses: appleboy/ssh-action@v1 with: host: "foo.com,bar.com" + sync: true username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} port: ${{ secrets.PORT }} script: | whoami ls -al ``` ### Pass environment variables to shell script ```diff - name: Pass environment uses: appleboy/ssh-action@v1 + env: + FOO: "BAR" + BAR: "FOO" + SHA: ${{ github.sha }} with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} port: ${{ secrets.PORT }} + envs: FOO,BAR,SHA script: | echo "I am $FOO" echo "I am $BAR" echo "sha: $SHA" ``` > _All environment variables in the `env` object must be strings. Using integers or other types may cause unexpected results._ --- ## 🌐 Proxy & Jump Host Usage You can connect to remote hosts via a proxy (jump host) for advanced network topologies. ```bash +--------+ +----------+ +-----------+ | Laptop | <--> | Jumphost | <--> | FooServer | +--------+ +----------+ +-----------+ ``` Example `~/.ssh/config`: ```bash Host Jumphost HostName Jumphost User ubuntu Port 22 IdentityFile ~/.ssh/keys/jump_host.pem Host FooServer HostName FooServer User ubuntu Port 22 ProxyCommand ssh -q -W %h:%p Jumphost ``` **GitHub Actions YAML:** ```diff - name: SSH proxy command uses: appleboy/ssh-action@v1 with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} port: ${{ secrets.PORT }} + proxy_host: ${{ secrets.PROXY_HOST }} + proxy_username: ${{ secrets.PROXY_USERNAME }} + proxy_key: ${{ secrets.PROXY_KEY }} + proxy_port: ${{ secrets.PROXY_PORT }} script: | mkdir abc/def ls -al ``` --- ## đŸ›Ąī¸ Security Best Practices ### Protecting Your Private Key A passphrase encrypts your private key, making it useless to attackers if leaked. Always store your private key securely. ```diff - name: SSH key passphrase uses: appleboy/ssh-action@v1 with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} port: ${{ secrets.PORT }} + passphrase: ${{ secrets.PASSPHRASE }} script: | whoami ls -al ``` ### Host Fingerprint Verification Verifying the SSH host fingerprint helps prevent man-in-the-middle attacks. To get your host's fingerprint (replace `ed25519` with your key type and `example.com` with your host): ```sh ssh example.com ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub | cut -d ' ' -f2 ``` Update your config: ```diff - name: SSH key passphrase uses: appleboy/ssh-action@v1 with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} port: ${{ secrets.PORT }} + fingerprint: ${{ secrets.FINGERPRINT }} script: | whoami ls -al ``` --- ## 🚨 Error Handling & Troubleshooting ### Q&A #### Command not found (npm or other command) If you encounter "command not found" errors, see [this issue comment](https://github.com/appleboy/ssh-action/issues/31#issuecomment-1006565847) about interactive vs non-interactive shells. On many Linux distros, `/etc/bash.bashrc` contains: ```sh # If not running interactively, don't do anything [ -z "$PS1" ] && return ``` Comment out this line or use absolute paths for your commands. --- ## 🤝 Contributing Contributions are welcome! Please submit a pull request to help improve `appleboy/ssh-action`. --- ## 📝 License This project is licensed under the [MIT License](LICENSE).