Compare commits
95 Commits
v0.0.4
...
bf323b8729
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf323b8729 | ||
|
|
e55e4eabdc | ||
|
|
bc835cba55 | ||
|
|
98b7132065 | ||
|
|
3d59448764 | ||
|
|
2a829c0ed2 | ||
|
|
4f2c27bbc2 | ||
|
|
b03d7257ae | ||
|
|
f042d742db | ||
|
|
ad9a378b7f | ||
|
|
172d4632fe | ||
|
|
93e17a47f9 | ||
|
|
c6ff96b7f6 | ||
|
|
7622c01418 | ||
|
|
9ba62064d2 | ||
|
|
f314a5399e | ||
|
|
699fa1181c | ||
|
|
9f34cc3e55 | ||
|
|
917f8b81df | ||
|
|
78e7f475f3 | ||
|
|
dfde1f50eb | ||
|
|
35093a99f9 | ||
|
|
96947ea2bd | ||
|
|
aef31a66e3 | ||
|
|
950981a11d | ||
|
|
5878fc908f | ||
|
|
abb6d70bee | ||
|
|
b03ad1c124 | ||
|
|
d398e64fcf | ||
|
|
3710327b67 | ||
|
|
d47d818ab7 | ||
|
|
7435be3541 | ||
|
|
11da0c97f9 | ||
|
|
ec73feb854 | ||
|
|
cdeb36bbf9 | ||
|
|
c209399d24 | ||
|
|
952a6e3250 | ||
|
|
82ebdbe3ed | ||
|
|
2cd029d317 | ||
|
|
e0dbae8ff0 | ||
|
|
3e84ad0651 | ||
|
|
8a92fcdb1e | ||
|
|
6cd4f0cace | ||
|
|
43abfa778d | ||
|
|
c8e0cef94a | ||
|
|
d47875703f | ||
|
|
a4e63f0f1a | ||
|
|
7cc0f645c7 | ||
|
|
dcb457bb6f | ||
|
|
11c56b52d3 | ||
|
|
51b937b31f | ||
|
|
98930d398c | ||
|
|
9ec0623f29 | ||
|
|
7f8a7c1019 | ||
|
|
2ce642f289 | ||
|
|
6291b9e86b | ||
|
|
6d1fc191a8 | ||
|
|
233f762873 | ||
|
|
b3f8d37ead | ||
|
|
f57f59fba9 | ||
|
|
8a54b39459 | ||
|
|
eb319b796f | ||
|
|
3c3575eb49 | ||
|
|
edc8ec9139 | ||
|
|
167d6d7ff4 | ||
|
|
ecd6a3623b | ||
|
|
31b514c281 | ||
|
|
5924b2b6d4 | ||
|
|
a2fea1040d | ||
|
|
8535291767 | ||
|
|
faff4f4218 | ||
|
|
ddd9176502 | ||
|
|
326e14ddd1 | ||
|
|
0329cffdaa | ||
|
|
6df9245376 | ||
|
|
4df1959b05 | ||
|
|
1c43a9662a | ||
|
|
2949c755a2 | ||
|
|
aefa39daef | ||
|
|
2305e4000a | ||
|
|
c51573a06a | ||
|
|
d068cd7f84 | ||
|
|
8ba3cfe760 | ||
|
|
1a6b426141 | ||
|
|
8b3eebcc8d | ||
|
|
861f2e0e7b | ||
|
|
6971270956 | ||
|
|
bac6c9e026 | ||
|
|
b04405242e | ||
|
|
5cb36bda99 | ||
|
|
6c5771fab8 | ||
|
|
193fd4eaa9 | ||
|
|
a5de8af6d5 | ||
|
|
671c934c80 | ||
|
|
8da6e3d335 |
13
.github/FUNDING.yml
vendored
Normal file
13
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: ['https://www.paypal.me/appleboy46']
|
||||
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
175
.github/workflows/ci.yml
vendored
175
.github/workflows/ci.yml
vendored
@@ -1,39 +1,150 @@
|
||||
name: scp files
|
||||
on: [push]
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
testing:
|
||||
name: test scp action
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: copy file via ssh password
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
password: ${{ secrets.PASSWORD }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: "tests/a.txt,tests/b.txt"
|
||||
target: "test"
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: copy file via ssh key
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.KEY }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: "tests/a.txt,tests/b.txt"
|
||||
target: "test"
|
||||
- name: copy file via ssh password
|
||||
uses: ./
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
password: ${{ secrets.PASSWORD }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: "tests/a.txt,tests/b.txt"
|
||||
target: "test"
|
||||
|
||||
- name: remove the specified number of leading path elements
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.KEY }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: "tests/a.txt,tests/b.txt"
|
||||
target: "foobar"
|
||||
strip_components: 1
|
||||
- name: copy file via ssh key
|
||||
uses: ./
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.KEY }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: "tests/a.txt,tests/b.txt"
|
||||
target: "test"
|
||||
|
||||
- name: remove the specified number of leading path elements
|
||||
uses: ./
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.KEY }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: "tests/a.txt,tests/b.txt"
|
||||
target: "foobar"
|
||||
strip_components: 1
|
||||
|
||||
- name: ssh key with passphrase
|
||||
uses: ./
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.SSH2 }}
|
||||
passphrase: ${{ secrets.PASSPHRASE }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: "tests/a.txt,tests/b.txt"
|
||||
target: "test"
|
||||
|
||||
- name: use insecure cipher
|
||||
uses: ./
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.SSH2 }}
|
||||
passphrase: ${{ secrets.PASSPHRASE }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: "tests/a.txt,tests/b.txt"
|
||||
target: "test"
|
||||
use_insecure_cipher: true
|
||||
|
||||
deploy:
|
||||
name: test deploy artifact
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- run: echo hello > world.txt
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: my-artifact
|
||||
path: world.txt
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: my-artifact
|
||||
path: distfiles
|
||||
|
||||
- name: copy file to server
|
||||
uses: ./
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.KEY }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: distfiles/*
|
||||
target: test
|
||||
|
||||
changes:
|
||||
name: test changed-files
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v45
|
||||
with:
|
||||
since_last_remote_commit: true
|
||||
separator: ","
|
||||
|
||||
- name: copy file to server
|
||||
uses: ./
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.KEY }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
target: test
|
||||
|
||||
target:
|
||||
name: test target folder
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: copy file to server
|
||||
uses: ./
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.KEY }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: tests/a.txt,tests/b.txt
|
||||
target: foobar foobar 1234
|
||||
|
||||
multipleHost:
|
||||
name: test Multiple Host
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: copy file to server
|
||||
uses: ./
|
||||
with:
|
||||
host: ${{ secrets.HOST }}:${{ secrets.PORT }},${{ secrets.HOST }}:${{ secrets.PORT }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.KEY }}
|
||||
port: 1024
|
||||
source: tests/a.txt,tests/b.txt
|
||||
target: foobar
|
||||
|
||||
33
.github/workflows/goreleaser.yml
vendored
Normal file
33
.github/workflows/goreleaser.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Goreleaser
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "^1"
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
# either 'goreleaser' (default) or 'goreleaser-pro'
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
28
.goreleaser.yaml
Normal file
28
.goreleaser.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
builds:
|
||||
- # If true, skip the build.
|
||||
# Useful for library projects.
|
||||
# Default is false
|
||||
skip: true
|
||||
|
||||
changelog:
|
||||
use: github
|
||||
groups:
|
||||
- title: Features
|
||||
regexp: "^.*feat[(\\w)]*:+.*$"
|
||||
order: 0
|
||||
- title: "Bug fixes"
|
||||
regexp: "^.*fix[(\\w)]*:+.*$"
|
||||
order: 1
|
||||
- title: "Enhancements"
|
||||
regexp: "^.*chore[(\\w)]*:+.*$"
|
||||
order: 2
|
||||
- title: "Refactor"
|
||||
regexp: "^.*refactor[(\\w)]*:+.*$"
|
||||
order: 3
|
||||
- title: "Build process updates"
|
||||
regexp: ^.*?(build|ci)(\(.+\))??!?:.+$
|
||||
order: 4
|
||||
- title: "Documentation updates"
|
||||
regexp: ^.*?docs?(\(.+\))??!?:.+$
|
||||
order: 4
|
||||
- title: Others
|
||||
@@ -1,5 +0,0 @@
|
||||
FROM appleboy/drone-scp:1.5.3-linux-amd64
|
||||
|
||||
ADD entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
379
README.md
379
README.md
@@ -2,143 +2,344 @@
|
||||
|
||||
[GitHub Action](https://github.com/features/actions) for copying files and artifacts via SSH.
|
||||
|
||||

|
||||
|
||||
[](https://github.com/appleboy/scp-action/actions)
|
||||
|
||||
## Usage
|
||||
> **Note:** Only supports **Linux** [docker](https://www.docker.com/) containers.
|
||||
|
||||
copy files and artifacts via SSH as blow.
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- ✅ Copy files and artifacts to one or multiple remote servers via SSH
|
||||
- ✅ Supports both SSH key and password authentication
|
||||
- ✅ Full SSH Proxy (jump host) support
|
||||
- ✅ Handles Linux ↔ Windows path conversion
|
||||
- ✅ Integrates with GitHub Artifacts workflow
|
||||
- ✅ Incremental and differential file transfer
|
||||
- ✅ Rich configuration options for advanced use cases
|
||||
|
||||
---
|
||||
|
||||
## 📦 Table of Contents
|
||||
|
||||
- [🚀 SCP for GitHub Actions](#-scp-for-github-actions)
|
||||
- [✨ Features](#-features)
|
||||
- [📦 Table of Contents](#-table-of-contents)
|
||||
- [🚀 Quick Start](#-quick-start)
|
||||
- [⚙️ Configuration](#️-configuration)
|
||||
- [🔌 Connection Settings](#-connection-settings)
|
||||
- [📁 File Transfer Settings](#-file-transfer-settings)
|
||||
- [🌐 Proxy Settings](#-proxy-settings)
|
||||
- [🛡️ Best Practices \& Security](#️-best-practices--security)
|
||||
- [🖥️ Cross-Platform Notes](#️-cross-platform-notes)
|
||||
- [💡 Usage Examples](#-usage-examples)
|
||||
- [🧩 Scenario Guide](#-scenario-guide)
|
||||
- [Example 1: Basic SSH Password](#example-1-basic-ssh-password)
|
||||
- [Example 2: Multi-server](#example-2-multi-server)
|
||||
- [Example 3: Changed Files Only](#example-3-changed-files-only)
|
||||
- [Example 4: Artifacts Integration](#example-4-artifacts-integration)
|
||||
- [Example 5: Windows Server](#example-5-windows-server)
|
||||
- [🗝️ SSH Key Setup](#️-ssh-key-setup)
|
||||
- [🧰 Common Error Codes](#-common-error-codes)
|
||||
- [🔄 Workflow Diagram](#-workflow-diagram)
|
||||
- [FAQ \& Troubleshooting](#faq--troubleshooting)
|
||||
- [📝 License](#-license)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
Copy files and artifacts via SSH in your GitHub Actions workflow:
|
||||
|
||||
```yaml
|
||||
name: scp files
|
||||
on: [push]
|
||||
jobs:
|
||||
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: copy file via ssh password
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
password: ${{ secrets.PASSWORD }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: "tests/a.txt,tests/b.txt"
|
||||
target: "test"
|
||||
- uses: actions/checkout@v4
|
||||
- name: Copy files via SSH
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
password: ${{ secrets.PASSWORD }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: "tests/a.txt,tests/b.txt"
|
||||
target: your_server_target_folder_path
|
||||
```
|
||||
|
||||
## Input variables
|
||||
---
|
||||
|
||||
see the [action.yml](./action.yml) file for more detail imformation.
|
||||
## ⚙️ Configuration
|
||||
|
||||
* host - scp remote host
|
||||
* port - scp remote port
|
||||
* username - scp username
|
||||
* password - scp password
|
||||
* timeout - timeout for ssh to remote host, default is `30s`
|
||||
* command_timeout - timeout for scp command, default is `1m`
|
||||
* key - content of ssh private key. ex raw content of ~/.ssh/id_rsa
|
||||
* key_path - path of ssh private key
|
||||
* target - target path on the server
|
||||
* source - scp file list
|
||||
* rm - remove target folder before upload data
|
||||
* strip_components - remove the specified number of leading path elements.
|
||||
* overwrite - use `--overwrite` flag with tar
|
||||
* tar_tmp_path - temporary path for tar file on the dest host
|
||||
### 🔌 Connection Settings
|
||||
|
||||
### Example
|
||||
| Variable | Description | Default | Required |
|
||||
| --------------- | -------------------------------------------- | ------- | -------- |
|
||||
| host | Remote host(s), comma-separated for multiple | - | ✓ |
|
||||
| port | SSH port | 22 | |
|
||||
| username | SSH username | - | ✓ |
|
||||
| password | SSH password (prefer SSH key for security) | - | |
|
||||
| key | SSH private key content | - | |
|
||||
| key_path | Path to SSH private key file | - | |
|
||||
| passphrase | Passphrase for SSH private key | - | |
|
||||
| fingerprint | SHA256 fingerprint for host key verification | - | |
|
||||
| protocol | IP protocol: 'tcp', 'tcp4', or 'tcp6' | tcp | |
|
||||
| timeout | SSH connection timeout | 30s | |
|
||||
| command_timeout | SCP command timeout | 10m | |
|
||||
|
||||
Copy file via ssh password
|
||||
### 📁 File Transfer Settings
|
||||
|
||||
| Variable | Description | Default | Security Note |
|
||||
| ---------------- | ------------------------------------------------------- | ------- | ---------------------- |
|
||||
| source | Local files/directories to transfer (comma-separated) | - | Use explicit paths |
|
||||
| target | Target directory on remote server (must be a directory) | - | Avoid root directories |
|
||||
| rm | Remove target directory before upload | - | Use with caution |
|
||||
| strip_components | Remove leading path elements when extracting | - | |
|
||||
| overwrite | Overwrite existing files with tar | - | |
|
||||
| tar_dereference | Follow symlinks with tar | - | |
|
||||
| tar_tmp_path | Temp path for tar file on destination | - | |
|
||||
| tar_exec | Path to tar executable on destination | tar | |
|
||||
| debug | Enable debug output | - | |
|
||||
| curl_insecure | Use --insecure with curl | false | Not recommended |
|
||||
| capture_stdout | Capture command stdout as action output | false | |
|
||||
| version | Version of drone-scp to use | - | |
|
||||
|
||||
### 🌐 Proxy Settings
|
||||
|
||||
| Variable | Description | Default | Required |
|
||||
| ------------------------- | ------------------------------------ | ------- | -------- |
|
||||
| proxy_host | SSH proxy host | - | |
|
||||
| proxy_port | SSH proxy port | 22 | |
|
||||
| proxy_username | SSH proxy username | - | |
|
||||
| proxy_password | SSH proxy password | - | |
|
||||
| proxy_key | SSH proxy private key content | - | |
|
||||
| proxy_key_path | Path to SSH proxy private key file | - | |
|
||||
| proxy_passphrase | Passphrase for SSH proxy private key | - | |
|
||||
| proxy_fingerprint | SHA256 fingerprint for proxy host | - | |
|
||||
| proxy_use_insecure_cipher | Enable less secure ciphers for proxy | - | |
|
||||
| proxy_timeout | SSH proxy connection timeout | 30s | |
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Best Practices & Security
|
||||
|
||||
- **Prefer SSH key authentication** over passwords for better security.
|
||||
- Store all sensitive values (host, username, password, key) in **GitHub Secrets**.
|
||||
- Regularly **rotate deployment keys** (suggested every 90 days).
|
||||
- Restrict write permissions on the target server directory.
|
||||
- Enable host key fingerprint verification to prevent MITM attacks.
|
||||
- Avoid using root as the SSH user.
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ Cross-Platform Notes
|
||||
|
||||
| Scenario | Linux Server | Windows Server |
|
||||
| ---------------- | -------------- | ----------------------- |
|
||||
| Path Format | `/path/to/dir` | `/c/path/to/dir` |
|
||||
| Required Setting | None | `tar_dereference: true` |
|
||||
| Permissions | Preserved | May require manual ACL |
|
||||
| Shell | bash (default) | Git Bash via OpenSSH |
|
||||
|
||||
> 🚩 **Important:**
|
||||
> When copying to Windows servers:
|
||||
>
|
||||
> - Install Git for Windows and set OpenSSH default shell to Git Bash
|
||||
> - Use Unix-style target paths (e.g., `/c/Users/...`)
|
||||
> - Enable `tar_dereference` for symlink handling
|
||||
|
||||
---
|
||||
|
||||
## 💡 Usage Examples
|
||||
|
||||
### 🧩 Scenario Guide
|
||||
|
||||
- **Basic file transfer** → [Example 1](#example-1-basic-ssh-password)
|
||||
- **Multi-server deployment** → [Example 2](#example-2-multi-server)
|
||||
- **Incremental/changed files only** → [Example 3](#example-3-changed-files-only)
|
||||
- **Artifacts integration** → [Example 4](#example-4-artifacts-integration)
|
||||
- **Windows server setup** → [Example 5](#example-5-windows-server)
|
||||
|
||||
---
|
||||
|
||||
#### Example 1: Basic SSH Password
|
||||
|
||||
```yaml
|
||||
- name: copy file via ssh password
|
||||
uses: appleboy/scp-action@master
|
||||
- name: Copy file via SSH password
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: example.com
|
||||
username: foo
|
||||
password: bar
|
||||
port: 22
|
||||
source: "tests/a.txt,tests/b.txt"
|
||||
target: "test"
|
||||
target: your_server_target_folder_path
|
||||
```
|
||||
|
||||
Copy file via ssh key
|
||||
#### Example 2: Multi-server
|
||||
|
||||
```yaml
|
||||
- name: copy file via ssh key
|
||||
uses: appleboy/scp-action@master
|
||||
env:
|
||||
HOST: ${{ secrets.HOST }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PORT: ${{ secrets.PORT }}
|
||||
KEY: ${{ secrets.KEY }}
|
||||
- name: Copy to multiple servers
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
source: "tests/a.txt,tests/b.txt"
|
||||
target: "test"
|
||||
```
|
||||
|
||||
Example configuration for ignore list:
|
||||
|
||||
```yaml
|
||||
- name: copy file via ssh key
|
||||
uses: appleboy/scp-action@master
|
||||
env:
|
||||
HOST: ${{ secrets.HOST }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
PORT: ${{ secrets.PORT }}
|
||||
KEY: ${{ secrets.KEY }}
|
||||
with:
|
||||
source: "tests/*.txt,!tests/a.txt"
|
||||
target: "test"
|
||||
```
|
||||
|
||||
Example configuration for multiple server
|
||||
|
||||
```diff
|
||||
- name: copy file via ssh password
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
- host: "example.com"
|
||||
+ host: "foo.com,bar.com"
|
||||
host: "foo.com,bar.com"
|
||||
username: foo
|
||||
password: bar
|
||||
port: 22
|
||||
source: "tests/a.txt,tests/b.txt"
|
||||
target: "test"
|
||||
target: your_server_target_folder_path
|
||||
```
|
||||
|
||||
remove the specified number of leading path elements
|
||||
#### Example 3: Changed Files Only
|
||||
|
||||
```yaml
|
||||
- name: remove the specified number of leading path elements
|
||||
uses: appleboy/scp-action@master
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v35
|
||||
with:
|
||||
since_last_remote_commit: true
|
||||
separator: ","
|
||||
|
||||
- name: Copy changed files to server
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.KEY }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: "tests/a.txt,tests/b.txt"
|
||||
target: "foobar"
|
||||
strip_components: 1
|
||||
source: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
target: your_server_target_folder_path
|
||||
```
|
||||
|
||||
old target structure:
|
||||
#### Example 4: Artifacts Integration
|
||||
|
||||
```yaml
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: my-artifact
|
||||
path: world.txt
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: my-artifact
|
||||
path: distfiles
|
||||
|
||||
- name: Copy artifact to server
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.KEY }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: distfiles/*
|
||||
target: your_server_target_folder_path
|
||||
```
|
||||
|
||||
#### Example 5: Windows Server
|
||||
|
||||
```yaml
|
||||
- name: Copy to Windows
|
||||
uses: appleboy/scp-action@v0.1.7
|
||||
with:
|
||||
host: ${{ secrets.HOST }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
port: 22
|
||||
source: "your_source_path"
|
||||
target: "/c/path/to/target/"
|
||||
tar_dereference: true
|
||||
rm: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🗝️ SSH Key Setup
|
||||
|
||||
1. **Generate SSH Key** (on your local machine):
|
||||
|
||||
```bash
|
||||
# RSA
|
||||
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
|
||||
# ED25519
|
||||
ssh-keygen -t ed25519 -a 200 -C "your_email@example.com"
|
||||
```
|
||||
|
||||
2. **Add Public Key to Server**:
|
||||
|
||||
```bash
|
||||
cat .ssh/id_rsa.pub | ssh user@host 'cat >> .ssh/authorized_keys'
|
||||
# or for ed25519
|
||||
cat .ssh/id_ed25519.pub | ssh user@host 'cat >> .ssh/authorized_keys'
|
||||
```
|
||||
|
||||
3. **Copy Private Key Content to GitHub Secrets**:
|
||||
|
||||
```bash
|
||||
clip < ~/.ssh/id_rsa
|
||||
# or
|
||||
clip < ~/.ssh/id_ed25519
|
||||
```
|
||||
|
||||
> See [SSH login without password](http://www.linuxproblem.org/art_9.html) for more details.
|
||||
|
||||
**OpenSSH Note:**
|
||||
If you see `ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey]`, ensure your key algorithm is supported.
|
||||
On Ubuntu 20.04+, add to `/etc/ssh/sshd_config` or `/etc/ssh/sshd_config.d/`:
|
||||
|
||||
```sh
|
||||
foobar
|
||||
└── tests
|
||||
├── a.txt
|
||||
└── b.txt
|
||||
CASignatureAlgorithms +ssh-rsa
|
||||
```
|
||||
|
||||
new target structure:
|
||||
Or use ed25519 keys, which are accepted by default.
|
||||
|
||||
```sh
|
||||
foobar
|
||||
├── a.txt
|
||||
└── b.txt
|
||||
---
|
||||
|
||||
## 🧰 Common Error Codes
|
||||
|
||||
| Error Code | Possible Cause | Solution |
|
||||
| -------------- | ---------------------------- | --------------------------------------------- |
|
||||
| `ECONNREFUSED` | Wrong port / firewall blocks | Check port and firewall settings |
|
||||
| `ENOENT` | Source file not found | Use absolute path or check checkout step |
|
||||
| `EAUTH` | Authentication failed | Check key format and permissions (PEM format) |
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Workflow Diagram
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant G as GitHub Runner
|
||||
participant S as Target Server
|
||||
G->>S: Establish SSH connection
|
||||
S-->>G: Authenticate credentials
|
||||
G->>S: (Optional) Remove target directory
|
||||
G->>G: Archive source files
|
||||
G->>S: Transfer archive
|
||||
S->>S: Extract and process files
|
||||
S-->>G: Return result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FAQ & Troubleshooting
|
||||
|
||||
- **Q: Why does authentication fail?**
|
||||
A: Check SSH key format, permissions, and that the key is added to the server.
|
||||
|
||||
- **Q: How do I copy only changed files?**
|
||||
A: Use `tj-actions/changed-files` to get changed files and pass to `source`.
|
||||
|
||||
- **Q: How to deploy to multiple servers?**
|
||||
A: Use comma-separated host list: `host: "foo.com,bar.com"`
|
||||
|
||||
- **Q: How to copy to Windows?**
|
||||
A: Set up Git Bash, use Unix-style paths, and enable `tar_dereference`.
|
||||
|
||||
---
|
||||
|
||||
## 📝 License
|
||||
|
||||
MIT License
|
||||
|
||||
148
action.yml
148
action.yml
@@ -1,44 +1,142 @@
|
||||
name: 'SCP Command to Transfer Files'
|
||||
description: 'How to Use SCP Command to Transfer Files/Folders in Linux'
|
||||
author: 'Bo-Yi Wu'
|
||||
name: "SCP Command to Transfer Files"
|
||||
description: "Easily transfer files and folders using the SCP command in Linux."
|
||||
author: "Bo-Yi Wu"
|
||||
inputs:
|
||||
host:
|
||||
description: 'scp remote host'
|
||||
description: "Remote host address for SCP (e.g., example.com or 192.168.1.1)."
|
||||
port:
|
||||
description: 'scp remote port'
|
||||
default: 22
|
||||
description: "Remote SSH port for SCP. Default: 22."
|
||||
default: "22"
|
||||
username:
|
||||
description: 'scp username'
|
||||
description: "Username for SSH authentication."
|
||||
password:
|
||||
description: 'scp password'
|
||||
description: "Password for SSH authentication (not recommended; use SSH keys if possible)."
|
||||
protocol:
|
||||
description: "IP protocol to use. Valid values: 'tcp', 'tcp4', or 'tcp6'. Default: tcp."
|
||||
default: "tcp"
|
||||
timeout:
|
||||
description: 'timeout for ssh to remote host'
|
||||
description: "Timeout for establishing SSH connection to the remote host. Default: 30s."
|
||||
default: "30s"
|
||||
command_timeout:
|
||||
description: 'timeout for scp command'
|
||||
default: "1m"
|
||||
description: "Timeout for the SCP command execution. Default: 10m."
|
||||
default: "10m"
|
||||
key:
|
||||
description: 'content of ssh private key. ex raw content of ~/.ssh/id_rsa'
|
||||
description: "Content of the SSH private key (e.g., the raw content of ~/.ssh/id_rsa)."
|
||||
key_path:
|
||||
description: 'path of ssh private key'
|
||||
description: "Path to the SSH private key file."
|
||||
passphrase:
|
||||
description: "Passphrase for the SSH private key, if required."
|
||||
fingerprint:
|
||||
description: "SHA256 fingerprint of the host's public key. If not set, host key verification is skipped (not recommended for production)."
|
||||
use_insecure_cipher:
|
||||
description: "Enable additional, less secure ciphers for compatibility. Not recommended unless required."
|
||||
target:
|
||||
description: 'target path on the server'
|
||||
description: "Target directory path on the remote server. Must be a directory."
|
||||
source:
|
||||
description: 'scp file list'
|
||||
description: "List of files or directories to transfer (local paths)."
|
||||
rm:
|
||||
description: 'remove target folder before upload data'
|
||||
description: "Remove the target directory on the server before uploading new data."
|
||||
debug:
|
||||
description: "Enable debug messages for troubleshooting."
|
||||
strip_components:
|
||||
description: 'remove the specified number of leading path elements'
|
||||
default: 0
|
||||
description: "Remove the specified number of leading path elements when extracting files."
|
||||
overwrite:
|
||||
description: 'use `--overwrite` flag with tar'
|
||||
default: false
|
||||
description: "Use the --overwrite flag with tar to overwrite existing files."
|
||||
tar_dereference:
|
||||
description: "Use the --dereference flag with tar to follow symlinks."
|
||||
tar_tmp_path:
|
||||
description: 'temporary path for tar file on the dest host'
|
||||
description: "Temporary path for the tar file on the destination host."
|
||||
tar_exec:
|
||||
description: "Path to the tar executable on the destination host. Default: tar."
|
||||
default: "tar"
|
||||
proxy_host:
|
||||
description: "Remote host address for SSH proxy."
|
||||
proxy_port:
|
||||
description: "SSH proxy port. Default: 22."
|
||||
default: "22"
|
||||
proxy_username:
|
||||
description: "Username for SSH proxy authentication."
|
||||
proxy_password:
|
||||
description: "Password for SSH proxy authentication."
|
||||
proxy_passphrase:
|
||||
description: "Passphrase for the SSH proxy private key, if required."
|
||||
proxy_timeout:
|
||||
description: "Timeout for establishing SSH connection to the proxy host. Default: 30s."
|
||||
default: "30s"
|
||||
proxy_key:
|
||||
description: "Content of the SSH proxy private key (e.g., the raw content of ~/.ssh/id_rsa)."
|
||||
proxy_key_path:
|
||||
description: "Path to the SSH proxy private key file."
|
||||
proxy_fingerprint:
|
||||
description: "SHA256 fingerprint of the proxy host's public key. If not set, host key verification is skipped (not recommended for production)."
|
||||
proxy_use_insecure_cipher:
|
||||
description: "Enable additional, less secure ciphers for the proxy connection. Not recommended unless required."
|
||||
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-scp to use.
|
||||
|
||||
outputs:
|
||||
stdout:
|
||||
description: "Standard output of the executed commands when capture_stdout is enabled."
|
||||
value: ${{ steps.entrypoint.outputs.stdout }}
|
||||
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'Dockerfile'
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Set GitHub Path
|
||||
run: echo "$GITHUB_ACTION_PATH" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_ACTION_PATH: ${{ github.action_path }}
|
||||
- id: entrypoint
|
||||
name: Run entrypoint.sh
|
||||
run: entrypoint.sh
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_ACTION_PATH: ${{ github.action_path }}
|
||||
INPUT_HOST: ${{ inputs.host }}
|
||||
INPUT_PORT: ${{ inputs.port }}
|
||||
INPUT_PROTOCOL: ${{ inputs.protocol }}
|
||||
INPUT_USERNAME: ${{ inputs.username }}
|
||||
INPUT_PASSWORD: ${{ inputs.password }}
|
||||
INPUT_PASSPHRASE: ${{ inputs.passphrase }}
|
||||
INPUT_KEY: ${{ inputs.key }}
|
||||
INPUT_KEY_PATH: ${{ inputs.key_path }}
|
||||
INPUT_FINGERPRINT: ${{ inputs.fingerprint }}
|
||||
INPUT_PROXY_HOST: ${{ inputs.proxy_host }}
|
||||
INPUT_PROXY_PORT: ${{ inputs.proxy_port }}
|
||||
INPUT_PROXY_USERNAME: ${{ inputs.proxy_username }}
|
||||
INPUT_PROXY_PASSWORD: ${{ inputs.proxy_password }}
|
||||
INPUT_PROXY_PASSPHRASE: ${{ inputs.proxy_passphrase }}
|
||||
INPUT_PROXY_KEY: ${{ inputs.proxy_key }}
|
||||
INPUT_PROXY_KEY_PATH: ${{ inputs.proxy_key_path }}
|
||||
INPUT_PROXY_FINGERPRINT: ${{ inputs.proxy_fingerprint }}
|
||||
INPUT_USE_INSECURE_CIPHER: ${{ inputs.use_insecure_cipher }}
|
||||
INPUT_CIPHER: ${{ inputs.cipher }}
|
||||
INPUT_PROXY_USE_INSECURE_CIPHER: ${{ inputs.proxy_use_insecure_cipher }}
|
||||
INPUT_PROXY_CIPHER: ${{ inputs.proxy_cipher }}
|
||||
INPUT_DEBUG: ${{ inputs.debug }}
|
||||
INPUT_TIMEOUT: ${{ inputs.timeout }}
|
||||
INPUT_COMMAND_TIMEOUT: ${{ inputs.command_timeout }}
|
||||
INPUT_TARGET: ${{ inputs.target }}
|
||||
INPUT_SOURCE: ${{ inputs.source }}
|
||||
INPUT_RM: ${{ inputs.rm }}
|
||||
INPUT_STRIP_COMPONENTS: ${{ inputs.strip_components }}
|
||||
INPUT_OVERWRITE: ${{ inputs.overwrite }}
|
||||
INPUT_TAR_DEREFERENCE: ${{ inputs.tar_dereference }}
|
||||
INPUT_TAR_TMP_PATH: ${{ inputs.tar_tmp_path }}
|
||||
INPUT_TAR_EXEC: ${{ inputs.tar_exec }}
|
||||
INPUT_PROXY_TIMEOUT: ${{ inputs.proxy_timeout }}
|
||||
INPUT_CAPTURE_STDOUT: ${{ inputs.capture_stdout }}
|
||||
INPUT_CURL_INSECURE: ${{ inputs.curl_insecure }}
|
||||
DRONE_SCP_VERSION: ${{ inputs.version }}
|
||||
|
||||
branding:
|
||||
icon: 'copy'
|
||||
color: 'gray-dark'
|
||||
icon: "copy"
|
||||
color: "gray-dark"
|
||||
|
||||
@@ -1,9 +1,56 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
set -euo pipefail
|
||||
|
||||
export GITHUB="true"
|
||||
|
||||
[ -n "$INPUT_STRIP_COMPONENTS" ] && export INPUT_STRIP_COMPONENTS=$((INPUT_STRIP_COMPONENTS + 0))
|
||||
GITHUB_ACTION_PATH="${GITHUB_ACTION_PATH%/}"
|
||||
DRONE_SCP_RELEASE_URL="${DRONE_SCP_RELEASE_URL:-https://github.com/appleboy/drone-scp/releases/download}"
|
||||
DRONE_SCP_VERSION="${DRONE_SCP_VERSION:-1.7.0}"
|
||||
|
||||
sh -c "/bin/drone-scp $*"
|
||||
function log_error() {
|
||||
echo "$1" >&2
|
||||
exit "$2"
|
||||
}
|
||||
|
||||
function detect_client_info() {
|
||||
CLIENT_PLATFORM="${SCP_CLIENT_OS:-$(uname -s | tr '[:upper:]' '[:lower:]')}"
|
||||
CLIENT_ARCH="${SCP_CLIENT_ARCH:-$(uname -m)}"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
detect_client_info
|
||||
DOWNLOAD_URL_PREFIX="${DRONE_SCP_RELEASE_URL}/v${DRONE_SCP_VERSION}"
|
||||
CLIENT_BINARY="drone-scp-${DRONE_SCP_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
|
||||
|
||||
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}"
|
||||
else
|
||||
"${TARGET}" "$@"
|
||||
fi
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
foo
|
||||
foobar
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
bar
|
||||
foobar
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
c
|
||||
foobar
|
||||
test1234
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
d
|
||||
foobar
|
||||
foobar
|
||||
|
||||
Reference in New Issue
Block a user