mirror of
				https://github.com/easingthemes/ssh-deploy.git
				synced 2025-10-30 23:30:09 +08:00 
			
		
		
		
	feat: Add multi source and multi target support
This commit is contained in:
		
							parent
							
								
									98ee38d52e
								
							
						
					
					
						commit
						73a65ec97c
					
				
							
								
								
									
										104
									
								
								.github/workflows/e2e-manual.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								.github/workflows/e2e-manual.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,104 @@ | ||||
| name: e2e Manual Test | ||||
| 
 | ||||
| on: | ||||
|   workflow_dispatch: | ||||
|     inputs: | ||||
|       ARGS: | ||||
|         description: 'ARGS' | ||||
|         required: true | ||||
|         default: '-rltgoDzvO --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@v3 | ||||
| 
 | ||||
|         # ################################################################# | ||||
|         # 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<<EOF" >> $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<<EOF" >> $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@feature/multi-src | ||||
|         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 | ||||
							
								
								
									
										79
									
								
								.github/workflows/e2e.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										79
									
								
								.github/workflows/e2e.yml
									
									
									
									
										vendored
									
									
								
							| @ -3,25 +3,11 @@ name: e2e Test | ||||
| on: | ||||
|   push: | ||||
|     branches: [ 'feature/multi-src' ] | ||||
|   workflow_dispatch: | ||||
|     inputs: | ||||
|       ARGS: | ||||
|         description: 'ARGS' | ||||
|         required: true | ||||
|         default: '-rltgoDzvO --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 | ||||
|   TEST_USER2: test2 | ||||
| 
 | ||||
| jobs: | ||||
|   e2e: | ||||
| @ -31,11 +17,15 @@ jobs: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
| 
 | ||||
|       - name: Clean up old test files | ||||
|         # ################################################################# | ||||
|         # 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: Create ssh keys | ||||
|       - name: [E2E Test Specific] Create ssh keys | ||||
|         run: | | ||||
|           echo $HOME | ||||
|           ls -la $HOME | ||||
| @ -47,13 +37,12 @@ jobs: | ||||
|           cat $HOME/.ssh/id_rsa >> $GITHUB_ENV | ||||
|           echo "EOF" >> $GITHUB_ENV | ||||
| 
 | ||||
|       - name: Build Host Server Image | ||||
|       - 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)" \ | ||||
|             --build-arg ssh_user="${{ env.TEST_USER }}" | ||||
|             --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<<EOF" >> $GITHUB_ENV | ||||
| @ -61,7 +50,7 @@ jobs: | ||||
|           echo "EOF" >> $GITHUB_ENV | ||||
|         shell: bash | ||||
| 
 | ||||
|       - name: Create project file | ||||
|       - name: [E2E Test Specific] Create project file | ||||
|         run: | | ||||
|           mkdir test_project2 && cd "$_" | ||||
|           truncate -s 5MB info2.txt | ||||
| @ -76,24 +65,38 @@ jobs: | ||||
|           truncate -s 5MB text_in_skip_dir.txt | ||||
|           cd ../ | ||||
|           cat index.html | ||||
|           echo "test_project:" && ls -l | ||||
|           echo "skip_dir:" && ls -l skip_dir | ||||
|           echo "test_project:" && ls -lR | ||||
|           echo "skip_dir:" && ls -lR skip_dir | ||||
| 
 | ||||
|       - name: e2e Test published ssh-deploy action | ||||
|         # ################################################################# | ||||
|         # END [E2E Test Specific] steps | ||||
|         # ################################################################# | ||||
| 
 | ||||
