Compare commits

...

39 Commits

Author SHA1 Message Date
Dragan Filipovic
7f1c0e0111 [docs] Update version naming 2020-04-11 23:20:56 +02:00
Dragan Filipovic
54f2361d27 [docs] Update readme 2020-04-11 23:08:36 +02:00
Dragan Filipovic
101b6c13a0 [code] hotfix dist 2020-04-11 17:44:56 +02:00
Dragan Filipovic
ac4f0895a6 [code] hotfix 2020-04-11 17:42:37 +02:00
Dragan Filipović
0711330570 Merge pull request #5 from easingthemes/feature/SSH-DEPLOY-refactor
Feature/ssh deploy refactor
2020-04-11 17:25:18 +02:00
Dragan Filipovic
62063532be Merge branch 'master' into feature/SSH-DEPLOY-refactor
# Conflicts:
#	.gitignore
#	README.md
#	dist/index.js
#	src/index.js
2020-04-11 16:54:21 +02:00
Dragan Filipović
1a29114d7b Merge pull request #4 from GarryOne/patch-1
Improved error handling. Added to README.md PM key generation details
2020-04-11 16:40:33 +02:00
Dragan Filipovic
e23436526e [readme] update Readme 2020-04-11 16:33:22 +02:00
Dragan Filipovic
bf3218eb12 [readme] update Readme 2020-04-11 16:30:28 +02:00
Dragan Filipovic
7269cb396a [dist] rebuild 2020-04-11 16:30:11 +02:00
Dragan Filipovic
460751d7c3 [code] split helpers into separate files 2020-04-11 16:29:20 +02:00
Dragan Filipovic
9cdc72374b [action] add required params 2020-04-11 16:27:09 +02:00
Dragan Filipovic
19689edd76 [tests] add editorconfig and eslint 2020-04-11 16:26:19 +02:00
Mardari Igor
53c00dce2b Update README.md 2020-04-11 13:11:34 +03:00
GarryOne
140e959df8 Added key generation details to README.md 2020-03-27 01:17:37 +02:00
GarryOne
00e4d7e856 Added cmd to rsync error output 2020-03-27 01:10:01 +02:00
GarryOne
37e21b5b6b Added error display for rsync 2020-03-27 00:05:33 +02:00
GarryOne
19dbe22d4f Removed console.log 2020-03-26 23:47:55 +02:00
GarryOne
733f9fc153 Added console.log(process.env) 2020-03-26 23:27:22 +02:00
GarryOne
749cc36d5d Added .idea to .gitignore 2020-03-25 05:33:08 +02:00
GarryOne
f09a8d0277 Removed console.log() 2020-03-25 05:28:27 +02:00
GarryOne
82ccbba7dc Added field key to validation message 2020-03-25 05:20:29 +02:00
GarryOne
ff832e4e77 Built src/index.js 2020-03-25 05:08:01 +02:00
Mardari Igor
3f84fa4ae1 Set ssh to false 2020-03-25 04:50:26 +02:00
Dragan Filipović
c28aa7215e Merge pull request #3 from JZLeung/patch-1
fix: 修复 ARGS 为空时导致 rsync 执行错误问题。
2020-03-24 00:04:15 +01:00
Leung-JZ
caaa95b651 fix: 修复 ARGS 为空时导致 rsync 执行错误问题。
由于 ARGS 非必填,当其为空时,报错:

⚠️ An error happened:(. Cannot read property 'match' of undefined TypeError: Cannot read property 'match' of undefined
2020-03-12 09:16:23 +08:00
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
14 changed files with 2783 additions and 71 deletions

11
.editorconfig Normal file
View File

@@ -0,0 +1,11 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 4
trim_trailing_whitespace = true

25
.eslintrc.js Normal file
View File

@@ -0,0 +1,25 @@
module.exports = {
env: {
commonjs: true,
es6: true,
node: true
},
extends: [
'airbnb-base'
],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly'
},
parserOptions: {
ecmaVersion: 2018,
},
rules: {
"comma-dangle": [
"error",
"never"
],
"no-console": "off",
"object-curly-newline": "off"
}
};

