commit 2375e4541b3650cdd15cc748e0ff007b041bbbed Author: Kai Date: Mon Sep 21 00:20:31 2026 +0800 first commit diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..df9ae63 --- /dev/null +++ b/.github/FUNDING.yml @@ -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'] diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..632e8eb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml new file mode 100644 index 0000000..afc9600 --- /dev/null +++ b/.github/workflows/goreleaser.yml @@ -0,0 +1,33 @@ +name: Goreleaser + +on: + push: + tags: + - "*" + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Setup go + uses: actions/setup-go@v7 + with: + go-version: "^1" + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v7 + with: + # either 'goreleaser' (default) or 'goreleaser-pro' + distribution: goreleaser + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/stable.yml b/.github/workflows/stable.yml new file mode 100644 index 0000000..c4665a5 --- /dev/null +++ b/.github/workflows/stable.yml @@ -0,0 +1,150 @@ +name: v1 version +on: [push] +jobs: + testing: + name: test scp action + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v7 + + - name: copy file via ssh password + uses: appleboy/scp-action@v1 + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + password: ${{ secrets.PASSWORD }} + port: ${{ secrets.PORT }} + source: "tests/a.txt,tests/b.txt" + target: "test" + + - name: copy file via ssh key + uses: appleboy/scp-action@v1 + 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: appleboy/scp-action@v1 + 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: appleboy/scp-action@v1 + 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: appleboy/scp-action@v1 + 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@v7 + + - run: echo hello > world.txt + + - uses: actions/upload-artifact@v7 + with: + name: my-artifact + path: world.txt + + - uses: actions/download-artifact@v8 + with: + name: my-artifact + path: distfiles + + - name: copy file to server + uses: appleboy/scp-action@v1 + 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@v7 + + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v47 + with: + since_last_remote_commit: true + separator: "," + + - name: copy file to server + uses: appleboy/scp-action@v1 + 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@v7 + + - name: copy file to server + uses: appleboy/scp-action@v1 + 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@v7 + + - name: copy file to server + uses: appleboy/scp-action@v1 + 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 diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 0000000..be05e5a --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,150 @@ +name: lint and test +on: [push] +jobs: + testing: + name: test scp action + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@v7 + + - 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: 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@v7 + + - run: echo hello > world.txt + + - uses: actions/upload-artifact@v7 + with: + name: my-artifact + path: world.txt + + - uses: actions/download-artifact@v8 + 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@v7 + + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v47 + 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@v7 + + - 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@v7 + + - 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 diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml new file mode 100644 index 0000000..842679c --- /dev/null +++ b/.github/workflows/trivy.yml @@ -0,0 +1,50 @@ +name: Trivy Security Scan + +on: + schedule: + # Run at 00:00 on day 1 of every 3rd month (January, April, July, October) + - cron: "0 0 1 */3 *" + workflow_dispatch: # Allow manual trigger + push: + branches: + - master + pull_request: + branches: + - master + +permissions: + contents: read + security-events: write # For uploading results to GitHub Security tab + +jobs: + trivy-scan: + name: Trivy Security Scan + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v7 + + - name: Run Trivy vulnerability scanner in repo mode + uses: aquasecurity/trivy-action@v0.36.0 + with: + scan-type: "fs" + scan-ref: "." + format: "sarif" + output: "trivy-results.sarif" + severity: "CRITICAL,HIGH" + + - name: Upload Trivy results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v4 + if: always() + with: + sarif_file: "trivy-results.sarif" + + - name: Run Trivy vulnerability scanner (table output) + uses: aquasecurity/trivy-action@v0.36.0 + with: + scan-type: "fs" + scan-ref: "." + format: "table" + severity: "CRITICAL,HIGH" + exit-code: "1" diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..01b1081 --- /dev/null +++ b/.goreleaser.yaml @@ -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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dc6b84f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Bo-Yi Wu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..eb7c683 --- /dev/null +++ b/README.md @@ -0,0 +1,348 @@ +# 🚀 SCP for GitHub Actions + +[繁體中文](README.zh-tw.md) | [简体中文](README.zh-cn.md) + +[GitHub Action](https://github.com/features/actions) for copying files and artifacts via SSH. + +[![v1 version](https://github.com/appleboy/scp-action/actions/workflows/stable.yml/badge.svg)](https://github.com/appleboy/scp-action/actions/workflows/stable.yml) +[![lint and test](https://github.com/appleboy/scp-action/actions/workflows/testing.yml/badge.svg)](https://github.com/appleboy/scp-action/actions/workflows/testing.yml) + +> **Note:** Only supports **Linux** [docker](https://www.docker.com/) containers. + +--- + +## ✨ 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: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Copy files via SSH + uses: appleboy/scp-action@v1 + 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 +``` + +--- + +## ⚙️ Configuration + +### 🔌 Connection Settings + +| 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 | | + +### 📁 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@v1 + with: + host: example.com + username: foo + password: bar + port: 22 + source: "tests/a.txt,tests/b.txt" + target: your_server_target_folder_path +``` + +#### Example 2: Multi-server + +```yaml +- name: Copy to multiple servers + uses: appleboy/scp-action@v1 + with: + host: "foo.com,bar.com" + username: foo + password: bar + port: 22 + source: "tests/a.txt,tests/b.txt" + target: your_server_target_folder_path +``` + +#### Example 3: Changed Files Only + +```yaml +- 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@v1 + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + key: ${{ secrets.KEY }} + port: ${{ secrets.PORT }} + source: ${{ steps.changed-files.outputs.all_changed_files }} + target: your_server_target_folder_path +``` + +#### 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@v1 + 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@v1 + 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 +CASignatureAlgorithms +ssh-rsa +``` + +Or use ed25519 keys, which are accepted by default. + +--- + +## 🧰 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 diff --git a/README.zh-cn.md b/README.zh-cn.md new file mode 100644 index 0000000..4fe61eb --- /dev/null +++ b/README.zh-cn.md @@ -0,0 +1,348 @@ +# 🚀 GitHub Actions 的 SCP + +[English](README.md) | [繁體中文](README.zh-tw.md) + +[GitHub Action](https://github.com/features/actions) 用于通过 SSH 复制文件和构建产物。 + +[![v1 version](https://github.com/appleboy/scp-action/actions/workflows/stable.yml/badge.svg)](https://github.com/appleboy/scp-action/actions/workflows/stable.yml) +[![lint and test](https://github.com/appleboy/scp-action/actions/workflows/testing.yml/badge.svg)](https://github.com/appleboy/scp-action/actions/workflows/testing.yml) + +> **注意:** 仅支持 **Linux** [docker](https://www.docker.com/) 容器。 + +--- + +## ✨ 功能特性 + +- ✅ 通过 SSH 将文件和产物复制到一台或多台远程服务器 +- ✅ 支持 SSH 密钥和密码认证 +- ✅ 完全支持 SSH 代理(跳板机) +- ✅ 处理 Linux ↔ Windows 路径转换 +- ✅ 集成 GitHub Artifacts 工作流 +- ✅ 支持增量与差异文件传输 +- ✅ 丰富的高级配置选项 + +--- + +## 📦 目录 + +- [🚀 GitHub Actions 的 SCP](#-github-actions-的-scp) + - [✨ 功能特性](#-功能特性) + - [📦 目录](#-目录) + - [🚀 快速开始](#-快速开始) + - [⚙️ 配置说明](#️-配置说明) + - [🔌 连接设置](#-连接设置) + - [📁 文件传输设置](#-文件传输设置) + - [🌐 代理设置](#-代理设置) + - [🛡️ 最佳实践与安全性](#️-最佳实践与安全性) + - [🖥️ 跨平台注意事项](#️-跨平台注意事项) + - [💡 使用示例](#-使用示例) + - [🧩 场景导览](#-场景导览) + - [示例 1:基本 SSH 密码](#示例-1基本-ssh-密码) + - [示例 2:多台服务器](#示例-2多台服务器) + - [示例 3:仅传输变更文件](#示例-3仅传输变更文件) + - [示例 4:集成 Artifacts](#示例-4集成-artifacts) + - [示例 5:Windows 服务器](#示例-5windows-服务器) + - [🗝️ SSH 密钥设置](#️-ssh-密钥设置) + - [🧰 常见错误代码](#-常见错误代码) + - [🔄 工作流程图](#-工作流程图) + - [FAQ 与故障排查](#faq-与故障排查) + - [📝 许可证](#-许可证) + +--- + +## 🚀 快速开始 + +在 GitHub Actions 工作流中通过 SSH 复制文件和产物: + +```yaml +name: scp files +on: [push] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: 通过 SSH 复制文件 + uses: appleboy/scp-action@v1 + 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 +``` + +--- + +## ⚙️ 配置说明 + +### 🔌 连接设置 + +| 变量 | 说明 | 默认值 | 必填 | +| --------------- | -------------------------------- | ------ | ---- | +| host | 远程主机(多台用逗号分隔) | - | ✓ | +| port | SSH 端口 | 22 | | +| username | SSH 用户名 | - | ✓ | +| password | SSH 密码(建议优先使用密钥认证) | - | | +| key | SSH 私钥内容 | - | | +| key_path | SSH 私钥文件路径 | - | | +| passphrase | SSH 私钥密码 | - | | +| fingerprint | 主机密钥 SHA256 指纹验证 | - | | +| protocol | IP 协议:'tcp'、'tcp4' 或 'tcp6' | tcp | | +| timeout | SSH 连接超时 | 30s | | +| command_timeout | SCP 命令超时 | 10m | | + +### 📁 文件传输设置 + +| 变量 | 说明 | 默认值 | 安全性说明 | +| ---------------- | --------------------------------- | ------ | -------------- | +| source | 本地要传输的文件/目录(逗号分隔) | - | 请使用明确路径 | +| target | 远程目标目录(必须为目录) | - | 避免使用根目录 | +| rm | 上传前移除目标目录 | - | 谨慎使用 | +| strip_components | 传输时移除前置路径元素 | - | | +| overwrite | 使用 tar 覆盖现有文件 | - | | +| tar_dereference | tar 传输时跟随符号链接 | - | | +| tar_tmp_path | 目标端 tar 临时文件路径 | - | | +| tar_exec | 目标端 tar 执行文件路径 | tar | | +| debug | 启用调试输出 | - | | +| curl_insecure | curl 使用 --insecure | false | 不推荐 | +| capture_stdout | 将命令 stdout 作为 action 输出 | false | | +| version | 指定 drone-scp 版本 | - | | + +### 🌐 代理设置 + +| 变量 | 说明 | 默认值 | 必填 | +| ------------------------- | -------------------------- | ------ | ---- | +| proxy_host | SSH 代理主机 | - | | +| proxy_port | SSH 代理端口 | 22 | | +| proxy_username | SSH 代理用户名 | - | | +| proxy_password | SSH 代理密码 | - | | +| proxy_key | SSH 代理私钥内容 | - | | +| proxy_key_path | SSH 代理私钥文件路径 | - | | +| proxy_passphrase | SSH 代理私钥密码 | - | | +| proxy_fingerprint | 代理主机 SHA256 指纹验证 | - | | +| proxy_use_insecure_cipher | 启用较不安全的代理加密算法 | - | | +| proxy_timeout | SSH 代理连接超时 | 30s | | + +--- + +## 🛡️ 最佳实践与安全性 + +- **建议优先使用 SSH 密钥认证**,提升安全性。 +- 将所有敏感信息(host、username、password、key)存放于 **GitHub Secrets**。 +- 定期**更换部署密钥**(建议每 90 天一次)。 +- 限制目标服务器目录的写入权限。 +- 启用主机密钥指纹验证以防止中间人攻击。 +- 避免使用 root 用户登录 SSH。 + +--- + +## 🖥️ 跨平台注意事项 + +| 场景 | Linux 服务器 | Windows 服务器 | +| -------- | -------------- | ----------------------- | +| 路径格式 | `/path/to/dir` | `/c/path/to/dir` | +| 必要设置 | 无 | `tar_dereference: true` | +| 权限 | 保留 | 可能需手动设置 ACL | +| Shell | bash(默认) | Git Bash(OpenSSH) | + +> 🚩 **重要提醒:** +> 复制到 Windows 服务器时: +> +> - 安装 Git for Windows 并将 OpenSSH 默认 shell 设为 Git Bash +> - 使用类 Unix 目标路径(如 `/c/Users/...`) +> - 启用 `tar_dereference` 处理符号链接 + +--- + +## 💡 使用示例 + +### 🧩 场景导览 + +- **基本文件传输** → [示例 1](#示例-1基本-ssh-密码) +- **多台服务器部署** → [示例 2](#示例-2多台服务器) +- **仅传输变更文件** → [示例 3](#示例-3仅传输变更文件) +- **集成 Artifacts** → [示例 4](#示例-4集成-artifacts) +- **Windows 服务器设置** → [示例 5](#示例-5windows-服务器) + +--- + +#### 示例 1:基本 SSH 密码 + +```yaml +- name: 通过 SSH 密码复制文件 + uses: appleboy/scp-action@v1 + with: + host: example.com + username: foo + password: bar + port: 22 + source: "tests/a.txt,tests/b.txt" + target: your_server_target_folder_path +``` + +#### 示例 2:多台服务器 + +```yaml +- name: 复制到多台服务器 + uses: appleboy/scp-action@v1 + with: + host: "foo.com,bar.com" + username: foo + password: bar + port: 22 + source: "tests/a.txt,tests/b.txt" + target: your_server_target_folder_path +``` + +#### 示例 3:仅传输变更文件 + +```yaml +- name: 获取变更文件 + id: changed-files + uses: tj-actions/changed-files@v35 + with: + since_last_remote_commit: true + separator: "," + +- name: 复制变更文件到服务器 + uses: appleboy/scp-action@v1 + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + key: ${{ secrets.KEY }} + port: ${{ secrets.PORT }} + source: ${{ steps.changed-files.outputs.all_changed_files }} + target: your_server_target_folder_path +``` + +#### 示例 4:集成 Artifacts + +```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: 复制 artifact 到服务器 + uses: appleboy/scp-action@v1 + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + key: ${{ secrets.KEY }} + port: ${{ secrets.PORT }} + source: distfiles/* + target: your_server_target_folder_path +``` + +#### 示例 5:Windows 服务器 + +```yaml +- name: 复制到 Windows + uses: appleboy/scp-action@v1 + 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 密钥设置 + +1. **生成 SSH 密钥**(在本地执行): + + ```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. **将公钥添加到服务器**: + + ```bash + 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' + ``` + +3. **将私钥内容复制到 GitHub Secrets**: + + ```bash + clip < ~/.ssh/id_rsa + # 或 + clip < ~/.ssh/id_ed25519 + ``` + +> 更多细节请参考 [SSH 免密登录](http://www.linuxproblem.org/art_9.html)。 + +**OpenSSH 注意事项:** +如遇到 `ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey]`,请确认密钥算法已被支持。 +Ubuntu 20.04+ 可在 `/etc/ssh/sshd_config` 或 `/etc/ssh/sshd_config.d/` 添加: + +```sh +CASignatureAlgorithms +ssh-rsa +``` + +或使用 ed25519 密钥(默认支持)。 + +--- + +## 🧰 常见错误代码 + +| 错误代码 | 可能原因 | 解决方法 | +| -------------- | ------------------- | --------------------------------- | +| `ECONNREFUSED` | 端口错误/防火墙阻挡 | 检查端口和防火墙设置 | +| `ENOENT` | 找不到源文件 | 使用绝对路径或检查 checkout 步骤 | +| `EAUTH` | 认证失败 | 检查密钥格式和权限(需 PEM 格式) | + +--- + +## 🔄 工作流程图 + +```mermaid +sequenceDiagram + participant G as GitHub Runner + participant S as Target Server + G->>S: 建立 SSH 连接 + S-->>G: 验证凭证 + G->>S: (可选)移除目标目录 + G->>G: 打包源文件 + G->>S: 传输打包文件 + S->>S: 解压和处理文件 + S-->>G: 返回结果 +``` + +--- + +## FAQ 与故障排查 + +- **Q: 为什么认证失败?** + A: 请检查 SSH 密钥格式、权限,以及密钥是否已添加到服务器。 + +- **Q: 如何只复制变更文件?** + A: 使用 `tj-actions/changed-files` 获取变更文件并传递给 `source`。 + +- **Q: 如何部署到多台服务器?** + A: `host` 参数用逗号分隔多台主机,例如:`host: "foo.com,bar.com"` + +- **Q: 如何复制到 Windows?** + A: 设置 Git Bash,使用类 Unix 路径,并启用 `tar_dereference`。 + +--- + +## 📝 许可证 + +MIT License diff --git a/README.zh-tw.md b/README.zh-tw.md new file mode 100644 index 0000000..9b39b42 --- /dev/null +++ b/README.zh-tw.md @@ -0,0 +1,348 @@ +# 🚀 GitHub Actions 的 SCP + +[English](README.md) | [简体中文](README.zh-cn.md) + +[GitHub Action](https://github.com/features/actions) 用於透過 SSH 複製檔案與產物。 + +[![v1 version](https://github.com/appleboy/scp-action/actions/workflows/stable.yml/badge.svg)](https://github.com/appleboy/scp-action/actions/workflows/stable.yml) +[![lint and test](https://github.com/appleboy/scp-action/actions/workflows/testing.yml/badge.svg)](https://github.com/appleboy/scp-action/actions/workflows/testing.yml) + +> **注意:** 只支援 **Linux** [docker](https://www.docker.com/) 容器。 + +--- + +## ✨ 功能特色 + +- ✅ 透過 SSH 將檔案與產物複製到一台或多台遠端伺服器 +- ✅ 支援 SSH 金鑰與密碼驗證 +- ✅ 完整支援 SSH Proxy(跳板機) +- ✅ 處理 Linux ↔ Windows 路徑轉換 +- ✅ 整合 GitHub Artifacts 工作流程 +- ✅ 支援增量與差異檔案傳輸 +- ✅ 豐富的進階設定選項 + +--- + +## 📦 目錄 + +- [🚀 GitHub Actions 的 SCP](#-github-actions-的-scp) + - [✨ 功能特色](#-功能特色) + - [📦 目錄](#-目錄) + - [🚀 快速開始](#-快速開始) + - [⚙️ 設定說明](#️-設定說明) + - [🔌 連線設定](#-連線設定) + - [📁 檔案傳輸設定](#-檔案傳輸設定) + - [🌐 Proxy 設定](#-proxy-設定) + - [🛡️ 最佳實踐與安全性](#️-最佳實踐與安全性) + - [🖥️ 跨平台注意事項](#️-跨平台注意事項) + - [💡 使用範例](#-使用範例) + - [🧩 情境導覽](#-情境導覽) + - [範例 1:基本 SSH 密碼](#範例-1基本-ssh-密碼) + - [範例 2:多台伺服器](#範例-2多台伺服器) + - [範例 3:僅傳送變更檔案](#範例-3僅傳送變更檔案) + - [範例 4:整合 Artifacts](#範例-4整合-artifacts) + - [範例 5:Windows 伺服器](#範例-5windows-伺服器) + - [🗝️ SSH 金鑰設定](#️-ssh-金鑰設定) + - [🧰 常見錯誤代碼](#-常見錯誤代碼) + - [🔄 工作流程圖](#-工作流程圖) + - [FAQ 與疑難排解](#faq-與疑難排解) + - [📝 授權條款](#-授權條款) + +--- + +## 🚀 快速開始 + +在 GitHub Actions 工作流程中透過 SSH 複製檔案與產物: + +```yaml +name: scp files +on: [push] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: 透過 SSH 複製檔案 + uses: appleboy/scp-action@v1 + 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 +``` + +--- + +## ⚙️ 設定說明 + +### 🔌 連線設定 + +| 變數 | 說明 | 預設值 | 必填 | +| --------------- | ------------------------------------ | ------ | ---- | +| host | 遠端主機(多台以逗號分隔) | - | ✓ | +| port | SSH 連接埠 | 22 | | +| username | SSH 使用者名稱 | - | ✓ | +| password | SSH 密碼(建議使用金鑰以提升安全性) | - | | +| key | SSH 私鑰內容 | - | | +| key_path | SSH 私鑰檔案路徑 | - | | +| passphrase | SSH 私鑰密碼 | - | | +| fingerprint | 主機金鑰 SHA256 指紋驗證 | - | | +| protocol | IP 協定:'tcp'、'tcp4' 或 'tcp6' | tcp | | +| timeout | SSH 連線逾時 | 30s | | +| command_timeout | SCP 指令逾時 | 10m | | + +### 📁 檔案傳輸設定 + +| 變數 | 說明 | 預設值 | 安全性說明 | +| ---------------- | --------------------------------- | ------ | -------------- | +| source | 本地要傳送的檔案/目錄(逗號分隔) | - | 請使用明確路徑 | +| target | 遠端目標目錄(必須為目錄) | - | 避免使用根目錄 | +| rm | 上傳前移除目標目錄 | - | 請小心使用 | +| strip_components | 傳送時移除前置路徑元素 | - | | +| overwrite | 使用 tar 覆蓋現有檔案 | - | | +| tar_dereference | tar 傳送時跟隨符號連結 | - | | +| tar_tmp_path | 目標端 tar 暫存檔路徑 | - | | +| tar_exec | 目標端 tar 執行檔路徑 | tar | | +| debug | 啟用除錯輸出 | - | | +| curl_insecure | curl 使用 --insecure | false | 不建議 | +| capture_stdout | 將指令 stdout 作為 action 輸出 | false | | +| version | 指定 drone-scp 版本 | - | | + +### 🌐 Proxy 設定 + +| 變數 | 說明 | 預設值 | 必填 | +| ------------------------- | ------------------------------- | ------ | ---- | +| proxy_host | SSH Proxy 主機 | - | | +| proxy_port | SSH Proxy 連接埠 | 22 | | +| proxy_username | SSH Proxy 使用者名稱 | - | | +| proxy_password | SSH Proxy 密碼 | - | | +| proxy_key | SSH Proxy 私鑰內容 | - | | +| proxy_key_path | SSH Proxy 私鑰檔案路徑 | - | | +| proxy_passphrase | SSH Proxy 私鑰密碼 | - | | +| proxy_fingerprint | Proxy 主機 SHA256 指紋驗證 | - | | +| proxy_use_insecure_cipher | 啟用較不安全的 Proxy 加密演算法 | - | | +| proxy_timeout | SSH Proxy 連線逾時 | 30s | | + +--- + +## 🛡️ 最佳實踐與安全性 + +- **建議優先使用 SSH 金鑰驗證**,提升安全性。 +- 將所有敏感資訊(host、username、password、key)存放於 **GitHub Secrets**。 +- 定期**更換部署金鑰**(建議每 90 天一次)。 +- 限制目標伺服器目錄的寫入權限。 +- 啟用主機金鑰指紋驗證以防止中間人攻擊。 +- 避免使用 root 帳號登入 SSH。 + +--- + +## 🖥️ 跨平台注意事項 + +| 情境 | Linux 伺服器 | Windows 伺服器 | +| -------- | -------------- | ----------------------- | +| 路徑格式 | `/path/to/dir` | `/c/path/to/dir` | +| 必要設定 | 無 | `tar_dereference: true` | +| 權限 | 保留 | 可能需手動設定 ACL | +| Shell | bash (預設) | Git Bash(OpenSSH) | + +> 🚩 **重要提醒:** +> 複製到 Windows 伺服器時: +> +> - 安裝 Git for Windows 並將 OpenSSH 預設 shell 設為 Git Bash +> - 使用 Unix 風格目標路徑(如 `/c/Users/...`) +> - 啟用 `tar_dereference` 處理符號連結 + +--- + +## 💡 使用範例 + +### 🧩 情境導覽 + +- **基本檔案傳輸** → [範例 1](#範例-1基本-ssh-密碼) +- **多台伺服器部署** → [範例 2](#範例-2多台伺服器) +- **僅傳送變更檔案** → [範例 3](#範例-3僅傳送變更檔案) +- **整合 Artifacts** → [範例 4](#範例-4整合-artifacts) +- **Windows 伺服器設定** → [範例 5](#範例-5windows-伺服器) + +--- + +#### 範例 1:基本 SSH 密碼 + +```yaml +- name: 透過 SSH 密碼複製檔案 + uses: appleboy/scp-action@v1 + with: + host: example.com + username: foo + password: bar + port: 22 + source: "tests/a.txt,tests/b.txt" + target: your_server_target_folder_path +``` + +#### 範例 2:多台伺服器 + +```yaml +- name: 複製到多台伺服器 + uses: appleboy/scp-action@v1 + with: + host: "foo.com,bar.com" + username: foo + password: bar + port: 22 + source: "tests/a.txt,tests/b.txt" + target: your_server_target_folder_path +``` + +#### 範例 3:僅傳送變更檔案 + +```yaml +- name: 取得變更檔案 + id: changed-files + uses: tj-actions/changed-files@v35 + with: + since_last_remote_commit: true + separator: "," + +- name: 複製變更檔案到伺服器 + uses: appleboy/scp-action@v1 + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + key: ${{ secrets.KEY }} + port: ${{ secrets.PORT }} + source: ${{ steps.changed-files.outputs.all_changed_files }} + target: your_server_target_folder_path +``` + +#### 範例 4:整合 Artifacts + +```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: 複製 artifact 到伺服器 + uses: appleboy/scp-action@v1 + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + key: ${{ secrets.KEY }} + port: ${{ secrets.PORT }} + source: distfiles/* + target: your_server_target_folder_path +``` + +#### 範例 5:Windows 伺服器 + +```yaml +- name: 複製到 Windows + uses: appleboy/scp-action@v1 + 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 金鑰設定 + +1. **產生 SSH 金鑰**(於本地端執行): + + ```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. **將公鑰加入伺服器**: + + ```bash + 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' + ``` + +3. **將私鑰內容複製到 GitHub Secrets**: + + ```bash + clip < ~/.ssh/id_rsa + # 或 + clip < ~/.ssh/id_ed25519 + ``` + +> 更多細節請參考 [SSH 無密碼登入](http://www.linuxproblem.org/art_9.html)。 + +**OpenSSH 注意事項:** +若出現 `ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey]`,請確認金鑰演算法支援。 +Ubuntu 20.04+ 可於 `/etc/ssh/sshd_config` 或 `/etc/ssh/sshd_config.d/` 加入: + +```sh +CASignatureAlgorithms +ssh-rsa +``` + +或改用 ed25519 金鑰(預設支援)。 + +--- + +## 🧰 常見錯誤代碼 + +| 錯誤代碼 | 可能原因 | 解決方式 | +| -------------- | --------------------- | --------------------------------- | +| `ECONNREFUSED` | 連接埠錯誤/防火牆阻擋 | 檢查連接埠與防火牆設定 | +| `ENOENT` | 找不到來源檔案 | 請用絕對路徑或檢查 checkout 步驟 | +| `EAUTH` | 驗證失敗 | 檢查金鑰格式與權限(需 PEM 格式) | + +--- + +## 🔄 工作流程圖 + +```mermaid +sequenceDiagram + participant G as GitHub Runner + participant S as Target Server + G->>S: 建立 SSH 連線 + S-->>G: 驗證憑證 + G->>S: (可選)移除目標目錄 + G->>G: 封存來源檔案 + G->>S: 傳送封存檔 + S->>S: 解壓與處理檔案 + S-->>G: 回傳結果 +``` + +--- + +## FAQ 與疑難排解 + +- **Q: 為什麼驗證失敗?** + A: 請檢查 SSH 金鑰格式、權限,以及金鑰是否已加入伺服器。 + +- **Q: 如何只複製變更檔案?** + A: 使用 `tj-actions/changed-files` 取得變更檔案並傳給 `source`。 + +- **Q: 如何部署到多台伺服器?** + A: `host` 參數用逗號分隔多台主機,例如:`host: "foo.com,bar.com"` + +- **Q: 如何複製到 Windows?** + A: 設定 Git Bash,使用 Unix 風格路徑,並啟用 `tar_dereference`。 + +--- + +## 📝 授權條款 + +MIT License diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..e523cb6 --- /dev/null +++ b/action.yml @@ -0,0 +1,142 @@ +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: "Remote host address for SCP (e.g., example.com or 192.168.1.1)." + port: + description: "Remote SSH port for SCP. Default: 22." + default: "22" + username: + description: "Username for SSH authentication." + 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 establishing SSH connection to the remote host. Default: 30s." + default: "30s" + command_timeout: + description: "Timeout for the SCP command execution. Default: 10m." + default: "10m" + key: + description: "Content of the SSH private key (e.g., the raw content of ~/.ssh/id_rsa)." + key_path: + 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 directory path on the remote server. Must be a directory." + source: + description: "List of files or directories to transfer (local paths)." + rm: + 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 when extracting files." + overwrite: + 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 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: "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" diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..f9449e8 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +set -euo pipefail + +export GITHUB="true" + +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.8.0}" + +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 + +if [[ ! -x "${TARGET}" ]]; then + curl -fsSL --retry 5 --keepalive-time 2 ${INSECURE_OPTION} "${DOWNLOAD_URL_PREFIX}/${CLIENT_BINARY}" -o "${TARGET}" + chmod +x "${TARGET}" +else + echo "Binary ${CLIENT_BINARY} already exists and is executable, skipping download." +fi + +echo "======= CLI Version Information =======" +"${TARGET}" --version +echo "=======================================" +if [[ "${INPUT_CAPTURE_STDOUT}" == 'true' ]]; then + { + echo 'stdout<>"${GITHUB_OUTPUT}" +else + "${TARGET}" "$@" +fi diff --git a/images/copy-multiple-file.png b/images/copy-multiple-file.png new file mode 100644 index 0000000..c062db0 Binary files /dev/null and b/images/copy-multiple-file.png differ diff --git a/tests/a.txt b/tests/a.txt new file mode 100644 index 0000000..4f6c252 --- /dev/null +++ b/tests/a.txt @@ -0,0 +1,2 @@ +foo +foobar diff --git a/tests/b.txt b/tests/b.txt new file mode 100644 index 0000000..c494264 --- /dev/null +++ b/tests/b.txt @@ -0,0 +1,2 @@ +bar +foobar diff --git a/tests/c.txt b/tests/c.txt new file mode 100644 index 0000000..06cab46 --- /dev/null +++ b/tests/c.txt @@ -0,0 +1,3 @@ +c +foobar +test1234 diff --git a/tests/d.txt b/tests/d.txt new file mode 100644 index 0000000..13a80d9 --- /dev/null +++ b/tests/d.txt @@ -0,0 +1,3 @@ +d +foobar +foobar