Compare commits

..

No commits in common. "master" and "v1" have entirely different histories.
master ... v1

8 changed files with 485 additions and 727 deletions

View File

@ -51,21 +51,6 @@ jobs:
port: 2222
script_path: testdata/test.sh
# https://github.com/appleboy/ssh-action/issues/377
- name: multiple commands
uses: ./
with:
host: ${{ env.REMOTE_HOST }}
username: linuxserver.io
password: password
port: 2222
script: |
mkdir -p /tmp/test
echo "hello world" > /tmp/test/hello.txt
cd /tmp/test
ls -al
cat /tmp/test/hello.txt
check-ssh-key:
runs-on: ubuntu-latest
steps:

View File

@ -31,7 +31,7 @@ jobs:
sleep 2
- name: ssh by username and password
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ env.REMOTE_HOST }}
username: linuxserver.io
@ -43,7 +43,7 @@ jobs:
whoami
- name: ssh commands from a file
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ env.REMOTE_HOST }}
username: linuxserver.io
@ -95,7 +95,7 @@ jobs:
sleep 2
- name: ssh by private key
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ env.REMOTE_HOST }}
username: linuxserver.io
@ -104,7 +104,7 @@ jobs:
script: whoami
- name: wrong password but correct key
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ env.REMOTE_HOST }}
username: linuxserver.io
@ -114,7 +114,7 @@ jobs:
script: whoami
- name: correct password but wrong key
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ env.REMOTE_HOST }}
username: linuxserver.io
@ -167,7 +167,7 @@ jobs:
sleep 2
- name: ssh key passphrase
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ env.REMOTE_HOST }}
username: linuxserver.io
@ -179,7 +179,7 @@ jobs:
ls -al
- name: missing ssh key passphrase
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
continue-on-error: true
with:
host: ${{ env.REMOTE_HOST }}
@ -192,7 +192,7 @@ jobs:
# https://github.com/appleboy/ssh-action/issues/75#issuecomment-668314271
- name: Multiline SSH commands interpreted as single lines
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ env.REMOTE_HOST }}
username: linuxserver.io
@ -269,7 +269,7 @@ jobs:
# https://github.com/appleboy/ssh-action/issues/85
- name: Deployment to multiple hosts with different ports
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
with:
host: "${{ env.REMOTE_HOST_01 }}:2222,${{ env.REMOTE_HOST_02 }}:2222"
username: linuxserver.io
@ -322,7 +322,7 @@ jobs:
sleep 2
- name: testing id_ed25519 key
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ env.REMOTE_HOST }}
username: linuxserver.io
@ -375,7 +375,7 @@ jobs:
sleep 2
- name: testing id_ed25519 key
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ env.REMOTE_HOST }}
username: linuxserver.io
@ -386,7 +386,7 @@ jobs:
ls -al
- name: pass environment
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
env:
FOO: "BAR"
with:
@ -400,7 +400,7 @@ jobs:
echo "I am $BAR, thanks"
- name: pass multiple environment
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
env:
FOO: "BAR"
BAR: "FOO"
@ -419,7 +419,7 @@ jobs:
echo "port: $PORT"
- name: custom envs format
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
env:
FOO: "BAR"
AAA: "BBB"
@ -437,7 +437,7 @@ jobs:
echo "I am $TEST_AAA, thanks"
- name: pass all ENV variables to script
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
env:
INPUT_FOO: "BAR"
INPUT_AAA: "BBB"
@ -454,7 +454,7 @@ jobs:
echo "$GITHUB_REF"
- name: switch to root user
uses: appleboy/ssh-action@v1
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ env.REMOTE_HOST }}
username: linuxserver.io

View File

@ -24,5 +24,5 @@ changelog:
order: 4
- title: "Documentation updates"
regexp: ^.*?docs?(\(.+\))??!?:.+$
order: 5
order: 4
- title: Others

358
README.md
View File