|       - name: set shared ENV variables for multi target deployment | ||||
|         run: | | ||||
|           echo "ARGS=-rltgoDzvOR --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<<EOF" >> $GITHUB_ENV | ||||
|           echo "whoami" >> $GITHUB_ENV | ||||
|           echo "ls -lR /var/www/html/" >> $GITHUB_ENV | ||||
|           echo "EOF" >> $GITHUB_ENV | ||||
|           echo "SCRIPT_AFTER<<EOF" >> $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 | ||||
|         uses: easingthemes/ssh-deploy@feature/multi-src | ||||
|         env: | ||||
|           # ENV Vars created in previous steps: | ||||
|           # SSH_PRIVATE_KEY: $EXAMPLE_SSH_PRIVATE_KEY | ||||
|           # REMOTE_HOST: $EXAMPLE_REMOTE_HOST | ||||
|           # Shared ENV Vars created in previous steps | ||||
|           REMOTE_USER: ${{ env.TEST_USER }} | ||||
|           ARGS: ${{ github.event.inputs.ARGS || '-rltgoDzvO --delete --chmod=ugo=rwX --progress' }} | ||||
|           SSH_CMD_ARGS: ${{ github.event.inputs.SSH_CMD_ARGS || '-o StrictHostKeyChecking=no, -o UserKnownHostsFile=/dev/null' }} | ||||
|           SOURCE: ["test_project/, test_project2/"] | ||||
|           TARGET: "/var/www/html/" | ||||
|           EXCLUDE: ${{ github.event.inputs.EXCLUDE || 'skip_dir/, /node_modules/' }} | ||||
|           SCRIPT_BEFORE: | | ||||
|             whoami | ||||
|             ls -al /var/www/html/ | ||||
|           SCRIPT_AFTER: | | ||||
|             ls -al /var/www/html/ | ||||
|             echo $RSYNC_STDOUT | ||||
|           TARGET: /var/www/html/${{ env.TEST_USER }} | ||||
| 
 | ||||
|       - name: e2e Test ssh-deploy action - Target 2 | ||||
|         uses: easingthemes/ssh-deploy@feature/multi-src | ||||
|         env: | ||||
|           # Shared ENV Vars created in previous steps | ||||
|           REMOTE_USER: ${{ env.TEST_USER2 }} | ||||
|           TARGET: /var/www/html/${{ env.TEST_USER2 }} | ||||
|  | ||||
							
								
								
									
										91
									
								
								.github/workflows/manual-release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										91
									
								
								.github/workflows/manual-release.yml
									
									
									
									
										vendored
									
									
								
							| @ -2,38 +2,20 @@ name: Manual Release | ||||
| on: | ||||
|   workflow_dispatch: | ||||
|     inputs: | ||||
|       dryrun: | ||||
|         description: 'DryRUn' | ||||
|       version: | ||||
|         description: 'Version' | ||||
|         type: choice | ||||
|         required: true | ||||
|         default: 'false' | ||||
| jobs: | ||||
|   release: | ||||
|     name: Test, Build and Release | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: [ ubuntu-latest ] | ||||
|         node-version: [ 16.x ] | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v3 | ||||
|       - name: Setup Node.js | ||||
|         uses: actions/setup-node@v3 | ||||
|         with: | ||||
|           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: Release | ||||
|         uses: cycjimmy/semantic-release-action@v3 | ||||
|         with: | ||||
|           dry_run: ${{ github.event.inputs.dryrun == 'true' }} | ||||
|           extra_plugins: | | ||||
|             @semantic-release/changelog | ||||
|             @semantic-release/git | ||||
|         default: patch | ||||
|         options: | ||||
|           - patch | ||||
|           - minor | ||||
|           - major | ||||
|       dryRun: | ||||
|         description: 'DryRun' | ||||
|         type: boolean | ||||
|         default: true | ||||
| # ENV and Config | ||||
| env: | ||||
|   GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
|   NPM_TOKEN: ${{ secrets.NPM_TOKEN }} | ||||
| @ -42,3 +24,50 @@ jobs: | ||||
|   GIT_COMMITTER_NAME: github-actions | ||||
|   GIT_COMMITTER_EMAIL: github-actions@github.com | ||||
|   CI: true | ||||
|   CONFIG_NODE_VERSION: '["lts/*"]' | ||||
|   CONFIG_OS: '["ubuntu-latest"]' | ||||
| # Main Job | ||||
| 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 }} | ||||
|     strategy: | ||||
|       matrix: | ||||
|         OS: ${{ fromJSON(needs.config.outputs.OS) }} | ||||
|         NODE_VERSION: ${{ fromJSON(needs.config.outputs.NODE_VERSION) }} | ||||
| 
 | ||||
