Compare commits

...

13 Commits

Author SHA1 Message Date
Dragan Filipović
d86da2b0b7 Merge pull request #2 from Triloworld/patch-1
Update README.md - version bump
2020-01-09 22:21:19 +01:00
Dragan Filipović
445db7826b Merge pull request #1 from fmal/feat/remote-port
add optional `REMOTE_PORT` env to configuration
2020-01-09 22:19:49 +01:00
Patryk Padus
4a2dc47c39 Update README.md
It is required as node10 isn't supported and generate error

Download action repository 'easingthemes/ssh-deploy@v2.0.2'
##[error]Specified argument was out of the range of valid values. (Parameter ''using: node10' is not supported, use 'docker' or 'node12' instead.')
##[error]Fail to load /home/runner/work/_actions/easingthemes/ssh-deploy/v2.0.2/action.yml
2020-01-08 15:41:02 +01:00
Filip Malinowski
0d77a6681b add optional REMOTE_PORT env to configuration 2019-12-29 18:17:28 +01:00
Dragan Filipovic
a3d2df0501 [version] Add author 2019-10-03 01:17:24 +02:00
Dragan Filipovic
7d63f7f134 [version] Description update 2019-10-03 01:07:28 +02:00
Dragan Filipovic
5fdc019220 [version] version bump 2019-10-03 00:52:55 +02:00
Dragan Filipovic
177ffc3a2a [version] Readme update 2019-10-03 00:51:32 +02:00
Dragan Filipovic
b5d54bf446 [action] fix node version 2019-10-03 00:24:52 +02:00
Dragan Filipovic
a20c19a8d1 [version] Readme update 2019-10-03 00:17:19 +02:00
Dragan Filipovic
4bf88310d4 [engine] use NodeJS instead of Docker 2019-10-02 23:58:26 +02:00
Dragan Filipovic
81256f2671 [engine] use NodeJS instead of Docker 2019-10-02 23:52:52 +02:00
Dragan Filipovic
3c2d851430 [ci] remove already available cli 2019-10-01 00:23:18 +02:00
9 changed files with 965 additions and 69 deletions

19
.gitignore vendored Normal file
View File

@@ -0,0 +1,19 @@
### Node template
# Logs
logs
*.log
npm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Dependency directories
node_modules/
# Optional npm cache directory
.npm
# dotenv environment variables file
.env
.env.test

View File

