diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e7e594e..cace0c9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,13 +14,13 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node-version: [16.x] + node-version: [20.x] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install dependencies diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 781156d..6c93953 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -31,11 +31,11 @@ jobs: language: [ 'javascript' ] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} @@ -44,4 +44,4 @@ jobs: npm run build --if-present - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/e2e-manual.yml b/.github/workflows/e2e-manual.yml index bd4762f..11fb9f6 100644 --- a/.github/workflows/e2e-manual.yml +++ b/.github/workflows/e2e-manual.yml @@ -27,7 +27,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 # ---------------------------------------------------------------- # START E2E Test Specific - steps diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index cf1f758..09bf162 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 # : --------------------------------------------------------------- # : START E2E Test Specific - steps @@ -100,12 +100,18 @@ jobs: # Shared ENV Vars created in previous steps REMOTE_USER: ${{ env.TEST_USER2 }} TARGET: /var/www/html/${{ env.TEST_USER2 }} + + - name: e2e Test ssh-deploy action - Default values + uses: easingthemes/ssh-deploy@main + env: + # Shared ENV Vars created in previous steps + REMOTE_USER: ${{ env.TEST_USER }} e2e-v3: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 # : --------------------------------------------------------------- # : START E2E Test Specific - steps @@ -163,7 +169,7 @@ jobs: # ---------------------------------------------------------------- - name: e2e Test ssh-deploy action - Target 1 - uses: easingthemes/ssh-deploy@v3 + uses: easingthemes/ssh-deploy@main env: # Shared ENV Vars created in previous steps REMOTE_USER: ${{ env.TEST_USER }} diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index d713e2e..6545a29 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -6,11 +6,10 @@ on: description: 'Version' type: choice required: true - default: patch + default: fix options: - - patch - - minor - - major + - fix + - feat dryRun: description: 'DryRun' type: boolean @@ -50,9 +49,9 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node.js ${{ matrix.NODE_VERSION }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.NODE_VERSION }} - name: Commit trigger @@ -64,8 +63,8 @@ jobs: run: npm run build --if-present - name: Run Tests run: npm test --if-present - - name: Publish npm package - uses: cycjimmy/semantic-release-action@v3 + - name: Create a release - ${{ github.event.inputs.version }} + uses: cycjimmy/semantic-release-action@v4 with: dry_run: ${{ github.event.inputs.dryRun == 'true' }} extra_plugins: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b658860..0999d40 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,13 +12,13 @@ jobs: strategy: matrix: os: [ ubuntu-latest ] - node-version: [ 16.x ] + node-version: [ 20.x ] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix['node-version'] }} - name: Install dependencies @@ -28,7 +28,7 @@ jobs: - name: Run Tests run: npm test --if-present - name: Release - uses: cycjimmy/semantic-release-action@v3 + uses: cycjimmy/semantic-release-action@v4 with: dry_run: false extra_plugins: | diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 0a768d3..43ecafd 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -17,10 +17,10 @@ jobs: issues: write steps: - - uses: actions/stale@v7 + - uses: actions/stale@v9 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.' - days-before-stale: 30 - days-before-close: 5 + days-before-stale: 90 + days-before-close: 15 stale-issue-label: 'stale' diff --git a/README.md b/README.md index be84de2..a52946a 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ The keys should be generated using the PEM format. You can use this command ``` ssh-keygen -m PEM -t rsa -b 4096 ``` +**Please Note:** You should not set a Passphrase (keep it empty) for the private key you generated. +Because rsync ssh (used for deploy) does not support private key password to be entered as a command line parameter. ##### 2. `REMOTE_HOST` [required] @@ -65,25 +67,33 @@ Execution is preformed by storing commands in `.sh` file and executing it via `. If you have issues with `ssh` connection, use this var, eg `SCRIPT_BEFORE: ls`. This will force `known_hosts` update, adding your host via `ssh-keyscan`. -##### 10. `SCRIPT_AFTER` (optional, default '') +##### 10. `SCRIPT_BEFORE_REQUIRED` (optional, default false) + +If set to `true`, Job will fail if SCRIPT_BEFORE fails. + +##### 11. `SCRIPT_AFTER` (optional, default '') Script to run on host machine after rsync. Rsync output is stored in `$RSYNC_STDOUT` env variable. -##### 11. `SSH_CMD_ARGS` (optional, default '-o StrictHostKeyChecking=no') +##### 12. `SCRIPT_AFTER_REQUIRED` (optional, default false) + +If set to `true`, Job will fail if SCRIPT_AFTER fails. + +##### 13. `SSH_CMD_ARGS` (optional, default '-o StrictHostKeyChecking=no') A list of ssh arguments, they must be prefixed with -o and separated by a comma, for example: -o SomeArgument=no, -o SomeOtherArgument=5 # Usage -Use the latest version from Marketplace,eg: ssh-deploy@v2 +Use the latest version from Marketplace,eg: ssh-deploy@v5.1.0 or use the latest version from a branch, eg: ssh-deploy@main ``` - name: Deploy to Staging server uses: easingthemes/ssh-deploy@main - env: + with: SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} ARGS: "-rlgoDzvc -i" SOURCE: "dist/" @@ -113,18 +123,18 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Install Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: - node-version: '10.x' + node-version: '16.x' - name: Install npm dependencies run: npm install - name: Run build task run: npm run build --if-present - name: Deploy to Server uses: easingthemes/ssh-deploy@main - env: + with: SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} ARGS: "-rlgoDzvc -i --delete" SOURCE: "dist/" @@ -142,13 +152,18 @@ Almost 95% of the issues are related to wrong SSH connection or `rsync` params a These issues are not related to the action itself. - Check manually your ssh connection from your client before opening a bug report. -- Check `rsync` params for your use-case. 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 necessarily going to be enough for everyone, it highly depends on your setup. - Check manually your rsync command from your client before opening a bug report. +- `Deployment Failed, Permission denied (publickey,password)`: This issue occures in some cases, it is related to OS and ssh. This action can only provide a workaround: + - Use `SCRIPT_BEFORE` param, eg `SCRIPT_BEFORE: ls`. This will force `known_hosts` update, adding your host via `ssh-keyscan`. + - Or manually add public key to authorized_keys and add a new line to a private key. I've added e2e test for this action. Real example is executed on every PR merge to `main`. Check actions tab for example. +When opening an issue, please add example of your step with env vars. You can add dummy values. + More info for SSH keys: https://www.ssh.com/ssh/public-key-authentication ## Tips diff --git a/action.yml b/action.yml index 395f3e8..1c30b61 100644 --- a/action.yml +++ b/action.yml @@ -22,7 +22,7 @@ inputs: TARGET: description: "Target directory" required: false - default: "/home/REMOTE_USER/" + default: "" ARGS: description: "Arguments to pass to rsync" required: false @@ -39,15 +39,23 @@ inputs: description: "Script to run on host machine before rsync" required: false default: "" + SCRIPT_BEFORE_REQUIRED: + description: "If not an empty string, the action will fail if the before script fails. Note: The string 'false' will be treated as true" + required: false + default: "" SCRIPT_AFTER: description: "Script to run on host machine after rsync" required: false default: "" + SCRIPT_AFTER_REQUIRED: + description: "If not an empty string, the action will fail if the after script fails. Note: The string 'false' will be treated as true" + required: false + default: "" outputs: status: description: "Status" runs: - using: "node16" + using: "node20" main: "dist/index.js" branding: color: "green" diff --git a/dist/index.js b/dist/index.js index f6e07a5..b0a43df 100755 --- a/dist/index.js +++ b/dist/index.js @@ -1,2 +1,2 @@ #!/usr/bin/env node -(()=>{var e={898:(e,s,r)=>{"use strict";var o=r(81).spawn;var t=r(837);var escapeSpaces=function(e){if(typeof e==="string"){return e.replace(/\b\s/g,"\\ ")}else{return e}};var escapeSpacesInOptions=function(e){["src","dest","include","exclude","excludeFirst"].forEach((function(s){var r=e[s];if(typeof r==="string"){e[s]=escapeSpaces(r)}else if(Array.isArray(r)===true){e[s]=r.map(escapeSpaces)}}));return e};e.exports=function(e,s){e=e||{};e=t._extend({},e);e=escapeSpacesInOptions(e);var r=e.platform||process.platform;var n=r==="win32";if(typeof e.src==="undefined"){throw new Error("'src' directory is missing from options")}if(typeof e.dest==="undefined"){throw new Error("'dest' directory is missing from options")}var c=e.dest;if(typeof e.host!=="undefined"){c=e.host+":"+e.dest}if(!Array.isArray(e.src)){e.src=[e.src]}var i=[].concat(e.src);i.push(c);var a=(e.args||[]).find((function(e){return e.match(/--chmod=/)}));if(n&&!a){i.push("--chmod=ugo=rwX")}if(typeof e.host!=="undefined"||e.ssh){i.push("--rsh");var l="ssh";if(typeof e.port!=="undefined"){l+=" -p "+e.port}if(typeof e.privateKey!=="undefined"){l+=" -i "+e.privateKey}if(typeof e.sshCmdArgs!=="undefined"){l+=" "+e.sshCmdArgs.join(" ")}i.push(l)}if(e.recursive===true){i.push("--recursive")}if(e.times===true){i.push("--times")}if(e.syncDest===true||e.deleteAll===true){i.push("--delete");i.push("--delete-excluded")}if(e.syncDestIgnoreExcl===true||e.delete===true){i.push("--delete")}if(e.dryRun===true){i.push("--dry-run");i.push("--verbose")}if(typeof e.excludeFirst!=="undefined"&&t.isArray(e.excludeFirst)){e.excludeFirst.forEach((function(e,s){i.push("--exclude="+e)}))}if(typeof e.include!=="undefined"&&t.isArray(e.include)){e.include.forEach((function(e,s){i.push("--include="+e)}))}if(typeof e.exclude!=="undefined"&&t.isArray(e.exclude)){e.exclude.forEach((function(e,s){i.push("--exclude="+e)}))}switch(e.compareMode){case"sizeOnly":i.push("--size-only");break;case"checksum":i.push("--checksum");break}if(typeof e.args!=="undefined"&&t.isArray(e.args)){i=[...new Set([...i,...e.args])]}i=[...new Set(i)];var noop=function(){};var d=e.onStdout||noop;var u=e.onStderr||noop;var f="rsync ";i.forEach((function(e){if(e.substr(0,4)==="ssh "){e='"'+e+'"'}f+=e+" "}));f=f.trim();if(e.noExec){s(null,null,null,f);return}try{var p="";var h="";var y;if(n){y=o("cmd.exe",["/s","/c",'"'+f+'"'],{windowsVerbatimArguments:true,stdio:[process.stdin,"pipe","pipe"]})}else{y=o("/bin/sh",["-c",f])}y.stdout.on("data",(function(e){d(e);p+=e}));y.stderr.on("data",(function(e){u(e);h+=e}));y.on("exit",(function(e){var r=null;if(e!==0){r=new Error("rsync exited with code "+e);r.code=e}s(r,p,h,f)}))}catch(e){s(e,null,null,f)}}},505:(e,s,r)=>{const{existsSync:o,mkdirSync:t,writeFileSync:n}=r(147);const{join:c}=r(17);const validateDir=e=>{if(!e){console.warn("⚠️ [DIR] dir is not defined");return}if(o(e)){console.log(`✅ [DIR] ${e} dir exist`);return}console.log(`[DIR] Creating ${e} dir in workspace root`);t(e);console.log("✅ [DIR] dir created.")};const handleError=(e,s)=>{if(s){throw new Error(e)}console.warn(e)};const writeToFile=({dir:e,filename:s,content:r,isRequired:t,mode:i="0644"})=>{validateDir(e);const a=c(e,s);if(o(a)){const e=`⚠️ [FILE] ${a} Required file exist.`;handleError(e,t);return}try{console.log(`[FILE] writing ${a} file ...`,r.length);n(a,r,{encoding:"utf8",mode:i})}catch(e){const s=`⚠️[FILE] Writing to file error. filePath: ${a}, message: ${e.message}`;handleError(s,t)}};const validateRequiredInputs=e=>{const s=Object.keys(e);const r=s.filter((s=>{const r=e[s];if(!r){console.error(`❌ [INPUTS] ${s} is mandatory`)}return r}));if(r.length!==s.length){throw new Error("⚠️ [INPUTS] Inputs not valid, aborting ...")}};const snakeToCamel=e=>e.replace(/[^a-zA-Z0-9]+(.)/g,((e,s)=>s.toUpperCase()));e.exports={writeToFile:writeToFile,validateRequiredInputs:validateRequiredInputs,snakeToCamel:snakeToCamel}},229:(e,s,r)=>{const{snakeToCamel:o}=r(505);const t=["REMOTE_HOST","REMOTE_USER","REMOTE_PORT","SSH_PRIVATE_KEY","DEPLOY_KEY_NAME","SOURCE","TARGET","ARGS","SSH_CMD_ARGS","EXCLUDE","SCRIPT_BEFORE","SCRIPT_AFTER"];const n=process.env.GITHUB_WORKSPACE;const c=process.env.REMOTE_USER;const i={source:"",target:`/home/${c}/`,exclude:"",args:"-rlgoDzvc -i",sshCmdArgs:"-o StrictHostKeyChecking=no",deployKeyName:`deploy_key_${c}_${Date.now()}`};const a={githubWorkspace:n};t.forEach((e=>{const s=o(e.toLowerCase());const r=process.env[e]||process.env[`INPUT_${e}`];const t=r===undefined?i[s]:r;let c=t;switch(s){case"source":c=t.split(" ").map((e=>`${n}/${e}`));break;case"args":c=t.split(" ");break;case"exclude":case"sshCmdArgs":c=t.split(",").map((e=>e.trim()));break}a[s]=c}));a.sshServer=`${a.remoteUser}@${a.remoteHost}`;a.rsyncServer=`${a.remoteUser}@${a.remoteHost}:${a.target}`;e.exports=a},976:(e,s,r)=>{const{exec:o}=r(81);const{sshServer:t,githubWorkspace:n}=r(229);const{writeToFile:c}=r(505);const handleError=(e,s,r)=>{if(s){r(new Error(e))}else{console.warn(e)}};const remoteCmd=async(e,s,r,i)=>new Promise(((a,l)=>{const d=`local_ssh_script-${i}.sh`;try{c({dir:n,filename:d,content:e});console.log(`Executing remote script: ssh -i ${s} ${t}`);o(`DEBIAN_FRONTEND=noninteractive ssh -i ${s} -o StrictHostKeyChecking=no ${t} 'RSYNC_STDOUT="${process.env.RSYNC_STDOUT}" bash -s' < ${d}`,((e,s,o)=>{if(e){const t=`⚠️ [CMD] Remote script failed: ${e.message}`;console.warn(`${t} \n`,s,o);handleError(t,r,l)}else{console.log("✅ [CMD] Remote script executed. \n",s,o);a(s)}}))}catch(e){handleError(e.message,r,l)}}));e.exports={remoteCmdBefore:async(e,s,r)=>remoteCmd(e,s,r,"before"),remoteCmdAfter:async(e,s,r)=>remoteCmd(e,s,r,"after")}},447:(e,s,r)=>{const{execSync:o}=r(81);const t=r(898);const nodeRsyncPromise=async e=>new Promise(((s,r)=>{const logCMD=e=>{console.warn("================================================================");console.log(e);console.warn("================================================================")};try{t(e,((e,o,t,n)=>{if(e){console.error("❌ [Rsync] error: ");console.error(e);console.error("❌ [Rsync] stderr: ");console.error(t);console.error("❌️ [Rsync] stdout: ");console.error(o);console.error("❌ [Rsync] command: ");logCMD(n);r(new Error(`${e.message}\n\n${t}`))}else{console.log("⭐ [Rsync] command finished: ");logCMD(n);s(o)}}))}catch(e){console.error("❌ [Rsync] command error: ",e.message,e.stack);r(e)}}));const validateRsync=async()=>{try{o("rsync --version",{stdio:"inherit"});console.log("✅️ [CLI] Rsync exists");return}catch(e){console.warn("⚠️ [CLI] Rsync doesn't exists",e.message)}console.log('[CLI] Start rsync installation with "apt-get" \n');try{o("sudo DEBIAN_FRONTEND=noninteractive apt-get -y update && sudo DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends -y install rsync",{stdio:"inherit"});console.log("✅ [CLI] Rsync installed. \n")}catch(e){throw new Error(`⚠️ [CLI] Rsync installation failed. Aborting ... error: ${e.message}`)}};const rsyncCli=async({source:e,rsyncServer:s,exclude:r,remotePort:o,privateKeyPath:t,args:n,sshCmdArgs:c})=>{console.log(`[Rsync] Starting Rsync Action: ${e} to ${s}`);if(r&&r.length>0)console.log(`[Rsync] excluding folders ${r}`);const i={ssh:true,recursive:true,onStdout:e=>console.log(e.toString()),onStderr:e=>console.error(e.toString())};return nodeRsyncPromise({...i,src:e,dest:s,excludeFirst:r,port:o,privateKey:t,args:n,sshCmdArgs:c})};const sshDeploy=async e=>{await validateRsync();const s=await rsyncCli(e);console.log("✅ [Rsync] finished.",s);process.env.RSYNC_STDOUT=`${s}`;return s};e.exports={sshDeploy:sshDeploy}},822:(e,s,r)=>{const{join:o}=r(17);const{execSync:t}=r(81);const{writeToFile:n}=r(505);const c="known_hosts";const getPrivateKeyPath=(e="")=>{const{HOME:s}=process.env;const r=o(s||"~",".ssh");const t=o(r,c);return{dir:r,filename:e,path:o(r,e),knownHostsPath:t}};const addSshKey=(e,s)=>{const{dir:r,filename:o}=getPrivateKeyPath(s);n({dir:r,filename:c,content:""});console.log("✅ [SSH] known_hosts file ensured",r);n({dir:r,filename:o,content:e,isRequired:true,mode:"0400"});console.log("✅ [SSH] key added to `.ssh` dir ",r,o)};const updateKnownHosts=e=>{const{knownHostsPath:s}=getPrivateKeyPath();console.log("[SSH] Adding host to `known_hosts` ....",e,s);try{t(`ssh-keyscan -H ${e} >> ${s}`,{stdio:"inherit"})}catch(s){console.error("❌ [SSH] Adding host to `known_hosts` ERROR",e,s.message)}console.log("✅ [SSH] Adding host to `known_hosts` DONE",e,s)};e.exports={getPrivateKeyPath:getPrivateKeyPath,updateKnownHosts:updateKnownHosts,addSshKey:addSshKey}},81:e=>{"use strict";e.exports=require("child_process")},147:e=>{"use strict";e.exports=require("fs")},17:e=>{"use strict";e.exports=require("path")},837:e=>{"use strict";e.exports=require("util")}};var s={};function __nccwpck_require__(r){var o=s[r];if(o!==undefined){return o.exports}var t=s[r]={exports:{}};var n=true;try{e[r](t,t.exports,__nccwpck_require__);n=false}finally{if(n)delete s[r]}return t.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var r={};(()=>{const{sshDeploy:e}=__nccwpck_require__(447);const{remoteCmdBefore:s,remoteCmdAfter:r}=__nccwpck_require__(976);const{addSshKey:o,getPrivateKeyPath:t,updateKnownHosts:n}=__nccwpck_require__(822);const{validateRequiredInputs:c}=__nccwpck_require__(505);const i=__nccwpck_require__(229);const run=async()=>{const{source:a,remoteUser:l,remoteHost:d,remotePort:u,deployKeyName:f,sshPrivateKey:p,args:h,exclude:y,sshCmdArgs:m,scriptBefore:g,scriptAfter:_,rsyncServer:w}=i;c({sshPrivateKey:p,remoteHost:d,remoteUser:l});o(p,f);const{path:v}=t(f);if(g||_){n(d)}if(g){await s(g,v)}await e({source:a,rsyncServer:w,exclude:y,remotePort:u,privateKeyPath:v,args:h,sshCmdArgs:m});if(_){await r(_,v)}};run().then(((e="")=>{console.log("✅ [DONE]",e)})).catch((e=>{console.error("❌ [ERROR]",e.message);process.exit(1)}))})();module.exports=r})(); \ No newline at end of file +(()=>{var e={898:(e,s,r)=>{"use strict";var o=r(81).spawn;var t=r(837);var escapeSpaces=function(e){if(typeof e==="string"){return e.replace(/\b\s/g,"\\ ")}else{return e}};var escapeSpacesInOptions=function(e){["src","dest","include","exclude","excludeFirst"].forEach((function(s){var r=e[s];if(typeof r==="string"){e[s]=escapeSpaces(r)}else if(Array.isArray(r)===true){e[s]=r.map(escapeSpaces)}}));return e};e.exports=function(e,s){e=e||{};e=t._extend({},e);e=escapeSpacesInOptions(e);var r=e.platform||process.platform;var n=r==="win32";if(typeof e.src==="undefined"){throw new Error("'src' directory is missing from options")}if(typeof e.dest==="undefined"){throw new Error("'dest' directory is missing from options")}var c=e.dest;if(typeof e.host!=="undefined"){c=e.host+":"+e.dest}if(!Array.isArray(e.src)){e.src=[e.src]}var i=[].concat(e.src);i.push(c);var a=(e.args||[]).find((function(e){return e.match(/--chmod=/)}));if(n&&!a){i.push("--chmod=ugo=rwX")}if(typeof e.host!=="undefined"||e.ssh){i.push("--rsh");var d="ssh";if(typeof e.port!=="undefined"){d+=" -p "+e.port}if(typeof e.privateKey!=="undefined"){d+=" -i "+e.privateKey}if(typeof e.sshCmdArgs!=="undefined"){d+=" "+e.sshCmdArgs.join(" ")}i.push(d)}if(e.recursive===true){i.push("--recursive")}if(e.times===true){i.push("--times")}if(e.syncDest===true||e.deleteAll===true){i.push("--delete");i.push("--delete-excluded")}if(e.syncDestIgnoreExcl===true||e.delete===true){i.push("--delete")}if(e.dryRun===true){i.push("--dry-run");i.push("--verbose")}if(typeof e.excludeFirst!=="undefined"&&t.isArray(e.excludeFirst)){e.excludeFirst.forEach((function(e,s){i.push("--exclude="+e)}))}if(typeof e.include!=="undefined"&&t.isArray(e.include)){e.include.forEach((function(e,s){i.push("--include="+e)}))}if(typeof e.exclude!=="undefined"&&t.isArray(e.exclude)){e.exclude.forEach((function(e,s){i.push("--exclude="+e)}))}switch(e.compareMode){case"sizeOnly":i.push("--size-only");break;case"checksum":i.push("--checksum");break}if(typeof e.args!=="undefined"&&t.isArray(e.args)){i=[...new Set([...i,...e.args])]}i=[...new Set(i)];var noop=function(){};var l=e.onStdout||noop;var u=e.onStderr||noop;var p="rsync ";i.forEach((function(e){if(e.substr(0,4)==="ssh "){e='"'+e+'"'}p+=e+" "}));p=p.trim();if(e.noExec){s(null,null,null,p);return}try{var f="";var h="";var y;if(n){y=o("cmd.exe",["/s","/c",'"'+p+'"'],{windowsVerbatimArguments:true,stdio:[process.stdin,"pipe","pipe"]})}else{y=o("/bin/sh",["-c",p])}y.stdout.on("data",(function(e){l(e);f+=e}));y.stderr.on("data",(function(e){u(e);h+=e}));y.on("exit",(function(e){var r=null;if(e!==0){r=new Error("rsync exited with code "+e);r.code=e}s(r,f,h,p)}))}catch(e){s(e,null,null,p)}}},505:(e,s,r)=>{const{existsSync:o,mkdirSync:t,writeFileSync:n}=r(147);const{join:c}=r(17);const validateDir=e=>{if(!e){console.warn("⚠️ [DIR] dir is not defined");return}if(o(e)){console.log(`✅ [DIR] ${e} dir exist`);return}console.log(`[DIR] Creating ${e} dir in workspace root`);t(e);console.log("✅ [DIR] dir created.")};const handleError=(e,s)=>{if(s){throw new Error(e)}console.warn(e)};const writeToFile=({dir:e,filename:s,content:r,isRequired:t,mode:i="0644"})=>{validateDir(e);const a=c(e,s);if(o(a)){const e=`⚠️ [FILE] ${a} Required file exist.`;handleError(e,t);return}try{console.log(`[FILE] writing ${a} file ...`,r.length);n(a,r,{encoding:"utf8",mode:i})}catch(e){const s=`⚠️[FILE] Writing to file error. filePath: ${a}, message: ${e.message}`;handleError(s,t)}};const validateRequiredInputs=e=>{const s=Object.keys(e);const r=s.filter((s=>{const r=e[s];if(!r){console.error(`❌ [INPUTS] ${s} is mandatory`)}return r}));if(r.length!==s.length){throw new Error("⚠️ [INPUTS] Inputs not valid, aborting ...")}};const snakeToCamel=e=>e.replace(/[^a-zA-Z0-9]+(.)/g,((e,s)=>s.toUpperCase()));e.exports={writeToFile:writeToFile,validateRequiredInputs:validateRequiredInputs,snakeToCamel:snakeToCamel}},229:(e,s,r)=>{const{snakeToCamel:o}=r(505);const t=["REMOTE_HOST","REMOTE_USER","REMOTE_PORT","SSH_PRIVATE_KEY","DEPLOY_KEY_NAME","SOURCE","TARGET","ARGS","SSH_CMD_ARGS","EXCLUDE","SCRIPT_BEFORE","SCRIPT_AFTER","SCRIPT_BEFORE_REQUIRED","SCRIPT_AFTER_REQUIRED"];const n=process.env.GITHUB_WORKSPACE;const c=process.env.REMOTE_USER||process.env.INPUT_REMOTE_USER;const i={source:"",target:`/home/${c}/`,exclude:"",args:"-rlgoDzvc -i",sshCmdArgs:"-o StrictHostKeyChecking=no",deployKeyName:`deploy_key_${c}_${Date.now()}`};const a={githubWorkspace:n};t.forEach((e=>{const s=o(e.toLowerCase());const r=process.env[e]||process.env[`INPUT_${e}`]||i[s];const t=r===undefined?i[s]:r;let c=t;switch(s){case"source":c=t.split(" ").map((e=>`${n}/${e}`));break;case"args":c=t.split(" ");break;case"exclude":case"sshCmdArgs":c=t.split(",").map((e=>e.trim()));break}a[s]=c}));a.sshServer=`${a.remoteUser}@${a.remoteHost}`;a.rsyncServer=`${a.remoteUser}@${a.remoteHost}:${a.target}`;e.exports=a},976:(e,s,r)=>{const{exec:o}=r(81);const t=r(113);const{sshServer:n,githubWorkspace:c,remotePort:i}=r(229);const{writeToFile:a}=r(505);const handleError=(e,s,r)=>{if(s){r(new Error(e))}else{console.warn(e)}};const remoteCmd=async(e,s,r,d)=>new Promise(((l,u)=>{const p=t.randomUUID();const f=`local_ssh_script-${d}-${p}.sh`;try{a({dir:c,filename:f,content:e});const t=1e4;const d=(process.env.RSYNC_STDOUT||"").substring(0,t);console.log(`Executing remote script: ssh -i ${s} ${n}`);o(`DEBIAN_FRONTEND=noninteractive ssh -p ${i||22} -i ${s} -o StrictHostKeyChecking=no ${n} 'RSYNC_STDOUT="${d}" bash -s' < ${f}`,((e,s="",o="")=>{if(e){const t=`⚠️ [CMD] Remote script failed: ${e.message}`;console.warn(`${t} \n`,s,o);handleError(t,r,u)}else{const e=s.substring(0,t);console.log("✅ [CMD] Remote script executed. \n",e,o);l(e)}}))}catch(e){handleError(e.message,r,u)}}));e.exports={remoteCmdBefore:async(e,s,r)=>remoteCmd(e,s,r,"before"),remoteCmdAfter:async(e,s,r)=>remoteCmd(e,s,r,"after")}},447:(e,s,r)=>{const{execSync:o}=r(81);const t=r(898);const nodeRsyncPromise=async e=>new Promise(((s,r)=>{const logCMD=e=>{console.warn("================================================================");console.log(e);console.warn("================================================================")};try{t(e,((e,o,t,n)=>{if(e){console.error("❌ [Rsync] error: ");console.error(e);console.error("❌ [Rsync] stderr: ");console.error(t);console.error("❌️ [Rsync] stdout: ");console.error(o);console.error("❌ [Rsync] command: ");logCMD(n);r(new Error(`${e.message}\n\n${t}`))}else{console.log("⭐ [Rsync] command finished: ");logCMD(n);s(o)}}))}catch(e){console.error("❌ [Rsync] command error: ",e.message,e.stack);r(e)}}));const validateRsync=async()=>{try{o("rsync --version",{stdio:"inherit"});console.log("✅️ [CLI] Rsync exists");return}catch(e){console.warn("⚠️ [CLI] Rsync doesn't exists",e.message)}console.log('[CLI] Start rsync installation with "apt-get" \n');try{o("sudo DEBIAN_FRONTEND=noninteractive apt-get -y update && sudo DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends -y install rsync",{stdio:"inherit"});console.log("✅ [CLI] Rsync installed. \n")}catch(e){throw new Error(`⚠️ [CLI] Rsync installation failed. Aborting ... error: ${e.message}`)}};const rsyncCli=async({source:e,rsyncServer:s,exclude:r,remotePort:o,privateKeyPath:t,args:n,sshCmdArgs:c})=>{console.log(`[Rsync] Starting Rsync Action: ${e} to ${s}`);if(r&&r.length>0)console.log(`[Rsync] excluding folders ${r}`);const i={ssh:true,recursive:true,onStdout:e=>console.log(e.toString()),onStderr:e=>console.error(e.toString())};return nodeRsyncPromise({...i,src:e,dest:s,excludeFirst:r,port:o,privateKey:t,args:n,sshCmdArgs:c})};const sshDeploy=async e=>{await validateRsync();const s=await rsyncCli(e);console.log("✅ [Rsync] finished.",s);process.env.RSYNC_STDOUT=`${s}`;return s};e.exports={sshDeploy:sshDeploy}},822:(e,s,r)=>{const{join:o}=r(17);const{execSync:t}=r(81);const{EOL:n}=r(37);const{writeToFile:c}=r(505);const i="known_hosts";const getPrivateKeyPath=(e="")=>{const{HOME:s}=process.env;const r=o(s||"~",".ssh");const t=o(r,i);return{dir:r,filename:e,path:o(r,e),knownHostsPath:t}};const addSshKey=(e,s)=>{const{dir:r,filename:o}=getPrivateKeyPath(s);c({dir:r,filename:i,content:""});console.log("✅ [SSH] known_hosts file ensured",r);c({dir:r,filename:o,content:`${e}${n}`,isRequired:true,mode:"0400"});console.log("✅ [SSH] key added to `.ssh` dir ",r,o)};const updateKnownHosts=(e,s)=>{const{knownHostsPath:r}=getPrivateKeyPath();console.log("[SSH] Adding host to `known_hosts` ....",e,r);try{t(`ssh-keyscan -p ${s||22} -H ${e} >> ${r}`,{stdio:"inherit"})}catch(s){console.error("❌ [SSH] Adding host to `known_hosts` ERROR",e,s.message)}console.log("✅ [SSH] Adding host to `known_hosts` DONE",e,r)};e.exports={getPrivateKeyPath:getPrivateKeyPath,updateKnownHosts:updateKnownHosts,addSshKey:addSshKey}},81:e=>{"use strict";e.exports=require("child_process")},113:e=>{"use strict";e.exports=require("crypto")},147:e=>{"use strict";e.exports=require("fs")},37:e=>{"use strict";e.exports=require("os")},17:e=>{"use strict";e.exports=require("path")},837:e=>{"use strict";e.exports=require("util")}};var s={};function __nccwpck_require__(r){var o=s[r];if(o!==undefined){return o.exports}var t=s[r]={exports:{}};var n=true;try{e[r](t,t.exports,__nccwpck_require__);n=false}finally{if(n)delete s[r]}return t.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var r={};(()=>{const{sshDeploy:e}=__nccwpck_require__(447);const{remoteCmdBefore:s,remoteCmdAfter:r}=__nccwpck_require__(976);const{addSshKey:o,getPrivateKeyPath:t,updateKnownHosts:n}=__nccwpck_require__(822);const{validateRequiredInputs:c}=__nccwpck_require__(505);const i=__nccwpck_require__(229);const run=async()=>{const{source:a,remoteUser:d,remoteHost:l,remotePort:u,deployKeyName:p,sshPrivateKey:f,args:h,exclude:y,sshCmdArgs:m,scriptBefore:g,scriptBeforeRequired:_,scriptAfter:R,scriptAfterRequired:E,rsyncServer:w}=i;c({sshPrivateKey:f,remoteHost:l,remoteUser:d});o(f,p);const{path:v}=t(p);if(g||R){n(l,u)}if(g){await s(g,v,_)}await e({source:a,rsyncServer:w,exclude:y,remotePort:u,privateKeyPath:v,args:h,sshCmdArgs:m});if(R){await r(R,v,E)}};run().then(((e="")=>{console.log("✅ [DONE]",e)})).catch((e=>{console.error("❌ [ERROR]",e.message);process.exit(1)}))})();module.exports=r})(); \ No newline at end of file diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ffd36be..873cbcf 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,136 @@ +## [5.1.1](https://github.com/easingthemes/ssh-deploy/compare/v5.1.0...v5.1.1) (2024-07-24) + + +### Bug Fixes + +* Update README.md ([f007431](https://github.com/easingthemes/ssh-deploy/commit/f007431332cb2dae49153363ad22fb9f90f4aa75)) + +# [5.1.0](https://github.com/easingthemes/ssh-deploy/compare/v5.0.3...v5.1.0) (2024-07-24) + + +### Features + +* Add deleteFile function to helpers module ([1befdb1](https://github.com/easingthemes/ssh-deploy/commit/1befdb1c6bf3282aa34e6caa431cb2da23d2b17d)) +* apply deleteFile function to remoteCmd ([b82eced](https://github.com/easingthemes/ssh-deploy/commit/b82eced4571cb3f63369d51760a81820ffb1bc7f)) + +## [5.0.3](https://github.com/easingthemes/ssh-deploy/compare/v5.0.2...v5.0.3) (2024-02-27) + + +### Bug Fixes + +* trigger automated release ([4d8bbf0](https://github.com/easingthemes/ssh-deploy/commit/4d8bbf0debaade9fb03b8dc3be3c020955557b12)) + +## [5.0.2](https://github.com/easingthemes/ssh-deploy/compare/v5.0.1...v5.0.2) (2024-02-18) + + +### Bug Fixes + +* added the missing declarations [#177](https://github.com/easingthemes/ssh-deploy/issues/177) ([bb271fe](https://github.com/easingthemes/ssh-deploy/commit/bb271fe4c69eeeacb986a38cdb3347104143c61f)) +* Fix default values used incorrectly. ([a1b383f](https://github.com/easingthemes/ssh-deploy/commit/a1b383f560a7f52a65da3670e61efe6e02f8639a)) + +## [5.0.1](https://github.com/easingthemes/ssh-deploy/compare/v5.0.0...v5.0.1) (2024-01-31) + + +### Bug Fixes + +* Add info for Permission denied issue. ([845b578](https://github.com/easingthemes/ssh-deploy/commit/845b578606c0c5a956c70caf61e00a7d2b13ee37)) + +# [5.0.0](https://github.com/easingthemes/ssh-deploy/compare/v4.1.10...v5.0.0) (2023-12-12) + + +* Merge pull request #173 from jeromelachaud/main ([ac1908e](https://github.com/easingthemes/ssh-deploy/commit/ac1908e5d2dc749496fdbe8a918aa073e3357d85)), closes [#173](https://github.com/easingthemes/ssh-deploy/issues/173) + + +### BREAKING CHANGES + +* update to use nodeJS v20 +* update to use nodeJS v20 + +## [4.1.10](https://github.com/easingthemes/ssh-deploy/compare/v4.1.9...v4.1.10) (2023-09-30) + + +### Bug Fixes + +* normalize line endings in SSH key for the underlying OS ([3f5d9aa](https://github.com/easingthemes/ssh-deploy/commit/3f5d9aab1a743bd426a4d132d07f1f5e9ed0310c)) + +## [4.1.9](https://github.com/easingthemes/ssh-deploy/compare/v4.1.8...v4.1.9) (2023-09-24) + + +### Bug Fixes + +* add compiled file ([627ac29](https://github.com/easingthemes/ssh-deploy/commit/627ac29ece9dc2f1185a50d1002bc2c968fc973c)) +* add uuid for ssh scripts ([66f6e4b](https://github.com/easingthemes/ssh-deploy/commit/66f6e4b367ea39479c285234797a4e86c90d9abd)) + +## [4.1.8](https://github.com/easingthemes/ssh-deploy/compare/v4.1.7...v4.1.8) (2023-02-21) + + +### Bug Fixes + +* rebuild and update readme ([98025d6](https://github.com/easingthemes/ssh-deploy/commit/98025d680e96a5c6c805e377a1b81de2f626aa1e)) + +## [4.1.7](https://github.com/easingthemes/ssh-deploy/compare/v4.1.6...v4.1.7) (2023-02-21) + + +### Bug Fixes + +* [#120](https://github.com/easingthemes/ssh-deploy/issues/120) check undefined data ([2fbb060](https://github.com/easingthemes/ssh-deploy/commit/2fbb06015d1a6ffd32e0100aaf1a1a46949e990e)) + +## [4.1.6](https://github.com/easingthemes/ssh-deploy/compare/v4.1.5...v4.1.6) (2023-02-21) + + +### Bug Fixes + +* [#118](https://github.com/easingthemes/ssh-deploy/issues/118) check undefined default values rebuild ([54f8b6c](https://github.com/easingthemes/ssh-deploy/commit/54f8b6c60b8f2f926d5ed9538557e5521a905d87)) + +## [4.1.5](https://github.com/easingthemes/ssh-deploy/compare/v4.1.4...v4.1.5) (2023-02-21) + + +### Bug Fixes + +* [#118](https://github.com/easingthemes/ssh-deploy/issues/118) check undefined default values ([f0c02fb](https://github.com/easingthemes/ssh-deploy/commit/f0c02fb2a5b3b69bb91004dd49d409eb6adfe7cd)) + +## [4.1.4](https://github.com/easingthemes/ssh-deploy/compare/v4.1.3...v4.1.4) (2023-02-21) + + +### Bug Fixes + +* [#113](https://github.com/easingthemes/ssh-deploy/issues/113) limit ssh script input ([5894f5e](https://github.com/easingthemes/ssh-deploy/commit/5894f5e29008feccaf42787330ec8f49f3ad50b0)) + +## [4.1.3](https://github.com/easingthemes/ssh-deploy/compare/v4.1.2...v4.1.3) (2023-02-21) + + +### Bug Fixes + +* [#113](https://github.com/easingthemes/ssh-deploy/issues/113) limit ssh script output - rebuild ([756a522](https://github.com/easingthemes/ssh-deploy/commit/756a522533d2206203b5d13b5aa11c88b3313784)) + +## [4.1.2](https://github.com/easingthemes/ssh-deploy/compare/v4.1.1...v4.1.2) (2023-02-21) + + +### Bug Fixes + +* [#113](https://github.com/easingthemes/ssh-deploy/issues/113) limit ssh script output ([59827af](https://github.com/easingthemes/ssh-deploy/commit/59827af83c934996efda72f9fbd1fcd0bb9ccaac)) + +## [4.1.1](https://github.com/easingthemes/ssh-deploy/compare/v4.1.0...v4.1.1) (2023-02-21) + + +### Bug Fixes + +* fix default TARGET ([4d08634](https://github.com/easingthemes/ssh-deploy/commit/4d086346af62ac5d57fa37ee6bb46f8de8ad48c3)) + +# [4.1.0](https://github.com/easingthemes/ssh-deploy/compare/v4.0.5...v4.1.0) (2023-02-19) + + +### Features + +* add ssh port from VARs, fix package-lock ([808b002](https://github.com/easingthemes/ssh-deploy/commit/808b0020d44b7c25ef1c13b3979ffdab4f503236)) + +## [4.0.5](https://github.com/easingthemes/ssh-deploy/compare/v4.0.4...v4.0.5) (2023-01-06) + + +### Bug Fixes + +* fix manual release action ([245b7a9](https://github.com/easingthemes/ssh-deploy/commit/245b7a9d2fe5272ee34608f86a612d643694c23b)) + ## [4.0.4](https://github.com/easingthemes/ssh-deploy/compare/v4.0.3...v4.0.4) (2023-01-03) diff --git a/package-lock.json b/package-lock.json index 982a0cf..7cc9222 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@draganfilipovic/ssh-deploy", - "version": "4.0.3", + "version": "4.1.10", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@draganfilipovic/ssh-deploy", - "version": "4.0.3", + "version": "4.1.10", "license": "MIT", "dependencies": { "rsyncwrapper": "^3.0.1" @@ -1357,6 +1357,18 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1483,6 +1495,16 @@ "node": ">= 0.8.0" } }, + "node_modules/optionator/node_modules/word-wrap": { + "name": "@aashutoshrathi/word-wrap", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-plhoNEfSVdHMKXQyAxvH0Zyv3/4NL8r6pwgMQdmHR2vBUXn2t74PN2pBRppqKUa6RMT0yldyvOHG5Dbjwy2mBQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -1722,12 +1744,18 @@ } }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/shebang-command": { @@ -1947,21 +1975,18 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -2354,7 +2379,7 @@ "confusing-browser-globals": "^1.0.10", "object.assign": "^4.1.2", "object.entries": "^1.1.5", - "semver": "^6.3.0" + "semver": "^7.5.2" } }, "eslint-import-resolver-node": { @@ -2965,6 +2990,15 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3058,7 +3092,15 @@ "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "npm:@aashutoshrathi/word-wrap@1.2.5" + }, + "dependencies": { + "word-wrap": { + "version": "npm:@aashutoshrathi/word-wrap@1.2.5", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-plhoNEfSVdHMKXQyAxvH0Zyv3/4NL8r6pwgMQdmHR2vBUXn2t74PN2pBRppqKUa6RMT0yldyvOHG5Dbjwy2mBQ==", + "dev": true + } } }, "p-limit": { @@ -3205,10 +3247,13 @@ } }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } }, "shebang-command": { "version": "2.0.0", @@ -3370,18 +3415,18 @@ "is-symbol": "^1.0.3" } }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index a98d09c..22b5373 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@draganfilipovic/ssh-deploy", - "version": "4.0.4", + "version": "5.1.1", "description": "Fast NodeJS action to deploy specific directory from `GITHUB_WORKSPACE` to a server via rsync over ssh.", "main": "dist/index.js", "files": [ @@ -37,5 +37,9 @@ "eslint": "^8.30.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-plugin-import": "^2.26.0" + }, + "overrides": { + "word-wrap": "npm:@aashutoshrathi/word-wrap@1.2.5", + "semver": "^7.5.2" } } diff --git a/src/helpers.js b/src/helpers.js index 1c705df..78379e1 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,4 +1,4 @@ -const { existsSync, mkdirSync, writeFileSync } = require('fs'); +const { existsSync, mkdirSync, writeFileSync, unlink } = require('fs'); const { join } = require('path'); const validateDir = (dir) => { @@ -45,6 +45,29 @@ const writeToFile = ({ dir, filename, content, isRequired, mode = '0644' }) => { } }; +const deleteFile = ({ dir, filename, isRequired }) => { + validateDir(dir); + const filePath = join(dir, filename); + + if (existsSync(filePath)) { + const message = `⚠️ [FILE] ${filePath} Required file exist.`; + handleError(message, isRequired); + return; + } + + try { + console.log(`[FILE] Deleting ${filePath} file ...`); + unlink(filePath, (error) => { + if (error) { + throw new Error(error); + } + }); + } catch (error) { + const message = `⚠️[FILE] Deleting file error. filePath: ${filePath}, message: ${error.message}`; + handleError(message, isRequired); + } +}; + const validateRequiredInputs = (inputs) => { const inputKeys = Object.keys(inputs); const validInputs = inputKeys.filter((inputKey) => { @@ -66,6 +89,7 @@ const snakeToCamel = (str) => str.replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.t module.exports = { writeToFile, + deleteFile, validateRequiredInputs, snakeToCamel }; diff --git a/src/index.js b/src/index.js index 0586e40..7266355 100644 --- a/src/index.js +++ b/src/index.js @@ -10,7 +10,8 @@ const run = async () => { source, remoteUser, remoteHost, remotePort, deployKeyName, sshPrivateKey, args, exclude, sshCmdArgs, - scriptBefore, scriptAfter, + scriptBefore, scriptBeforeRequired, + scriptAfter, scriptAfterRequired, rsyncServer } = inputs; // Validate required inputs @@ -20,11 +21,11 @@ const run = async () => { const { path: privateKeyPath } = getPrivateKeyPath(deployKeyName); // Update known hosts if ssh command is present to avoid prompt if (scriptBefore || scriptAfter) { - updateKnownHosts(remoteHost); + updateKnownHosts(remoteHost, remotePort); } // Check Script before if (scriptBefore) { - await remoteCmdBefore(scriptBefore, privateKeyPath); + await remoteCmdBefore(scriptBefore, privateKeyPath, scriptBeforeRequired); } /* eslint-disable object-property-newline */ await sshDeploy({ @@ -33,7 +34,7 @@ const run = async () => { }); // Check script after if (scriptAfter) { - await remoteCmdAfter(scriptAfter, privateKeyPath); + await remoteCmdAfter(scriptAfter, privateKeyPath, scriptAfterRequired); } }; diff --git a/src/inputs.js b/src/inputs.js index 0aa9aba..2c5dd19 100644 --- a/src/inputs.js +++ b/src/inputs.js @@ -4,10 +4,10 @@ const inputNames = [ 'REMOTE_HOST', 'REMOTE_USER', 'REMOTE_PORT', 'SSH_PRIVATE_KEY', 'DEPLOY_KEY_NAME', 'SOURCE', 'TARGET', 'ARGS', 'SSH_CMD_ARGS', 'EXCLUDE', - 'SCRIPT_BEFORE', 'SCRIPT_AFTER']; + 'SCRIPT_BEFORE', 'SCRIPT_AFTER', 'SCRIPT_BEFORE_REQUIRED', 'SCRIPT_AFTER_REQUIRED']; const githubWorkspace = process.env.GITHUB_WORKSPACE; -const remoteUser = process.env.REMOTE_USER; +const remoteUser = process.env.REMOTE_USER || process.env.INPUT_REMOTE_USER; const defaultInputs = { source: '', @@ -24,7 +24,7 @@ const inputs = { inputNames.forEach((input) => { const inputName = snakeToCamel(input.toLowerCase()); - const inputVal = process.env[input] || process.env[`INPUT_${input}`]; + const inputVal = process.env[input] || process.env[`INPUT_${input}`] || defaultInputs[inputName]; const validVal = inputVal === undefined ? defaultInputs[inputName] : inputVal; let extendedVal = validVal; // eslint-disable-next-line default-case diff --git a/src/remoteCmd.js b/src/remoteCmd.js index b5b57c7..70f7656 100644 --- a/src/remoteCmd.js +++ b/src/remoteCmd.js @@ -1,6 +1,7 @@ const { exec } = require('child_process'); -const { sshServer, githubWorkspace } = require('./inputs'); -const { writeToFile } = require('./helpers'); +const crypto = require('crypto'); +const { sshServer, githubWorkspace, remotePort } = require('./inputs'); +const { writeToFile, deleteFile } = require('./helpers'); const handleError = (message, isRequired, callback) => { if (isRequired) { @@ -12,20 +13,26 @@ const handleError = (message, isRequired, callback) => { // eslint-disable-next-line max-len const remoteCmd = async (content, privateKeyPath, isRequired, label) => new Promise((resolve, reject) => { - const filename = `local_ssh_script-${label}.sh`; + const uuid = crypto.randomUUID(); + const filename = `local_ssh_script-${label}-${uuid}.sh`; try { writeToFile({ dir: githubWorkspace, filename, content }); + const dataLimit = 10000; + const rsyncStdout = (process.env.RSYNC_STDOUT || '').substring(0, dataLimit); console.log(`Executing remote script: ssh -i ${privateKeyPath} ${sshServer}`); exec( - `DEBIAN_FRONTEND=noninteractive ssh -i ${privateKeyPath} -o StrictHostKeyChecking=no ${sshServer} 'RSYNC_STDOUT="${process.env.RSYNC_STDOUT}" bash -s' < ${filename}`, - (err, data, stderr) => { + `DEBIAN_FRONTEND=noninteractive ssh -p ${(remotePort || 22)} -i ${privateKeyPath} -o StrictHostKeyChecking=no ${sshServer} 'RSYNC_STDOUT="${rsyncStdout}" bash -s' < ${filename}`, + (err, data = '', stderr = '') => { if (err) { const message = `⚠️ [CMD] Remote script failed: ${err.message}`; console.warn(`${message} \n`, data, stderr); handleError(message, isRequired, reject); } else { - console.log('✅ [CMD] Remote script executed. \n', data, stderr); - resolve(data); + const limited = data.substring(0, dataLimit); + console.log('✅ [CMD] Remote script executed. \n', limited, stderr); + deleteFile({ dir: githubWorkspace, filename }); + console.log('✅ [FILE] Script file deleted.'); + resolve(limited); } } ); diff --git a/src/sshKey.js b/src/sshKey.js index ce40d50..066ed02 100644 --- a/src/sshKey.js +++ b/src/sshKey.js @@ -1,5 +1,6 @@ const { join } = require('path'); const { execSync } = require('child_process'); +const { EOL } = require('os'); const { writeToFile } = require('./helpers'); const KNOWN_HOSTS = 'known_hosts'; @@ -19,15 +20,15 @@ const addSshKey = (content, deployKeyName) => { const { dir, filename } = getPrivateKeyPath(deployKeyName); writeToFile({ dir, filename: KNOWN_HOSTS, content: '' }); console.log('✅ [SSH] known_hosts file ensured', dir); - writeToFile({ dir, filename, content, isRequired: true, mode: '0400' }); + writeToFile({ dir, filename, content: `${content}${EOL}`, isRequired: true, mode: '0400' }); console.log('✅ [SSH] key added to `.ssh` dir ', dir, filename); }; -const updateKnownHosts = (host) => { +const updateKnownHosts = (host, remotePort) => { const { knownHostsPath } = getPrivateKeyPath(); console.log('[SSH] Adding host to `known_hosts` ....', host, knownHostsPath); try { - execSync(`ssh-keyscan -H ${host} >> ${knownHostsPath}`, { + execSync(`ssh-keyscan -p ${(remotePort || 22)} -H ${host} >> ${knownHostsPath}`, { stdio: 'inherit' }); } catch (error) {