|     steps: | ||||
|       - name: Checkout repo | ||||
|         uses: actions/checkout@v3 | ||||
|       - name: Setup Node.js ${{ matrix.NODE_VERSION }} | ||||
|         uses: actions/setup-node@v3 | ||||
|         with: | ||||
|           node-version: ${{ matrix.NODE_VERSION }} | ||||
|       - name: Commit trigger | ||||
|         run: | | ||||
|           git commit --allow-empty -m "${{ github.event.inputs.version }}: Trigger Manual Release" | ||||
|       - name: Install dependencies | ||||
|         run: npm ci | ||||
|       - name: Build Library | ||||
|         run: npm run build --if-present | ||||
|       - name: Run Tests | ||||
|         run: npm test --if-present | ||||
|       - name: Publish npm package | ||||
|         uses: cycjimmy/semantic-release-action@v3 | ||||
|         with: | ||||
|           dry_run: ${{ github.event.inputs.dryRun == 'true' }} | ||||
|           extra_plugins: | | ||||
|             @semantic-release/changelog | ||||
|             @semantic-release/git | ||||
|  | ||||
							
								
								
									
										11
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README.md
									
									
									
									
									
								
							| @ -47,7 +47,8 @@ For any initial/required rsync flags, eg: `-avzr --delete` | ||||
| 
 | ||||
| ##### 6. `SOURCE` (optional, default '') | ||||
| 
 | ||||
| The source directory, path relative to `$GITHUB_WORKSPACE` root, eg: `dist/` | ||||
| The source directory, path relative to `$GITHUB_WORKSPACE` root, eg: `dist/`. | ||||
| Multiple sources should be separated by space. | ||||
| 
 | ||||
| ##### 7. `TARGET` (optional, default '/home/REMOTE_USER/') | ||||
| 
 | ||||
| @ -148,6 +149,14 @@ Check actions tab for example. | ||||
| 
 | ||||
| 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. | ||||
| 
 | ||||
| 
 | ||||
| ## Disclaimer | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										2
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -10,12 +10,12 @@ const githubWorkspace = process.env.GITHUB_WORKSPACE; | ||||
| const remoteUser = process.env.REMOTE_USER; | ||||
| 
 | ||||
| const defaultInputs = { | ||||
|   source: '', | ||||
|   source: './', | ||||
|   target: `/home/${remoteUser}/`, | ||||
|   exclude: '', | ||||
|   args: '-rltgoDzvO', | ||||
|   sshCmdArgs: '-o StrictHostKeyChecking=no', | ||||
|   deployKeyName: 'deploy_key' | ||||
|   deployKeyName: `deploy_key_${remoteUser}_${Date.now()}` | ||||
| }; | ||||
| 
 | ||||
| const inputs = { | ||||
| @ -29,6 +29,9 @@ inputNames.forEach((input) => { | ||||
|   let extendedVal = validVal; | ||||
|   // eslint-disable-next-line default-case
 | ||||
|   switch (inputName) { | ||||
|     case 'source': | ||||
|       extendedVal = validVal.indexOf(' ') > -1 ? validVal.split(' ') : validVal; | ||||
|       break; | ||||
|     case 'args': | ||||
|       extendedVal = validVal.split(' '); | ||||
|       break; | ||||
|  | ||||
| @ -17,7 +17,7 @@ const remoteCmd = async (content, privateKeyPath, isRequired, label) => new Prom | ||||
|     writeToFile({ dir: githubWorkspace, filename, content }); | ||||
|     console.log(`Executing remote script: ssh -i ${privateKeyPath} ${sshServer}`); | ||||
|     exec( | ||||
|       `DEBIAN_FRONTEND=noninteractive ssh -i ${privateKeyPath} ${sshServer} 'RSYNC_STDOUT="${process.env.RSYNC_STDOUT}" bash -s' < ${filename}`, | ||||
|       `DEBIAN_FRONTEND=noninteractive ssh -i ${privateKeyPath} -o StrictHostKeyChecking=no ${sshServer} 'RSYNC_STDOUT="${process.env.RSYNC_STDOUT}" bash -s' < ${filename}`, | ||||
|       (err, data, stderr) => { | ||||
|         if (err) { | ||||
|           const message = `⚠️ [CMD] Remote script failed: ${err.message}`; | ||||
|  | ||||
| @ -55,7 +55,7 @@ const rsyncCli = async ({ | ||||
|   privateKeyPath, args, sshCmdArgs | ||||
| }) => { | ||||
|   console.log(`[Rsync] Starting Rsync Action: ${source} to ${rsyncServer}`); | ||||
|   if (exclude) console.log(`[Rsync] excluding folders ${exclude}`); | ||||
|   if (exclude && exclude.length > 0) console.log(`[Rsync] excluding folders ${exclude}`); | ||||
| 
 | ||||
|   const defaultOptions = { | ||||
|     ssh: true, | ||||
|  | ||||
| @ -6,23 +6,32 @@ 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 chown -R test /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 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 | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Dragan Filipovic
						Dragan Filipovic