@@ -1,22 +0,0 @@
FROM debian:9.5-slim
# Update
RUN apt-get update
# Install packages
RUN apt-get -yq install rsync openssh-client
# Label
LABEL "com.github.actions.name"="ssh deploy"
LABEL "com.github.actions.description"="For deploying code over ssh"
LABEL "com.github.actions.icon"="truck"
LABEL "com.github.actions.color"="green"
LABEL "repository"="http://github.com/easingthemes/ssh-deploy"
LABEL "homepage"="https://github.com/easingthemes/ssh-deploy"
LABEL "maintainer"="Dragan Filipovic <info@frontenddot.com>"
# Copy entrypoint
ADD entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -1,6 +1,10 @@
# ssh deployments
This GitHub Action deploys specific directory from `GITHUB_WORKSPACE` to a folder on a server via rsync over ssh.
Deploy code with rsync over ssh, using NodeJS.
NodeJS version is more than a minute `faster` than simple Docker version.
This GitHub Action deploys specific directory from `GITHUB_WORKSPACE` to a folder on a server via rsync over ssh, using NodeJS.
This action would usually follow a build/test action which leaves deployable code in `GITHUB_WORKSPACE`, eg `dist`;
@@ -8,30 +12,49 @@ This action would usually follow a build/test action which leaves deployable cod
Pass configuration with `env` vars
1. `DEPLOY_KEY`
1. `SSH_PRIVATE_KEY` [required]
This should be the private key part of an ssh key pair. The public key part should be added to the authorized_keys file on the server that receives the deployment.
2. `ARGS`
2. `REMOTE_HOST` [required]
eg: mydomain.com
3. `REMOTE_USER` [required]
eg: myusername
3. `REMOTE_PORT` (optional, default '22')
eg: '59184'
2. `ARGS` (optional, default '-rltgoDzvO')
For any initial/required rsync flags, eg: `-avzr --delete`
3. `SOURCE`
The source directory, path relative to `$GITHUB_WORKSPACE` root, eg: `dist`
3. `SOURCE` (optional, default '')
4. `TARGET`
The target directory, in the format`[USER]@[HOST]:[PATH]`
The source directory, path relative to `$GITHUB_WORKSPACE` root, eg: `dist/`
4. `TARGET` (optional, default '/home/REMOTE_USER/')
The target directory
# Usage
```
- name: Deploy to Staging server
uses: easingthemes/ssh-deploy@v1.0.0
env:
DEPLOY_KEY: ${{ secrets.SERVER_SSH_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "dist"
TARGET: ${{ secrets.SERVER_STAGING }}
- name: Deploy to Staging server
uses: easingthemes/ssh-deploy@v2.0.7
env:
SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }}
ARGS: "-rltgoDzvO"
SOURCE: "dist/"
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
TARGET: ${{ secrets.REMOTE_TARGET }}
```
# Example usage
# Example usage in workflow
```
name: Node CI
@@ -43,33 +66,29 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x]
steps:
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }}
- name: Install Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
node-version: '10.x'
- name: Install npm dependencies
run: |
npm install
run: npm install
- name: Run build task
run: |
npm run build --if-present
- name: Deploy to Staging server
uses: easingthemes/ssh-deploy@v1.0.0
env:
DEPLOY_KEY: ${{ secrets.SERVER_SSH_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "dist"
TARGET: ${{ secrets.SERVER_STAGING }}
run: npm run build --if-present
- name: Deploy to Server
uses: easingthemes/ssh-deploy@v2.0.7
env:
SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "dist/"
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
TARGET: ${{ secrets.REMOTE_TARGET }}
```
## Disclaimer
If you're using GitHub Actions, you'll probably already know that it's still in limited public beta, and GitHub advise against using Actions in production.
If you're using GitHub Actions, you'll probably already know that it's still in limited public beta, and GitHub advise against using Actions in production.
So, check your keys. Check your deployment paths. And use at your own risk.
So, check your keys. Check your deployment paths. And use at your own risk.

31
action.yml Normal file
View File

@@ -0,0 +1,31 @@
name: "ssh deploy"
description: "NodeJS action for FAST deployment with rsync/ssh"
author: "easingthemes"
inputs:
SSH_PRIVATE_KEY: # Private Key
description: "Private Key"
required: true
REMOTE_HOST:
description: "Remote host"
required: true
REMOTE_USER:
description: "Remote user"
required: true
REMOTE_PORT:
description: "Remote port"
default: "22"
SOURCE:
description: "Source directory"
default: ""
TARGET:
description: "Target directory"
default: "/home/REMOTE_USER/"
outputs:
status:
description: "Status"
runs:
using: "node12"
main: "dist/index.js"
branding:
color: "green"
icon: "truck"

648
dist/index.js vendored Executable file
View File