@ -2,137 +2,70 @@
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.
A [GitHub Action](https://github.com/features/actions) for executing remote SSH commands.
![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)
---
This project is built with [Golang](https://go.dev) and [drone-ssh](https://github.com/appleboy/drone-ssh). 🚀
## 🧩 Core Concepts & Input Parameters
## Input variables
This action provides flexible SSH command execution with a rich set of configuration options.
Refer to [action.yml](./action.yml) for more detailed information.
For full details, see [action.yml](./action.yml).
| Input Parameter | Description | Default Value |
| ------------------------- | ---------------------------------------------------------------------------------------- | ------------- |
| host | SSH host address | |
| port | SSH port number | 22 |
| passphrase | SSH key passphrase | |
| username | SSH username | |
| password | SSH password | |
| protocol | SSH protocol version (tcp, tcp4, tcp6) | tcp |
| sync | Enable synchronous execution if multiple hosts are specified | false |
| use_insecure_cipher | Include more ciphers with use_insecure_cipher | false |
| cipher | Allowed cipher algorithms. If unspecified, sensible defaults are used | |
| timeout | Timeout duration for SSH to host | 30s |
| command_timeout | Timeout duration for SSH command | 10m |
| key | Content of SSH private key. e.g., raw content of ~/.ssh/id_rsa | |
| key_path | Path of SSH private key | |
| fingerprint | SHA256 fingerprint of the host public key | |
| proxy_host | SSH proxy host | |
| proxy_port | SSH proxy port | 22 |
| proxy_protocol | SSH proxy protocol version (tcp, tcp4, tcp6) | tcp |
| proxy_username | SSH proxy username | |
| proxy_password | SSH proxy password | |
| proxy_passphrase | SSH proxy key passphrase | |
| proxy_timeout | Timeout for SSH to proxy host | 30s |
| proxy_key | Content of SSH proxy private key | |
| proxy_key_path | Path of 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 | Include more ciphers with use_insecure_cipher for the proxy | false |
| script | Execute commands | |
| script_path | Execute commands from a file | |
| envs | Pass environment variables to the shell script | |
| envs_format | Flexible configuration of environment value transfer | |
| debug | Enable debug mode | false |
| allenvs | Pass the environment variables with prefix value of `GITHUB_` and `INPUT_` to the script | false |
| request_pty | Request a pseudo-terminal from the server | false |
### 🔌 Connection Settings
**Note:** Users can add `set -e` in their shell script to achieve similar functionality to the removed `script_stop` option.
These parameters control how the action connects to your remote host.
## Usage
| 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:
Executing remote SSH commands.
```yaml
name: Remote SSH Command
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
- name: executing remote ssh commands using password
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: linuxserver.io
@ -141,7 +74,7 @@ jobs:
script: whoami
```
**Output:**
output:
```sh
======CMD======
@ -153,37 +86,50 @@ linuxserver.io
===============================================
```
---
### Setting up a SSH Key
## 🔑 SSH Key Setup & OpenSSH Compatibility
Follow the steps below to create and use SSH Keys.
It is best practice to create SSH Keys on your local machine, not on a remote machine.
Log in with the username specified in GitHub Secrets and generate an RSA Key-Pair:
### 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
### Generate rsa key
```bash
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
```
#### Generate ED25519 key
### 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/)
Add the newly generated key to the Authorized keys. Read more about authorized keys [here](https://www.ssh.com/ssh/authorized_keys/).
### Add rsa key into 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'
cat .ssh/id_rsa.pub | ssh b@B 'cat >> .ssh/authorized_keys'
```
Copy the private key content and paste it into GitHub Secrets.
### Add ed25519 key into Authorized keys
```bash
cat .ssh/id_ed25519.pub | ssh b@B 'cat >> .ssh/authorized_keys'
```
Copy the Private Key content and paste it into GitHub Secrets.
### Copy rsa Private key
Before copying the private key, install the `clip` command as shown below:
```bash
# Ubuntu
sudo apt-get install xclip
```
Copy the private key:
```bash
# macOS
@ -192,9 +138,9 @@ pbcopy < ~/.ssh/id_rsa
xclip < ~/.ssh/id_rsa
```
> **Tip:** Copy from `-----BEGIN OPENSSH PRIVATE KEY-----` to `-----END OPENSSH PRIVATE KEY-----` (inclusive).
Starting from and including the comment section `-----BEGIN OPENSSH PRIVATE KEY-----` and ending at and including the comment section `-----END OPENSSH PRIVATE KEY-----`, copy the private key and paste it into GitHub Secrets.
For ED25519:
### Copy ed25519 Private key
```bash
# macOS
@ -203,45 +149,41 @@ pbcopy < ~/.ssh/id_ed25519
xclip < ~/.ssh/id_ed25519
```
See more: [SSH login without a password](http://www.linuxproblem.org/art_9.html).
See detailed information about [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
**Note**: Depending on your version of SSH, you might also need to make the following changes:
### OpenSSH Compatibility
- Put the public key in `.ssh/authorized_keys2`
- Change the permissions of `.ssh` to 700
- Change the permissions of `.ssh/authorized_keys2` to 640
If you see this error:
### If you are using OpenSSH
If you are currently using OpenSSH and are getting the following 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/`):
Ensure that your chosen key algorithm is supported. On Ubuntu 20.04 or later, you must explicitly allow the use of the ssh-rsa algorithm. Add the following line to your OpenSSH daemon file (either `/etc/ssh/sshd_config` or a drop-in file under `/etc/ssh/sshd_config.d/`):
```bash
CASignatureAlgorithms +ssh-rsa
```
Alternatively, use ED25519 keys (supported by default):
Alternatively, `ed25519` keys are accepted by default in OpenSSH. You can use this instead of rsa if needed:
```bash
ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
```
---
### Example
## 🛠️ Usage Scenarios & Advanced Examples
This section covers common and advanced usage patterns, including multi-host, proxy, and environment variable passing.
### Using password authentication
#### Executing remote ssh commands using password
```yaml
- name: Execute remote SSH commands using password
uses: appleboy/ssh-action@v1
- name: executing remote ssh commands using password
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -250,11 +192,11 @@ This section covers common and advanced usage patterns, including multi-host, pr
script: whoami
```
### Using private key authentication
#### Using private key
```yaml
- name: Execute remote SSH commands using SSH key
uses: appleboy/ssh-action@v1
- name: executing remote ssh commands using ssh key
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -263,11 +205,11 @@ This section covers common and advanced usage patterns, including multi-host, pr
script: whoami
```
### Multiple commands
#### Multiple Commands
```yaml
- name: Multiple commands
uses: appleboy/ssh-action@v1
- name: multiple command
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -280,11 +222,11 @@ This section covers common and advanced usage patterns, including multi-host, pr
![result](./images/output-result.png)
### Run commands from a file
#### Commands from a file
```yaml
- name: File commands
uses: appleboy/ssh-action@v1
- name: file commands
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -293,11 +235,11 @@ This section covers common and advanced usage patterns, including multi-host, pr
script_path: scripts/script.sh
```
### Multiple hosts
#### Multiple Hosts
```diff
- name: Multiple hosts
uses: appleboy/ssh-action@v1
- name: multiple host
uses: appleboy/ssh-action@v1.2.2
with:
- host: "foo.com"
+ host: "foo.com,bar.com"
@ -309,13 +251,13 @@ This section covers common and advanced usage patterns, including multi-host, pr
ls -al
```
Default `port` is `22`.
The default value of `port` is `22`.
### Multiple hosts with different ports
#### Multiple hosts with different port
```diff
- name: Multiple hosts
uses: appleboy/ssh-action@v1
- name: multiple host
uses: appleboy/ssh-action@v1.2.2
with:
- host: "foo.com"
+ host: "foo.com:1234,bar.com:5678"
@ -326,11 +268,11 @@ Default `port` is `22`.
ls -al
```
### Synchronous execution on multiple hosts
#### Synchronous execution on multiple hosts
```diff
- name: Multiple hosts
uses: appleboy/ssh-action@v1
- name: multiple host
uses: appleboy/ssh-action@v1.2.2
with:
host: "foo.com,bar.com"
+ sync: true
@ -342,11 +284,11 @@ Default `port` is `22`.
ls -al
```
### Pass environment variables to shell script
#### Pass environment variable to shell script
```diff
- name: Pass environment
uses: appleboy/ssh-action@v1
- name: pass environment
uses: appleboy/ssh-action@v1.2.2
+ env:
+ FOO: "BAR"
+ BAR: "FOO"
@ -363,13 +305,9 @@ Default `port` is `22`.
echo "sha: $SHA"
```
> _All environment variables in the `env` object must be strings. Using integers or other types may cause unexpected results._
_Inside `env` object, you need to pass every environment variable as a string, passing `Integer` data type or any other may output unexpected results._
---
## 🌐 Proxy & Jump Host Usage
You can connect to remote hosts via a proxy (jump host) for advanced network topologies.
#### How to connect remote server using `ProxyCommand`?
```bash
+--------+ +----------+ +-----------+
@ -377,7 +315,7 @@ You can connect to remote hosts via a proxy (jump host) for advanced network top
+--------+ +----------+ +-----------+
```
Example `~/.ssh/config`:
in your `~/.ssh/config`, you will see the following.
```bash
Host Jumphost
@ -393,11 +331,11 @@ Host FooServer
ProxyCommand ssh -q -W %h:%p Jumphost
```
**GitHub Actions YAML:**
#### How to convert to YAML format of GitHubActions
```diff
- name: SSH proxy command
uses: appleboy/ssh-action@v1
- name: ssh proxy command
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -412,17 +350,15 @@ Host FooServer
ls -al
```
---
#### Protecting a Private Key
## 🛡️ 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.
The purpose of the passphrase is usually to encrypt the private key.
This makes the key file by itself useless to an attacker.
It is not uncommon for files to leak from backups or decommissioned hardware, and hackers commonly exfiltrate files from compromised systems.
```diff
- name: SSH key passphrase
uses: appleboy/ssh-action@v1
- name: ssh key passphrase
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -434,19 +370,21 @@ A passphrase encrypts your private key, making it useless to attackers if leaked
ls -al
```
### Host Fingerprint Verification
#### Using 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):
Setting up SSH host fingerprint verification can help to prevent Person-in-the-Middle attacks. Before setting this up, run the command below to get your SSH host fingerprint. Remember to replace `ed25519` with your appropriate key type (`rsa`, `dsa`, etc.) that your server is using and `example.com` with your host.
In modern OpenSSH releases, the _default_ key types to be fetched are `rsa` (since version 5.1), `ecdsa` (since version 6.0), and `ed25519` (since version 6.7).
```sh
ssh example.com ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub | cut -d ' ' -f2
```
Update your config:
Now you can adjust you config:
```diff
- name: SSH key passphrase
uses: appleboy/ssh-action@v1
- name: ssh key passphrase
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -458,33 +396,33 @@ Update your config:
ls -al
```
---
## Q&A
## 🚨 Error Handling & Troubleshooting
### Command not found (npm or other command)
### Q&A
See the [issue comment](https://github.com/appleboy/ssh-action/issues/31#issuecomment-1006565847) about interactive vs non interactive shell. Thanks @kocyigityunus for the solution.
#### Command not found (npm or other command)
If you are running a command in a non-interactive shell, like ssh-action, on many Linux distros,
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:
`/etc/bash.bashrc` file has a specific command that returns only, so some of the files didn't run and some specific commands doesn't add to path,
```sh
# /etc/bash.bashrc
# System-wide .bashrc file for interactive bash(1) shells.
# To enable the settings / commands in this file for login shells as well,
# this file has to be sourced in /etc/profile.
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
[ -z "$PS1" ] && return`
```
Comment out this line or use absolute paths for your commands.
comment out the line that returns early, and everything should work fine. Alternatively, you can use the real paths of the commands you want to use.
---
## Contributing
## 🤝 Contributing
We would love for you to contribute to `appleboy/ssh-action`, pull requests are welcome!
Contributions are welcome! Please submit a pull request to help improve `appleboy/ssh-action`.
## License
---
## 📝 License
This project is licensed under the [MIT License](LICENSE).
The scripts and documentation in this project are released under the [MIT License](LICENSE)

View File

@ -2,137 +2,70 @@
[English](./README.md) | [繁體中文](./README.zh-tw.md) | 简体中文
## 目录
- [🚀 用于 GitHub Actions 的 SSH](#-用于-github-actions-的-ssh)
- [目录](#目录)
- [📖 简介](#-简介)
- [🧩 核心概念与输入参数](#-核心概念与输入参数)
- [🔌 连接设置](#-连接设置)
- [🛠️ 指令设置](#-指令设置)
- [🌐 代理设置](#-代理设置)
- [⚡ 快速开始](#-快速开始)
- [🔑 SSH 密钥配置与 OpenSSH 兼容性](#-ssh-密钥配置与-openssh-兼容性)
- [配置 SSH 密钥](#配置-ssh-密钥)
- [生成 RSA 密钥](#生成-rsa-密钥)
- [生成 ED25519 密钥](#生成-ed25519-密钥)
- [OpenSSH 兼容性](#openssh-兼容性)
- [🛠️ 用法场景与进阶示例](#-用法场景与进阶示例)
- [使用密码认证](#使用密码认证)
- [使用私钥认证](#使用私钥认证)
- [多条命令](#多条命令)
- [从文件执行命令](#从文件执行命令)
- [多主机](#多主机)
- [多主机不同端口](#多主机不同端口)
- [多主机同步执行](#多主机同步执行)
- [传递环境变量到 shell 脚本](#传递环境变量到-shell-脚本)
- [🌐 代理与跳板机用法](#-代理与跳板机用法)
- [🛡️ 安全最佳实践](#-安全最佳实践)
- [保护你的私钥](#保护你的私钥)
- [主机指纹验证](#主机指纹验证)
- [🚨 错误处理与疑难解答](#-错误处理与疑难解答)
- [常见问题](#常见问题)
- [命令未找到npm 或其他命令)](#命令未找到npm-或其他命令)
- [🤝 贡献](#-贡献)
- [📝 许可证](#-许可证)
---
## 📖 简介
**SSH for GitHub Actions** 是一个强大的 [GitHub Action](https://github.com/features/actions),可让你在 CI/CD 工作流中轻松且安全地执行远程 SSH 命令。
本项目基于 [Golang](https://go.dev) 和 [drone-ssh](https://github.com/appleboy/drone-ssh) 构建,支持多主机、代理、高级认证等多种 SSH 场景。
一个用于执行远程 SSH 命令的 [GitHub Action](https://github.com/features/actions)。
![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)
---
该项目使用 [Golang](https://go.dev) 和 [drone-ssh](https://github.com/appleboy/drone-ssh) 构建。🚀
## 🧩 核心概念与输入参数
## 输入变量
本 Action 提供灵活的 SSH 命令执行能力,并具备丰富的配置选项
有关更详细的信息,请参阅 [action.yml](./action.yml)。
详细参数请参阅 [action.yml](./action.yml)。
| 输入参数 | 描述 | 默认值 |
| ------------------------- | ----------------------------------------------------- | ------ |
| host | SSH 主机地址 | |
| port | SSH 端口号 | 22 |
| passphrase | SSH 密钥密码短语 | |
| username | SSH 用户名 | |
| password | SSH 密码 | |
| protocol | SSH 协议版本tcp, tcp4, tcp6 | tcp |
| sync | 如果指定了多个主机,则启用同步执行 | false |
| use_insecure_cipher | 使用不安全的密码算法 | false |
| cipher | 允许的密码算法。如果未指定,则使用适当的默认值 | |
| timeout | SSH 连接到主机的超时时间 | 30s |
| command_timeout | SSH 命令的超时时间 | 10m |
| key | SSH 私钥的内容,例如 ~/.ssh/id_rsa 的原始内容 | |
| key_path | SSH 私钥的路径 | |
| fingerprint | 主机公钥的 SHA256 指纹 | |
| proxy_host | SSH 代理主机 | |
| proxy_port | SSH 代理端口 | 22 |
| proxy_protocol | SSH 代理协议版本tcp, tcp4, tcp6 | tcp |
| proxy_username | SSH 代理用户名 | |
| proxy_password | SSH 代理密码 | |
| proxy_passphrase | SSH 代理密钥密码短语 | |
| proxy_timeout | SSH 连接到代理主机的超时时间 | 30s |
| proxy_key | SSH 代理私钥的内容 | |
| proxy_key_path | SSH 代理私钥的路径 | |
| proxy_fingerprint | 代理主机公钥的 SHA256 指纹 | |
| proxy_cipher | 代理允许的密码算法 | |
| proxy_use_insecure_cipher | 使用不安全的密码算法 | false |
| script | 执行命令 | |
| script_path | 从文件执行命令 | |
| envs | 传递环境变量到 shell 脚本 | |
| envs_format | 环境变量传递的灵活配置 | |
| debug | 启用调试模式 | false |
| allenvs | 将带有 `GITHUB_``INPUT_` 前缀的环境变量传递给脚本 | false |
| request_pty | 请求伪终端 | false |
### 🔌 连接设置
**注意:** 用户可以在他们的 shell 脚本中添加 `set -e` 以实现类似于已删除的 `script_stop` 选项的功能。
这些参数用于控制如何连接到远程主机。
## 使用方法
| 参数 | 描述 | 默认值 |
| ------------------- | --------------------------------------------- | ------ |
| host | SSH 主机地址 | |
| port | SSH 端口号 | 22 |
| username | SSH 用户名 | |
| password | SSH 密码 | |
| protocol | SSH 协议版本(`tcp``tcp4``tcp6` | tcp |
| sync | 指定多个主机时同步执行 | false |
| timeout | SSH 连接主机的超时时间 | 30s |
| key | SSH 私钥内容(如 `~/.ssh/id_rsa` 的原始内容) | |
| key_path | SSH 私钥路径 | |
| passphrase | SSH 私钥密码短语 | |
| fingerprint | 主机公钥的 SHA256 指纹 | |
| use_insecure_cipher | 允许额外(不安全)的加密算法 | false |
| cipher | 允许的加密算法,未指定时使用默认值 | |
---
### 🛠️ 指令设置
这些参数用于控制在远程主机上执行的命令及相关行为。
| 参数 | 描述 | 默认值 |
| --------------- | ----------------------------------------------------- | ------ |
| script | 远程执行的命令 | |
| script_path | 包含要执行命令的文件路径 | |
| envs | 传递给 shell 脚本的环境变量 | |
| envs_format | 环境变量传递的灵活配置 | |
| allenvs | 传递所有带 `GITHUB_``INPUT_` 前缀的环境变量到脚本 | false |
| command_timeout | SSH 命令执行超时时间 | 10m |
| debug | 启用调试模式 | false |
| request_pty | 向服务器请求伪终端 | false |
| curl_insecure | 允许 curl 连接无证书的 SSL 站点 | false |
| version | drone-ssh 二进制版本,未指定时使用最新版本 | |
---
### 🌐 代理设置
这些参数用于通过代理(跳板机)连接到目标主机。
| 参数 | 描述 | 默认值 |
| ------------------------- | ----------------------------------------- | ------ |
| proxy_host | SSH 代理主机 | |
| proxy_port | SSH 代理端口 | 22 |
| proxy_username | SSH 代理用户名 | |
| proxy_password | SSH 代理密码 | |
| proxy_passphrase | SSH 代理私钥密码短语 | |
| proxy_protocol | SSH 代理协议版本(`tcp``tcp4``tcp6` | tcp |
| proxy_timeout | SSH 连接代理主机的超时时间 | 30s |
| proxy_key | SSH 代理私钥内容 | |
| proxy_key_path | SSH 代理私钥路径 | |
| proxy_fingerprint | 代理主机公钥的 SHA256 指纹 | |
| proxy_cipher | 代理允许的加密算法 | |
| proxy_use_insecure_cipher | 代理允许额外(不安全)的加密算法 | false |
> **注意:** 如需实现已移除的 `script_stop` 功能,请在 shell 脚本顶部添加 `set -e`
---
## ⚡ 快速开始
只需简单配置,即可在工作流中执行远程 SSH 命令:
执行远程 SSH 命令。
```yaml
name: Remote SSH Command
name: remote ssh command
on: [push]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: 执行远程 SSH 命令(密码认证)
uses: appleboy/ssh-action@v1
- name: executing remote ssh commands using password
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: linuxserver.io
@ -141,7 +74,7 @@ jobs:
script: whoami
```
**输出:**
输出:
```sh
======CMD======
@ -153,37 +86,50 @@ linuxserver.io
===============================================
```
---
### 设置 SSH 密钥
## 🔑 SSH 密钥配置与 OpenSSH 兼容性
请按照以下步骤创建和使用 SSH 密钥。
最佳做法是在本地机器上创建 SSH 密钥,而不是在远程机器上。
使用 GitHub Secrets 中指定的用户名登录并生成 RSA 密钥对:
### 配置 SSH 密钥
建议在本地机器(而非远程服务器)上创建 SSH 密钥。请使用 GitHub Secrets 中指定的用户名登录并生成密钥对:
#### 生成 RSA 密钥
### 生成 RSA 密钥
```bash
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
```
#### 生成 ED25519 密钥
### 生成 ed25519 密钥
```bash
ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
```
将新生成的公钥添加到服务器的 authorized_keys。 [了解更多 authorized_keys](https://www.ssh.com/ssh/authorized_keys/)
将新生成的密钥添加到已授权的密钥中。详细了解已授权的密钥请点[此处](https://www.ssh.com/ssh/authorized_keys/)。
### 将 RSA 密钥添加到已授权密钥中
```bash
# 添加 RSA 公钥
cat .ssh/id_rsa.pub | ssh user@host 'cat >> .ssh/authorized_keys'
# 添加 ED25519 公钥
cat .ssh/id_ed25519.pub | ssh user@host 'cat >> .ssh/authorized_keys'
cat .ssh/id_rsa.pub | ssh b@B 'cat >> .ssh/authorized_keys'
```
复制私钥内容并粘贴到 GitHub Secrets。
### 将 ed25519 密钥添加到已授权密钥中
```bash
cat .ssh/id_ed25519.pub | ssh b@B 'cat >> .ssh/authorized_keys'
```
复制私钥内容,然后将其粘贴到 GitHub Secrets 中。
### 复制 RSA 私钥内容
在复制私钥之前,按照以下步骤安装 `clip` 命令:
```bash
# Ubuntu
sudo apt-get install xclip
```
复制私钥:
```bash
# macOS
@ -192,9 +138,9 @@ pbcopy < ~/.ssh/id_rsa
xclip < ~/.ssh/id_rsa
```
> **提示:** 复制内容需包含 `-----BEGIN OPENSSH PRIVATE KEY-----``-----END OPENSSH PRIVATE KEY-----`(含)
从包含注释部分 `-----BEGIN OPENSSH PRIVATE KEY-----` 开始,到包含注释部分 `-----END OPENSSH PRIVATE KEY-----` 结束,复制私钥并将其粘贴到 GitHub Secrets 中
ED25519 同理:
### 复制 ed25519 私钥内容
```bash
# macOS
@ -203,45 +149,41 @@ pbcopy < ~/.ssh/id_ed25519
xclip < ~/.ssh/id_ed25519
```
更多信息:[SSH 无密码登录](http://www.linuxproblem.org/art_9.html)。
有关无需密码登录 SSH 的详细信息,请[见该网站](http://www.linuxproblem.org/art_9.html)。
> **注意:** 根据 SSH 版本,可能还需:
>
> - 将公钥放入 `.ssh/authorized_keys2`
> - 设置 `.ssh` 权限为 700
> - 设置 `.ssh/authorized_keys2` 权限为 640
**注意**:根据您的 SSH 版本,您可能还需要进行以下更改:
### OpenSSH 兼容性
- 将公钥放在 `.ssh/authorized_keys2`
- 将 `.ssh` 的权限更改为 700
- 将 `.ssh/authorized_keys2` 的权限更改为 640
如果出现如下错误:
### 如果你使用的是 OpenSSH
如果您正在使用 OpenSSH并出现以下错误
```bash
ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey]
```
在 Ubuntu 20.04+,你可能需要显式允许 `ssh-rsa` 算法。请在 OpenSSH 配置文件(`/etc/ssh/sshd_config``/etc/ssh/sshd_config.d/` 下的 drop-in 文件)中添加
请确保您所选择的密钥算法得到支持。在 Ubuntu 20.04 或更高版本上,您必须明确允许使用 ssh-rsa 算法。请在 OpenSSH 守护进程文件中添加以下行(它可以是 `/etc/ssh/sshd_config``/etc/ssh/sshd_config.d/` 中的一个附加文件)
```bash
CASignatureAlgorithms +ssh-rsa
```
或者,直接使用默认支持的 ED25519 密钥
或者,`ed25519` 密钥在 OpenSSH 中默认被接受。如果需要,您可以使用它来替代 RSA
```bash
ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
```
---
### 示例
## 🛠️ 用法场景与进阶示例
本节涵盖常见与进阶用法,包括多主机、代理、环境变量传递等。
### 使用密码认证
#### 使用密码执行远程 SSH 命令
```yaml
- name: 执行远程 SSH 命令(密码认证)
uses: appleboy/ssh-action@v1
- name: executing remote ssh commands using password
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -250,11 +192,11 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
script: whoami
```
### 使用私钥认证
#### 使用私钥
```yaml
- name: 执行远程 SSH 命令(密钥认证)
uses: appleboy/ssh-action@v1
- name: executing remote ssh commands using ssh key
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -263,11 +205,11 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
script: whoami
```
### 多条命令
#### 多个命令
```yaml
- name: 多条命令
uses: appleboy/ssh-action@v1
- name: multiple command
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -280,11 +222,11 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
![result](./images/output-result.png)
### 从文件执行命令
#### 从文件执行命令
```yaml
- name: 文件命令
uses: appleboy/ssh-action@v1
- name: file commands
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -293,11 +235,11 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
script_path: scripts/script.sh
```
### 多主机
####主机
```diff
- name: 多主机
uses: appleboy/ssh-action@v1
- name: multiple host
uses: appleboy/ssh-action@v1.2.2
with:
- host: "foo.com"
+ host: "foo.com,bar.com"
@ -309,13 +251,13 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
ls -al
```
默认 `port` `22`
默认`port` 值是 `22`
### 多主机不同端口
#### 多个不同端口的主机
```diff
- name: 多主机
uses: appleboy/ssh-action@v1
- name: multiple host
uses: appleboy/ssh-action@v1.2.2
with:
- host: "foo.com"
+ host: "foo.com:1234,bar.com:5678"
@ -326,11 +268,11 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
ls -al
```
### 多主机同步执行
#### 主机同步执行
```diff
- name: 多主机
uses: appleboy/ssh-action@v1
- name: multiple host
uses: appleboy/ssh-action@v1.2.2
with:
host: "foo.com,bar.com"
+ sync: true
@ -342,11 +284,11 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
ls -al
```
### 传递环境变量到 shell 脚本
#### 将环境变量传递到 shell 脚本
```diff
- name: 传递环境变量
uses: appleboy/ssh-action@v1
- name: pass environment
uses: appleboy/ssh-action@v1.2.2
+ env:
+ FOO: "BAR"
+ BAR: "FOO"
@ -363,13 +305,9 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
echo "sha: $SHA"
```
> _`env` 对象中的所有环境变量必须为字符串。传递整数或其他类型可能导致意外结果。_
_在 `env` 对象中,您需要将每个环境变量作为字符串传递,传递 `Integer` 数据类型或任何其他类型可能会产生意外结果。_
---
## 🌐 代理与跳板机用法
你可以通过代理(跳板机)连接到远程主机,适用于进阶网络拓扑。
#### 如何使用 `ProxyCommand` 连接远程服务器?
```bash
+--------+ +----------+ +-----------+
@ -377,7 +315,7 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
+--------+ +----------+ +-----------+
```
示例 `~/.ssh/config`
在您的 `~/.ssh/config` 文件中,您会看到以下内容。
```bash
Host Jumphost
@ -393,11 +331,11 @@ Host FooServer
ProxyCommand ssh -q -W %h:%p Jumphost
```
**GitHub Actions YAML:**
#### 如何将其转换为 GitHubActions 的 YAML 格式?
```diff
- name: SSH 代理命令
uses: appleboy/ssh-action@v1
- name: ssh proxy command
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -412,17 +350,13 @@ Host FooServer
ls -al
```
---
#### 保护私钥
## 🛡️ 安全最佳实践
### 保护你的私钥
密码短语会加密你的私钥,即使泄露也无法被攻击者直接利用。请务必妥善保管私钥。
密码短语通常用于加密私钥。这使得密钥文件本身对攻击者无用。文件泄露可能来自备份或停用的硬件,黑客通常可以从受攻击系统中泄露文件。
```diff
- name: SSH 密钥密码短语
uses: appleboy/ssh-action@v1
- name: ssh key passphrase
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -434,19 +368,21 @@ Host FooServer
ls -al
```
### 主机指纹验证
#### 使用主机指纹验证
验证 SSH 主机指纹有助于防止中间人攻击。获取主机指纹(将 `ed25519` 替换为你的密钥类型,`example.com` 替换为你的主机):
设置 SSH 主机指纹验证可以帮助防止中间人攻击。在设置之前,运行以下命令以获取 SSH 主机指纹。请记得将 `ed25519` 替换为您适当的密钥类型(`rsa``dsa`等),而 `example.com` 则替换为您的主机。
在现代 OpenSSH 版本中,默认提取的密钥类型是 `rsa`(从版本 5.1 开始)、`ecdsa`(从版本 6.0 开始)和 `ed25519`(从版本 6.7 开始)。
```sh
ssh example.com ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub | cut -d ' ' -f2
```
更新配置:
现在您可以调整您的配置:
```diff
- name: SSH 密钥密码短语
uses: appleboy/ssh-action@v1
- name: ssh key passphrase
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -458,33 +394,10 @@ ssh example.com ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub | cut -d ' '
ls -al
```
---
## 贡献
## 🚨 错误处理与疑难解答
我们非常希望您为 `appleboy/ssh-action` 做出贡献,欢迎提交请求!
### 常见问题
## 授权方式
#### 命令未找到npm 或其他命令)
如果遇到 "command not found" 错误,请参考 [此评论](https://github.com/appleboy/ssh-action/issues/31#issuecomment-1006565847) 了解交互式与非交互式 shell 的区别。
许多 Linux 发行版的 `/etc/bash.bashrc` 包含如下内容:
```sh
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
```
注释掉该行或使用命令的绝对路径。
---
## 🤝 贡献
欢迎贡献!请提交 Pull Request 改进 `appleboy/ssh-action`
---
## 📝 许可证
本项目采用 [MIT License](LICENSE) 授权。
本项目中的脚本和文档采用 [MIT 许可证](LICENSE) 发布。

View File

@ -2,137 +2,70 @@
[English](./README.md) | 繁體中文 | [简体中文](./README.zh-cn.md)
## 目錄
- [🚀 GitHub Actions 的 SSH](#-github-actions-的-ssh)
- [目錄](#目錄)
- [📖 簡介](#-簡介)
- [🧩 核心概念與輸入參數](#-核心概念與輸入參數)
- [🔌 連線設定](#-連線設定)
- [🛠️ 指令設定](#-指令設定)
- [🌐 代理設定](#-代理設定)
- [⚡ 快速開始](#-快速開始)
- [🔑 SSH 金鑰設定與 OpenSSH 相容性](#-ssh-金鑰設定與-openssh-相容性)
- [設定 SSH 金鑰](#設定-ssh-金鑰)
- [產生 RSA 金鑰](#產生-rsa-金鑰)
- [產生 ED25519 金鑰](#產生-ed25519-金鑰)
- [OpenSSH 相容性](#openssh-相容性)
- [🛠️ 用法場景與進階範例](#-用法場景與進階範例)
- [使用密碼認證](#使用密碼認證)
- [使用私鑰認證](#使用私鑰認證)
- [多條指令](#多條指令)
- [從檔案執行指令](#從檔案執行指令)
- [多主機](#多主機)
- [多主機不同埠號](#多主機不同埠號)
- [多主機同步執行](#多主機同步執行)
- [傳遞環境變數到 shell 腳本](#傳遞環境變數到-shell-腳本)
- [🌐 代理與跳板機用法](#-代理與跳板機用法)
- [🛡️ 安全最佳實踐](#-安全最佳實踐)
- [保護你的私鑰](#保護你的私鑰)
- [主機指紋驗證](#主機指紋驗證)
- [🚨 錯誤處理與疑難排解](#-錯誤處理與疑難排解)
- [常見問題](#常見問題)
- [指令找不到npm 或其他指令)](#指令找不到npm-或其他指令)
- [🤝 貢獻](#-貢獻)
- [📝 授權](#-授權)
---
## 📖 簡介
**SSH for GitHub Actions** 是一個強大的 [GitHub Action](https://github.com/features/actions),可讓你在 CI/CD 工作流程中輕鬆且安全地執行遠端 SSH 指令。
本專案以 [Golang](https://go.dev) 和 [drone-ssh](https://github.com/appleboy/drone-ssh) 建立,支援多主機、代理、進階認證等多種 SSH 場景。
[GitHub Action](https://github.com/features/actions) 用於執行遠端 SSH 命令。
![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)
---
此專案使用 [Golang](https://go.dev) 和 [drone-ssh](https://github.com/appleboy/drone-ssh) 建立。🚀
## 🧩 核心概念與輸入參
## 輸入變數
本 Action 提供彈性的 SSH 指令執行能力,並具備豐富的設定選項
請參閱 [action.yml](./action.yml) 以獲取更詳細的信息。
完整參數請參閱 [action.yml](./action.yml)。
| 輸入參數 | 描述 | 預設值 |
| ------------------------- | ----------------------------------------------------- | ------ |
| host | SSH 主機地址 | |
| port | SSH 埠號 | 22 |
| passphrase | SSH 金鑰密碼 | |
| username | SSH 使用者名稱 | |
| password | SSH 密碼 | |
| protocol | SSH 協議版本 (tcp, tcp4, tcp6) | tcp |
| sync | 如果有多個主機,啟用同步執行 | false |
| use_insecure_cipher | 包含更多不安全的加密算法 | false |
| cipher | 允許的加密算法。如果未指定,則使用合理的預設值 | |
| timeout | SSH 連接主機的超時時間 | 30s |
| command_timeout | SSH 命令的超時時間 | 10m |
| key | SSH 私鑰的內容。例如,~/.ssh/id_rsa 的原始內容 | |
| key_path | SSH 私鑰的路徑 | |
| fingerprint | 主機公鑰的 SHA256 指紋 | |
| proxy_host | SSH 代理主機 | |
| proxy_port | SSH 代理埠號 | 22 |
| proxy_protocol | SSH 代理協議版本 (tcp, tcp4, tcp6) | tcp |
| proxy_username | SSH 代理使用者名稱 | |
| proxy_password | SSH 代理密碼 | |
| proxy_passphrase | SSH 代理金鑰密碼 | |
| proxy_timeout | SSH 連接代理主機的超時時間 | 30s |
| proxy_key | SSH 代理私鑰的內容 | |
| proxy_key_path | SSH 代理私鑰的路徑 | |
| proxy_fingerprint | 代理主機公鑰的 SHA256 指紋 | |
| proxy_cipher | 代理允許的加密算法 | |
| proxy_use_insecure_cipher | 包含更多不安全的加密算法 | false |
| script | 執行命令 | |
| script_path | 從文件中執行命令 | |
| envs | 將環境變數傳遞給 shell 腳本 | |
| envs_format | 環境值傳遞的靈活配置 | |
| debug | 啟用調試模式 | false |
| allenvs | 將帶有 `GITHUB_``INPUT_` 前綴的環境變數傳遞給腳本 | false |
| request_pty | 從伺服器請求偽終端 | false |
### 🔌 連線設定
**注意:** 用戶可以在他們的 shell 腳本中添加 `set -e` 以實現類似於已刪除的 `script_stop` 選項的功能。
這些參數用於控制如何連線到遠端主機。
## 用法
| 參數 | 說明 | 預設值 |
| ------------------- | --------------------------------------------- | ------ |
| host | SSH 主機位址 | |
| port | SSH 埠號 | 22 |
| username | SSH 使用者名稱 | |
| password | SSH 密碼 | |
| protocol | SSH 協議版本(`tcp``tcp4``tcp6` | tcp |
| sync | 指定多個主機時同步執行 | false |
| timeout | SSH 連線主機的逾時時間 | 30s |
| key | SSH 私鑰內容(如 `~/.ssh/id_rsa` 的原始內容) | |
| key_path | SSH 私鑰路徑 | |
| passphrase | SSH 私鑰密碼 | |
| fingerprint | 主機公鑰的 SHA256 指紋 | |
| use_insecure_cipher | 允許額外(不安全)的加密演算法 | false |
| cipher | 允許的加密演算法,未指定時使用預設值 | |
---
### 🛠️ 指令設定
這些參數用於控制在遠端主機上執行的指令及相關行為。
| 參數 | 說明 | 預設值 |
| --------------- | ----------------------------------------------------- | ------ |
| script | 遠端執行的指令 | |
| script_path | 包含要執行指令的檔案路徑 | |
| envs | 傳遞給 shell 腳本的環境變數 | |
| envs_format | 環境變數傳遞的彈性設定 | |
| allenvs | 傳遞所有帶 `GITHUB_``INPUT_` 前綴的環境變數到腳本 | false |
| command_timeout | SSH 指令執行逾時時間 | 10m |
| debug | 啟用除錯模式 | false |
| request_pty | 向伺服器請求偽終端 | false |
| curl_insecure | 允許 curl 連線無憑證的 SSL 網站 | false |
| version | drone-ssh 執行檔版本,未指定時使用最新版本 | |
---
### 🌐 代理設定
這些參數用於透過代理(跳板機)連線到目標主機。
| 參數 | 說明 | 預設值 |
| ------------------------- | ----------------------------------------- | ------ |
| proxy_host | SSH 代理主機 | |
| proxy_port | SSH 代理埠號 | 22 |
| proxy_username | SSH 代理使用者名稱 | |
| proxy_password | SSH 代理密碼 | |
| proxy_passphrase | SSH 代理私鑰密碼 | |
| proxy_protocol | SSH 代理協議版本(`tcp``tcp4``tcp6` | tcp |
| proxy_timeout | SSH 連線代理主機的逾時時間 | 30s |
| proxy_key | SSH 代理私鑰內容 | |
| proxy_key_path | SSH 代理私鑰路徑 | |
| proxy_fingerprint | 代理主機公鑰的 SHA256 指紋 | |
| proxy_cipher | 代理允許的加密演算法 | |
| proxy_use_insecure_cipher | 代理允許額外(不安全)的加密演算法 | false |
> **注意:** 如需實現已移除的 `script_stop` 功能,請在 shell 腳本最上方加上 `set -e`
---
## ⚡ 快速開始
只需簡單設定,即可在工作流程中執行遠端 SSH 指令:
執行遠端 SSH 命令
```yaml
name: Remote SSH Command
name: remote ssh command
on: [push]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: 執行遠端 SSH 指令(密碼認證)
uses: appleboy/ssh-action@v1
- name: executing remote ssh commands using password
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: linuxserver.io
@ -141,7 +74,7 @@ jobs:
script: whoami
```
**輸出:**
畫面輸出
```sh
======CMD======
@ -153,37 +86,48 @@ linuxserver.io
===============================================
```
---
### 設置 SSH 金鑰
## 🔑 SSH 金鑰設定與 OpenSSH 相容性
請在創建 SSH 金鑰並使用 SSH 金鑰時遵循以下步驟。最佳做法是在本地機器上創建 SSH 金鑰而不是遠端機器上。請使用 Github Secrets 中指定的用戶名登錄。生成 RSA 金鑰:
### 設定 SSH 金鑰
建議於本地端(非遠端伺服器)產生 SSH 金鑰。請以 GitHub Secrets 指定的使用者名稱登入並產生金鑰對:
#### 產生 RSA 金鑰
### 生成 RSA 金鑰
```bash
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
```
#### 產生 ED25519 金鑰
### 生成 ed25519 金鑰
```bash
ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
```
將新產生的公鑰加入伺服器的 authorized_keys。 [了解更多 authorized_keys](https://www.ssh.com/ssh/authorized_keys/)
將新生成的金鑰添加到已授權的金鑰中。詳細了解已授權的金鑰請點擊[此處](https://www.ssh.com/ssh/authorized_keys/).
### 將 RSA 金鑰添加到已授權金鑰中
```bash
# 加入 RSA 公鑰
cat .ssh/id_rsa.pub | ssh user@host 'cat >> .ssh/authorized_keys'
# 加入 ED25519 公鑰
cat .ssh/id_ed25519.pub | ssh user@host 'cat >> .ssh/authorized_keys'
cat .ssh/id_rsa.pub | ssh b@B 'cat >> .ssh/authorized_keys'
```
複製私鑰內容並貼到 GitHub Secrets。
### 將 ed25519 金鑰添加到已授權金鑰中
```bash
cat .ssh/id_ed25519.pub | ssh b@B 'cat >> .ssh/authorized_keys'
```
複製私鑰內容,然後將其粘貼到 Github Secrets 中。
### 複製 rsa 私鑰內容
在複製私鑰之前,請按照以下說明安裝 `clip` 命令:
```bash
# Ubuntu
sudo apt-get install xclip
```
複製私鑰:
```bash
# macOS
@ -192,9 +136,9 @@ pbcopy < ~/.ssh/id_rsa
xclip < ~/.ssh/id_rsa
```
> **提示:** 複製內容需包含 `-----BEGIN OPENSSH PRIVATE KEY-----``-----END OPENSSH PRIVATE KEY-----`(含)
從包含註釋部分 `-----BEGIN OPENSSH PRIVATE KEY-----` 開始,到包含註釋部分 `-----END OPENSSH PRIVATE KEY-----` 結束,複製私鑰並將其粘貼到 GitHub Secrets 中
ED25519 同理:
### 複製 ed25519 私鑰內容
```bash
# macOS
@ -203,45 +147,41 @@ pbcopy < ~/.ssh/id_ed25519
xclip < ~/.ssh/id_ed25519
```
更多資訊:[SSH 免密碼登入](http://www.linuxproblem.org/art_9.html)。
有關無需密碼登錄 SSH 的詳細信息,請[參見該網站](http://www.linuxproblem.org/art_9.html)。
> **注意:** 根據 SSH 版本,可能還需:
>
> - 將公鑰放入 `.ssh/authorized_keys2`
> - 設定 `.ssh` 權限為 700
> - 設定 `.ssh/authorized_keys2` 權限為 640
**注意**:根據您的 SSH 版本,您可能還需要進行以下更改:
### OpenSSH 相容性
- 將公鑰放在 `.ssh/authorized_keys2`
- 將 `.ssh` 的權限更改為 700
- 將 `.ssh/authorized_keys2` 的權限更改為 640
若出現以下錯誤:
### 如果你使用的是 OpenSSH
如果您正在使用 OpenSSH並出現以下錯誤
```bash
ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey]
```
在 Ubuntu 20.04+,你可能需明確允許 `ssh-rsa` 演算法。請於 OpenSSH 設定檔(`/etc/ssh/sshd_config``/etc/ssh/sshd_config.d/` 下的 drop-in 檔案)加入
請確保您所選擇的密鑰演算法得到支援。在 Ubuntu 20.04 或更高版本上,您必須明確允許使用 SSH-RSA 演算法。請在 OpenSSH 守護進程文件中添加以下行(它可以是 `/etc/ssh/sshd_config``/etc/ssh/sshd_config.d/` 中的一個附著文件)
```bash
CASignatureAlgorithms +ssh-rsa
```
直接使用預設支援的 ED25519 金鑰:
者,`Ed25519` 密鑰在 OpenSSH 中默認被接受。如果需要,您可以使用它來替代 RSA。
```bash
ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
```
---
### Example
## 🛠️ 用法場景與進階範例
本節涵蓋常見與進階用法,包括多主機、代理、環境變數傳遞等。
### 使用密碼認證
#### 使用密碼執行遠端 SSH 命令
```yaml
- name: 執行遠端 SSH 指令(密碼認證)
uses: appleboy/ssh-action@v1
- name: executing remote ssh commands using password
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -250,11 +190,11 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
script: whoami
```
### 使用私鑰認證
#### 使用私鑰
```yaml
- name: 執行遠端 SSH 指令(私鑰認證)
uses: appleboy/ssh-action@v1
- name: executing remote ssh commands using ssh key
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -263,11 +203,11 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
script: whoami
```
### 多條指
#### 多個命
```yaml
- name: 多條指令
uses: appleboy/ssh-action@v1
- name: multiple command
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -280,11 +220,11 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
![result](./images/output-result.png)
### 從檔案執行指
#### 從文件中執行命
```yaml
- name: 檔案指令
uses: appleboy/ssh-action@v1
- name: file commands
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -293,11 +233,11 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
script_path: scripts/script.sh
```
### 多主機
####主機
```diff
- name: 多主機
uses: appleboy/ssh-action@v1
- name: multiple host
uses: appleboy/ssh-action@v1.2.2
with:
- host: "foo.com"
+ host: "foo.com,bar.com"
@ -309,13 +249,11 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
ls -al
```
預設 `port``22`
### 多主機不同埠號
#### 多個不同端口的主機
```diff
- name: 多主機
uses: appleboy/ssh-action@v1
- name: multiple host
uses: appleboy/ssh-action@v1.2.2
with:
- host: "foo.com"
+ host: "foo.com:1234,bar.com:5678"
@ -326,11 +264,11 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
ls -al
```
### 多主機同步執行
#### 主機同步執行
```diff
- name: 多主機
uses: appleboy/ssh-action@v1
- name: multiple host
uses: appleboy/ssh-action@v1.2.2
with:
host: "foo.com,bar.com"
+ sync: true
@ -342,11 +280,11 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
ls -al
```
### 傳遞環境變數到 shell 腳本
#### 將環境變量傳遞到 Shell 腳本
```diff
- name: 傳遞環境變數
uses: appleboy/ssh-action@v1
- name: pass environment
uses: appleboy/ssh-action@v1.2.2
+ env:
+ FOO: "BAR"
+ BAR: "FOO"
@ -363,13 +301,9 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
echo "sha: $SHA"
```
> _`env` 物件中的所有環境變數必須為字串。傳遞整數或其他型別可能導致非預期結果。_
_在 `env` 對象中,您需要將每個環境變量作為字符串傳遞,傳遞 `Integer` 數據類型或任何其他類型可能會產生意外結果。_
---
## 🌐 代理與跳板機用法
你可以透過代理(跳板機)連線到遠端主機,適用於進階網路拓撲。
#### 如何使用 `ProxyCommand` 連接遠程服務器?
```bash
+--------+ +----------+ +-----------+
@ -377,7 +311,7 @@ ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
+--------+ +----------+ +-----------+
```
範例 `~/.ssh/config`
在您的 `~/.ssh/config` 文件中,您會看到以下內容。
```bash
Host Jumphost
@ -393,11 +327,11 @@ Host FooServer
ProxyCommand ssh -q -W %h:%p Jumphost
```
**GitHub Actions YAML:**
#### 如何將其轉換為 GitHubActions 的 YAML 格式?
```diff
- name: SSH 代理指令
uses: appleboy/ssh-action@v1
- name: ssh proxy command
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -412,17 +346,13 @@ Host FooServer
ls -al
```
---
#### 如何保護私鑰?
## 🛡️ 安全最佳實踐
### 保護你的私鑰
密碼短語會加密你的私鑰,即使外洩也無法被攻擊者直接利用。請務必妥善保管私鑰。
密碼短語通常用於加密私鑰。這使得攻擊者無法單獨使用密鑰文件。文件泄露可能來自備份或停用的硬件,黑客通常可以從受攻擊系統中洩露文件。因此,保護私鑰非常重要。
```diff
- name: SSH 私鑰密碼
uses: appleboy/ssh-action@v1
- name: ssh key passphrase
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -434,19 +364,21 @@ Host FooServer
ls -al
```
### 主機指紋驗證
#### 使用主機指紋驗證
驗證 SSH 主機指紋有助於防止中間人攻擊。取得主機指紋(將 `ed25519` 換成你的金鑰型別,`example.com` 換成你的主機):
設置 SSH 主機指紋驗證可以幫助防止中間人攻擊。在設置之前,運行以下命令以獲取 SSH 主機指紋。請記得將 `ed25519` 替換為您的適當金鑰類型(`rsa``dsa`等),而 `example.com` 則替換為您的主機。
現代 OpenSSH 版本中,需要提取的**默認金鑰**類型是 `rsa`(從版本 5.1 開始)、`ecdsa`(從版本 6.0 開始)和 `ed25519`(從版本 6.7 開始)。
```sh
ssh example.com ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub | cut -d ' ' -f2
```
更新設定
現在您可以調整您的配置
```diff
- name: SSH 私鑰密碼
uses: appleboy/ssh-action@v1
- name: ssh key passphrase
uses: appleboy/ssh-action@v1.2.2
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
@ -458,33 +390,10 @@ ssh example.com ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub | cut -d ' '
ls -al
```
---
## 貢獻
## 🚨 錯誤處理與疑難排解
我們非常希望您為 `appleboy/ssh-action` 做出貢獻,歡迎提交請求!
### 常見問題
## 授權方式
#### 指令找不到npm 或其他指令)
若遇到 "command not found" 錯誤,請參考 [此討論](https://github.com/appleboy/ssh-action/issues/31#issuecomment-1006565847) 了解互動式與非互動式 shell 差異。
許多 Linux 發行版的 `/etc/bash.bashrc` 包含如下內容:
```sh
# If not running interactively, don't do anything
[ -z "$PS1" ] && return
```
請將該行註解掉或使用指令的絕對路徑。
---
## 🤝 貢獻
歡迎貢獻!請提交 Pull Request 改善 `appleboy/ssh-action`
---
## 📝 授權
本專案採用 [MIT License](LICENSE) 授權。
本項目中的腳本和文檔采用 [MIT](LICENSE) 許可證 發布。

View File

@ -75,15 +75,9 @@ inputs:
description: "When true, passes all GitHub Actions environment variables to the remote script."
request_pty:
description: "Request a pseudo-terminal from the server (required for interactive commands or sudo)."
curl_insecure:
description: "When true, uses the --insecure option with curl for insecure downloads."
default: "false"
capture_stdout:
description: "When true, captures and returns standard output from the commands as action output."
default: "false"
version:
description: |
The version of drone-ssh to use.
outputs:
stdout:
@ -137,8 +131,6 @@ runs:
INPUT_PROXY_CIPHER: ${{ inputs.proxy_cipher }}
INPUT_SYNC: ${{ inputs.sync }}
INPUT_CAPTURE_STDOUT: ${{ inputs.capture_stdout }}
INPUT_CURL_INSECURE: ${{ inputs.curl_insecure }}
DRONE_SSH_VERSION: ${{ inputs.version }}
branding:
icon: "terminal"

View File

@ -1,6 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
set -o errexit
set -o nounset
set -o pipefail
export GITHUB="true"
@ -8,49 +10,68 @@ GITHUB_ACTION_PATH="${GITHUB_ACTION_PATH%/}"
DRONE_SSH_RELEASE_URL="${DRONE_SSH_RELEASE_URL:-https://github.com/appleboy/drone-ssh/releases/download}"
DRONE_SSH_VERSION="${DRONE_SSH_VERSION:-1.8.1}"
function log_error() {
echo "$1" >&2
exit "$2"
}
function detect_client_info() {
CLIENT_PLATFORM="${SSH_CLIENT_OS:-$(uname -s | tr '[:upper:]' '[:lower:]')}"
CLIENT_ARCH="${SSH_CLIENT_ARCH:-$(uname -m)}"
if [ -n "${SSH_CLIENT_OS-}" ]; then
CLIENT_PLATFORM="${SSH_CLIENT_OS}"
else
local kernel
kernel="$(uname -s)"
case "${kernel}" in
Darwin)
CLIENT_PLATFORM="darwin"
;;
Linux)
CLIENT_PLATFORM="linux"
;;
Windows)
CLIENT_PLATFORM="windows"
;;
*)
echo "Unknown, unsupported platform: ${kernel}." >&2
echo "Supported platforms: Linux, Darwin and Windows." >&2
echo "Bailing out." >&2
exit 2
;;
esac
fi
case "${CLIENT_PLATFORM}" in
darwin | linux | windows) ;;
*) log_error "Unknown or unsupported platform: ${CLIENT_PLATFORM}. Supported platforms are Linux, Darwin, and Windows." 2 ;;
esac
case "${CLIENT_ARCH}" in
x86_64* | i?86_64* | amd64*) CLIENT_ARCH="amd64" ;;
aarch64* | arm64*) CLIENT_ARCH="arm64" ;;
*) log_error "Unknown or unsupported architecture: ${CLIENT_ARCH}. Supported architectures are x86_64, i686, and arm64." 3 ;;
esac
if [ -n "${SSH_CLIENT_ARCH-}" ]; then
CLIENT_ARCH="${SSH_CLIENT_ARCH}"
else
local machine
machine="$(uname -m)"
case "${machine}" in
x86_64* | i?86_64* | amd64*)
CLIENT_ARCH="amd64"
;;
aarch64* | arm64*)
CLIENT_ARCH="arm64"
;;
*)
echo "Unknown, unsupported architecture (${machine})." >&2
echo "Supported architectures x86_64, i686, arm64." >&2
echo "Bailing out." >&2
exit 3
;;
esac
fi
}
detect_client_info
DOWNLOAD_URL_PREFIX="${DRONE_SSH_RELEASE_URL}/v${DRONE_SSH_VERSION}"
CLIENT_BINARY="drone-ssh-${DRONE_SSH_VERSION}-${CLIENT_PLATFORM}-${CLIENT_ARCH}"
TARGET="${GITHUB_ACTION_PATH}/${CLIENT_BINARY}"
echo "Downloading ${CLIENT_BINARY} from ${DOWNLOAD_URL_PREFIX}"
INSECURE_OPTION=""
if [[ "${INPUT_CURL_INSECURE}" == 'true' ]]; then
INSECURE_OPTION="--insecure"
fi
echo "Will download ${CLIENT_BINARY} from ${DOWNLOAD_URL_PREFIX}"
curl -fsSL --retry 5 --keepalive-time 2 "${DOWNLOAD_URL_PREFIX}/${CLIENT_BINARY}" -o ${TARGET}
chmod +x ${TARGET}
curl -fsSL --retry 5 --keepalive-time 2 ${INSECURE_OPTION} "${DOWNLOAD_URL_PREFIX}/${CLIENT_BINARY}" -o "${TARGET}"
chmod +x "${TARGET}"
echo "======= CLI Version Information ======="
"${TARGET}" --version
echo "======================================="
if [[ "${INPUT_CAPTURE_STDOUT}" == 'true' ]]; then
{
echo 'stdout<<EOF'
"${TARGET}" "$@" | tee -a "${GITHUB_OUTPUT}"
echo 'EOF'
} >>"${GITHUB_OUTPUT}"
echo "======= CLI Version ======="
sh -c "${TARGET} --version" # print version
echo "==========================="
if [[ "$INPUT_CAPTURE_STDOUT" == 'true' ]]; then
echo 'stdout<<EOF' >>$GITHUB_OUTPUT # use heredoc for multiline output
sh -c "${TARGET} $*" | tee -a $GITHUB_OUTPUT # run the command
echo 'EOF' >>$GITHUB_OUTPUT
else
"${TARGET}" "$@"
sh -c "${TARGET} $*" # run the command
fi