mirror of
				https://github.com/easingthemes/ssh-deploy.git
				synced 2025-10-31 23:40:10 +08:00 
			
		
		
		
	Add option to execute script before and after rsync
This commit is contained in:
		
							parent
							
								
									0e7d8019db
								
							
						
					
					
						commit
						5dfe8ebb5a
					
				
							
								
								
									
										12
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								.eslintrc.js
									
									
									
									
									
								
							| @ -12,14 +12,14 @@ module.exports = { | |||||||
|     SharedArrayBuffer: 'readonly' |     SharedArrayBuffer: 'readonly' | ||||||
|   }, |   }, | ||||||
|   parserOptions: { |   parserOptions: { | ||||||
|     ecmaVersion: 2018, |     ecmaVersion: 2018 | ||||||
|   }, |   }, | ||||||
|   rules: { |   rules: { | ||||||
|     "comma-dangle": [ |     'comma-dangle': [ | ||||||
|       "error", |       'error', | ||||||
|       "never" |       'never' | ||||||
|     ], |     ], | ||||||
|     "no-console": "off", |     'no-console': 'off', | ||||||
|     "object-curly-newline": "off" |     'object-curly-newline': 'off' | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  | |||||||
							
								
								
									
										9
									
								
								.github/workflows/e2e.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/e2e.yml
									
									
									
									
										vendored
									
									
								
							| @ -2,7 +2,7 @@ name: e2e Test | |||||||
| 
 | 
 | ||||||
| on: | on: | ||||||
|   push: |   push: | ||||||
|     branches: [ 'main' ] |     branches: [ 'feature/ssh-cmd' ] | ||||||
| 
 | 
 | ||||||
| env: | env: | ||||||
|   TEST_HOST_DOCKER: ./test |   TEST_HOST_DOCKER: ./test | ||||||
| @ -55,7 +55,7 @@ jobs: | |||||||
|           cat index.html |           cat index.html | ||||||
| 
 | 
 | ||||||
|       - name: e2e Test published ssh-deploy action |       - name: e2e Test published ssh-deploy action | ||||||
|         uses: easingthemes/ssh-deploy@v3.1.0 |         uses: easingthemes/ssh-deploy@feature/ssh-cmd | ||||||
|         with: |         with: | ||||||
|           # SSH_PRIVATE_KEY: $EXAMPLE_SSH_PRIVATE_KEY |           # SSH_PRIVATE_KEY: $EXAMPLE_SSH_PRIVATE_KEY | ||||||
|           # REMOTE_HOST: $EXAMPLE_REMOTE_HOST1 |           # REMOTE_HOST: $EXAMPLE_REMOTE_HOST1 | ||||||
| @ -64,6 +64,9 @@ jobs: | |||||||
|           SOURCE: "test_project/" |           SOURCE: "test_project/" | ||||||
|           TARGET: "/var/www/html/" |           TARGET: "/var/www/html/" | ||||||
|           EXCLUDE: "/dist/, /node_modules/" |           EXCLUDE: "/dist/, /node_modules/" | ||||||
|           SCRIPT: | |           SCRIPT_BEFORE: | | ||||||
|  |             whoami | ||||||
|  |             ls -al | ||||||
|  |           SCRIPT_AFTER: | | ||||||
|             whoami |             whoami | ||||||
|             ls -al |             ls -al | ||||||
|  | |||||||
| @ -107,13 +107,13 @@ jobs: | |||||||
| 
 | 
 | ||||||
| ## Issues | ## Issues | ||||||
| 
 | 
 | ||||||
| This is a Github Action wrapping `rsync` via `ssh`. Only issues with action functionality can be fixed here. | This is a GitHub Action wrapping `rsync` via `ssh`. Only issues with action functionality can be fixed here. | ||||||
| 
 | 
 | ||||||
| Almost 95% of the issues are related to wrong SSH connection or `rsync` params and permissions. | Almost 95% of the issues are related to wrong SSH connection or `rsync` params and permissions. | ||||||
| This issues are not related to the action itself. | These issues are not related to the action itself. | ||||||
| 
 | 
 | ||||||
| - Check manually your ssh connection from your client before opening a bug report. | - Check manually your ssh connection from your client before opening a bug report. | ||||||
| - Check `rsync` params for your usecase. Default params are not going to be enough wor everyone, it highly depends on your setup. | - Check `rsync` params for your use-case. Default params are not going to be enough wor everyone, it highly depends on your setup. | ||||||
| - Check manually your rsync command from your client before opening a bug report. | - Check manually your rsync command from your client before opening a bug report. | ||||||
| 
 | 
 | ||||||
| I've added e2e test for this action. | I've added e2e test for this action. | ||||||
|  | |||||||
							
								
								
									
										10
									
								
								action.yml
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								action.yml
									
									
									
									
									
								
							| @ -31,10 +31,14 @@ inputs: | |||||||
|     description: "An array of folder to exclude" |     description: "An array of folder to exclude" | ||||||
|     required: false |     required: false | ||||||
|     default: "" |     default: "" | ||||||
|   SCRIPT: |   SCRIPT_BEFORE: | ||||||
|     description: "Script to run on host machine" |     description: "Script to run on host machine before rsync" | ||||||
|     required: false |     required: false | ||||||
|     default: "ls" |     default: "echo 'Before rsync'" | ||||||
|  |   SCRIPT_AFTER: | ||||||
|  |     description: "Script to run on host machine after rsync" | ||||||
|  |     required: false | ||||||
|  |     default: "echo 'After rsync'" | ||||||
| outputs: | outputs: | ||||||
|   status: |   status: | ||||||
|     description: "Status" |     description: "Status" | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										68
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										68
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,16 +1,16 @@ | |||||||
| { | { | ||||||
|   "name": "@draganfilipovic/ssh-deploy", |   "name": "@draganfilipovic/ssh-deploy", | ||||||
|   "version": "3.0.1", |   "version": "3.1.0", | ||||||
|   "lockfileVersion": 2, |   "lockfileVersion": 2, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "packages": { |   "packages": { | ||||||
|     "": { |     "": { | ||||||
|       "name": "@draganfilipovic/ssh-deploy", |       "name": "@draganfilipovic/ssh-deploy", | ||||||
|       "version": "3.0.1", |       "version": "3.1.0", | ||||||
|       "license": "MIT", |       "license": "MIT", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "command-exists": "^1.2.9", |         "rsyncwrapper": "^3.0.1", | ||||||
|         "rsyncwrapper": "^3.0.1" |         "which": "^3.0.0" | ||||||
|       }, |       }, | ||||||
|       "devDependencies": { |       "devDependencies": { | ||||||
|         "@vercel/ncc": "^0.36.0", |         "@vercel/ncc": "^0.36.0", | ||||||
| @ -301,11 +301,6 @@ | |||||||
|       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", |       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "node_modules/command-exists": { |  | ||||||
|       "version": "1.2.9", |  | ||||||
|       "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", |  | ||||||
|       "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" |  | ||||||
|     }, |  | ||||||
|     "node_modules/concat-map": { |     "node_modules/concat-map": { | ||||||
|       "version": "0.0.1", |       "version": "0.0.1", | ||||||
|       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", |       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", | ||||||
| @ -332,6 +327,21 @@ | |||||||
|         "node": ">= 8" |         "node": ">= 8" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/cross-spawn/node_modules/which": { | ||||||
|  |       "version": "2.0.2", | ||||||
|  |       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", | ||||||
|  |       "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", | ||||||
|  |       "dev": true, | ||||||
|  |       "dependencies": { | ||||||
|  |         "isexe": "^2.0.0" | ||||||
|  |       }, | ||||||
|  |       "bin": { | ||||||
|  |         "node-which": "bin/node-which" | ||||||
|  |       }, | ||||||
|  |       "engines": { | ||||||
|  |         "node": ">= 8" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/debug": { |     "node_modules/debug": { | ||||||
|       "version": "4.3.4", |       "version": "4.3.4", | ||||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", |       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", | ||||||
| @ -1280,8 +1290,7 @@ | |||||||
|     "node_modules/isexe": { |     "node_modules/isexe": { | ||||||
|       "version": "2.0.0", |       "version": "2.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", |       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", | ||||||
|       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", |       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" | ||||||
|       "dev": true |  | ||||||
|     }, |     }, | ||||||
|     "node_modules/js-sdsl": { |     "node_modules/js-sdsl": { | ||||||
|       "version": "4.2.0", |       "version": "4.2.0", | ||||||
| @ -1923,18 +1932,17 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/which": { |     "node_modules/which": { | ||||||
|       "version": "2.0.2", |       "version": "3.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", |       "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", | ||||||
|       "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", |       "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", | ||||||
|       "dev": true, |  | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "isexe": "^2.0.0" |         "isexe": "^2.0.0" | ||||||
|       }, |       }, | ||||||
|       "bin": { |       "bin": { | ||||||
|         "node-which": "bin/node-which" |         "node-which": "bin/which.js" | ||||||
|       }, |       }, | ||||||
|       "engines": { |       "engines": { | ||||||
|         "node": ">= 8" |         "node": "^14.17.0 || ^16.13.0 || >=18.0.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/which-boxed-primitive": { |     "node_modules/which-boxed-primitive": { | ||||||
| @ -2188,11 +2196,6 @@ | |||||||
|       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", |       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", | ||||||
|       "dev": true |       "dev": true | ||||||
|     }, |     }, | ||||||
|     "command-exists": { |  | ||||||
|       "version": "1.2.9", |  | ||||||
|       "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", |  | ||||||
|       "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==" |  | ||||||
|     }, |  | ||||||
|     "concat-map": { |     "concat-map": { | ||||||
|       "version": "0.0.1", |       "version": "0.0.1", | ||||||
|       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", |       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", | ||||||
| @ -2214,6 +2217,17 @@ | |||||||
|         "path-key": "^3.1.0", |         "path-key": "^3.1.0", | ||||||
|         "shebang-command": "^2.0.0", |         "shebang-command": "^2.0.0", | ||||||
|         "which": "^2.0.1" |         "which": "^2.0.1" | ||||||
|  |       }, | ||||||
|  |       "dependencies": { | ||||||
|  |         "which": { | ||||||
|  |           "version": "2.0.2", | ||||||
|  |           "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", | ||||||
|  |           "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", | ||||||
|  |           "dev": true, | ||||||
|  |           "requires": { | ||||||
|  |             "isexe": "^2.0.0" | ||||||
|  |           } | ||||||
|  |         } | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "debug": { |     "debug": { | ||||||
| @ -2912,8 +2926,7 @@ | |||||||
|     "isexe": { |     "isexe": { | ||||||
|       "version": "2.0.0", |       "version": "2.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", |       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", | ||||||
|       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", |       "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" | ||||||
|       "dev": true |  | ||||||
|     }, |     }, | ||||||
|     "js-sdsl": { |     "js-sdsl": { | ||||||
|       "version": "4.2.0", |       "version": "4.2.0", | ||||||
| @ -3360,10 +3373,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "which": { |     "which": { | ||||||
|       "version": "2.0.2", |       "version": "3.0.0", | ||||||
|       "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", |       "resolved": "https://registry.npmjs.org/which/-/which-3.0.0.tgz", | ||||||
|       "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", |       "integrity": "sha512-nla//68K9NU6yRiwDY/Q8aU6siKlSs64aEC7+IV56QoAuyQT2ovsJcgGYGyqMOmI/CGN1BOR6mM5EN0FBO+zyQ==", | ||||||
|       "dev": true, |  | ||||||
|       "requires": { |       "requires": { | ||||||
|         "isexe": "^2.0.0" |         "isexe": "^2.0.0" | ||||||
|       } |       } | ||||||
|  | |||||||
| @ -30,8 +30,8 @@ | |||||||
|   }, |   }, | ||||||
|   "homepage": "https://github.com/easingthemes/ssh-deploy#readme", |   "homepage": "https://github.com/easingthemes/ssh-deploy#readme", | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "command-exists": "^1.2.9", |     "rsyncwrapper": "^3.0.1", | ||||||
|     "rsyncwrapper": "^3.0.1" |     "which": "^3.0.0" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@vercel/ncc": "^0.36.0", |     "@vercel/ncc": "^0.36.0", | ||||||
|  | |||||||
| @ -1,38 +1,60 @@ | |||||||
| const { existsSync, mkdirSync, writeFileSync } = require('fs'); | const { existsSync, mkdirSync, writeFileSync } = require('fs'); | ||||||
| 
 | const { join } = require('path'); | ||||||
| const { |  | ||||||
|   GITHUB_WORKSPACE |  | ||||||
| } = process.env; |  | ||||||
| 
 | 
 | ||||||
| const validateDir = (dir) => { | const validateDir = (dir) => { | ||||||
|   if (!existsSync(dir)) { |   if (existsSync(dir)) { | ||||||
|     console.log(`[SSH] Creating ${dir} dir in `, GITHUB_WORKSPACE); |     console.log(`[SSH] ${dir} dir exist`); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   console.log(`[SSH] Creating ${dir} dir in workspace root`); | ||||||
|   mkdirSync(dir); |   mkdirSync(dir); | ||||||
|   console.log('✅ [SSH] dir created.'); |   console.log('✅ [SSH] dir created.'); | ||||||
|   } else { |  | ||||||
|     console.log(`[SSH] ${dir} dir exist`); |  | ||||||
|   } |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const validateFile = (filePath) => { | const writeToFile = ({ dir, filename, content, isRequired }) => { | ||||||
|   if (!existsSync(filePath)) { |   validateDir(dir); | ||||||
|     console.log(`[SSH] Creating ${filePath} file in `, GITHUB_WORKSPACE); |   const filePath = join(dir, filename); | ||||||
|  | 
 | ||||||
|  |   if (existsSync(filePath)) { | ||||||
|  |     console.log(`[FILE] ${filePath} file exist`); | ||||||
|  |     if (isRequired) { | ||||||
|  |       throw new Error(`⚠️ [FILE] ${filePath} Required file exist, aborting ...`); | ||||||
|  |     } | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   try { |   try { | ||||||
|       writeFileSync(filePath, '', { |     writeFileSync(filePath, content, { | ||||||
|       encoding: 'utf8', |       encoding: 'utf8', | ||||||
|       mode: 0o600 |       mode: 0o600 | ||||||
|     }); |     }); | ||||||
|       console.log('✅ [SSH] file created.'); |  | ||||||
|   } catch (e) { |   } catch (e) { | ||||||
|       console.error('⚠️ [SSH] writeFileSync error', filePath, e.message); |     throw new Error(`⚠️[FILE] Writing to file error. filePath: ${filePath}, message:  ${e.message}`); | ||||||
|       process.abort(); |  | ||||||
|     } |  | ||||||
|   } else { |  | ||||||
|     console.log(`[SSH] ${filePath} file exist`); |  | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| module.exports = { | const validateRequiredInputs = (inputs) => { | ||||||
|   validateDir, |   const inputKeys = Object.keys(inputs); | ||||||
|   validateFile |   const validInputs = inputKeys.filter((inputKey) => { | ||||||
|  |     const inputValue = inputs[inputKey]; | ||||||
|  | 
 | ||||||
|  |     if (!inputValue) { | ||||||
|  |       console.error(`⚠️ [INPUTS] ${inputKey} is mandatory`); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return inputValue; | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   if (validInputs.length !== inputKeys.length) { | ||||||
|  |     throw new Error('⚠️ [INPUTS] Inputs not valid, aborting ...'); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const snakeToCamel = (str) => str.replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase()); | ||||||
|  | 
 | ||||||
|  | module.exports = { | ||||||
|  |   writeToFile, | ||||||
|  |   validateRequiredInputs, | ||||||
|  |   snakeToCamel | ||||||
| }; | }; | ||||||
|  | |||||||
							
								
								
									
										101
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								src/index.js
									
									
									
									
									
								
							| @ -1,78 +1,37 @@ | |||||||
| #!/usr/bin/env node
 | #!/usr/bin/env node
 | ||||||
| const nodeRsync = require('rsyncwrapper'); | const { sshDeploy } = require('./rsyncCli'); | ||||||
| 
 | const { remoteCmdBefore, remoteCmdAfter } = require('./remoteCmd'); | ||||||
| const { validateRsync, validateInputs } = require('./rsyncCli'); |  | ||||||
| const { addSshKey } = require('./sshKey'); | const { addSshKey } = require('./sshKey'); | ||||||
| 
 | const { validateRequiredInputs } = require('./helpers'); | ||||||
| const { | const inputs = require('./inputs'); | ||||||
|   REMOTE_HOST, REMOTE_USER, |  | ||||||
|   REMOTE_PORT, SSH_PRIVATE_KEY, DEPLOY_KEY_NAME, |  | ||||||
|   SOURCE, TARGET, ARGS, EXCLUDE, |  | ||||||
|   GITHUB_WORKSPACE |  | ||||||
| } = require('./inputs'); |  | ||||||
| 
 |  | ||||||
| const defaultOptions = { |  | ||||||
|   ssh: true, |  | ||||||
|   sshCmdArgs: ['-o StrictHostKeyChecking=no'], |  | ||||||
|   recursive: true |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| console.log('GITHUB_WORKSPACE: ', GITHUB_WORKSPACE); |  | ||||||
| console.log('REMOTE_HOST: ', process.env.REMOTE_HOST); |  | ||||||
| console.log('REMOTE_USER: ', process.env.REMOTE_USER); |  | ||||||
| 
 |  | ||||||
| const sshDeploy = (() => { |  | ||||||
|   const rsync = ({ privateKey, port, src, dest, args, exclude }) => { |  | ||||||
|     console.log(`[Rsync] Starting Rsync Action: ${src} to ${dest}`); |  | ||||||
|     if (exclude) console.log(`[Rsync] exluding folders ${exclude}`); |  | ||||||
| 
 |  | ||||||
|     try { |  | ||||||
|       // RSYNC COMMAND
 |  | ||||||
|       nodeRsync({ |  | ||||||
|         src, dest, args, privateKey, port, excludeFirst: exclude, ...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, exclude = [] }) => { |  | ||||||
|     validateRsync(() => { |  | ||||||
|       const privateKey = addSshKey(privateKeyContent, DEPLOY_KEY_NAME || 'deploy_key'); |  | ||||||
|       const remoteDest = `${username}@${host}:${dest}`; |  | ||||||
| 
 |  | ||||||
|       rsync({ privateKey, port, src, dest: remoteDest, args, exclude }); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   return { |  | ||||||
|     init |  | ||||||
|   }; |  | ||||||
| })(); |  | ||||||
| 
 | 
 | ||||||
| const run = () => { | const run = () => { | ||||||
|   validateInputs({ SSH_PRIVATE_KEY, REMOTE_HOST, REMOTE_USER }); |   const { | ||||||
| 
 |     source, remoteUser, remoteHost, remotePort, | ||||||
|   sshDeploy.init({ |     deployKeyName, sshPrivateKey, | ||||||
|     src: `${GITHUB_WORKSPACE}/${SOURCE || ''}`, |     args, exclude, | ||||||
|     dest: TARGET || `/home/${REMOTE_USER}/`, |     scriptBefore, scriptAfter, | ||||||
|     args: ARGS ? [ARGS] : ['-rltgoDzvO'], |     sshServer | ||||||
|     host: REMOTE_HOST, |   } = inputs; | ||||||
|     port: REMOTE_PORT || '22', |   // Validate required inputs
 | ||||||
|     username: REMOTE_USER, |   validateRequiredInputs({ sshPrivateKey, remoteHost, remoteUser }); | ||||||
|     privateKeyContent: SSH_PRIVATE_KEY, |   // Add SSH key
 | ||||||
|     exclude: (EXCLUDE || '').split(',').map((item) => item.trim()) // split by comma and trim whitespace
 |   const privateKey = addSshKey(sshPrivateKey, deployKeyName); | ||||||
|  |   // Check Script before
 | ||||||
|  |   if (scriptBefore) { | ||||||
|  |     remoteCmdBefore(scriptBefore); | ||||||
|  |   } | ||||||
|  |   // Check script after
 | ||||||
|  |   let callback = () => {}; | ||||||
|  |   if (scriptAfter) { | ||||||
|  |     callback = (...result) => { | ||||||
|  |       remoteCmdAfter(scriptAfter, result); | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |   /* eslint-disable object-property-newline */ | ||||||
|  |   sshDeploy({ | ||||||
|  |     source, sshServer, exclude, remotePort, | ||||||
|  |     privateKey, args, callback | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,11 +1,47 @@ | |||||||
| const inputNames = ['REMOTE_HOST', 'REMOTE_USER', 'REMOTE_PORT', 'SSH_PRIVATE_KEY', 'DEPLOY_KEY_NAME', 'SOURCE', 'TARGET', 'ARGS', 'EXCLUDE']; | const { snakeToCamel } = require('./helpers'); | ||||||
|  | 
 | ||||||
|  | const inputNames = [ | ||||||
|  |   'REMOTE_HOST', 'REMOTE_USER', 'REMOTE_PORT', | ||||||
|  |   'SSH_PRIVATE_KEY', 'DEPLOY_KEY_NAME', | ||||||
|  |   'SOURCE', 'TARGET', 'ARGS', 'EXCLUDE', | ||||||
|  |   'SCRIPT_BEFORE', 'SCRIPT_AFTER']; | ||||||
|  | 
 | ||||||
|  | const githubWorkspace = process.env.GITHUB_WORKSPACE; | ||||||
|  | const remoteUser = process.env.REMOTE_USER; | ||||||
|  | 
 | ||||||
|  | const defaultInputs = { | ||||||
|  |   source: '', // TODO
 | ||||||
|  |   target: `/home/${remoteUser}/`, | ||||||
|  |   exclude: '', // TODO
 | ||||||
|  |   args: '-rltgoDzvO', // TODO
 | ||||||
|  |   deployKeyName: 'deploy_key' | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| const inputs = { | const inputs = { | ||||||
|   GITHUB_WORKSPACE: process.env.GITHUB_WORKSPACE |   githubWorkspace | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| inputNames.forEach((input) => { | inputNames.forEach((input) => { | ||||||
|   inputs[input] = process.env[input] || process.env[`INPUT_${input}`]; |   const inputName = snakeToCamel(input.toLowerCase()); | ||||||
|  |   const inputVal = process.env[input] || process.env[`INPUT_${input}`]; | ||||||
|  |   const validVal = inputVal === undefined ? defaultInputs[inputName] : inputVal; | ||||||
|  |   let extendedVal = validVal; | ||||||
|  |   // eslint-disable-next-line default-case
 | ||||||
|  |   switch (inputName) { | ||||||
|  |     case 'source': | ||||||
|  |       extendedVal = `${githubWorkspace}/${validVal}`; | ||||||
|  |       break; | ||||||
|  |     case 'exclude': | ||||||
|  |       extendedVal = validVal.split(',').map((item) => item.trim()); | ||||||
|  |       break; | ||||||
|  |     case 'args': | ||||||
|  |       extendedVal = [validVal]; | ||||||
|  |       break; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   inputs[inputName] = extendedVal; | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | inputs.sshServer = `${inputs.remoteUser}@${inputs.remoteHost}:${inputs.target}`; | ||||||
|  | 
 | ||||||
| module.exports = inputs; | module.exports = inputs; | ||||||
|  | |||||||
| @ -0,0 +1,27 @@ | |||||||
|  | const { join } = require('path'); | ||||||
|  | const { exec } = require('child_process'); | ||||||
|  | 
 | ||||||
|  | const { sshServer, githubWorkspace } = require('./inputs'); | ||||||
|  | const { writeToFile } = require('./helpers'); | ||||||
|  | 
 | ||||||
|  | const remoteCmd = (cmd, label) => { | ||||||
|  |   const localScriptPath = join(githubWorkspace, `local_ssh_script-${label}.sh`); | ||||||
|  |   try { | ||||||
|  |     writeToFile(localScriptPath, cmd); | ||||||
|  | 
 | ||||||
|  |     exec(`ssh ${sshServer} 'bash -s' < ${localScriptPath}`, (err, data, stderr) => { | ||||||
|  |       if (err) { | ||||||
|  |         console.log('⚠️ [CMD] Remote script failed. ', err.message); | ||||||
|  |       } else { | ||||||
|  |         console.log('✅ [CMD] Remote script executed. \n', data, stderr); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } catch (err) { | ||||||
|  |     console.log('⚠️ [CMD] Starting Remote script execution failed. ', err.message); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | module.exports = { | ||||||
|  |   remoteCmdBefore: (cmd) => remoteCmd(cmd, 'before'), | ||||||
|  |   remoteCmdAfter: (cmd) => remoteCmd(cmd, 'after') | ||||||
|  | }; | ||||||
| @ -1,46 +1,78 @@ | |||||||
| const { sync: commandExists } = require("command-exists"); | const { execSync } = require('child_process'); | ||||||
| const { exec, execSync } = require("child_process"); | const which = require('which'); | ||||||
|  | const nodeRsync = require('rsyncwrapper'); | ||||||
| 
 | 
 | ||||||
| const validateRsync = (callback = () => {}) => { | const validateRsync = async () => { | ||||||
|   const rsyncCli = commandExists("rsync"); |   const rsyncCli = await which('rsync', { nothrow: true }); | ||||||
|  |   execSync('rsync --version', { stdio: 'inherit' }); | ||||||
|   if (rsyncCli) { |   if (rsyncCli) { | ||||||
|     console.log('⚠️ [CLI] Rsync exists'); |     console.log('⚠️ [CLI] Rsync exists'); | ||||||
|     execSync("rsync --version", { stdio: 'inherit' }); |     execSync('rsync --version', { stdio: 'inherit' }); | ||||||
|     return callback(); |     return; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   console.log('⚠️ [CLI] Rsync doesn\'t exists. Start installation with "apt-get" \n'); |   console.log('⚠️ [CLI] Rsync doesn\'t exists. Start installation with "apt-get" \n'); | ||||||
| 
 | 
 | ||||||
|   exec("sudo apt-get update && sudo apt-get --no-install-recommends install rsync", (err, data, stderr) => { |   try { | ||||||
|     if (err) { |     execSync('sudo apt-get update && sudo apt-get --no-install-recommends install rsync', { stdio: 'inherit' }); | ||||||
|       console.log("⚠️ [CLI] Rsync installation failed. Aborting ... ", err.message); |     console.log('✅ [CLI] Rsync installed. \n'); | ||||||
|       process.abort(); |   } catch (err) { | ||||||
|     } else { |     throw new Error(`⚠️ [CLI] Rsync installation failed. Aborting ... error: ${err.message}`); | ||||||
|       console.log("✅ [CLI] Rsync installed. \n", data, stderr); |  | ||||||
|       callback(); |  | ||||||
|   } |   } | ||||||
|   }); |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const validateInputs = (inputs) => { | const rsyncCli = ({ | ||||||
|   const inputKeys = Object.keys(inputs); |   source, sshServer, exclude, remotePort, | ||||||
|   const validInputs = inputKeys.filter((inputKey) => { |   privateKey, args, callback | ||||||
|     const inputValue = inputs[inputKey]; | }) => { | ||||||
|  |   console.log(`[Rsync] Starting Rsync Action: ${source} to ${sshServer}`); | ||||||
|  |   if (exclude) console.log(`[Rsync] excluding folders ${exclude}`); | ||||||
| 
 | 
 | ||||||
|     if (!inputValue) { |   const defaultOptions = { | ||||||
|       console.error(`⚠️ [INPUTS] ${inputKey} is mandatory`); |     ssh: true, | ||||||
|  |     sshCmdArgs: ['-o StrictHostKeyChecking=no'], | ||||||
|  |     recursive: true | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     // RSYNC COMMAND
 | ||||||
|  |     /* eslint-disable object-property-newline */ | ||||||
|  |     nodeRsync({ | ||||||
|  |       src: source, dest: sshServer, excludeFirst: exclude, port: remotePort, | ||||||
|  |       privateKey, args, callback, | ||||||
|  |       ...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); | ||||||
|  |       } else { | ||||||
|  |         console.log('✅ [Rsync] finished.', stdout); | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|     return inputValue; |       callback(error, stdout, stderr, cmd); | ||||||
|   }); |  | ||||||
| 
 | 
 | ||||||
|   if (validInputs.length !== inputKeys.length) { |       if (error) { | ||||||
|     console.error("⚠️ [INPUTS] Inputs not valid, aborting ..."); |  | ||||||
|         process.abort(); |         process.abort(); | ||||||
|       } |       } | ||||||
|  |     }); | ||||||
|  |   } catch (err) { | ||||||
|  |     console.error('⚠️ [Rsync] command error: ', err.message, err.stack); | ||||||
|  |     process.abort(); | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const sshDeploy = (params) => { | ||||||
|  |   validateRsync | ||||||
|  |     .then(() => { | ||||||
|  |       rsyncCli(params); | ||||||
|  |     }) | ||||||
|  |     .catch((err) => { | ||||||
|  |       throw err; | ||||||
|  |     }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|   validateRsync, |   sshDeploy | ||||||
|   validateInputs, |  | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,37 +1,20 @@ | |||||||
| const { writeFileSync } = require('fs'); |  | ||||||
| const { join } = require('path'); | const { join } = require('path'); | ||||||
| 
 | 
 | ||||||
| const { | const { writeToFile } = require('./helpers'); | ||||||
|   validateDir, |  | ||||||
|   validateFile |  | ||||||
| } = require('./helpers'); |  | ||||||
| 
 | 
 | ||||||
| const { | const addSshKey = (content, filename) => { | ||||||
|   HOME |   const { HOME } = process.env; | ||||||
| } = process.env; |   const dir = join(HOME || __dirname, '.ssh'); | ||||||
|  |   const filePath = join(dir, filename); | ||||||
| 
 | 
 | ||||||
| const addSshKey = (key, name) => { |   writeToFile({ dir, filename: 'known_hosts', content: '' }); | ||||||
|   const sshDir = join(HOME || __dirname, '.ssh'); |   writeToFile({ dir, filename, content, isRequired: true }); | ||||||
|   const filePath = join(sshDir, name); |  | ||||||
| 
 | 
 | ||||||
|   validateDir(sshDir); |   console.log('✅ Ssh key added to `.ssh` dir ', dir); | ||||||
|   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; |   return filePath; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| module.exports = { | module.exports = { | ||||||
|   addSshKey |   addSshKey | ||||||
| } | }; | ||||||
|  | |||||||
							
								
								
									
										13
									
								
								src/test.js
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								src/test.js
									
									
									
									
									
								
							| @ -1,13 +0,0 @@ | |||||||
| console.log('||||||||||||||||||||||||||||||||||||||'); |  | ||||||
| console.log('EXAMPLE_REMOTE_HOST: ', process.env.EXAMPLE_REMOTE_HOST); |  | ||||||
| console.log('EXAMPLE_REMOTE_USER: ', process.env.EXAMPLE_REMOTE_USER); |  | ||||||
| console.log('EXAMPLE_SSH_PRIVATE_KEY: ', process.env.EXAMPLE_SSH_PRIVATE_KEY); |  | ||||||
| console.log('||||||||||||||||||||||||||||||||||||||'); |  | ||||||
| console.log('EXAMPLE_REMOTE_HOST1: ', process.env.EXAMPLE_REMOTE_HOST1); |  | ||||||
| console.log('EXAMPLE_REMOTE_USER1: ', process.env.EXAMPLE_REMOTE_USER1); |  | ||||||
| console.log('EXAMPLE_SSH_PRIVATE_KEY1: ', process.env.EXAMPLE_SSH_PRIVATE_KEY1); |  | ||||||
| console.log('||||||||||||||||||||||||||||||||||||||'); |  | ||||||
| console.log('REMOTE_USER: ', process.env.REMOTE_USER); |  | ||||||
| console.log('REMOTE_HOST: ', process.env.REMOTE_HOST); |  | ||||||
| console.log('SSH_PRIVATE_KEY: ', process.env.SSH_PRIVATE_KEY); |  | ||||||
| console.log('||||||||||||||||||||||||||||||||||||||'); |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Dragan Filipovic
						Dragan Filipovic