21
.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
### 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
# IDE
.idea

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,56 @@ This action would usually follow a build/test action which leaves deployable cod
Pass configuration with `env` vars
1. `DEPLOY_KEY`
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.
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.
The keys should be generated using the PEM format. You can us this command
```
ssh-keygen -m PEM -t rsa -b 4096
```
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')
2. `ARGS`
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
uses: easingthemes/ssh-deploy@v2.0.7
env:
DEPLOY_KEY: ${{ secrets.SERVER_SSH_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "dist"
TARGET: ${{ secrets.SERVER_STAGING }}
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 +73,27 @@ 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
run: npm run build --if-present
- name: Deploy to Server
uses: easingthemes/ssh-deploy@v2.1.1
env:
DEPLOY_KEY: ${{ secrets.SERVER_SSH_KEY }}
SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "dist"
TARGET: ${{ secrets.SERVER_STAGING }}
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.
So, check your keys. Check your deployment paths. And use at your own risk.
Check your keys. Check your deployment paths. And use at your own risk.

34
action.yml Normal file
View File

@@ -0,0 +1,34 @@
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"
required: false
default: "22"
SOURCE:
description: "Source directory"
required: false
default: ""
TARGET:
description: "Target directory"
required: false
default: "/home/REMOTE_USER/"
outputs:
status:
description: "Status"
runs:
using: "node12"
main: "dist/index.js"
branding:
color: "green"
icon: "truck"

712
dist/index.js vendored Executable file
View File

@@ -0,0 +1,712 @@
#!/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");
/***/ }),
/***/ 197:
/***/ (function(module, __unusedexports, __webpack_require__) {
const { existsSync, mkdirSync, writeFileSync } = __webpack_require__(747);
const {
GITHUB_WORKSPACE
} = process.env;
const validateDir = (dir) => {
if (!existsSync(dir)) {
console.log(`[SSH] Creating ${dir} dir in `, GITHUB_WORKSPACE);
mkdirSync(dir);
console.log('✅ [SSH] dir created.');
} else {
console.log(`[SSH] ${dir} dir exist`);
}
};
const validateFile = (filePath) => {
if (!existsSync(filePath)) {
console.log(`[SSH] Creating ${filePath} file in `, GITHUB_WORKSPACE);
try {
writeFileSync(filePath, '', {
encoding: 'utf8',
mode: 0o600
});
console.log('✅ [SSH] file created.');
} catch (e) {
console.error('⚠️ [SSH] writeFileSync error', filePath, e.message);
process.abort();
}
} else {
console.log(`[SSH] ${filePath} file exist`);
}
};
module.exports = {
validateDir,
validateFile
};
/***/ }),
/***/ 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;
/***/ }),
/***/ 613:
/***/ (function(module, __unusedexports, __webpack_require__) {
const { writeFileSync } = __webpack_require__(747);
const { join } = __webpack_require__(622);
const {
validateDir,
validateFile
} = __webpack_require__(197);
const {
HOME
} = process.env;
const addSshKey = (key, name) => {
const sshDir = join(HOME || __dirname, '.ssh');
const filePath = join(sshDir, name);
validateDir(sshDir);
validateFile(`${sshDir}/known_hosts`);
try {
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;
};
module.exports = {
addSshKey
}
/***/ }),
/***/ 622:
/***/ (function(module) {
module.exports = require("path");
/***/ }),
/***/ 669:
/***/ (function(module) {
module.exports = require("util");
/***/ }),
/***/ 676:
/***/ (function(__unusedmodule, __unusedexports, __webpack_require__) {
const nodeRsync = __webpack_require__(250);
const { validateRsync, validateInputs } = __webpack_require__(735);
const { addSshKey } = __webpack_require__(613);
const {
REMOTE_HOST, REMOTE_USER,
REMOTE_PORT, SSH_PRIVATE_KEY, DEPLOY_KEY_NAME,
SOURCE, TARGET, ARGS,
GITHUB_WORKSPACE
} = process.env;
const defaultOptions = {
ssh: true,
sshCmdArgs: ['-o StrictHostKeyChecking=no'],
recursive: true
};
console.log('[general] GITHUB_WORKSPACE: ', GITHUB_WORKSPACE);
const sshDeploy = (() => {
const rsync = ({ privateKey, port, src, dest, args }) => {
console.log(`[Rsync] Starting Rsync Action: ${src} to ${dest}`);
try {
// RSYNC COMMAND
nodeRsync({
src, dest, args, privateKey, port, ...defaultOptions
}, (error, stdout, stderr, cmd) => {
if (error) {
console.error('⚠️ [Rsync] error: ', error.message);
console.log('⚠️ [Rsync] stderr: ', stderr);
console.log('⚠️ [Rsync] stdout: ', stdout);
console.log('⚠️ [Rsync] cmd: ', cmd);
process.abort();
} else {
console.log('✅ [Rsync] finished.', stdout);
}
});
} catch (err) {
console.error('⚠️ [Rsync] command error: ', err.message, err.stack);
process.abort();
}
};
const init = ({ src, dest, args, host = 'localhost', port, username, privateKeyContent }) => {
validateRsync(() => {
const privateKey = addSshKey(privateKeyContent, DEPLOY_KEY_NAME || 'deploy_key');
const remoteDest = `${username}@${host}:${dest}`;
rsync({ privateKey, port, src, dest: remoteDest, args });
});
};
return {
init
};
})();
const run = () => {
validateInputs({ SSH_PRIVATE_KEY, REMOTE_HOST, REMOTE_USER });
sshDeploy.init({
src: `${GITHUB_WORKSPACE}/${SOURCE}` || '',
dest: TARGET || `/home/${REMOTE_USER}/`,
args: ARGS ? [ARGS] : ['-rltgoDzvO'],
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);
/***/ }),
/***/ 735:
/***/ (function(module, __unusedexports, __webpack_require__) {
const { sync: commandExists } = __webpack_require__(677);
const { get: nodeCmd } = __webpack_require__(428);
const validateRsync = (callback = () => {}) => {
const rsyncCli = commandExists('rsync');
if (!rsyncCli) {
nodeCmd(
'sudo apt-get --no-install-recommends install rsync',
(err, data, stderr) => {
if (err) {
console.log('⚠️ [CLI] Rsync installation failed. Aborting ... ', err.message);
process.abort();
} else {
console.log('✅ [CLI] Rsync installed. \n', data, stderr);
callback();
}
}
);
} else {
callback();
}
};
const validateInputs = (inputs) => {
const inputKeys = Object.keys(inputs);
const validInputs = inputKeys.filter((inputKey) => {
const inputValue = inputs[inputKey];
if (!inputValue) {
console.error(`⚠️ [INPUTS] ${inputKey} is mandatory`);
}
return inputValue;
});
if (validInputs.length !== inputKeys.length) {
console.error(`⚠️ [INPUTS] Inputs not valid, aborting ...`);
process.abort();
}
};
module.exports = {
validateRsync,
validateInputs
}
/***/ }),
/***/ 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"

1687
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

37
package.json Normal file
View File

@@ -0,0 +1,37 @@
{
"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",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-plugin-import": "^2.20.2"
},
"scripts": {
"build": "npm run lint && ncc build ./src/index.js -o dist",
"lint": "eslint ./src/index.js",
"lint:fix": "eslint ./src/index.js --fix"
},
"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"
}

38
src/helpers.js Normal file
View File

@@ -0,0 +1,38 @@
const { existsSync, mkdirSync, writeFileSync } = require('fs');
const {
GITHUB_WORKSPACE
} = process.env;
const validateDir = (dir) => {
if (!existsSync(dir)) {
console.log(`[SSH] Creating ${dir} dir in `, GITHUB_WORKSPACE);
mkdirSync(dir);
console.log('✅ [SSH] dir created.');
} else {
console.log(`[SSH] ${dir} dir exist`);
}
};
const validateFile = (filePath) => {
if (!existsSync(filePath)) {
console.log(`[SSH] Creating ${filePath} file in `, GITHUB_WORKSPACE);
try {
writeFileSync(filePath, '', {
encoding: 'utf8',
mode: 0o600
});
console.log('✅ [SSH] file created.');
} catch (e) {
console.error('⚠️ [SSH] writeFileSync error', filePath, e.message);
process.abort();
}
} else {
console.log(`[SSH] ${filePath} file exist`);
}
};
module.exports = {
validateDir,
validateFile
};

75
src/index.js Normal file
View File

@@ -0,0 +1,75 @@
#!/usr/bin/env node
const nodeRsync = require('rsyncwrapper');
const { validateRsync, validateInputs } = require('./rsyncCli');
const { addSshKey } = require('./sshKey');
const {
REMOTE_HOST, REMOTE_USER,
REMOTE_PORT, SSH_PRIVATE_KEY, DEPLOY_KEY_NAME,
SOURCE, TARGET, ARGS,
GITHUB_WORKSPACE
} = process.env;
const defaultOptions = {
ssh: true,
sshCmdArgs: ['-o StrictHostKeyChecking=no'],
recursive: true
};
console.log('[general] GITHUB_WORKSPACE: ', GITHUB_WORKSPACE);
const sshDeploy = (() => {
const rsync = ({ privateKey, port, src, dest, args }) => {
console.log(`[Rsync] Starting Rsync Action: ${src} to ${dest}`);
try {
// RSYNC COMMAND
nodeRsync({
src, dest, args, privateKey, port, ...defaultOptions
}, (error, stdout, stderr, cmd) => {
if (error) {
console.error('⚠️ [Rsync] error: ', error.message);
console.log('⚠️ [Rsync] stderr: ', stderr);
console.log('⚠️ [Rsync] stdout: ', stdout);
console.log('⚠️ [Rsync] cmd: ', cmd);
process.abort();
} else {
console.log('✅ [Rsync] finished.', stdout);
}
});
} catch (err) {
console.error('⚠️ [Rsync] command error: ', err.message, err.stack);
process.abort();
}
};
const init = ({ src, dest, args, host = 'localhost', port, username, privateKeyContent }) => {
validateRsync(() => {
const privateKey = addSshKey(privateKeyContent, DEPLOY_KEY_NAME || 'deploy_key');
const remoteDest = `${username}@${host}:${dest}`;
rsync({ privateKey, port, src, dest: remoteDest, args });
});
};
return {
init
};
})();
const run = () => {
validateInputs({ SSH_PRIVATE_KEY, REMOTE_HOST, REMOTE_USER });
sshDeploy.init({
src: `${GITHUB_WORKSPACE}/${SOURCE}` || '',
dest: TARGET || `/home/${REMOTE_USER}/`,
args: ARGS ? [ARGS] : ['-rltgoDzvO'],
host: REMOTE_HOST,
port: REMOTE_PORT || '22',
username: REMOTE_USER,
privateKeyContent: SSH_PRIVATE_KEY
});
};
run();

46
src/rsyncCli.js Normal file
View File

@@ -0,0 +1,46 @@
const { sync: commandExists } = require('command-exists');
const { get: nodeCmd } = require('node-cmd');
const validateRsync = (callback = () => {}) => {
const rsyncCli = commandExists('rsync');
if (!rsyncCli) {
nodeCmd(
'sudo apt-get --no-install-recommends install rsync',
(err, data, stderr) => {
if (err) {
console.log('⚠️ [CLI] Rsync installation failed. Aborting ... ', err.message);
process.abort();
} else {
console.log('✅ [CLI] Rsync installed. \n', data, stderr);
callback();
}
}
);
} else {
callback();
}
};
const validateInputs = (inputs) => {
const inputKeys = Object.keys(inputs);
const validInputs = inputKeys.filter((inputKey) => {
const inputValue = inputs[inputKey];
if (!inputValue) {
console.error(`⚠️ [INPUTS] ${inputKey} is mandatory`);
}
return inputValue;
});
if (validInputs.length !== inputKeys.length) {
console.error(`⚠️ [INPUTS] Inputs not valid, aborting ...`);
process.abort();
}
};
module.exports = {
validateRsync,
validateInputs
}

37
src/sshKey.js Normal file
View File

@@ -0,0 +1,37 @@
const { writeFileSync } = require('fs');
const { join } = require('path');
const {
validateDir,
validateFile
} = require('./helpers');
const {
HOME
} = process.env;
const addSshKey = (key, name) => {
const sshDir = join(HOME || __dirname, '.ssh');
const filePath = join(sshDir, name);
validateDir(sshDir);
validateFile(`${sshDir}/known_hosts`);
try {
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;
};
module.exports = {
addSshKey
}