Compare commits

...

13 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] a157cbef43 Add troubleshooting section for missing rsync on remote host
Co-authored-by: Burnett01 <1208707+Burnett01@users.noreply.github.com>
2025-09-23 19:03:15 +00:00
copilot-swe-agent[bot] 9327d1cb7f Initial plan 2025-09-23 18:57:38 +00:00
Copilot f1607ccbba Add comprehensive SSH troubleshooting section with documentation links and improved secret naming to README (#83)
* chore: Add comprehensive SSH troubleshooting section to README

* chore: Enhance README with rsync documentation link, SSH key guide

* chore: further enrich readme with more troubleshooting + change secrets name from DEPLOY_* to REMOTE_*

* chore: Added a new section for versions and updated heading formatting.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Burnett01 <1208707+Burnett01@users.noreply.github.com>
Co-authored-by: sovetski
2025-09-19 15:40:13 +02:00
Steven 3de7ffff44 Revise Media section and add new pingback links
Updated section title and added media links.
2025-09-01 12:52:06 +02:00
Steven 9ffbf95175 chore: Add CI badges to README 2025-09-01 12:37:49 +02:00
Steven 02794c2f5a Rename CI workflow file to ci-validating-linting-testing.yml 2025-09-01 12:34:44 +02:00
Steven 7cbc96a81e Rename snyk-container.yml to snyk-docker-vulnerability-scan.yml 2025-09-01 12:34:18 +02:00
Steven 1eeed00fb4 chore: Update CI workflow name for clarity 2025-09-01 12:32:53 +02:00
Steven 581feb8967 Rename workflow to 'Snyk Docker Vulnerability Scan' 2025-09-01 12:31:06 +02:00
Steven fbd408424a feat: Snyk container workflow configuration (#79)
* Update Snyk container workflow configuration

* Update Docker image name in Snyk workflow

* Update Snyk action to use latest master version
2025-09-01 12:25:11 +02:00
Steven 51bdd07760 chore: issue templates 2025-09-01 11:41:30 +02:00
Steven eb1d171493 feat: restrict workflow permissions to PoLP (principle of least privilege)
Discovered by Code scanning alert no. 6: Workflow does not contain permissions (#78)

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-09-01 11:37:07 +02:00
Steven 53581dff6d Add bats tests (#76)
* Add Bats tests and workflow for entrypoint.sh

* Add Bats tests and workflow

* Add comprehensive GitHub Actions CI workflow (#77)

* Initial plan

* Add comprehensive GitHub Actions CI workflow

Co-authored-by: Burnett01 <1208707+Burnett01@users.noreply.github.com>

* Enhance CI workflow with job dependencies and documentation

Co-authored-by: Burnett01 <1208707+Burnett01@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Burnett01 <1208707+Burnett01@users.noreply.github.com>

* Update CI workflow to only include master branch

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
2025-08-29 22:50:40 +02:00
5 changed files with 429 additions and 22 deletions
+33
View File
@@ -0,0 +1,33 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Action version**
eg. 7.0.1
**Runner OS+Version**
eg. ubuntu-latest
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
@@ -0,0 +1,165 @@
# GitHub Actions CI workflow for rsync-deployments
# This workflow validates the action on every push and pull request by:
# - Running BATS tests for the entrypoint script
# - Validating the action.yml definition
# - Building and testing the Docker image
# - Checking file structure and permissions
# - Linting shell scripts
# - Running a final integration check
name: CI - Validating, Linting, Testing
permissions:
contents: read
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
test:
runs-on: ubuntu-latest
name: Test BATS Suite
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install BATS
run: |
sudo apt-get update
sudo apt-get install -y bats
- name: Run BATS tests
run: bats test/entrypoint.bats
validate-action:
runs-on: ubuntu-latest
name: Validate Action Definition
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Validate action.yml
run: |
# Check if action.yml exists and has required fields
if [ ! -f "action.yml" ]; then
echo "Error: action.yml not found"
exit 1
fi
# Basic validation that action.yml contains required fields
python3 -c "
import yaml
import sys
with open('action.yml', 'r') as f:
action = yaml.safe_load(f)
required_fields = ['name', 'description', 'inputs', 'runs']
for field in required_fields:
if field not in action:
print(f'Missing required field: {field}')
sys.exit(1)
# Check required inputs exist
required_inputs = ['switches', 'remote_path', 'remote_host', 'remote_user', 'remote_key']
for input_name in required_inputs:
if input_name not in action['inputs']:
print(f'Missing required input: {input_name}')
sys.exit(1)
if not action['inputs'][input_name].get('required', False):
print(f'Input {input_name} should be marked as required')
sys.exit(1)
print('Action definition is valid')
"
docker-build:
runs-on: ubuntu-latest
name: Build Docker Image
needs: [validate-action, action-structure]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build Docker image
run: |
echo "Building Docker image..."
docker build -t rsync-deployments . --no-cache
echo "Docker image built successfully"
action-structure:
runs-on: ubuntu-latest
name: Validate Action Structure
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Check required files
run: |
echo "Checking required files exist..."
# Check all required files exist
required_files=("action.yml" "Dockerfile" "entrypoint.sh")
for file in "${required_files[@]}"; do
if [ ! -f "$file" ]; then
echo "Error: Required file $file not found"
exit 1
fi
echo "✓ $file exists"
done
# Check entrypoint is executable
if [ ! -x "entrypoint.sh" ]; then
echo "Error: entrypoint.sh is not executable"
exit 1
fi
echo "✓ entrypoint.sh is executable"
# Check basic script syntax
bash -n entrypoint.sh
echo "✓ entrypoint.sh has valid syntax"
echo "All structure checks passed!"
lint-shell:
runs-on: ubuntu-latest
name: Lint Shell Scripts
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install ShellCheck
run: |
sudo apt-get update
sudo apt-get install -y shellcheck
- name: Lint entrypoint.sh
run: |
echo "Linting shell scripts..."
# Run shellcheck with exclusions for Docker-specific dependencies
shellcheck -e SC1091 -e SC3046 entrypoint.sh || {
echo "ShellCheck found issues, but running with Docker-specific exclusions..."
shellcheck -e SC1091 -e SC3046 entrypoint.sh
}
echo "Shell script linting completed"
integration-check:
runs-on: ubuntu-latest
name: Integration Check
needs: [test, validate-action, docker-build, action-structure, lint-shell]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Final integration check
run: |
echo "All CI jobs completed successfully!"
echo "✅ BATS tests passed"
echo "✅ Action definition validated"
echo "✅ Docker image built and tested"
echo "✅ File structure validated"
echo "✅ Shell scripts linted"
echo ""
echo "🎉 rsync-deployments action is ready for use!"
@@ -0,0 +1,36 @@
name: Snyk Docker Vulnerability Scan
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
schedule:
- cron: '39 13 * * 4'
permissions:
contents: read
jobs:
snyk:
permissions:
contents: read
security-events: write
actions: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build a Docker image
run: docker build -t burnett01/rsync-deployments .
- name: Run Snyk to check Docker image for vulnerabilities
continue-on-error: true
uses: snyk/actions/docker@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
image: burnett01/rsync-deployments
args: --file=Dockerfile
- name: Upload result to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: snyk.sarif
+130 -22
View File
@@ -1,5 +1,11 @@
# rsync deployments
[![CI - Validating, Linting, Testing](https://github.com/Burnett01/rsync-deployments/actions/workflows/ci-validating-linting-testing.yml/badge.svg)](https://github.com/Burnett01/rsync-deployments/actions/workflows/ci-validating-linting-testing.yml)
[![Snyk Docker Vulnerability Scan](https://github.com/Burnett01/rsync-deployments/actions/workflows/snyk-docker-vulnerability-scan.yml/badge.svg)](https://github.com/Burnett01/rsync-deployments/actions/workflows/snyk-docker-vulnerability-scan.yml)
[![CodeQL](https://github.com/Burnett01/rsync-deployments/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/Burnett01/rsync-deployments/actions/workflows/github-code-scanning/codeql)
[![Dependabot Updates](https://github.com/Burnett01/rsync-deployments/actions/workflows/dependabot/dependabot-updates/badge.svg)](https://github.com/Burnett01/rsync-deployments/actions/workflows/dependabot/dependabot-updates)
This GitHub Action (amd64) deploys files in `GITHUB_WORKSPACE` to a remote folder via rsync over ssh.
Use this action in a CD workflow which leaves deployable code in `GITHUB_WORKSPACE`.
@@ -29,9 +35,9 @@ Rsync version: [3.4.1-r0](https://download.samba.org/pub/rsync/NEWS#3.4.1)
- `remote_user`* - The remote user
- `remote_key`* - The remote ssh key
- `remote_key`* - The remote ssh private key
- `remote_key_pass` - The remote ssh key passphrase (if any)
- `remote_key_pass` - The remote ssh private key passphrase (if any)
``* = Required``
@@ -41,7 +47,7 @@ This action needs secret variables for the ssh private key of your key pair. The
> Always use secrets when dealing with sensitive inputs!
For simplicity, we are using `DEPLOY_*` as the secret variables throughout the examples.
For simplicity, we are using `REMOTE_*` as the secret variables throughout the examples.
## Current Version: 7.1.0
@@ -69,7 +75,7 @@ jobs:
remote_path: /var/www/html/
remote_host: example.com
remote_user: debian
remote_key: ${{ secrets.DEPLOY_KEY }}
remote_key: ${{ secrets.REMOTE_PRIVATE_KEY }}
```
Advanced:
@@ -89,7 +95,7 @@ jobs:
remote_host: example.com
remote_port: 5555
remote_user: debian
remote_key: ${{ secrets.DEPLOY_KEY }}
remote_key: ${{ secrets.REMOTE_PRIVATE_KEY }}
```
For better **security**, I suggest you create additional secrets for remote_host, remote_port, remote_user and remote_path inputs.
@@ -105,11 +111,11 @@ jobs:
with:
switches: -avzr --delete
path: src/
remote_path: ${{ secrets.DEPLOY_PATH }}
remote_host: ${{ secrets.DEPLOY_HOST }}
remote_port: ${{ secrets.DEPLOY_PORT }}
remote_user: ${{ secrets.DEPLOY_USER }}
remote_key: ${{ secrets.DEPLOY_KEY }}
remote_path: ${{ secrets.REMOTE_PATH }}
remote_host: ${{ secrets.REMOTE_HOST }}
remote_port: ${{ secrets.REMOTE_PORT }}
remote_user: ${{ secrets.REMOTE_USER }}
remote_key: ${{ secrets.REMOTE_PRIVATE_KEY }}
```
If your private key is passphrase protected you should use:
@@ -125,12 +131,12 @@ jobs:
with:
switches: -avzr --delete
path: src/
remote_path: ${{ secrets.DEPLOY_PATH }}
remote_host: ${{ secrets.DEPLOY_HOST }}
remote_port: ${{ secrets.DEPLOY_PORT }}
remote_user: ${{ secrets.DEPLOY_USER }}
remote_key: ${{ secrets.DEPLOY_KEY }}
remote_key_pass: ${{ secrets.DEPLOY_KEY_PASS }}
remote_path: ${{ secrets.REMOTE_PATH }}
remote_host: ${{ secrets.REMOTE_HOST }}
remote_port: ${{ secrets.REMOTE_PORT }}
remote_user: ${{ secrets.REMOTE_USER }}
remote_key: ${{ secrets.REMOTE_PRIVATE_KEY }}
remote_key_pass: ${{ secrets.REMOTE_PRIVATE_KEY_PASS }}
```
---
@@ -152,17 +158,111 @@ jobs:
switches: -avzr --delete
legacy_allow_rsa_hostkeys: "true"
path: src/
remote_path: ${{ secrets.DEPLOY_PATH }}
remote_host: ${{ secrets.DEPLOY_HOST }}
remote_port: ${{ secrets.DEPLOY_PORT }}
remote_user: ${{ secrets.DEPLOY_USER }}
remote_key: ${{ secrets.DEPLOY_KEY }}
remote_path: ${{ secrets.REMOTE_PATH }}
remote_host: ${{ secrets.REMOTE_HOST }}
remote_port: ${{ secrets.REMOTE_PORT }}
remote_user: ${{ secrets.REMOTE_USER }}
remote_key: ${{ secrets.REMOTE_PRIVATE_KEY }}
```
See [#49](https://github.com/Burnett01/rsync-deployments/issues/49) and [#24](https://github.com/Burnett01/rsync-deployments/issues/24) for more information.
**Note:** Only use this if necessary. It's recommended to upgrade your remote OpenSSH server instead.
--
## Advanced Rsync switches/flags/options
For advanced rsync configuration options and switches, refer to the [rsync manual](https://linux.die.net/man/1/rsync).
---
## Troubleshooting
### SSH Permission Denied Errors
If you encounter "Permission denied (publickey,password)" errors, here are the most common solutions:
#### 1. SSH Key Setup
Ensure your SSH key pair is correctly generated and configured:
```bash
# Generate a new SSH key pair (recommended: Ed25519 or RSA 4096-bit)
ssh-keygen -t ed25519 -C "deploy@yourproject" -f ~/.ssh/deploy_yourproject -N ""
# OR for RSA:
ssh-keygen -t rsa -b 4096 -C "deploy@yourproject" -f ~/.ssh/deploy_yourproject -N ""
```
**Important Steps:**
- Add the **public key** (`.pub` file) to your server's `~/.ssh/authorized_keys`
- Add the **private key** (without `.pub` extension) to GitHub Secrets as `REMOTE_PRIVATE_KEY`
- Ensure correct file permissions on your server:
```bash
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
```
For detailed information on creating and managing SSH keys, see [GitHub's SSH Key Guide](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent).
#### 2. ``remote_path`` permissions
Make sure the ``remote_user`` has write access to ``remote_path``.
See: https://github.com/Burnett01/rsync-deployments/issues/81#issuecomment-3308152891
#### 3. Firewall / GitHub Actions IP Restrictions
If your remote server has firewall restrictions:
- **Option A:** Whitelist [GitHub Actions IP ranges](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#ip-addresses) (Azure-based)
- **Option B:** Use self-hosted runners on your server (recommended for strict firewall environments)
### Excluding files/folders (eg .git)
By default, rsync copies dot files and folder if present at ``path:``. To exclude them, you can use the ``--exclude`` switch:
```yml
switches: -avzr --delete --exclude='.git/'
```
Other common exclusions:
```yml
switches: -avzr --delete --exclude='.git/' --exclude='node_modules/' --exclude='.env'
```
More advanced examples:
- https://github.com/Burnett01/rsync-deployments/issues/5#issuecomment-667589874
- https://github.com/Burnett01/rsync-deployments/issues/16
- https://github.com/Burnett01/rsync-deployments/issues/71
- https://github.com/Burnett01/rsync-deployments/issues/52
### Missing rsync on Remote Host
If the action fails with "rsync: command not found" or similar errors, rsync is not installed on your remote server. Install it using your system's package manager:
**Ubuntu/Debian:**
```bash
sudo apt-get update && sudo apt-get install rsync
```
**CentOS/RHEL/Rocky/AlmaLinux:**
```bash
sudo yum install rsync
# OR on newer versions:
sudo dnf install rsync
```
**Alpine Linux:**
```bash
sudo apk add rsync
```
---
## Versions
## Version 7.0.2
Check here:
@@ -241,12 +341,20 @@ Please note that version 1.0 has reached end of life state.
---
## Media
## Media & Pingback
This action was featured in multiple blogs across the globe:
> Disclaimer: The author & co-authors are not responsible for the content of the site-links below.
- https://hosting.xyz/wiki/hosting/other/github-actions/
- https://www.alexander-palm.de/2025/07/22/sichere-rsync-deployments-mit-github-actions-und-rrsync/
- https://lab.uberspace.de/howto_automatic-deployment/
- https://blog.devops.dev/setting-up-an-ubuntu-instance-for-nodejs-apps-in-ovh-cloud-using-nginx-pm2-github-actions-7618c768d081
- https://elijahverdoorn.com/2020/04/14/automating-deployment-with-github-actions/
- https://www.vektor-inc.co.jp/post/github-actions-deploy/
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env bats
setup() {
# Create a dummy ssh agent and agent-add for sourcing
echo 'echo "agent started"' > agent-start
echo 'echo "key added"' > agent-add
chmod +x agent-start agent-add
# Create a dummy rsync to capture its arguments
echo 'echo "rsync $@"' > rsync
chmod +x rsync
PATH="$PWD:$PATH"
}
teardown() {
rm -f agent-start agent-add rsync
}
@test "fails if INPUT_REMOTE_PATH is empty" {
export INPUT_REMOTE_PATH=" "
run ./entrypoint.sh
[ "$status" -eq 1 ]
[[ "${output}" == *"can not be empty"* ]]
}
@test "includes legacy RSA switches when allowed" {
export INPUT_LEGACY_ALLOW_RSA_HOSTKEYS="true"
export INPUT_REMOTE_PATH="remote/"
export INPUT_REMOTE_KEY="dummy"
export INPUT_REMOTE_KEY_PASS="dummy"
export GITHUB_ACTION="dummy"
export INPUT_SWITCHES="-avz"
export INPUT_REMOTE_PORT="22"
export INPUT_RSH=""
export INPUT_PATH=""
export INPUT_REMOTE_USER="user"
export INPUT_REMOTE_HOST="host"
export GITHUB_WORKSPACE="/tmp"
export DSN="user@host"
export LOCAL_PATH="/tmp/"
run ./entrypoint.sh
[[ "${output}" == *"HostKeyAlgorithms=+ssh-rsa"* ]]
}
@test "does not include legacy RSA switches when not allowed" {
export INPUT_LEGACY_ALLOW_RSA_HOSTKEYS="false"
export INPUT_REMOTE_PATH="remote/"
export INPUT_REMOTE_KEY="dummy"
export INPUT_REMOTE_KEY_PASS="dummy"
export GITHUB_ACTION="dummy"
export INPUT_SWITCHES="-avz"
export INPUT_REMOTE_PORT="22"
export INPUT_RSH=""
export INPUT_PATH=""
export INPUT_REMOTE_USER="user"
export INPUT_REMOTE_HOST="host"
export GITHUB_WORKSPACE="/tmp"
export DSN="user@host"
export LOCAL_PATH="/tmp/"
run ./entrypoint.sh
[[ "${output}" != *"HostKeyAlgorithms=+ssh-rsa"* ]]
}