Compare commits

...

6 Commits

Author SHA1 Message Date
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 951 additions and 67 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 # 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`; This action would usually follow a build/test action which leaves deployable code in `GITHUB_WORKSPACE`, eg `dist`;
@@ -8,30 +12,45 @@ This action would usually follow a build/test action which leaves deployable cod
Pass configuration with `env` vars 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. 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
2. `ARGS` (optional, default '-rltgoDzvO')
For any initial/required rsync flags, eg: `-avzr --delete` For any initial/required rsync flags, eg: `-avzr --delete`
3. `SOURCE` 3. `SOURCE` (optional, default '')
The source directory, path relative to `$GITHUB_WORKSPACE` root, eg: `dist`
4. `TARGET` The source directory, path relative to `$GITHUB_WORKSPACE` root, eg: `dist/`
The target directory, in the format`[USER]@[HOST]:[PATH]`
4. `TARGET` (optional, default '/home/REMOTE_USER/')
The target directory
# Usage
``` ```
- name: Deploy to Staging server - name: Deploy to Staging server
uses: easingthemes/ssh-deploy@v1.0.0 uses: easingthemes/ssh-deploy@v2.0.2
env: env:
DEPLOY_KEY: ${{ secrets.SERVER_SSH_KEY }} SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }}
ARGS: "-rltgoDzvO --delete" ARGS: "-rltgoDzvO"
SOURCE: "dist" SOURCE: "dist/"
TARGET: ${{ secrets.SERVER_STAGING }} REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
TARGET: ${{ secrets.REMOTE_TARGET }}
``` ```
# Example usage # Example usage in workflow
``` ```
name: Node CI name: Node CI
@@ -43,29 +62,25 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
node-version: [10.x]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }} - name: Install Node.js
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: ${{ matrix.node-version }} node-version: '10.x'
- name: Install npm dependencies - name: Install npm dependencies
run: | run: npm install
npm install
- name: Run build task - name: Run build task
run: | run: npm run build --if-present
npm run build --if-present - name: Deploy to Server
- name: Deploy to Staging server uses: easingthemes/ssh-deploy@v2.0.2
uses: easingthemes/ssh-deploy@v1.0.0
env: env:
DEPLOY_KEY: ${{ secrets.SERVER_SSH_KEY }} SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }}
ARGS: "-rltgoDzvO --delete" ARGS: "-rltgoDzvO --delete"
SOURCE: "dist" SOURCE: "dist/"
TARGET: ${{ secrets.SERVER_STAGING }} REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
TARGET: ${{ secrets.REMOTE_TARGET }}
``` ```
## Disclaimer ## Disclaimer

27
action.yml Normal file
View File

@@ -0,0 +1,27 @@
name: 'ssh deploy'
description: 'For deploying code over ssh'
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
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'

646
dist/index.js vendored Executable file
View File

@@ -0,0 +1,646 @@
#!/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, 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, src, dest, args }) => {
console.log(`Starting Rsync Action: ${src} to ${dest}`);
try {
// RSYNC COMMAND
nodeRsync({ src, dest, args, privateKey, ssh: true, 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
}) => {
validateRsync(() => {
const privateKey = addSshKey(privateKeyContent, DEPLOY_KEY_NAME ||'deploy_key');
const remoteDest = username + '@' + host + ':' + dest;
rsync({ privateKey, 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,
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.0.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.0.3",
"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"
}

151
src/index.js Normal file
View File

@@ -0,0 +1,151 @@
#!/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, 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, src, dest, args }) => {
console.log(`Starting Rsync Action: ${src} to ${dest}`);
try {
// RSYNC COMMAND
nodeRsync({ src, dest, args, privateKey, ssh: true, 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
}) => {
validateRsync(() => {
const privateKey = addSshKey(privateKeyContent, DEPLOY_KEY_NAME ||'deploy_key');
const remoteDest = username + '@' + host + ':' + dest;
rsync({ privateKey, 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,
username: REMOTE_USER,
privateKeyContent: SSH_PRIVATE_KEY,
});
};
run();