@@ -0,0 +1,648 @@
#!/usr/bin/env node
module.exports =
/******/ (function(modules, runtime) { // webpackBootstrap
/******/ "use strict";
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ __webpack_require__.ab = __dirname + "/";
/******/
/******/ // the startup function
/******/ function startup() {
/******/ // Load entry module and return exports
/******/ return __webpack_require__(676);
/******/ };
/******/
/******/ // run startup
/******/ return startup();
/******/ })
/************************************************************************/
/******/ ({
/***/ 129:
/***/ (function(module) {
module.exports = require("child_process");
/***/ }),
/***/ 243:
/***/ (function(module, __unusedexports, __webpack_require__) {
"use strict";
var exec = __webpack_require__(129).exec;
var execSync = __webpack_require__(129).execSync;
var fs = __webpack_require__(747);
var path = __webpack_require__(622);
var access = fs.access;
var accessSync = fs.accessSync;
var constants = fs.constants || fs;
var isUsingWindows = process.platform == 'win32'
var fileNotExists = function(commandName, callback){
access(commandName, constants.F_OK,
function(err){
callback(!err);
});
};
var fileNotExistsSync = function(commandName){
try{
accessSync(commandName, constants.F_OK);
return false;
}catch(e){
return true;
}
};
var localExecutable = function(commandName, callback){
access(commandName, constants.F_OK | constants.X_OK,
function(err){
callback(null, !err);
});
};
var localExecutableSync = function(commandName){
try{
accessSync(commandName, constants.F_OK | constants.X_OK);
return true;
}catch(e){
return false;
}
}
var commandExistsUnix = function(commandName, cleanedCommandName, callback) {
fileNotExists(commandName, function(isFile){
if(!isFile){
var child = exec('command -v ' + cleanedCommandName +
' 2>/dev/null' +
' && { echo >&1 ' + cleanedCommandName + '; exit 0; }',
function (error, stdout, stderr) {
callback(null, !!stdout);
});
return;
}
localExecutable(commandName, callback);
});
}
var commandExistsWindows = function(commandName, cleanedCommandName, callback) {
if (/[\x00-\x1f<>:"\|\?\*]/.test(commandName)) {
callback(null, false);
return;
}
var child = exec('where ' + cleanedCommandName,
function (error) {
if (error !== null){
callback(null, false);
} else {
callback(null, true);
}
}
)
}
var commandExistsUnixSync = function(commandName, cleanedCommandName) {
if(fileNotExistsSync(commandName)){
try {
var stdout = execSync('command -v ' + cleanedCommandName +
' 2>/dev/null' +
' && { echo >&1 ' + cleanedCommandName + '; exit 0; }'
);
return !!stdout;
} catch (error) {
return false;
}
}
return localExecutableSync(commandName);
}
var commandExistsWindowsSync = function(commandName, cleanedCommandName, callback) {
if (/[\x00-\x1f<>:"\|\?\*]/.test(commandName)) {
return false;
}
try {
var stdout = execSync('where ' + cleanedCommandName, {stdio: []});
return !!stdout;
} catch (error) {
return false;
}
}
var cleanInput = function(s) {
if (/[^A-Za-z0-9_\/:=-]/.test(s)) {
s = "'"+s.replace(/'/g,"'\\''")+"'";
s = s.replace(/^(?:'')+/g, '') // unduplicate single-quote at the beginning
.replace(/\\'''/g, "\\'" ); // remove non-escaped single-quote if there are enclosed between 2 escaped
}
return s;
}
if (isUsingWindows) {
cleanInput = function(s) {
var isPathName = /[\\]/.test(s);
if (isPathName) {
var dirname = '"' + path.dirname(s) + '"';
var basename = '"' + path.basename(s) + '"';
return dirname + ':' + basename;
}
return '"' + s + '"';
}
}
module.exports = function commandExists(commandName, callback) {
var cleanedCommandName = cleanInput(commandName);
if (!callback && typeof Promise !== 'undefined') {
return new Promise(function(resolve, reject){
commandExists(commandName, function(error, output) {
if (output) {
resolve(commandName);
} else {
reject(error);
}
});
});
}
if (isUsingWindows) {
commandExistsWindows(commandName, cleanedCommandName, callback);
} else {
commandExistsUnix(commandName, cleanedCommandName, callback);
}
};
module.exports.sync = function(commandName) {
var cleanedCommandName = cleanInput(commandName);
if (isUsingWindows) {
return commandExistsWindowsSync(commandName, cleanedCommandName);
} else {
return commandExistsUnixSync(commandName, cleanedCommandName);
}
};
/***/ }),
/***/ 250:
/***/ (function(module, __unusedexports, __webpack_require__) {
"use strict";
var spawn = __webpack_require__(129).spawn
var util = __webpack_require__(669)
var escapeSpaces = function(path) {
if (typeof path === 'string') {
return path.replace(/\b\s/g, '\\ ')
} else {
return path
}
}
var escapeSpacesInOptions = function(options) {
// Escape paths in the src, dest, include, exclude, and excludeFirst arguments
;['src', 'dest', 'include', 'exclude', 'excludeFirst'].forEach(function(
optionKey
) {
var option = options[optionKey]
if (typeof option === 'string') {
options[optionKey] = escapeSpaces(option)
} else if (Array.isArray(option) === true) {
options[optionKey] = option.map(escapeSpaces)
}
})
return options
}
module.exports = function(options, callback) {
options = options || {}
options = util._extend({}, options)
options = escapeSpacesInOptions(options)
var platform = options.platform || process.platform // Enable process.platform to be mocked in options for testing
var isWin = platform === 'win32'
if (typeof options.src === 'undefined') {
throw new Error("'src' directory is missing from options")
}
if (typeof options.dest === 'undefined') {
throw new Error("'dest' directory is missing from options")
}
var dest = options.dest
if (typeof options.host !== 'undefined') {
dest = options.host + ':' + options.dest
}
if (!Array.isArray(options.src)) {
options.src = [options.src]
}
var args = [].concat(options.src)
args.push(dest)
// [rsync failed on windows, copying persmissions](https://github.com/jedrichards/rsyncwrapper/issues/28)
// [set chmod flag by default on Windows](https://github.com/jedrichards/rsyncwrapper/pull/29)
var chmodArg = (options.args || []).find(function(arg) {
return arg.match(/--chmod=/)
})
if (isWin && !chmodArg) {
args.push('--chmod=ugo=rwX')
}
if (typeof options.host !== 'undefined' || options.ssh) {
args.push('--rsh')
var rshCmd = 'ssh'
if (typeof options.port !== 'undefined') {
rshCmd += ' -p ' + options.port
}
if (typeof options.privateKey !== 'undefined') {
rshCmd += ' -i ' + options.privateKey
}
if (typeof options.sshCmdArgs !== 'undefined') {
rshCmd += ' ' + options.sshCmdArgs.join(' ')
}
args.push(rshCmd)
}
if (options.recursive === true) {
args.push('--recursive')
}
if (options.times === true) {
args.push('--times')
}
if (options.syncDest === true || options.deleteAll === true) {
args.push('--delete')
args.push('--delete-excluded')
}
if (options.syncDestIgnoreExcl === true || options.delete === true) {
args.push('--delete')
}
if (options.dryRun === true) {
args.push('--dry-run')
args.push('--verbose')
}
if (
typeof options.excludeFirst !== 'undefined' &&
util.isArray(options.excludeFirst)
) {
options.excludeFirst.forEach(function(value, index) {
args.push('--exclude=' + value)
})
}
if (typeof options.include !== 'undefined' && util.isArray(options.include)) {
options.include.forEach(function(value, index) {
args.push('--include=' + value)
})
}
if (typeof options.exclude !== 'undefined' && util.isArray(options.exclude)) {
options.exclude.forEach(function(value, index) {
args.push('--exclude=' + value)
})
}
switch (options.compareMode) {
case 'sizeOnly':
args.push('--size-only')
break
case 'checksum':
args.push('--checksum')
break
}
if (typeof options.args !== 'undefined' && util.isArray(options.args)) {
args = [...new Set([...args, ...options.args])]
}
args = [...new Set(args)]
var noop = function() {}
var onStdout = options.onStdout || noop
var onStderr = options.onStderr || noop
var cmd = 'rsync '
args.forEach(function(arg) {
if (arg.substr(0, 4) === 'ssh ') {
arg = '"' + arg + '"'
}
cmd += arg + ' '
})
cmd = cmd.trim()
if (options.noExec) {
callback(null, null, null, cmd)
return
}
try {
var stdout = ''
var stderr = ''
// Launch cmd in a shell just like Node's child_process.exec() does:
// see https://github.com/joyent/node/blob/937e2e351b2450cf1e9c4d8b3e1a4e2a2def58bb/lib/child_process.js#L589
var child
if (isWin) {
child = spawn('cmd.exe', ['/s', '/c', '"' + cmd + '"'], {
windowsVerbatimArguments: true,
stdio: [process.stdin, 'pipe', 'pipe'],
})
} else {
child = spawn('/bin/sh', ['-c', cmd])
}
child.stdout.on('data', function(data) {
onStdout(data)
stdout += data
})
child.stderr.on('data', function(data) {
onStderr(data)
stderr += data
})
child.on('exit', function(code) {
var err = null
if (code !== 0) {
err = new Error('rsync exited with code ' + code)
err.code = code
}
callback(err, stdout, stderr, cmd)
})
} catch (err) {
callback(err, null, null, cmd)
}
}
/***/ }),
/***/ 428:
/***/ (function(module, __unusedexports, __webpack_require__) {
var exec = __webpack_require__(129).exec;
var commandline={
get:getString,
run:runCommand
};
function runCommand(command){
//return refrence to the child process
return exec(
command
);
}
function getString(command,callback){
//return refrence to the child process
return exec(
command,
(
function(){
return function(err,data,stderr){
if(!callback)
return;
callback(err, data, stderr);
}
}
)(callback)
);
}
module.exports=commandline;
/***/ }),
/***/ 622:
/***/ (function(module) {
module.exports = require("path");
/***/ }),
/***/ 669:
/***/ (function(module) {
module.exports = require("util");
/***/ }),
/***/ 676:
/***/ (function(__unusedmodule, __unusedexports, __webpack_require__) {
const fs = __webpack_require__(747);
const path = __webpack_require__(622);
const commandExists = __webpack_require__(677);
const nodeCmd = __webpack_require__(428);
const nodeRsync = __webpack_require__(250);
const { REMOTE_HOST, REMOTE_USER, REMOTE_PORT, SSH_PRIVATE_KEY, DEPLOY_KEY_NAME, SOURCE, TARGET, ARGS, GITHUB_WORKSPACE, HOME } = process.env;
console.log('GITHUB_WORKSPACE', GITHUB_WORKSPACE);
const sshDeploy = (() => {
const rsync = ({ privateKey, port, src, dest, args }) => {
console.log(`Starting Rsync Action: ${src} to ${dest}`);
try {
// RSYNC COMMAND
nodeRsync({ src, dest, args, privateKey, ssh: true, port, sshCmdArgs: ['-o StrictHostKeyChecking=no'], recursive: true }, (error, stdout, stderr, cmd) => {
if (error) {
console.error('⚠️ Rsync error', error.message);
process.abort();
} else {
console.log("✅ Rsync finished.", stdout);
}
});
} catch (err) {
console.error(`⚠️ An error happened:(.`, err.message, err.stack);
process.abort();
}
};
const init = ({
src,
dest,
args,
host = 'localhost',
username,
privateKeyContent,
port
}) => {
validateRsync(() => {
const privateKey = addSshKey(privateKeyContent, DEPLOY_KEY_NAME ||'deploy_key');
const remoteDest = username + '@' + host + ':' + dest;
rsync({ privateKey, port, src, dest: remoteDest, args });
});
};
const validateDir = (dir) => {
if (!fs.existsSync(dir)){
console.log(`Creating ${dir} dir in `, GITHUB_WORKSPACE);
fs.mkdirSync(dir);
} else {
console.log(`${dir} dir exist`);
}
};
const validateFile = (filePath) => {
if (!fs.existsSync(filePath)){
console.log(`Creating ${filePath} file in `, GITHUB_WORKSPACE);
try {
fs.writeFileSync(filePath, '', {
encoding: 'utf8',
mode: 0o600
});
} catch (e) {
console.error('⚠️ writeFileSync error', filePath, e.message);
process.abort();
}
} else {
console.log(`${filePath} file exist`);
}
};
const addSshKey = (key, name) => {
const sshDir = path.join(HOME || __dirname, '.ssh');
const filePath = path.join(sshDir, name);
validateDir(sshDir);
validateFile(sshDir + '/known_hosts');
try {
fs.writeFileSync(filePath, key, {
encoding: 'utf8',
mode: 0o600
});
} catch (e) {
console.error('⚠️ writeFileSync error', filePath, e.message);
process.abort();
}
console.log('✅ Ssh key added to `.ssh` dir ', filePath);
return filePath;
};
const validateRsync = (callback = () => {}) => {
const rsyncCli = commandExists.sync('rsync');
if (!rsyncCli) {
nodeCmd.get(
'sudo apt-get --no-install-recommends install rsync',
function(err, data, stderr){
if (err) {
console.log('⚠️ Rsync installation failed ', err.message);
process.abort();
} else {
console.log('✅ Rsync installed. \n', data, stderr);
callback();
}
}
);
} else {
callback();
}
};
return {
init
}
})();
const validateInputs = (inputs) => {
const validInputs = inputs.filter(input => {
if (!input) {
console.error(`⚠️ ${input} is mandatory`);
}
return input;
});
if (validInputs.length !== inputs.length) {
process.abort();
}
};
const run = () => {
validateInputs([SSH_PRIVATE_KEY, REMOTE_HOST, REMOTE_USER]);
sshDeploy.init({
src: GITHUB_WORKSPACE + '/' + SOURCE || '',
dest: TARGET || '/home/' + REMOTE_USER + '/',
args: [ARGS] || false,
host: REMOTE_HOST,
port: REMOTE_PORT || '22',
username: REMOTE_USER,
privateKeyContent: SSH_PRIVATE_KEY,
});
};
run();
/***/ }),
/***/ 677:
/***/ (function(module, __unusedexports, __webpack_require__) {
module.exports = __webpack_require__(243);
/***/ }),
/***/ 747:
/***/ (function(module) {
module.exports = require("fs");
/***/ })
/******/ });

View File

@@ -1,13 +0,0 @@
#!/bin/sh
set -eu
# Set deploy key
SSH_PATH="$HOME/.ssh"
mkdir "$SSH_PATH"
echo "$DEPLOY_KEY" > "$SSH_PATH/deploy_key"
chmod 600 "$SSH_PATH/deploy_key"
# Do deployment
sh -c "rsync $ARGS -e 'ssh -i $SSH_PATH/deploy_key -o StrictHostKeyChecking=no' $GITHUB_WORKSPACE/$SOURCE $TARGET"

29
package-lock.json generated Normal file
View File

@@ -0,0 +1,29 @@
{
"name": "ssh-deploy",
"version": "2.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@zeit/ncc": {
"version": "0.20.5",
"resolved": "https://registry.npmjs.org/@zeit/ncc/-/ncc-0.20.5.tgz",
"integrity": "sha512-XU6uzwvv95DqxciQx+aOLhbyBx/13ky+RK1y88Age9Du3BlA4mMPCy13BGjayOrrumOzlq1XV3SD/BWiZENXlw==",
"dev": true
},
"command-exists": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.8.tgz",
"integrity": "sha512-PM54PkseWbiiD/mMsbvW351/u+dafwTJ0ye2qB60G1aGQP9j3xK2gmMDc+R34L3nDtx4qMCitXT75mkbkGJDLw=="
},
"node-cmd": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/node-cmd/-/node-cmd-3.0.0.tgz",
"integrity": "sha1-OP/3CkqqT2WdID61eGJzcBjiT28="
},
"rsyncwrapper": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/rsyncwrapper/-/rsyncwrapper-3.0.1.tgz",
"integrity": "sha512-fkGmeEJRbKveT/6bBqTVzzHS1wtbGQwL6qnwT/+1AtMAsEV5dX1fSAiOJVZrDOnVsOr2lFl8ga1MZLoHekV3yg=="
}
}
}

32
package.json Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "ssh-deploy",
"version": "2.1.0",
"description": "This GitHub Action deploys specific directory from `GITHUB_WORKSPACE` to a folder on a server via rsync over ssh.",
"main": "src/index.js",
"dependencies": {
"command-exists": "1.2.8",
"node-cmd": "3.0.0",
"rsyncwrapper": "3.0.1"
},
"devDependencies": {
"@zeit/ncc": "^0.20.5"
},
"scripts": {
"build": "ncc build ./src/index.js -o dist"
},
"repository": {
"type": "git",
"url": "git+https://github.com/easingthemes/ssh-deploy.git"
},
"keywords": [
"deploy",
"ssh",
"rsync"
],
"author": "Dragan Filipovic",
"license": "ISC",
"bugs": {
"url": "https://github.com/easingthemes/ssh-deploy/issues"
},
"homepage": "https://github.com/easingthemes/ssh-deploy#readme"
}

153
src/index.js Normal file
View File

@@ -0,0 +1,153 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const commandExists = require('command-exists');
const nodeCmd = require('node-cmd');
const nodeRsync = require('rsyncwrapper');
const { REMOTE_HOST, REMOTE_USER, REMOTE_PORT, SSH_PRIVATE_KEY, DEPLOY_KEY_NAME, SOURCE, TARGET, ARGS, GITHUB_WORKSPACE, HOME } = process.env;
console.log('GITHUB_WORKSPACE', GITHUB_WORKSPACE);
const sshDeploy = (() => {
const rsync = ({ privateKey, port, src, dest, args }) => {
console.log(`Starting Rsync Action: ${src} to ${dest}`);
try {
// RSYNC COMMAND
nodeRsync({ src, dest, args, privateKey, ssh: true, port, sshCmdArgs: ['-o StrictHostKeyChecking=no'], recursive: true }, (error, stdout, stderr, cmd) => {
if (error) {
console.error('⚠️ Rsync error', error.message);
process.abort();
} else {
console.log("✅ Rsync finished.", stdout);
}
});
} catch (err) {
console.error(`⚠️ An error happened:(.`, err.message, err.stack);
process.abort();
}
};
const init = ({
src,
dest,
args,
host = 'localhost',
username,
privateKeyContent,
port
}) => {
validateRsync(() => {
const privateKey = addSshKey(privateKeyContent, DEPLOY_KEY_NAME ||'deploy_key');
const remoteDest = username + '@' + host + ':' + dest;
rsync({ privateKey, port, src, dest: remoteDest, args });
});
};
const validateDir = (dir) => {
if (!fs.existsSync(dir)){
console.log(`Creating ${dir} dir in `, GITHUB_WORKSPACE);
fs.mkdirSync(dir);
} else {
console.log(`${dir} dir exist`);
}
};
const validateFile = (filePath) => {
if (!fs.existsSync(filePath)){
console.log(`Creating ${filePath} file in `, GITHUB_WORKSPACE);
try {
fs.writeFileSync(filePath, '', {
encoding: 'utf8',
mode: 0o600
});
} catch (e) {
console.error('⚠️ writeFileSync error', filePath, e.message);
process.abort();
}
} else {
console.log(`${filePath} file exist`);
}
};
const addSshKey = (key, name) => {
const sshDir = path.join(HOME || __dirname, '.ssh');
const filePath = path.join(sshDir, name);
validateDir(sshDir);
validateFile(sshDir + '/known_hosts');
try {
fs.writeFileSync(filePath, key, {
encoding: 'utf8',
mode: 0o600
});
} catch (e) {
console.error('⚠️ writeFileSync error', filePath, e.message);
process.abort();
}
console.log('✅ Ssh key added to `.ssh` dir ', filePath);
return filePath;
};
const validateRsync = (callback = () => {}) => {
const rsyncCli = commandExists.sync('rsync');
if (!rsyncCli) {
nodeCmd.get(
'sudo apt-get --no-install-recommends install rsync',
function(err, data, stderr){
if (err) {
console.log('⚠️ Rsync installation failed ', err.message);
process.abort();
} else {
console.log('✅ Rsync installed. \n', data, stderr);
callback();
}
}
);
} else {
callback();
}
};
return {
init
}
})();
const validateInputs = (inputs) => {
const validInputs = inputs.filter(input => {
if (!input) {
console.error(`⚠️ ${input} is mandatory`);
}
return input;
});
if (validInputs.length !== inputs.length) {
process.abort();
}
};
const run = () => {
validateInputs([SSH_PRIVATE_KEY, REMOTE_HOST, REMOTE_USER]);
sshDeploy.init({
src: GITHUB_WORKSPACE + '/' + SOURCE || '',
dest: TARGET || '/home/' + REMOTE_USER + '/',
args: [ARGS] || ['-rltgoDzvO'],
host: REMOTE_HOST,
port: REMOTE_PORT || '22',
username: REMOTE_USER,
privateKeyContent: SSH_PRIVATE_KEY,
});
};
run();