diff --git a/.eslintrc.js b/.eslintrc.js index df1d632..1324c84 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -12,14 +12,14 @@ module.exports = { SharedArrayBuffer: 'readonly' }, parserOptions: { - ecmaVersion: 2018 + ecmaVersion: 2018, }, rules: { - 'comma-dangle': [ - 'error', - 'never' + "comma-dangle": [ + "error", + "never" ], - 'no-console': 'off', - 'object-curly-newline': 'off' + "no-console": "off", + "object-curly-newline": "off" } }; diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cace0c9..8b8389e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,13 +14,13 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node-version: [20.x] + node-version: [16.x] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v2 - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - name: Install dependencies diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 6c93953..ffd76f4 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@v4 + uses: actions/checkout@v2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v1 with: languages: ${{ matrix.language }} @@ -44,4 +44,4 @@ jobs: npm run build --if-present - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/e2e-manual.yml b/.github/workflows/e2e-manual.yml deleted file mode 100644 index 11fb9f6..0000000 --- a/.github/workflows/e2e-manual.yml +++ /dev/null @@ -1,104 +0,0 @@ -name: e2e Manual Test - -on: - workflow_dispatch: - inputs: - ARGS: - description: 'ARGS' - required: true - default: '-rlgoDzvc -i --delete --chmod=ugo+rwX --progress' - EXCLUDE: - description: 'EXCLUDE' - required: true - default: 'skip_dir/, /node_modules/' - SSH_CMD_ARGS: - description: 'SSH_CMD_ARGS' - required: true - default: '-o StrictHostKeyChecking=no, -o UserKnownHostsFile=/dev/null' - - -env: - TEST_HOST_DOCKER: ./test - TEST_USER: test - -jobs: - e2e-manual: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - -# ---------------------------------------------------------------- -# START E2E Test Specific - steps -# ---------------------------------------------------------------- - - - name: E2E Test Specific - Clean up old test files - run: | - docker stop ssh-host-container || true && docker rm ssh-host-container || true - - - name: E2E Test Specific - Create ssh keys - run: | - echo $HOME - ls -la $HOME - ssh-keygen -m PEM -t rsa -b 4096 -f "$HOME/.ssh/id_rsa" -N "" - eval `ssh-agent -s` - ssh-add "$HOME/.ssh/id_rsa" - ssh-add -l - echo "SSH_PRIVATE_KEY<> $GITHUB_ENV - cat $HOME/.ssh/id_rsa >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - - name: E2E Test Specific - Build Host Server Image - working-directory: ${{ env.TEST_HOST_DOCKER }} - run: | - docker build \ - -t ssh-host-image . \ - --build-arg SSH_PUB_KEY="$(cat $HOME/.ssh/id_rsa.pub)" - docker run -d -p 8822:22 --name=ssh-host-container ssh-host-image - docker exec ssh-host-container sh -c "hostname --ip-address" > ip.txt - echo "REMOTE_HOST<> $GITHUB_ENV - cat ip.txt >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - shell: bash - - - name: E2E Test Specific - Create project file - run: | - mkdir test_project2 && cd "$_" - truncate -s 5MB info2.txt - cd ../ - mkdir test_project && cd "$_" - touch index.html - date +"%Y-%m-%d %H:%M:%S,%3N" >> index.html - truncate -s 50MB image.svg - truncate -s 5MB info.txt - truncate -s 500MB big_file.txt - mkdir skip_dir && cd "$_" - truncate -s 5MB text_in_skip_dir.txt - cd ../ - cat index.html - echo "test_project:" && ls -lR - echo "skip_dir:" && ls -lR skip_dir - -# ---------------------------------------------------------------- -# END E2E Test Specific - steps -# ---------------------------------------------------------------- - - - name: e2e Test ssh-deploy action - uses: easingthemes/ssh-deploy@main - env: - # ENV Vars created in previous steps: - # SSH_PRIVATE_KEY: $EXAMPLE_SSH_PRIVATE_KEY - # REMOTE_HOST: $EXAMPLE_REMOTE_HOST - REMOTE_USER: ${{ env.TEST_USER }} - ARGS: ${{ github.event.inputs.ARGS }} - SSH_CMD_ARGS: ${{ github.event.inputs.SSH_CMD_ARGS }} - SOURCE: test_project/ test_project2/ - TARGET: /var/www/html/ - EXCLUDE: ${{ github.event.inputs.EXCLUDE }} - SCRIPT_BEFORE: | - whoami - ls -lR /var/www/html/ - SCRIPT_AFTER: | - ls -lR /var/www/html/ - echo $RSYNC_STDOUT diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 09bf162..07c07f0 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -7,7 +7,6 @@ on: env: TEST_HOST_DOCKER: ./test TEST_USER: test - TEST_USER2: test2 jobs: e2e: @@ -15,17 +14,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 -# : --------------------------------------------------------------- -# : START E2E Test Specific - steps -# : ---------------------------------------------------------------- - - - name: E2E Test Specific - Clean up old test files + - name: Clean up old test files run: | docker stop ssh-host-container || true && docker rm ssh-host-container || true - - name: E2E Test Specific - Create ssh keys + - name: Create ssh keys run: | echo $HOME ls -la $HOME @@ -37,12 +32,13 @@ jobs: cat $HOME/.ssh/id_rsa >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - - name: E2E Test Specific - Build Host Server Image + - name: Build Host Server Image working-directory: ${{ env.TEST_HOST_DOCKER }} run: | docker build \ -t ssh-host-image . \ - --build-arg SSH_PUB_KEY="$(cat $HOME/.ssh/id_rsa.pub)" + --build-arg SSH_PUB_KEY="$(cat $HOME/.ssh/id_rsa.pub)" \ + --build-arg ssh_user="${{ env.TEST_USER }}" docker run -d -p 8822:22 --name=ssh-host-container ssh-host-image docker exec ssh-host-container sh -c "hostname --ip-address" > ip.txt echo "REMOTE_HOST<> $GITHUB_ENV @@ -50,130 +46,21 @@ jobs: echo "EOF" >> $GITHUB_ENV shell: bash - - name: E2E Test Specific - Create project file + - name: Create project file run: | - mkdir test_project2 && cd "$_" - truncate -s 5MB info2.txt - cd ../ - mkdir test_project && cd "$_" + mkdir test_project + cd test_project touch index.html date +"%Y-%m-%d %H:%M:%S,%3N" >> index.html - truncate -s 50MB image.svg - truncate -s 5MB info.txt - truncate -s 500MB big_file.txt - mkdir skip_dir && cd "$_" - truncate -s 5MB text_in_skip_dir.txt - cd ../ cat index.html - echo "test_project:" && ls -lR - echo "skip_dir:" && ls -lR skip_dir -# ---------------------------------------------------------------- -# END E2E Test Specific - steps -# ---------------------------------------------------------------- - - - name: set shared ENV variables for multi target deployment - run: | - echo "ARGS=-rlgoDzvcR -i --delete --chmod=ugo+rwX --progress" >> $GITHUB_ENV - echo "SSH_CMD_ARGS=-o StrictHostKeyChecking=no, -o UserKnownHostsFile=/dev/null" >> $GITHUB_ENV - echo "SOURCE=test_project/ test_project2/" >> $GITHUB_ENV - echo "EXCLUDE=skip_dir/, /node_modules/" >> $GITHUB_ENV - echo "SCRIPT_BEFORE<> $GITHUB_ENV - echo "whoami" >> $GITHUB_ENV - echo "ls -lR /var/www/html/" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - echo "SCRIPT_AFTER<> $GITHUB_ENV - echo $RSYNC_STDOUT >> $GITHUB_ENV - echo "ls -lR /var/www/html/" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - - name: e2e Test ssh-deploy action - Target 1 + - name: e2e Test published ssh-deploy action uses: easingthemes/ssh-deploy@main env: - # Shared ENV Vars created in previous steps + # SSH_PRIVATE_KEY: $EXAMPLE_SSH_PRIVATE_KEY + # REMOTE_HOST: $EXAMPLE_REMOTE_HOST1 REMOTE_USER: ${{ env.TEST_USER }} - TARGET: /var/www/html/${{ env.TEST_USER }} - - - name: e2e Test ssh-deploy action - Target 2 - uses: easingthemes/ssh-deploy@main - env: - # 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@v4 - - # : --------------------------------------------------------------- - # : START E2E Test Specific - steps - # : ---------------------------------------------------------------- - - - name: E2E Test Specific - Clean up old test files - run: | - docker stop ssh-host-container || true && docker rm ssh-host-container || true - - - name: E2E Test Specific - Create ssh keys - run: | - echo $HOME - ls -la $HOME - ssh-keygen -m PEM -t rsa -b 4096 -f "$HOME/.ssh/id_rsa" -N "" - eval `ssh-agent -s` - ssh-add "$HOME/.ssh/id_rsa" - ssh-add -l - echo "SSH_PRIVATE_KEY<> $GITHUB_ENV - cat $HOME/.ssh/id_rsa >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - - name: E2E Test Specific - Build Host Server Image - working-directory: ${{ env.TEST_HOST_DOCKER }} - run: | - docker build \ - -t ssh-host-image . \ - --build-arg SSH_PUB_KEY="$(cat $HOME/.ssh/id_rsa.pub)" - docker run -d -p 8822:22 --name=ssh-host-container ssh-host-image - docker exec ssh-host-container sh -c "hostname --ip-address" > ip.txt - echo "REMOTE_HOST<> $GITHUB_ENV - cat ip.txt >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - shell: bash - - - name: E2E Test Specific - Create project file - run: | - mkdir test_project2 && cd "$_" - truncate -s 5MB info2.txt - cd ../ - mkdir test_project && cd "$_" - touch index.html - date +"%Y-%m-%d %H:%M:%S,%3N" >> index.html - truncate -s 50MB image.svg - truncate -s 5MB info.txt - truncate -s 500MB big_file.txt - mkdir skip_dir && cd "$_" - truncate -s 5MB text_in_skip_dir.txt - cd ../ - cat index.html - echo "test_project:" && ls -lR - echo "skip_dir:" && ls -lR skip_dir - - # ---------------------------------------------------------------- - # END E2E Test Specific - steps - # ---------------------------------------------------------------- - - - name: e2e Test ssh-deploy action - Target 1 - uses: easingthemes/ssh-deploy@main - env: - # Shared ENV Vars created in previous steps - REMOTE_USER: ${{ env.TEST_USER }} - TARGET: /var/www/html/${{ env.TEST_USER }} - SOURCE: test_project/ - EXCLUDE: skip_dir/, /node_modules/ - ARGS: -rlgoDzvcO + ARGS: "-rltgoDzvO" + SOURCE: "test_project/" + TARGET: "/var/www/html/" + EXCLUDE: "/dist/, /node_modules/" diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index 6545a29..43b48d3 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -2,71 +2,43 @@ name: Manual Release on: workflow_dispatch: inputs: - version: - description: 'Version' - type: choice + dryrun: + description: 'DryRUn' required: true - default: fix - options: - - fix - - feat - dryRun: - description: 'DryRun' - type: boolean - default: true -# ENV and Config -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - GIT_AUTHOR_NAME: github-actions - GIT_AUTHOR_EMAIL: github-actions@github.com - GIT_COMMITTER_NAME: github-actions - GIT_COMMITTER_EMAIL: github-actions@github.com - CI: true - CONFIG_NODE_VERSION: '["lts/*"]' - CONFIG_OS: '["ubuntu-latest"]' -# Main Job + default: 'false' jobs: - config: - runs-on: ubuntu-latest - outputs: - NODE_VERSION: ${{ steps.set-config.outputs.CONFIG_NODE_VERSION }} - OS: ${{ steps.set-config.outputs.CONFIG_OS }} - steps: - - id: set-config - run: | - echo "CONFIG_NODE_VERSION=${{ toJSON(env.CONFIG_NODE_VERSION) }}" >> $GITHUB_OUTPUT - echo "CONFIG_OS=${{ toJSON(env.CONFIG_OS) }}" >> $GITHUB_OUTPUT - release-manual: - name: Test, Build and force Release - needs: config - - runs-on: ${{ matrix.OS }} + release: + name: Test, Build and Release + runs-on: ${{ matrix.os }} strategy: matrix: - OS: ${{ fromJSON(needs.config.outputs.OS) }} - NODE_VERSION: ${{ fromJSON(needs.config.outputs.NODE_VERSION) }} - + os: [ ubuntu-latest ] + node-version: [ 16.x ] steps: - - name: Checkout repo - uses: actions/checkout@v4 - - name: Setup Node.js ${{ matrix.NODE_VERSION }} - uses: actions/setup-node@v4 + - name: Checkout + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v1 with: - node-version: ${{ matrix.NODE_VERSION }} - - name: Commit trigger - run: | - git commit --allow-empty -m "${{ github.event.inputs.version }}: Trigger Manual Release" + node-version: ${{ matrix['node-version'] }} - name: Install dependencies run: npm ci - name: Build Library run: npm run build --if-present - name: Run Tests run: npm test --if-present - - name: Create a release - ${{ github.event.inputs.version }} - uses: cycjimmy/semantic-release-action@v4 + - name: Release + uses: cycjimmy/semantic-release-action@v2 with: - dry_run: ${{ github.event.inputs.dryRun == 'true' }} + dry_run: ${{ github.event.inputs.dryrun == 'true' }} extra_plugins: | - @semantic-release/changelog + @semantic-release/changelog@3.0.0 @semantic-release/git + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + GIT_AUTHOR_NAME: github-actions + GIT_AUTHOR_EMAIL: github-actions@github.com + GIT_COMMITTER_NAME: github-actions + GIT_COMMITTER_EMAIL: github-actions@github.com + CI: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0999d40..b658860 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,13 +12,13 @@ jobs: strategy: matrix: os: [ ubuntu-latest ] - node-version: [ 20.x ] + node-version: [ 16.x ] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v3 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v3 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@v4 + uses: cycjimmy/semantic-release-action@v3 with: dry_run: false extra_plugins: | diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 43ecafd..0000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,26 +0,0 @@ -# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. -# -# You can adjust the behavior by modifying this file. -# For more information, see: -# https://github.com/actions/stale -name: Close stale issues - -on: - schedule: - - cron: '30 1 * * *' - -jobs: - stale: - - runs-on: ubuntu-latest - permissions: - issues: write - - steps: - - 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: 90 - days-before-close: 15 - stale-issue-label: 'stale' diff --git a/README.md b/README.md index a52946a..128e3b8 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # ssh deployments -Deploy code with rsync over ssh. - -Execute remote scripts before or after rsync +Deploy code with rsync over ssh, using NodeJS. NodeJS version is more than a minute `faster` than simple Docker version. @@ -10,8 +8,6 @@ This GitHub Action deploys specific directory from `GITHUB_WORKSPACE` to a folde This action would usually follow a build/test action which leaves deployable code in `GITHUB_WORKSPACE`, eg `dist`; -In addition to rsync, this action provides scripts execution on remote host before and/or after rsync. - # Configuration Pass configuration with `env` vars @@ -28,8 +24,6 @@ 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] @@ -43,14 +37,13 @@ eg: myusername eg: '59184' -##### 5. `ARGS` (optional, default '-rlgoDzvc -i') +##### 5. `ARGS` (optional, default '-rltgoDzvO') For any initial/required rsync flags, eg: `-avzr --delete` ##### 6. `SOURCE` (optional, default '') -The source directory, path relative to `$GITHUB_WORKSPACE` root, eg: `dist/`. -Multiple sources should be separated by space. +The source directory, path relative to `$GITHUB_WORKSPACE` root, eg: `dist/` ##### 7. `TARGET` (optional, default '/home/REMOTE_USER/') @@ -60,54 +53,22 @@ The target directory path to exclude separated by `,`, ie: `/dist/, /node_modules/` -##### 9. `SCRIPT_BEFORE` (optional, default '') - -Script to run on host machine before rsync. Single line or multiline commands. -Execution is preformed by storing commands in `.sh` file and executing it via `.bash` over `ssh` -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_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. - -##### 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@v5.1.0 +Use the latest version from Marketplace,eg: ssh-deploy@v2 or use the latest version from a branch, eg: ssh-deploy@main ``` - name: Deploy to Staging server uses: easingthemes/ssh-deploy@main - with: + env: SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - ARGS: "-rlgoDzvc -i" + ARGS: "-rltgoDzvO" SOURCE: "dist/" REMOTE_HOST: ${{ secrets.REMOTE_HOST }} REMOTE_USER: ${{ secrets.REMOTE_USER }} TARGET: ${{ secrets.REMOTE_TARGET }} EXCLUDE: "/dist/, /node_modules/" - SCRIPT_BEFORE: | - whoami - ls -al - SCRIPT_AFTER: | - whoami - ls -al - echo $RSYNC_STDOUT ``` # Example usage in workflow @@ -123,20 +84,20 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v1 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v1 with: - node-version: '16.x' + node-version: '10.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 - with: + env: SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - ARGS: "-rlgoDzvc -i --delete" + ARGS: "-rltgoDzvO --delete" SOURCE: "dist/" REMOTE_HOST: ${{ secrets.REMOTE_HOST }} REMOTE_USER: ${{ secrets.REMOTE_USER }} @@ -146,35 +107,12 @@ jobs: ## Issues -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. -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 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. - +Almost 95% of the issues are related to wrong SSH connection. 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 - -- Optional ENV variables are created for simple requirements. -For complex use cases, use `ARGS` and `SSH_CMD_ARGS` to fully configure `rsync` with all possible options. -- If you need to use multiple steps, eg multi targets deployment, save shared ENV variables in `>> $GITHUB_ENV`. -Check .github/workflows/e2e.yml for an example -- For multi sources, use -R ARG to manipulate folders structure. -- Great post about `rsync` options specific to usage of this action: https://logansnotes.com/2020/gh-action-site-deploy/ - +Check manually your ssh connection from your client before opening a bug report. +Thanks. ## Disclaimer diff --git a/action.yml b/action.yml index 1c30b61..15aa86e 100644 --- a/action.yml +++ b/action.yml @@ -1,9 +1,9 @@ name: "ssh deploy" -description: "NodeJS action for FAST deployment with rsync/ssh and remote script execution before/after rsync" +description: "NodeJS action for FAST deployment with rsync/ssh" author: "easingthemes" inputs: - SSH_PRIVATE_KEY: - description: "Private key part of an SSH key pair" + SSH_PRIVATE_KEY: # Private Key + description: "Private Key" required: true REMOTE_HOST: description: "Remote host" @@ -16,46 +16,26 @@ inputs: required: false default: "22" SOURCE: - description: "Source directory, path relative to `$GITHUB_WORKSPACE` root, eg: `dist/`" + description: "Source directory" required: false default: "" TARGET: description: "Target directory" required: false - default: "" + default: "/home/REMOTE_USER/" ARGS: description: "Arguments to pass to rsync" required: false - default: "-rlgoDzvc -i" - SSH_CMD_ARGS: - description: "An array of ssh arguments, they must be prefixed with -o and separated by a comma, for example: -o SomeArgument=no, -o SomeOtherArgument=5 " - required: false - default: "-o StrictHostKeyChecking=no" + default: "-rltgoDzvO" EXCLUDE: - description: "paths to exclude separated by `,`, ie: `/dist/, /node_modules/`" - required: false - default: "" - SCRIPT_BEFORE: - 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" + description: "An array of folder to exclude" required: false default: "" outputs: status: description: "Status" runs: - using: "node20" + using: "node16" main: "dist/index.js" branding: color: "green" diff --git a/dist/index.js b/dist/index.js index b0a43df..3d77b41 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 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 +(()=>{var e={569:(e,r,s)=>{e.exports=s(325)},325:(e,r,s)=>{"use strict";var t=s(81).exec;var n=s(81).execSync;var o=s(147);var c=s(17);var i=o.access;var a=o.accessSync;var u=o.constants||o;var l=process.platform=="win32";var fileNotExists=function(e,r){i(e,u.F_OK,(function(e){r(!e)}))};var fileNotExistsSync=function(e){try{a(e,u.F_OK);return false}catch(e){return true}};var localExecutable=function(e,r){i(e,u.F_OK|u.X_OK,(function(e){r(null,!e)}))};var localExecutableSync=function(e){try{a(e,u.F_OK|u.X_OK);return true}catch(e){return false}};var commandExistsUnix=function(e,r,s){fileNotExists(e,(function(n){if(!n){var o=t("command -v "+r+" 2>/dev/null"+" && { echo >&1 "+r+"; exit 0; }",(function(e,r,t){s(null,!!r)}));return}localExecutable(e,s)}))};var commandExistsWindows=function(e,r,s){if(!/^(?!(?:.*\s|.*\.|\W+)$)(?:[a-zA-Z]:)?(?:(?:[^<>:"\|\?\*\n])+(?:\/\/|\/|\\\\|\\)?)+$/m.test(e)){s(null,false);return}var n=t("where "+r,(function(e){if(e!==null){s(null,false)}else{s(null,true)}}))};var commandExistsUnixSync=function(e,r){if(fileNotExistsSync(e)){try{var s=n("command -v "+r+" 2>/dev/null"+" && { echo >&1 "+r+"; exit 0; }");return!!s}catch(e){return false}}return localExecutableSync(e)};var commandExistsWindowsSync=function(e,r,s){if(!/^(?!(?:.*\s|.*\.|\W+)$)(?:[a-zA-Z]:)?(?:(?:[^<>:"\|\?\*\n])+(?:\/\/|\/|\\\\|\\)?)+$/m.test(e)){return false}try{var t=n("where "+r,{stdio:[]});return!!t}catch(e){return false}};var cleanInput=function(e){if(/[^A-Za-z0-9_\/:=-]/.test(e)){e="'"+e.replace(/'/g,"'\\''")+"'";e=e.replace(/^(?:'')+/g,"").replace(/\\'''/g,"\\'")}return e};if(l){cleanInput=function(e){var r=/[\\]/.test(e);if(r){var s='"'+c.dirname(e)+'"';var t='"'+c.basename(e)+'"';return s+":"+t}return'"'+e+'"'}}e.exports=function commandExists(e,r){var s=cleanInput(e);if(!r&&typeof Promise!=="undefined"){return new Promise((function(r,s){commandExists(e,(function(t,n){if(n){r(e)}else{s(t)}}))}))}if(l){commandExistsWindows(e,s,r)}else{commandExistsUnix(e,s,r)}};e.exports.sync=function(e){var r=cleanInput(e);if(l){return commandExistsWindowsSync(e,r)}else{return commandExistsUnixSync(e,r)}}},898:(e,r,s)=>{"use strict";var t=s(81).spawn;var n=s(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(r){var s=e[r];if(typeof s==="string"){e[r]=escapeSpaces(s)}else if(Array.isArray(s)===true){e[r]=s.map(escapeSpaces)}}));return e};e.exports=function(e,r){e=e||{};e=n._extend({},e);e=escapeSpacesInOptions(e);var s=e.platform||process.platform;var o=s==="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(o&&!a){i.push("--chmod=ugo=rwX")}if(typeof e.host!=="undefined"||e.ssh){i.push("--rsh");var u="ssh";if(typeof e.port!=="undefined"){u+=" -p "+e.port}if(typeof e.privateKey!=="undefined"){u+=" -i "+e.privateKey}if(typeof e.sshCmdArgs!=="undefined"){u+=" "+e.sshCmdArgs.join(" ")}i.push(u)}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"&&n.isArray(e.excludeFirst)){e.excludeFirst.forEach((function(e,r){i.push("--exclude="+e)}))}if(typeof e.include!=="undefined"&&n.isArray(e.include)){e.include.forEach((function(e,r){i.push("--include="+e)}))}if(typeof e.exclude!=="undefined"&&n.isArray(e.exclude)){e.exclude.forEach((function(e,r){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"&&n.isArray(e.args)){i=[...new Set([...i,...e.args])]}i=[...new Set(i)];var noop=function(){};var l=e.onStdout||noop;var d=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){r(null,null,null,f);return}try{var p="";var y="";var v;if(o){v=t("cmd.exe",["/s","/c",'"'+f+'"'],{windowsVerbatimArguments:true,stdio:[process.stdin,"pipe","pipe"]})}else{v=t("/bin/sh",["-c",f])}v.stdout.on("data",(function(e){l(e);p+=e}));v.stderr.on("data",(function(e){d(e);y+=e}));v.on("exit",(function(e){var s=null;if(e!==0){s=new Error("rsync exited with code "+e);s.code=e}r(s,p,y,f)}))}catch(e){r(e,null,null,f)}}},505:(e,r,s)=>{const{existsSync:t,mkdirSync:n,writeFileSync:o}=s(147);const{GITHUB_WORKSPACE:c}=process.env;const validateDir=e=>{if(!t(e)){console.log(`[SSH] Creating ${e} dir in `,c);n(e);console.log("✅ [SSH] dir created.")}else{console.log(`[SSH] ${e} dir exist`)}};const validateFile=e=>{if(!t(e)){console.log(`[SSH] Creating ${e} file in `,c);try{o(e,"",{encoding:"utf8",mode:384});console.log("✅ [SSH] file created.")}catch(r){console.error("⚠️ [SSH] writeFileSync error",e,r.message);process.abort()}}else{console.log(`[SSH] ${e} file exist`)}};e.exports={validateDir:validateDir,validateFile:validateFile}},229:e=>{const r=["REMOTE_HOST","REMOTE_USER","REMOTE_PORT","SSH_PRIVATE_KEY","DEPLOY_KEY_NAME","SOURCE","TARGET","ARGS","EXCLUDE"];const s={GITHUB_WORKSPACE:process.env.GITHUB_WORKSPACE};r.forEach((e=>{s[e]=process.env[e]||process.env[`INPUT_${e}`]}));e.exports=s},447:(e,r,s)=>{const{sync:t}=s(569);const{exec:n,execSync:o}=s(81);const validateRsync=(e=(()=>{}))=>{const r=t("rsync");if(r){console.log("⚠️ [CLI] Rsync exists");const r=o("rsync --version",{stdio:"inherit"});return e()}console.log('⚠️ [CLI] Rsync doesn\'t exists. Start installation with "apt-get" \n');n("sudo apt-get update && sudo apt-get --no-install-recommends install rsync",((r,s,t)=>{if(r){console.log("⚠️ [CLI] Rsync installation failed. Aborting ... ",r.message);process.abort()}else{console.log("✅ [CLI] Rsync installed. \n",s,t);e()}}))};const validateInputs=e=>{const r=Object.keys(e);const s=r.filter((r=>{const s=e[r];if(!s){console.error(`⚠️ [INPUTS] ${r} is mandatory`)}return s}));if(s.length!==r.length){console.error("⚠️ [INPUTS] Inputs not valid, aborting ...");process.abort()}};e.exports={validateRsync:validateRsync,validateInputs:validateInputs}},822:(e,r,s)=>{const{writeFileSync:t}=s(147);const{join:n}=s(17);const{validateDir:o,validateFile:c}=s(505);const{HOME:i}=process.env;const addSshKey=(e,r)=>{const s=n(i||__dirname,".ssh");const a=n(s,r);o(s);c(`${s}/known_hosts`);try{t(a,e,{encoding:"utf8",mode:384})}catch(e){console.error("⚠️ writeFileSync error",a,e.message);process.abort()}console.log("✅ Ssh key added to `.ssh` dir ",a);return a};e.exports={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 r={};function __nccwpck_require__(s){var t=r[s];if(t!==undefined){return t.exports}var n=r[s]={exports:{}};var o=true;try{e[s](n,n.exports,__nccwpck_require__);o=false}finally{if(o)delete r[s]}return n.exports}if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var s={};(()=>{const e=__nccwpck_require__(898);const{validateRsync:r,validateInputs:s}=__nccwpck_require__(447);const{addSshKey:t}=__nccwpck_require__(822);const{REMOTE_HOST:n,REMOTE_USER:o,REMOTE_PORT:c,SSH_PRIVATE_KEY:i,DEPLOY_KEY_NAME:a,SOURCE:u,TARGET:l,ARGS:d,EXCLUDE:f,GITHUB_WORKSPACE:p}=__nccwpck_require__(229);const y={ssh:true,sshCmdArgs:["-o StrictHostKeyChecking=no"],recursive:true};console.log("GITHUB_WORKSPACE: ",p);console.log("REMOTE_HOST: ",process.env.REMOTE_HOST);console.log("REMOTE_USER: ",process.env.REMOTE_USER);const v=(()=>{const rsync=({privateKey:r,port:s,src:t,dest:n,args:o,exclude:c})=>{console.log(`[Rsync] Starting Rsync Action: ${t} to ${n}`);if(c)console.log(`[Rsync] exluding folders ${c}`);try{e({src:t,dest:n,args:o,privateKey:r,port:s,excludeFirst:c,...y},((e,r,s,t)=>{if(e){console.error("⚠️ [Rsync] error: ",e.message);console.log("⚠️ [Rsync] stderr: ",s);console.log("⚠️ [Rsync] stdout: ",r);console.log("⚠️ [Rsync] cmd: ",t);process.abort()}else{console.log("✅ [Rsync] finished.",r)}}))}catch(e){console.error("⚠️ [Rsync] command error: ",e.message,e.stack);process.abort()}};const init=({src:e,dest:s,args:n,host:o="localhost",port:c,username:i,privateKeyContent:u,exclude:l=[]})=>{r((()=>{const r=t(u,a||"deploy_key");const d=`${i}@${o}:${s}`;rsync({privateKey:r,port:c,src:e,dest:d,args:n,exclude:l})}))};return{init:init}})();const run=()=>{s({SSH_PRIVATE_KEY:i,REMOTE_HOST:n,REMOTE_USER:o});v.init({src:`${p}/${u||""}`,dest:l||`/home/${o}/`,args:d?[d]:["-rltgoDzvO"],host:n,port:c||"22",username:o,privateKeyContent:i,exclude:(f||"").split(",").map((e=>e.trim()))})};run()})();module.exports=s})(); \ No newline at end of file diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 873cbcf..6f31251 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,285 +1,3 @@ -## [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) - - -### Bug Fixes - -* update sub dependencies ([2a32c8d](https://github.com/easingthemes/ssh-deploy/commit/2a32c8dcde2d64394a3577decdd9c902f37a30f8)) - -## [4.0.3](https://github.com/easingthemes/ssh-deploy/compare/v4.0.2...v4.0.3) (2023-01-03) - - -### Bug Fixes - -* update v3 e2e test ([450bf42](https://github.com/easingthemes/ssh-deploy/commit/450bf423f388777bde052ad7b5bc24eca392434d)) - -## [4.0.2](https://github.com/easingthemes/ssh-deploy/compare/v4.0.1...v4.0.2) (2023-01-03) - - -### Bug Fixes - -* add githubWorkspace as default source root ([3ef66d0](https://github.com/easingthemes/ssh-deploy/commit/3ef66d0be999ee45434505e8bedea4f8b5b5a909)) - -## [4.0.1](https://github.com/easingthemes/ssh-deploy/compare/v4.0.0...v4.0.1) (2023-01-03) - - -### Bug Fixes - -* fix default source if empty ([9f191f4](https://github.com/easingthemes/ssh-deploy/commit/9f191f42633c4a0f66054f0fc229c9e30a08f00c)) - -# [4.0.0](https://github.com/easingthemes/ssh-deploy/compare/v3.4.3...v4.0.0) (2023-01-03) - - -### Bug Fixes - -* rebuild ([2169399](https://github.com/easingthemes/ssh-deploy/commit/2169399fef9a60a2fea1ab03cce4ec8c2371e5c2)) - - -### Performance Improvements - -* update default rsync options ([2be7efb](https://github.com/easingthemes/ssh-deploy/commit/2be7efb376866327c1d1209f51eb43f34f07b354)) - - -### BREAKING CHANGES - -* Default rsync options updated to speed up default deployments. -from `rltgoDzvO` to `-rlgoDzvc -i` - -## [3.4.3](https://github.com/easingthemes/ssh-deploy/compare/v3.4.2...v3.4.3) (2023-01-03) - - -### Bug Fixes - -* move e2e tests typo branch name ([6d3554b](https://github.com/easingthemes/ssh-deploy/commit/6d3554b01e9a05de9a9d2b30274bee411a4986ed)) - -## [3.4.2](https://github.com/easingthemes/ssh-deploy/compare/v3.4.1...v3.4.2) (2023-01-03) - - -### Bug Fixes - -* Update changelog ([765f5ff](https://github.com/easingthemes/ssh-deploy/commit/765f5ffd3153c76442521c61c04656cafc182731)) - -## [3.4.1](https://github.com/easingthemes/ssh-deploy/compare/v3.4.0...v3.4.1) (2023-01-03) - - -### Bug Fixes - -* move e2e tests to main branch ([0069fae](https://github.com/easingthemes/ssh-deploy/commit/0069faea9ff3ea3cdd095b0f2663c9e2bcd97480)) - -# [3.4.0](https://github.com/easingthemes/ssh-deploy/compare/v3.3.4...v3.4.0) (2023-01-03) - - -**Full Changelog**: https://github.com/easingthemes/ssh-deploy/compare/v3.1.0...v3.4.1 - -### Features - -* Add SSH remote script support - before and after rsync ([2cd8a82](https://github.com/easingthemes/ssh-deploy/commit/2cd8a820e22135b7002fbe6a47cf33f99e2d337b)) -* Add multi source and multi target support ([73a65ec](https://github.com/easingthemes/ssh-deploy/commit/73a65ec97c5046a53f6b4c40823be5fd3826ede0)) -* Add SSH_CMD_ARGS support ([51a0063](https://github.com/easingthemes/ssh-deploy/commit/51a00631e2b91983fb610ca2157a673b9ae03c95)) -* Add manual inputs for e2e test ([8f71595](https://github.com/easingthemes/ssh-deploy/commit/8f715957ab9283ab2934dd1e5be9d78f2eaf3fae)) - -### Bug Fixes - -* Add visible Rsync CMD for debugging ([91b6d28](https://github.com/easingthemes/ssh-deploy/commit/91b6d280aee6a7ae666a11426fb356406f4a25a5)) -* log buffer from rsync stdout ([8e19e0d](https://github.com/easingthemes/ssh-deploy/commit/8e19e0d6bc9b1c332925ce0268ad64b50728fae5)) -* fix e2e yaml step name ([5c13615](https://github.com/easingthemes/ssh-deploy/commit/5c13615d4b7b0bda47f622ac5f4444484d8bcdb4)) - -## [3.3.4](https://github.com/easingthemes/ssh-deploy/compare/v3.3.3...v3.3.4) (2023-01-02) - - -### Bug Fixes - -* Add visible Rsync CMD for debugging ([91b6d28](https://github.com/easingthemes/ssh-deploy/commit/91b6d280aee6a7ae666a11426fb356406f4a25a5)) - -## [3.3.3](https://github.com/easingthemes/ssh-deploy/compare/v3.3.2...v3.3.3) (2023-01-02) - - -### Bug Fixes - -* log buffer from rsync stdout ([8e19e0d](https://github.com/easingthemes/ssh-deploy/commit/8e19e0d6bc9b1c332925ce0268ad64b50728fae5)) - -## [3.3.2](https://github.com/easingthemes/ssh-deploy/compare/v3.3.1...v3.3.2) (2023-01-02) - - -### Bug Fixes - -* Add manual inputs for e2e test ([8f71595](https://github.com/easingthemes/ssh-deploy/commit/8f715957ab9283ab2934dd1e5be9d78f2eaf3fae)) - -## [3.3.1](https://github.com/easingthemes/ssh-deploy/compare/v3.3.0...v3.3.1) (2023-01-02) - - -### Bug Fixes - -* Log rsync command ([85f4a96](https://github.com/easingthemes/ssh-deploy/commit/85f4a967435d7d44edf2fea628c9f59db5e890ee)) - -# [3.3.0](https://github.com/easingthemes/ssh-deploy/compare/v3.2.0...v3.3.0) (2023-01-02) - - -### Features - -* Add SSH_CMD_ARGS support ([51a0063](https://github.com/easingthemes/ssh-deploy/commit/51a00631e2b91983fb610ca2157a673b9ae03c95)) - -# [3.2.0](https://github.com/easingthemes/ssh-deploy/pull/94/files) (2023-01-02) - - -### Features - -* Add SSH remote script support - before and after rsync ([2cd8a82](https://github.com/easingthemes/ssh-deploy/pull/94/files)) -* feat: add sshCmdArgs option -* feat: add onStderr and onStdout logs -* feat: Add RSYNC_STDOUT env variable -* feat: Update emojis - -### Bug Fixes - -* fix: remove _unsafe _dirname -* fix: Add promise instead of callback -* fix: improve logs -* fix: Add simple command exists instead of a plugin -* fix: add non interactive install -* fix: Improve reject messages -* fix: update workflow actions - - -## [3.1.1](https://github.com/easingthemes/ssh-deploy/compare/v3.1.0...v3.1.1) (2023-01-02) - - -### Bug Fixes - -* use main branch for e2e testes ([21451fe](https://github.com/easingthemes/ssh-deploy/commit/21451fec978a02b55672490a2c11c6c17995ff95)) - # [3.1.0](https://github.com/easingthemes/ssh-deploy/compare/v3.0.1...v3.1.0) (2022-12-31) diff --git a/package-lock.json b/package-lock.json index 7cc9222..4026c44 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "@draganfilipovic/ssh-deploy", - "version": "4.1.10", + "version": "3.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@draganfilipovic/ssh-deploy", - "version": "4.1.10", + "version": "3.0.1", "license": "MIT", "dependencies": { + "command-exists": "^1.2.9", "rsyncwrapper": "^3.0.1" }, "devDependencies": { @@ -19,9 +20,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.0.tgz", + "integrity": "sha512-7yfvXy6MWLgWSFsLhz5yH3iQ52St8cdUY6FoGieKkRDVxuxmrNuUetIuu6cmjNWwniUHiWXjxCr5tTXDrbYS5A==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -300,6 +301,11 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "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": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -455,12 +461,12 @@ } }, "node_modules/eslint": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", - "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", + "version": "8.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz", + "integrity": "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.4.1", + "@eslint/eslintrc": "^1.4.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -755,9 +761,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", "dev": true, "dependencies": { "reusify": "^1.0.4" @@ -1312,9 +1318,9 @@ "dev": true }, "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -1357,18 +1363,6 @@ "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", @@ -1495,16 +1489,6 @@ "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", @@ -1744,18 +1728,12 @@ } }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, "node_modules/shebang-command": { @@ -1975,18 +1953,21 @@ "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", @@ -2002,9 +1983,9 @@ }, "dependencies": { "@eslint/eslintrc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", - "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.0.tgz", + "integrity": "sha512-7yfvXy6MWLgWSFsLhz5yH3iQ52St8cdUY6FoGieKkRDVxuxmrNuUetIuu6cmjNWwniUHiWXjxCr5tTXDrbYS5A==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -2207,6 +2188,11 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "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": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2324,12 +2310,12 @@ "dev": true }, "eslint": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", - "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", + "version": "8.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.30.0.tgz", + "integrity": "sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.4.1", + "@eslint/eslintrc": "^1.4.0", "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -2379,7 +2365,7 @@ "confusing-browser-globals": "^1.0.10", "object.assign": "^4.1.2", "object.entries": "^1.1.5", - "semver": "^7.5.2" + "semver": "^6.3.0" } }, "eslint-import-resolver-node": { @@ -2563,9 +2549,9 @@ "dev": true }, "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz", + "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -2957,9 +2943,9 @@ "dev": true }, "json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -2990,15 +2976,6 @@ "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", @@ -3092,15 +3069,7 @@ "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "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 - } + "word-wrap": "^1.2.3" } }, "p-limit": { @@ -3247,13 +3216,10 @@ } }, "semver": { - "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" - } + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true }, "shebang-command": { "version": "2.0.0", @@ -3415,18 +3381,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 22b5373..756b0ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@draganfilipovic/ssh-deploy", - "version": "5.1.1", + "version": "3.1.0", "description": "Fast NodeJS action to deploy specific directory from `GITHUB_WORKSPACE` to a server via rsync over ssh.", "main": "dist/index.js", "files": [ @@ -30,6 +30,7 @@ }, "homepage": "https://github.com/easingthemes/ssh-deploy#readme", "dependencies": { + "command-exists": "^1.2.9", "rsyncwrapper": "^3.0.1" }, "devDependencies": { @@ -37,9 +38,5 @@ "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 78379e1..e9c66d4 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1,95 +1,38 @@ -const { existsSync, mkdirSync, writeFileSync, unlink } = require('fs'); -const { join } = require('path'); +const { existsSync, mkdirSync, writeFileSync } = require('fs'); + +const { + GITHUB_WORKSPACE +} = process.env; const validateDir = (dir) => { - if (!dir) { - console.warn('⚠️ [DIR] dir is not defined'); - return; - } - if (existsSync(dir)) { - console.log(`✅ [DIR] ${dir} dir exist`); - return; - } - - console.log(`[DIR] Creating ${dir} dir in workspace root`); - mkdirSync(dir); - console.log('✅ [DIR] dir created.'); -}; - -const handleError = (message, isRequired) => { - if (isRequired) { - throw new Error(message); - } - console.warn(message); -}; - -const writeToFile = ({ dir, filename, content, isRequired, mode = '0644' }) => { - 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] writing ${filePath} file ...`, content.length); - writeFileSync(filePath, content, { - encoding: 'utf8', - mode - }); - } catch (error) { - const message = `⚠️[FILE] Writing to file error. filePath: ${filePath}, message: ${error.message}`; - handleError(message, isRequired); + if (!existsSync(dir)) { + console.log(`[SSH] Creating ${dir} dir in `, GITHUB_WORKSPACE); + mkdirSync(dir); + console.log('✅ [SSH] dir created.'); + } else { + console.log(`[SSH] ${dir} dir exist`); } }; -const 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) => { - const inputValue = inputs[inputKey]; - - if (!inputValue) { - console.error(`❌ [INPUTS] ${inputKey} is mandatory`); +const validateFile = (filePath) => { + if (!existsSync(filePath)) { + console.log(`[SSH] Creating ${filePath} file in `, GITHUB_WORKSPACE); + try { + writeFileSync(filePath, '', { + encoding: 'utf8', + mode: 0o600 + }); + console.log('✅ [SSH] file created.'); + } catch (e) { + console.error('⚠️ [SSH] writeFileSync error', filePath, e.message); + process.abort(); } - - return inputValue; - }); - - if (validInputs.length !== inputKeys.length) { - throw new Error('⚠️ [INPUTS] Inputs not valid, aborting ...'); + } else { + console.log(`[SSH] ${filePath} file exist`); } }; -const snakeToCamel = (str) => str.replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase()); - module.exports = { - writeToFile, - deleteFile, - validateRequiredInputs, - snakeToCamel + validateDir, + validateFile }; diff --git a/src/index.js b/src/index.js index 7266355..8276749 100644 --- a/src/index.js +++ b/src/index.js @@ -1,48 +1,79 @@ #!/usr/bin/env node -const { sshDeploy } = require('./rsyncCli'); -const { remoteCmdBefore, remoteCmdAfter } = require('./remoteCmd'); -const { addSshKey, getPrivateKeyPath, updateKnownHosts } = require('./sshKey'); -const { validateRequiredInputs } = require('./helpers'); -const inputs = require('./inputs'); +const nodeRsync = require('rsyncwrapper'); -const run = async () => { - const { - source, remoteUser, remoteHost, remotePort, - deployKeyName, sshPrivateKey, - args, exclude, sshCmdArgs, - scriptBefore, scriptBeforeRequired, - scriptAfter, scriptAfterRequired, - rsyncServer - } = inputs; - // Validate required inputs - validateRequiredInputs({ sshPrivateKey, remoteHost, remoteUser }); - // Add SSH key - addSshKey(sshPrivateKey, deployKeyName); - const { path: privateKeyPath } = getPrivateKeyPath(deployKeyName); - // Update known hosts if ssh command is present to avoid prompt - if (scriptBefore || scriptAfter) { - updateKnownHosts(remoteHost, remotePort); - } - // Check Script before - if (scriptBefore) { - await remoteCmdBefore(scriptBefore, privateKeyPath, scriptBeforeRequired); - } - /* eslint-disable object-property-newline */ - await sshDeploy({ - source, rsyncServer, exclude, remotePort, - privateKeyPath, args, sshCmdArgs - }); - // Check script after - if (scriptAfter) { - await remoteCmdAfter(scriptAfter, privateKeyPath, scriptAfterRequired); - } +const { validateRsync, validateInputs } = require('./rsyncCli'); +const { addSshKey } = require('./sshKey'); + +const { + REMOTE_HOST, REMOTE_USER, + REMOTE_PORT, SSH_PRIVATE_KEY, DEPLOY_KEY_NAME, + SOURCE, TARGET, ARGS, EXCLUDE, + GITHUB_WORKSPACE +} = require('./inputs'); + +const defaultOptions = { + ssh: true, + sshCmdArgs: ['-o StrictHostKeyChecking=no'], + recursive: true }; -run() - .then((data = '') => { - console.log('✅ [DONE]', data); - }) - .catch((error) => { - console.error('❌ [ERROR]', error.message); - process.exit(1); +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 = () => { + validateInputs({ SSH_PRIVATE_KEY, REMOTE_HOST, REMOTE_USER }); + + sshDeploy.init({ + src: `${GITHUB_WORKSPACE}/${SOURCE || ''}`, + dest: TARGET || `/home/${REMOTE_USER}/`, + args: ARGS ? [ARGS] : ['-rltgoDzvO'], + host: REMOTE_HOST, + port: REMOTE_PORT || '22', + username: REMOTE_USER, + privateKeyContent: SSH_PRIVATE_KEY, + exclude: (EXCLUDE || '').split(',').map((item) => item.trim()) // split by comma and trim whitespace }); +}; + +run(); diff --git a/src/inputs.js b/src/inputs.js index 2c5dd19..9b7638e 100644 --- a/src/inputs.js +++ b/src/inputs.js @@ -1,50 +1,11 @@ -const { snakeToCamel } = require('./helpers'); - -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_REQUIRED', 'SCRIPT_AFTER_REQUIRED']; - -const githubWorkspace = process.env.GITHUB_WORKSPACE; -const remoteUser = process.env.REMOTE_USER || process.env.INPUT_REMOTE_USER; - -const defaultInputs = { - source: '', - target: `/home/${remoteUser}/`, - exclude: '', - args: '-rlgoDzvc -i', - sshCmdArgs: '-o StrictHostKeyChecking=no', - deployKeyName: `deploy_key_${remoteUser}_${Date.now()}` -}; +const inputNames = ['REMOTE_HOST', 'REMOTE_USER', 'REMOTE_PORT', 'SSH_PRIVATE_KEY', 'DEPLOY_KEY_NAME', 'SOURCE', 'TARGET', 'ARGS', 'EXCLUDE']; const inputs = { - githubWorkspace + GITHUB_WORKSPACE: process.env.GITHUB_WORKSPACE }; inputNames.forEach((input) => { - const inputName = snakeToCamel(input.toLowerCase()); - 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 - switch (inputName) { - case 'source': - extendedVal = validVal.split(' ').map((src) => `${githubWorkspace}/${src}`); - break; - case 'args': - extendedVal = validVal.split(' '); - break; - case 'exclude': - case 'sshCmdArgs': - extendedVal = validVal.split(',').map((item) => item.trim()); - break; - } - - inputs[inputName] = extendedVal; + inputs[input] = process.env[input] || process.env[`INPUT_${input}`]; }); -inputs.sshServer = `${inputs.remoteUser}@${inputs.remoteHost}`; -inputs.rsyncServer = `${inputs.remoteUser}@${inputs.remoteHost}:${inputs.target}`; - module.exports = inputs; diff --git a/src/remoteCmd.js b/src/remoteCmd.js deleted file mode 100644 index 70f7656..0000000 --- a/src/remoteCmd.js +++ /dev/null @@ -1,47 +0,0 @@ -const { exec } = require('child_process'); -const crypto = require('crypto'); -const { sshServer, githubWorkspace, remotePort } = require('./inputs'); -const { writeToFile, deleteFile } = require('./helpers'); - -const handleError = (message, isRequired, callback) => { - if (isRequired) { - callback(new Error(message)); - } else { - console.warn(message); - } -}; - -// eslint-disable-next-line max-len -const remoteCmd = async (content, privateKeyPath, isRequired, label) => new Promise((resolve, reject) => { - 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 -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 { - 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); - } - } - ); - } catch (err) { - handleError(err.message, isRequired, reject); - } -}); - -module.exports = { - remoteCmdBefore: async (cmd, privateKeyPath, isRequired) => remoteCmd(cmd, privateKeyPath, isRequired, 'before'), - remoteCmdAfter: async (cmd, privateKeyPath, isRequired) => remoteCmd(cmd, privateKeyPath, isRequired, 'after') -}; diff --git a/src/rsyncCli.js b/src/rsyncCli.js index 562a22e..aa8190b 100644 --- a/src/rsyncCli.js +++ b/src/rsyncCli.js @@ -1,86 +1,46 @@ -const { execSync } = require('child_process'); -const nodeRsync = require('rsyncwrapper'); +const { sync: commandExists } = require("command-exists"); +const { exec, execSync } = require("child_process"); -const nodeRsyncPromise = async (config) => new Promise((resolve, reject) => { - const logCMD = (cmd) => { - console.warn('================================================================'); - console.log(cmd); - console.warn('================================================================'); - }; - - try { - nodeRsync(config, (error, stdout, stderr, cmd) => { - if (error) { - console.error('❌ [Rsync] error: '); - console.error(error); - console.error('❌ [Rsync] stderr: '); - console.error(stderr); - console.error('❌️ [Rsync] stdout: '); - console.error(stdout); - console.error('❌ [Rsync] command: '); - logCMD(cmd); - reject(new Error(`${error.message}\n\n${stderr}`)); - } else { - console.log('⭐ [Rsync] command finished: '); - logCMD(cmd); - resolve(stdout); - } - }); - } catch (error) { - console.error('❌ [Rsync] command error: ', error.message, error.stack); - reject(error); - } -}); - -const validateRsync = async () => { - try { - execSync('rsync --version', { stdio: 'inherit' }); - console.log('✅️ [CLI] Rsync exists'); - return; - } catch (error) { - console.warn('⚠️ [CLI] Rsync doesn\'t exists', error.message); +const validateRsync = (callback = () => {}) => { + const rsyncCli = commandExists("rsync"); + if (rsyncCli) { + console.log('⚠️ [CLI] Rsync exists'); + const rsyncVersion = execSync("rsync --version", { stdio: 'inherit' }); + return callback(); } - console.log('[CLI] Start rsync installation with "apt-get" \n'); - try { - execSync('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 (error) { - throw new Error(`⚠️ [CLI] Rsync installation failed. Aborting ... error: ${error.message}`); - } -}; + console.log('⚠️ [CLI] Rsync doesn\'t exists. Start installation with "apt-get" \n'); -const rsyncCli = async ({ - source, rsyncServer, exclude, remotePort, - privateKeyPath, args, sshCmdArgs -}) => { - console.log(`[Rsync] Starting Rsync Action: ${source} to ${rsyncServer}`); - if (exclude && exclude.length > 0) console.log(`[Rsync] excluding folders ${exclude}`); - - const defaultOptions = { - ssh: true, - recursive: true, - onStdout: (data) => console.log(data.toString()), - onStderr: (data) => console.error(data.toString()) - }; - - // RSYNC COMMAND - /* eslint-disable object-property-newline */ - return nodeRsyncPromise({ - ...defaultOptions, - src: source, dest: rsyncServer, excludeFirst: exclude, port: remotePort, - privateKey: privateKeyPath, args, sshCmdArgs + exec("sudo apt-get update && sudo apt-get --no-install-recommends install rsync", (err, data, stderr) => { + if (err) { + console.log("⚠️ [CLI] Rsync installation failed. Aborting ... ", err.message); + process.abort(); + } else { + console.log("✅ [CLI] Rsync installed. \n", data, stderr); + callback(); + } }); }; -const sshDeploy = async (params) => { - await validateRsync(); - const stdout = await rsyncCli(params); - console.log('✅ [Rsync] finished.', stdout); - process.env.RSYNC_STDOUT = `${stdout}`; - return stdout; +const validateInputs = (inputs) => { + const inputKeys = Object.keys(inputs); + const validInputs = inputKeys.filter((inputKey) => { + const inputValue = inputs[inputKey]; + + if (!inputValue) { + console.error(`⚠️ [INPUTS] ${inputKey} is mandatory`); + } + + return inputValue; + }); + + if (validInputs.length !== inputKeys.length) { + console.error("⚠️ [INPUTS] Inputs not valid, aborting ..."); + process.abort(); + } }; module.exports = { - sshDeploy + validateRsync, + validateInputs, }; diff --git a/src/sshKey.js b/src/sshKey.js index 066ed02..72eca6a 100644 --- a/src/sshKey.js +++ b/src/sshKey.js @@ -1,44 +1,37 @@ +const { writeFileSync } = require('fs'); const { join } = require('path'); -const { execSync } = require('child_process'); -const { EOL } = require('os'); -const { writeToFile } = require('./helpers'); -const KNOWN_HOSTS = 'known_hosts'; -const getPrivateKeyPath = (filename = '') => { - const { HOME } = process.env; - const dir = join(HOME || '~', '.ssh'); - const knownHostsPath = join(dir, KNOWN_HOSTS); - return { - dir, - filename, - path: join(dir, filename), - knownHostsPath - }; -}; +const { + validateDir, + validateFile +} = require('./helpers'); -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: `${content}${EOL}`, isRequired: true, mode: '0400' }); - console.log('✅ [SSH] key added to `.ssh` dir ', dir, filename); -}; +const { + HOME +} = process.env; + +const addSshKey = (key, name) => { + const sshDir = join(HOME || __dirname, '.ssh'); + const filePath = join(sshDir, name); + + validateDir(sshDir); + validateFile(`${sshDir}/known_hosts`); -const updateKnownHosts = (host, remotePort) => { - const { knownHostsPath } = getPrivateKeyPath(); - console.log('[SSH] Adding host to `known_hosts` ....', host, knownHostsPath); try { - execSync(`ssh-keyscan -p ${(remotePort || 22)} -H ${host} >> ${knownHostsPath}`, { - stdio: 'inherit' + writeFileSync(filePath, key, { + encoding: 'utf8', + mode: 0o600 }); - } catch (error) { - console.error('❌ [SSH] Adding host to `known_hosts` ERROR', host, error.message); + } catch (e) { + console.error('⚠️ writeFileSync error', filePath, e.message); + process.abort(); } - console.log('✅ [SSH] Adding host to `known_hosts` DONE', host, knownHostsPath); + + console.log('✅ Ssh key added to `.ssh` dir ', filePath); + + return filePath; }; module.exports = { - getPrivateKeyPath, - updateKnownHosts, addSshKey -}; +} diff --git a/src/test.js b/src/test.js new file mode 100644 index 0000000..75ab1c1 --- /dev/null +++ b/src/test.js @@ -0,0 +1,13 @@ +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('||||||||||||||||||||||||||||||||||||||'); diff --git a/test/Dockerfile b/test/Dockerfile index fc9efdf..a79ba12 100644 --- a/test/Dockerfile +++ b/test/Dockerfile @@ -6,32 +6,23 @@ RUN apt update RUN apt install openssh-server rsync sudo -y +RUN useradd -rm -d /home/test -s /bin/bash -g root -G sudo -u 1000 test + +RUN usermod -aG sudo test + RUN echo "PubkeyAuthentication yes" >> /etc/ssh/sshd_config.d/pub.conf RUN echo "AuthorizedKeysFile .ssh/authorized_keys" >> /etc/ssh/sshd_config.d/pub.conf RUN mkdir -p /var/www/html -RUN mkdir -p /var/www/html/test -RUN mkdir -p /var/www/html/test2 -RUN chmod -R 775 /var/www/html - -RUN useradd -rm -d /home/test -s /bin/bash -g root -G sudo -u 1000 test -RUN usermod -aG sudo test +RUN chown -R test /var/www/html RUN mkdir -p /home/test/.ssh RUN echo "$SSH_PUB_KEY" > /home/test/.ssh/authorized_keys RUN chmod 700 /home/test/.ssh RUN chown -R test /home/test/.ssh -RUN useradd -rm -d /home/test2 -s /bin/bash -g root -G sudo -u 1002 test2 -RUN usermod -aG sudo test2 -RUN mkdir -p /home/test2/.ssh -RUN echo "$SSH_PUB_KEY" > /home/test2/.ssh/authorized_keys -RUN chmod 700 /home/test2/.ssh -RUN chown -R test2 /home/test2/.ssh - RUN service ssh start RUN echo 'test:test' | chpasswd -RUN echo 'test2:test2' | chpasswd EXPOSE 22