[engine] use NodeJS instead of Docker
This commit is contained in:
		
							parent
							
								
									3c2d851430
								
							
						
					
					
						commit
						81256f2671
					
				
							
								
								
									
										19
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal 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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										16
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								Dockerfile
									
									
									
									
									
								
							@ -1,16 +0,0 @@
 | 
			
		||||
FROM debian:9.5-slim
 | 
			
		||||
 | 
			
		||||
# 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"]
 | 
			
		||||
							
								
								
									
										27
									
								
								action.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								action.yaml
									
									
									
									
									
										Normal 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: 'node10'
 | 
			
		||||
  main: 'dist/index.js'
 | 
			
		||||
branding:
 | 
			
		||||
  color: 'green'
 | 
			
		||||
  icon: 'truck'
 | 
			
		||||
							
								
								
									
										646
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
										Executable file
									
								
							
							
						
						
									
										646
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
										Executable 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");
 | 
			
		||||
 | 
			
		||||
/***/ })
 | 
			
		||||
 | 
			
		||||
/******/ });
 | 
			
		||||
@ -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
									
								
							
							
						
						
									
										29
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal 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
									
								
							
							
						
						
									
										32
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "ssh-deploy",
 | 
			
		||||
  "version": "2.0.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"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										151
									
								
								src/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								src/index.js
									
									
									
									
									
										Normal 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();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user