├── .gitignore ├── LICENSE ├── README.md ├── libraries └── util.bash └── tunnel.bash /.gitignore: -------------------------------------------------------------------------------- 1 | # File 2 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Nam Nguyen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ssh-tunneling 2 | 3 | ``` 4 | SYNOPSIS : 5 | tunnel.bash 6 | --help 7 | --configure 8 | --local-port 9 | --remote-port 10 | --local-to-remote 11 | --remote-to-local 12 | --remote-user 13 | --remote-host 14 | --identity-file 15 | 16 | DESCRIPTION : 17 | --help Help page 18 | --configure Config remote server to support forwarding (optional) 19 | This option will require arguments '--remote-user' and '--remote-host' 20 | --local-port Local port number (require) 21 | --remote-port Remote port number (require) 22 | --local-to-remote Forward request from local machine to remote machine 23 | Either '--local-to-remote' or '--remote-to-local' must be specified 24 | --remote-to-local Forward request from remote machine to local machine (require) 25 | Either '--local-to-remote' or '--remote-to-local' must be specified 26 | --remote-user Remote user (require) 27 | --remote-host Remote host (require) 28 | --identity-file Path to private key (*.ppk, *.pem) to access remote server (optional) 29 | 30 | EXAMPLES : 31 | ./tunnel.bash --help 32 | 33 | ./tunnel.bash --configure --remote-user 'root' --remote-host 'my-server.com' 34 | ./tunnel.bash --configure --remote-user 'root' --remote-host 'my-server.com' --identity-file '/keys/my-server/key.ppk' 35 | 36 | ./tunnel.bash --local-port 8080 --remote-port 9090 --local-to-remote --remote-user 'root' --remote-host 'my-server.com' 37 | ./tunnel.bash --local-port 8080 --remote-port 9090 --local-to-remote --remote-user 'root' --remote-host 'my-server.com' --identity-file '/keys/my-server/key.ppk' 38 | 39 | ./tunnel.bash --local-port 8080 --remote-port 9090 --remote-to-local --remote-user 'root' --remote-host 'my-server.com' 40 | ./tunnel.bash --local-port 8080 --remote-port 9090 --remote-to-local --remote-user 'root' --remote-host 'my-server.com' --identity-file '/keys/my-server/key.ppk' 41 | ``` 42 | -------------------------------------------------------------------------------- /libraries/util.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | ################### 4 | # ARRAY UTILITIES # 5 | ################### 6 | 7 | function arrayToParameters() 8 | { 9 | local -r array=("${@}") 10 | 11 | local -r string="$(printf "'%s' " "${array[@]}")" 12 | 13 | echo "${string:0:${#string} - 1}" 14 | } 15 | 16 | function arrayToString() 17 | { 18 | local -r array=("${@}") 19 | 20 | arrayToStringWithDelimiter ',' "${array[@]}" 21 | } 22 | 23 | function arrayToStringWithDelimiter() 24 | { 25 | local -r delimiter="${1}" 26 | local -r list=("${@:2}") 27 | 28 | local -r string="$(printf "%s${delimiter}" "${list[@]}")" 29 | 30 | echo "${string:0:${#string} - ${#delimiter}}" 31 | } 32 | 33 | function checkNonEmptyArray() 34 | { 35 | local -r errorMessage="${1}" 36 | local -r array=("${@:2}") 37 | 38 | if [[ "${#array[@]}" -lt '1' ]] 39 | then 40 | if [[ "$(isEmptyString "${errorMessage}")" = 'true' ]] 41 | then 42 | fatal '\nFATAL : empty array detected' 43 | fi 44 | 45 | fatal "\nFATAL : ${errorMessage}" 46 | fi 47 | } 48 | 49 | function excludeElementFromArray() 50 | { 51 | local -r element="${1}" 52 | local array=("${@:2}") 53 | 54 | local i=0 55 | 56 | for ((i = 0; i < ${#array[@]}; i = i + 1)) 57 | do 58 | if [[ "${array[i]}" = "${element}" ]] 59 | then 60 | unset array['${i}'] 61 | fi 62 | done 63 | 64 | echo "${array[@]}" 65 | } 66 | 67 | function isElementInArray() 68 | { 69 | local -r element="${1}" 70 | local -r array=("${@:2}") 71 | 72 | local walker='' 73 | 74 | for walker in "${array[@]}" 75 | do 76 | [[ "${walker}" = "${element}" ]] && echo 'true' && return 0 77 | done 78 | 79 | echo 'false' && return 1 80 | } 81 | 82 | function sortUniqArray() 83 | { 84 | local -r array=("${@}") 85 | 86 | trimString "$(tr ' ' '\n' <<< "${array[@]}" | sort -u | tr '\n' ' ')" 87 | } 88 | 89 | ##################### 90 | # COMPILE UTILITIES # 91 | ##################### 92 | 93 | function compileAndInstallFromSource() 94 | { 95 | local -r downloadURL="${1}" 96 | local -r installFolderPath="${2}" 97 | local -r installFileOrFolderBinPath="${3}" 98 | local -r user="${4}" 99 | 100 | initializeFolder "${installFolderPath}" 101 | 102 | local -r currentWorkingDirectory="$(pwd)" 103 | local -r tempFolder="$(getTemporaryFolder)" 104 | 105 | unzipRemoteFile "${downloadURL}" "${tempFolder}" 106 | cd "${tempFolder}" 107 | "${tempFolder}/configure" --prefix="${installFolderPath}" 108 | make 109 | make install 110 | chown -R "${user}:$(getUserGroupName "${user}")" "${installFolderPath}" 111 | symlinkUsrBin "${installFileOrFolderBinPath}" 112 | cd "${currentWorkingDirectory}" 113 | rm -f -r "${tempFolder}" 114 | } 115 | 116 | ####################### 117 | # DATE TIME UTILITIES # 118 | ####################### 119 | 120 | function convertISO8601ToSeconds() 121 | { 122 | local -r time="${1}" 123 | 124 | if [[ "$(isMacOperatingSystem)" = 'true' ]] 125 | then 126 | date -j -u -f '%FT%T' "$(awk -F '.' '{ print $1 }' <<< "${time}" | tr -d 'Z')" +'%s' 127 | elif [[ "$(isAmazonLinuxDistributor)" = 'true' || "$(isCentOSDistributor)" = 'true' || "$(isRedHatDistributor)" = 'true' || "$(isRockyLinuxDistributor)" = 'true' || "$(isUbuntuDistributor)" = 'true' ]] 128 | then 129 | date -d "${time}" +'%s' 130 | else 131 | fatal '\nFATAL : only support Amazon-Linux, CentOS, Mac, RedHat, or Ubuntu OS' 132 | fi 133 | } 134 | 135 | function getISO8601DateTimeNow() 136 | { 137 | date -u +'%Y-%m-%dT%H:%M:%SZ' 138 | } 139 | 140 | function getUTCNowInSeconds() 141 | { 142 | date -u +'%s' 143 | } 144 | 145 | function secondsToReadableTime() 146 | { 147 | local -r time="${1}" 148 | 149 | local -r day="$((time / 60 / 60 / 24))" 150 | local -r hour="$((time / 60 / 60 % 24))" 151 | local -r minute="$((time / 60 % 60))" 152 | local -r second="$((time % 60))" 153 | 154 | if [[ "${day}" = '0' ]] 155 | then 156 | printf '%02d:%02d:%02d' "${hour}" "${minute}" "${second}" 157 | elif [[ "${day}" = '1' ]] 158 | then 159 | printf '%d day and %02d:%02d:%02d' "${day}" "${hour}" "${minute}" "${second}" 160 | else 161 | printf '%d days and %02d:%02d:%02d' "${day}" "${hour}" "${minute}" "${second}" 162 | fi 163 | } 164 | 165 | ######################## 166 | # FILE LOCAL UTILITIES # 167 | ######################## 168 | 169 | function appendToFileIfNotFound() 170 | { 171 | local -r file="${1}" 172 | local -r pattern="${2}" 173 | local -r string="${3}" 174 | local -r patternAsRegex="${4}" 175 | local -r stringAsRegex="${5}" 176 | local -r addNewLine="${6}" 177 | 178 | # Validate Inputs 179 | 180 | checkExistFile "${file}" 181 | checkNonEmptyString "${pattern}" 'undefined pattern' 182 | checkNonEmptyString "${string}" 'undefined string' 183 | checkTrueFalseString "${patternAsRegex}" 184 | checkTrueFalseString "${stringAsRegex}" 185 | 186 | if [[ "${stringAsRegex}" = 'false' ]] 187 | then 188 | checkTrueFalseString "${addNewLine}" 189 | fi 190 | 191 | # Append String 192 | 193 | if [[ "${patternAsRegex}" = 'true' ]] 194 | then 195 | local -r found="$(grep -E -o "${pattern}" "${file}")" 196 | else 197 | local -r found="$(grep -F -o "${pattern}" "${file}")" 198 | fi 199 | 200 | if [[ "$(isEmptyString "${found}")" = 'true' ]] 201 | then 202 | if [[ "${stringAsRegex}" = 'true' ]] 203 | then 204 | echo -e "${string}" >> "${file}" 205 | else 206 | if [[ "${addNewLine}" = 'true' ]] 207 | then 208 | echo >> "${file}" 209 | fi 210 | 211 | echo "${string}" >> "${file}" 212 | fi 213 | fi 214 | } 215 | 216 | function checkExistFile() 217 | { 218 | local -r file="${1}" 219 | local -r errorMessage="${2}" 220 | 221 | if [[ "${file}" = '' || ! -f "${file}" ]] 222 | then 223 | if [[ "$(isEmptyString "${errorMessage}")" = 'true' ]] 224 | then 225 | fatal "\nFATAL : file '${file}' not found" 226 | fi 227 | 228 | fatal "\nFATAL : ${errorMessage}" 229 | fi 230 | } 231 | 232 | function checkExistFolder() 233 | { 234 | local -r folder="${1}" 235 | local -r errorMessage="${2}" 236 | 237 | if [[ "${folder}" = '' || ! -d "${folder}" ]] 238 | then 239 | if [[ "$(isEmptyString "${errorMessage}")" = 'true' ]] 240 | then 241 | fatal "\nFATAL : folder '${folder}' not found" 242 | fi 243 | 244 | fatal "\nFATAL : ${errorMessage}" 245 | fi 246 | } 247 | 248 | function checkValidJSONContent() 249 | { 250 | local -r content="${1}" 251 | 252 | if [[ "$(isValidJSONContent "${content}")" = 'false' ]] 253 | then 254 | fatal '\nFATAL : invalid JSON' 255 | fi 256 | } 257 | 258 | function checkValidJSONFile() 259 | { 260 | local -r file="${1}" 261 | 262 | if [[ "$(isValidJSONFile "${file}")" = 'false' ]] 263 | then 264 | fatal "\nFATAL : invalid JSON file '${file}'" 265 | fi 266 | } 267 | 268 | function cleanUpSystemFolders() 269 | { 270 | header 'CLEANING UP SYSTEM FOLDERS' 271 | 272 | local -r folders=( 273 | '/tmp' 274 | '/var/tmp' 275 | ) 276 | 277 | local folder='' 278 | 279 | for folder in "${folders[@]}" 280 | do 281 | echo "Cleaning up folder '${folder}'" 282 | emptyFolder "${folder}" 283 | done 284 | } 285 | 286 | function clearFolder() 287 | { 288 | local -r folderPath="${1}" 289 | 290 | checkExistFolder "${folderPath}" 291 | 292 | rsync --archive --delete "$(getTemporaryFolder)/" "${folderPath}/" 293 | } 294 | 295 | function copyFolderContent() 296 | { 297 | local -r sourceFolder="${1}" 298 | local -r destinationFolder="${2}" 299 | 300 | checkExistFolder "${sourceFolder}" 301 | checkExistFolder "${destinationFolder}" 302 | 303 | find "${sourceFolder}" \ 304 | -mindepth 1 \ 305 | -maxdepth 1 \ 306 | -exec cp -p -r '{}' "${destinationFolder}" \; 307 | } 308 | 309 | function createAbsoluteUsrBin() 310 | { 311 | local -r binFileName="${1}" 312 | local -r sourceFilePath="${2}" 313 | 314 | checkExistFile "${sourceFilePath}" 315 | 316 | mkdir -p '/usr/bin' 317 | printf "#!/bin/bash -e\n\n'%s' \"\${@}\"" "${sourceFilePath}" > "/usr/bin/${binFileName}" 318 | chmod 755 "/usr/bin/${binFileName}" 319 | } 320 | 321 | function createFileFromTemplate() 322 | { 323 | local -r sourceFile="${1}" 324 | local -r destinationFile="${2}" 325 | local -r oldNewData=("${@:3}") 326 | 327 | checkExistFile "${sourceFile}" 328 | checkExistFolder "$(dirname "${destinationFile}")" 329 | 330 | local content='' 331 | content="$(cat "${sourceFile}")" 332 | 333 | local i=0 334 | 335 | for ((i = 0; i < ${#oldNewData[@]}; i = i + 2)) 336 | do 337 | content="$(replaceString "${content}" "${oldNewData[${i}]}" "${oldNewData[${i} + 1]}")" 338 | done 339 | 340 | echo "${content}" > "${destinationFile}" 341 | } 342 | 343 | function createInitFileFromTemplate() 344 | { 345 | local -r serviceName="${1}" 346 | local -r templateFolderPath="${2}" 347 | local -r initConfigDataFromTemplate=("${@:3}") 348 | 349 | createFileFromTemplate \ 350 | "${templateFolderPath}/${serviceName}.service.systemd" \ 351 | "/etc/systemd/system/${serviceName}.service" \ 352 | "${initConfigDataFromTemplate[@]}" 353 | } 354 | 355 | function deleteOldLogs() 356 | { 357 | local logFolderPaths=("${@}") 358 | 359 | header 'DELETING OLD LOGS' 360 | 361 | # Default Log Folder Path 362 | 363 | if [[ "${#logFolderPaths[@]}" -lt '1' ]] 364 | then 365 | logFolderPaths+=('/var/log') 366 | fi 367 | 368 | # Walk Each Log Folder Path 369 | 370 | local i=0 371 | 372 | for ((i = 0; i < ${#logFolderPaths[@]}; i = i + 1)) 373 | do 374 | checkExistFolder "${logFolderPaths[i]}" 375 | 376 | find \ 377 | -L \ 378 | "${logFolderPaths[i]}" \ 379 | -type f \ 380 | \( \ 381 | -regex '.*-[0-9]+' -o \ 382 | -regex '.*\.[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\.log' -o \ 383 | -regex '.*\.[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\.txt' -o \ 384 | -regex '.*\.[0-9]+' -o \ 385 | -regex '.*\.[0-9]+\.log' -o \ 386 | -regex '.*\.gz' -o \ 387 | -regex '.*\.log\.[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]' -o \ 388 | -regex '.*\.old' -o \ 389 | -regex '.*\.xz' \ 390 | \) \ 391 | -delete \ 392 | -print 393 | done 394 | } 395 | 396 | function emptyFolder() 397 | { 398 | local -r folder="${1}" 399 | 400 | checkExistFolder "${folder}" 401 | 402 | find "${folder}" \ 403 | -mindepth 1 \ 404 | -delete 405 | } 406 | 407 | function getFileExtension() 408 | { 409 | local -r string="${1}" 410 | 411 | local -r fullFileName="$(basename "${string}")" 412 | 413 | echo "${fullFileName##*.}" 414 | } 415 | 416 | function getFileName() 417 | { 418 | local -r string="${1}" 419 | 420 | local -r fullFileName="$(basename "${string}")" 421 | 422 | echo "${fullFileName%.*}" 423 | } 424 | 425 | function getTemporaryFile() 426 | { 427 | local extension="${1}" 428 | 429 | if [[ "$(isEmptyString "${extension}")" = 'false' && "$(grep -i -o "^." <<< "${extension}")" != '.' ]] 430 | then 431 | extension=".${extension}" 432 | fi 433 | 434 | mktemp "$(getTemporaryFolderRoot)/$(date +'%Y%m%d-%H%M%S')-XXXXXXXXXX${extension}" 435 | } 436 | 437 | function getTemporaryFolder() 438 | { 439 | mktemp -d "$(getTemporaryFolderRoot)/$(date +'%Y%m%d-%H%M%S')-XXXXXXXXXX" 440 | } 441 | 442 | function getTemporaryFolderRoot() 443 | { 444 | local temporaryFolder='/tmp' 445 | 446 | if [[ "$(isEmptyString "${TMPDIR}")" = 'false' ]] 447 | then 448 | temporaryFolder="$(formatPath "${TMPDIR}")" 449 | fi 450 | 451 | echo "${temporaryFolder}" 452 | } 453 | 454 | function initializeFolder() 455 | { 456 | local -r folder="${1}" 457 | 458 | if [[ -d "${folder}" ]] 459 | then 460 | emptyFolder "${folder}" 461 | else 462 | mkdir -p "${folder}" 463 | fi 464 | } 465 | 466 | function isEmptyFolder() 467 | { 468 | local -r folderPath="${1}" 469 | 470 | checkExistFolder "${folderPath}" 471 | 472 | if [[ "$(isEmptyString "$(find "${folderPath}" -maxdepth 1 -mindepth 1)")" = 'true' ]] 473 | then 474 | echo 'true' && return 0 475 | fi 476 | 477 | echo 'false' && return 1 478 | } 479 | 480 | function isValidJSONContent() 481 | { 482 | local -r content="${1}" 483 | 484 | if ( python -m 'json.tool' <<< "${content}" &> '/dev/null' || python3 -m 'json.tool' <<< "${content}" &> '/dev/null' ) 485 | then 486 | echo 'true' && return 0 487 | fi 488 | 489 | echo 'false' && return 1 490 | } 491 | 492 | function isValidJSONFile() 493 | { 494 | local -r file="${1}" 495 | 496 | checkExistFile "${file}" 497 | 498 | isValidJSONContent "$(cat "${file}")" 499 | } 500 | 501 | function moveFolderContent() 502 | { 503 | local -r sourceFolder="${1}" 504 | local -r destinationFolder="${2}" 505 | 506 | checkExistFolder "${sourceFolder}" 507 | checkExistFolder "${destinationFolder}" 508 | 509 | find "${sourceFolder}" \ 510 | -mindepth 1 \ 511 | -maxdepth 1 \ 512 | -exec mv '{}' "${destinationFolder}" \; 513 | } 514 | 515 | function redirectOutputToLogFile() 516 | { 517 | local -r logFile="${1}" 518 | 519 | mkdir -p "$(dirname "${logFile}")" 520 | exec > >(tee -a "${logFile}") 2>&1 521 | } 522 | 523 | function resetFolderPermission() 524 | { 525 | local -r folderPath="${1}" 526 | local -r userLogin="${2}" 527 | local -r groupName="${3}" 528 | 529 | checkExistFolder "${folderPath}" 530 | checkExistUserLogin "${userLogin}" 531 | checkExistGroupName "${groupName}" 532 | 533 | header "RESETTING FOLDER PERMISSION ${folderPath}" 534 | 535 | chown -R "${userLogin}:${groupName}" "${folderPath}" 536 | 537 | find "${folderPath}" \ 538 | -type d \ 539 | \( \ 540 | -not -path "*/.git" -a \ 541 | -not -path "*/.git/*" \ 542 | \) \ 543 | -exec chmod 700 '{}' \; \ 544 | -print 545 | 546 | find "${folderPath}" \ 547 | -type f \ 548 | \( \ 549 | -not -path "*/.git" -a \ 550 | -not -path "*/.git/*" \ 551 | \) \ 552 | -exec chmod 600 '{}' \; \ 553 | -print 554 | } 555 | 556 | function resetLogs() 557 | { 558 | local logFolderPaths=("${@}") 559 | 560 | # Default Log Folder Path 561 | 562 | if [[ "${#logFolderPaths[@]}" -lt '1' ]] 563 | then 564 | logFolderPaths+=('/var/log') 565 | fi 566 | 567 | # Delete Old Logs 568 | 569 | deleteOldLogs "${logFolderPaths[@]}" 570 | 571 | # Reset Logs 572 | 573 | header 'RESETTING LOGS' 574 | 575 | local i=0 576 | 577 | for ((i = 0; i < ${#logFolderPaths[@]}; i = i + 1)) 578 | do 579 | checkExistFolder "${logFolderPaths[i]}" 580 | 581 | find "${logFolderPaths[i]}" \ 582 | -type f \ 583 | -exec cp -f '/dev/null' '{}' \; \ 584 | -print 585 | done 586 | } 587 | 588 | function sortUniqueFile() 589 | { 590 | local -r filePath="${1}" 591 | 592 | checkExistFile "${filePath}" 593 | 594 | printf '%s' "$(awk 'NF' "${filePath}" | sort -u)" > "${filePath}" 595 | } 596 | 597 | function sortUniqueTrimFile() 598 | { 599 | local -r filePath="${1}" 600 | 601 | checkExistFile "${filePath}" 602 | 603 | printf '%s' "$(awk 'NF' "${filePath}" | awk '{$1=$1};1' | sort -u)" > "${filePath}" 604 | } 605 | 606 | function symlinkListUsrBin() 607 | { 608 | local -r sourceFilePaths=("${@}") 609 | 610 | local sourceFilePath='' 611 | 612 | for sourceFilePath in "${sourceFilePaths[@]}" 613 | do 614 | chmod 755 "${sourceFilePath}" 615 | rm -f -r "/usr/bin/$(basename "${sourceFilePath}")" 616 | ln -f -s "${sourceFilePath}" "/usr/bin/$(basename "${sourceFilePath}")" 617 | done 618 | } 619 | 620 | function symlinkUsrBin() 621 | { 622 | local -r sourceBinFileOrFolder="${1}" 623 | 624 | if [[ "$(isMacOperatingSystem)" = 'true' ]] 625 | then 626 | mkdir -p '/usr/bin' 627 | 628 | if [[ -d "${sourceBinFileOrFolder}" ]] 629 | then 630 | find "${sourceBinFileOrFolder}" -maxdepth 1 \( -type f -o -type l \) -perm -u+x -exec bash -c -e ' 631 | for file 632 | do 633 | fileType="$(stat -f "%HT" "${file}")" 634 | 635 | if [[ "${fileType}" = "Regular File" ]] 636 | then 637 | ln -f -s "${file}" "/usr/bin/$(basename "${file}")" 638 | elif [[ "${fileType}" = "Symbolic Link" ]] 639 | then 640 | cd "$(dirname "${file}")" 641 | 642 | if [[ -f "$(readlink "${file}")" ]] 643 | then 644 | ln -f -s "${file}" "/usr/bin/$(basename "${file}")" 645 | fi 646 | fi 647 | done' bash '{}' \; 648 | elif [[ -f "${sourceBinFileOrFolder}" ]] 649 | then 650 | ln -f -s "${sourceBinFileOrFolder}" "/usr/bin/$(basename "${sourceBinFileOrFolder}")" 651 | else 652 | fatal "\nFATAL : '${sourceBinFileOrFolder}' is not directory or file" 653 | fi 654 | elif [[ "$(isAmazonLinuxDistributor)" = 'true' || "$(isCentOSDistributor)" = 'true' || "$(isRedHatDistributor)" = 'true' || "$(isRockyLinuxDistributor)" = 'true' || "$(isUbuntuDistributor)" = 'true' ]] 655 | then 656 | mkdir -p '/usr/bin' 657 | 658 | if [[ -d "${sourceBinFileOrFolder}" ]] 659 | then 660 | find "${sourceBinFileOrFolder}" -maxdepth 1 -xtype f -perm -u+x -exec bash -c -e ' 661 | for file 662 | do 663 | ln -f -s "${file}" "/usr/bin/$(basename "${file}")" 664 | done' bash '{}' \; 665 | elif [[ -f "${sourceBinFileOrFolder}" ]] 666 | then 667 | ln -f -s "${sourceBinFileOrFolder}" "/usr/bin/$(basename "${sourceBinFileOrFolder}")" 668 | else 669 | fatal "\nFATAL : '${sourceBinFileOrFolder}' is not directory or file" 670 | fi 671 | else 672 | fatal '\nFATAL : only support Amazon-Linux, CentOS, Mac, RedHat, or Ubuntu OS' 673 | fi 674 | } 675 | 676 | function trimFile() 677 | { 678 | local -r filePath="${1}" 679 | 680 | checkExistFile "${filePath}" 681 | 682 | printf '%s' "$(< "${filePath}")" > "${filePath}" 683 | } 684 | 685 | ######################### 686 | # FILE REMOTE UTILITIES # 687 | ######################### 688 | 689 | function checkExistURL() 690 | { 691 | local -r url="${1}" 692 | 693 | if [[ "$(existURL "${url}")" = 'false' ]] 694 | then 695 | fatal "\nFATAL : url '${url}' not found" 696 | fi 697 | } 698 | 699 | function downloadFile() 700 | { 701 | local -r url="${1}" 702 | local -r destinationFile="${2}" 703 | local overwrite="${3}" 704 | 705 | checkExistURL "${url}" 706 | 707 | # Check Overwrite 708 | 709 | if [[ "$(isEmptyString "${overwrite}")" = 'true' ]] 710 | then 711 | overwrite='false' 712 | fi 713 | 714 | checkTrueFalseString "${overwrite}" 715 | 716 | # Validate 717 | 718 | if [[ -f "${destinationFile}" ]] 719 | then 720 | if [[ "${overwrite}" = 'false' ]] 721 | then 722 | fatal "\nFATAL : file '${destinationFile}' found" 723 | fi 724 | 725 | rm -f "${destinationFile}" 726 | elif [[ -e "${destinationFile}" ]] 727 | then 728 | fatal "\nFATAL : file '${destinationFile}' already exists" 729 | fi 730 | 731 | # Download 732 | 733 | debug "\nDownloading '${url}' to '${destinationFile}'\n" 734 | curl -L "${url}" -o "${destinationFile}" --retry 12 --retry-delay 5 735 | } 736 | 737 | function existURL() 738 | { 739 | local -r url="${1}" 740 | 741 | # Install Curl 742 | 743 | installCURLCommand > '/dev/null' 744 | 745 | # Check URL 746 | 747 | if ( curl -f --head -k -L "${url}" -o '/dev/null' -s --retry 12 --retry-delay 5 || 748 | curl -f -k -L "${url}" -o '/dev/null' -r 0-0 -s --retry 12 --retry-delay 5 ) 749 | then 750 | echo 'true' && return 0 751 | fi 752 | 753 | echo 'false' && return 1 754 | } 755 | 756 | function getRemoteFileContent() 757 | { 758 | local -r url="${1}" 759 | 760 | checkExistURL "${url}" 761 | curl -s -X 'GET' -L "${url}" --retry 12 --retry-delay 5 762 | } 763 | 764 | function unzipRemoteFile() 765 | { 766 | local -r downloadURL="${1}" 767 | local -r installFolder="${2}" 768 | local extension="${3}" 769 | 770 | # Install Curl 771 | 772 | installCURLCommand 773 | 774 | # Validate URL 775 | 776 | checkExistURL "${downloadURL}" 777 | 778 | # Find Extension 779 | 780 | local exExtension='' 781 | 782 | if [[ "$(isEmptyString "${extension}")" = 'true' ]] 783 | then 784 | extension="$(getFileExtension "${downloadURL}")" 785 | exExtension="$(rev <<< "${downloadURL}" | cut -d '.' -f 1-2 | rev)" 786 | fi 787 | 788 | # Unzip 789 | 790 | if [[ "$(grep -i '^tgz$' <<< "${extension}")" != '' || "$(grep -i '^tar\.gz$' <<< "${extension}")" != '' || "$(grep -i '^tar\.gz$' <<< "${exExtension}")" != '' ]] 791 | then 792 | debug "\nDownloading '${downloadURL}'\n" 793 | curl -L "${downloadURL}" --retry 12 --retry-delay 5 | tar -C "${installFolder}" -x -z --strip 1 794 | echo 795 | elif [[ "$(grep -i '^tar\.bz2$' <<< "${exExtension}")" != '' ]] 796 | then 797 | # Install BZip2 798 | 799 | installBZip2Command 800 | 801 | # Unzip 802 | 803 | debug "\nDownloading '${downloadURL}'\n" 804 | curl -L "${downloadURL}" --retry 12 --retry-delay 5 | tar -C "${installFolder}" -j -x --strip 1 805 | echo 806 | elif [[ "$(grep -i '^zip$' <<< "${extension}")" != '' ]] 807 | then 808 | # Install Unzip 809 | 810 | installUnzipCommand 811 | 812 | # Unzip 813 | 814 | if [[ "$(existCommand 'unzip')" = 'false' ]] 815 | then 816 | fatal 'FATAL : command unzip not found' 817 | fi 818 | 819 | local -r zipFile="${installFolder}/$(basename "${downloadURL}")" 820 | 821 | downloadFile "${downloadURL}" "${zipFile}" 'true' 822 | unzip -q "${zipFile}" -d "${installFolder}" 823 | rm -f "${zipFile}" 824 | echo 825 | else 826 | fatal "\nFATAL : file extension '${extension}' not supported" 827 | fi 828 | } 829 | 830 | ##################### 831 | # INSTALL UTILITIES # 832 | ##################### 833 | 834 | function installPortableBinary() 835 | { 836 | local -r appTitleName="${1}" 837 | local -r downloadURL="${2}" 838 | local -r installFolderPath="${3}" 839 | local -r binarySubPaths=($(sortUniqArray "$(replaceString "${4}" ',' ' ')")) 840 | local -r versionOption="${5}" 841 | local -r remoteUnzip="${6}" 842 | 843 | checkNonEmptyString "${appTitleName}" 'undefined app title name' 844 | checkNonEmptyString "${versionOption}" 'undefined version option' 845 | checkTrueFalseString "${remoteUnzip}" 846 | 847 | if [[ "${#binarySubPaths[@]}" -lt '1' ]] 848 | then 849 | fatal '\nFATAL : undefined binary sub paths' 850 | fi 851 | 852 | header "INSTALLING ${appTitleName}" 853 | 854 | checkRequireLinuxSystem 855 | checkRequireRootUser 856 | 857 | umask '0022' 858 | 859 | initializeFolder "${installFolderPath}" 860 | 861 | if [[ "${remoteUnzip}" = 'true' ]] 862 | then 863 | if [[ "$(getFileExtension "${downloadURL}")" = 'sh' ]] 864 | then 865 | curl -s -L "${downloadURL}" --retry 12 --retry-delay 5 | bash -e 866 | else 867 | unzipRemoteFile "${downloadURL}" "${installFolderPath}" 868 | fi 869 | 870 | printf '%s\n\nexport PATH="%s/%s:${PATH}"' \ 871 | '#!/bin/sh -e' \ 872 | "${installFolderPath}" \ 873 | "$(dirname "${binarySubPaths[0]}")" \ 874 | > "/etc/profile.d/$(basename "${installFolderPath}").sh" 875 | 876 | chmod 644 "/etc/profile.d/$(basename "${installFolderPath}").sh" 877 | else 878 | downloadFile "${downloadURL}" "${installFolderPath}/${binarySubPaths[0]}" 'true' 879 | fi 880 | 881 | chown -R "$(whoami):$(whoami)" "${installFolderPath}" 882 | 883 | local binarySubPath='' 884 | 885 | for binarySubPath in "${binarySubPaths[@]}" 886 | do 887 | symlinkListUsrBin "${installFolderPath}/${binarySubPath}" 888 | done 889 | 890 | displayVersion "$("/usr/bin/$(basename "${binarySubPaths[0]}")" "${versionOption}")" 891 | 892 | umask '0077' 893 | 894 | installCleanUp 895 | } 896 | 897 | ################# 898 | # MAC UTILITIES # 899 | ################# 900 | 901 | function closeMacApplications() 902 | { 903 | local -r headerMessage="${1}" 904 | local -r applicationNames=("${@:2}") 905 | 906 | checkRequireMacSystem 907 | 908 | if [[ "${#applicationNames[@]}" -gt '0' ]] 909 | then 910 | header "${headerMessage}" 911 | fi 912 | 913 | local applicationName='' 914 | 915 | for applicationName in "${applicationNames[@]}" 916 | do 917 | applicationName="$(getFileName "${applicationName}")" 918 | 919 | if [[ "${applicationName}" != 'Terminal' ]] 920 | then 921 | local errorMessage='' 922 | errorMessage="$(osascript -e "tell application \"${applicationName}\" to quit" 2>&1 || true)" 923 | 924 | if [[ "$(isEmptyString "${errorMessage}")" = 'true' || "$(grep -E -o '\(-128)$' <<< "${errorMessage}")" != '' ]] 925 | then 926 | info "closing '${applicationName}'" 927 | else 928 | error "${errorMessage}" 929 | fi 930 | fi 931 | done 932 | } 933 | 934 | function getMacCurrentUserICloudDriveFolderPath() 935 | { 936 | local -r iCloudFolderPath="$(getCurrentUserHomeFolder)/Library/Mobile Documents/com~apple~CloudDocs" 937 | 938 | if [[ -d "${iCloudFolderPath}" ]] 939 | then 940 | echo "${iCloudFolderPath}" 941 | else 942 | echo 943 | fi 944 | } 945 | 946 | function openMacApplications() 947 | { 948 | local -r headerMessage="${1}" 949 | local -r applicationNames=("${@:2}") 950 | 951 | checkRequireMacSystem 952 | 953 | if [[ "${#applicationNames[@]}" -gt '0' ]] 954 | then 955 | header "${headerMessage}" 956 | fi 957 | 958 | local applicationName='' 959 | 960 | for applicationName in "${applicationNames[@]}" 961 | do 962 | info "openning '${applicationName}'" 963 | osascript -e "tell application \"${applicationName}\" to activate" 964 | done 965 | } 966 | 967 | #################### 968 | # NUMBER UTILITIES # 969 | #################### 970 | 971 | function checkNaturalNumber() 972 | { 973 | local -r string="${1}" 974 | local -r errorMessage="${2}" 975 | 976 | if [[ "$(isNaturalNumber "${string}")" = 'false' ]] 977 | then 978 | if [[ "$(isEmptyString "${errorMessage}")" = 'true' ]] 979 | then 980 | fatal '\nFATAL : not natural number detected' 981 | fi 982 | 983 | fatal "\nFATAL : ${errorMessage}" 984 | fi 985 | } 986 | 987 | function checkPositiveInteger() 988 | { 989 | local -r string="${1}" 990 | local -r errorMessage="${2}" 991 | 992 | if [[ "$(isPositiveInteger "${string}")" = 'false' ]] 993 | then 994 | if [[ "$(isEmptyString "${errorMessage}")" = 'true' ]] 995 | then 996 | fatal '\nFATAL : not positive number detected' 997 | fi 998 | 999 | fatal "\nFATAL : ${errorMessage}" 1000 | fi 1001 | } 1002 | 1003 | function isNaturalNumber() 1004 | { 1005 | local -r string="${1}" 1006 | 1007 | if [[ "${string}" =~ ^[0-9]+$ ]] 1008 | then 1009 | echo 'true' && return 0 1010 | fi 1011 | 1012 | echo 'false' && return 1 1013 | } 1014 | 1015 | function isPositiveInteger() 1016 | { 1017 | local -r string="${1}" 1018 | 1019 | if [[ "${string}" =~ ^[1-9][0-9]*$ ]] 1020 | then 1021 | echo 'true' && return 0 1022 | fi 1023 | 1024 | echo 'false' && return 1 1025 | } 1026 | 1027 | ################ 1028 | # OS UTILITIES # 1029 | ################ 1030 | 1031 | function checkRequireLinuxSystem() 1032 | { 1033 | if [[ "$(isAmazonLinuxDistributor)" = 'false' && "$(isCentOSDistributor)" = 'false' && "$(isRedHatDistributor)" = 'false' && "$(isRockyLinuxDistributor)" = 'false' && "$(isUbuntuDistributor)" = 'false' ]] 1034 | then 1035 | fatal '\nFATAL : only support Amazon-Linux, CentOS, RedHat, or Ubuntu OS' 1036 | fi 1037 | 1038 | if [[ "$(is64BitSystem)" = 'false' ]] 1039 | then 1040 | fatal '\nFATAL : non x86_64 OS found' 1041 | fi 1042 | } 1043 | 1044 | function checkRequireMacSystem() 1045 | { 1046 | if [[ "$(isMacOperatingSystem)" = 'false' ]] 1047 | then 1048 | fatal '\nFATAL : only support Mac OS' 1049 | fi 1050 | 1051 | if [[ "$(is64BitSystem)" = 'false' ]] 1052 | then 1053 | fatal '\nFATAL : non x86_64 OS found' 1054 | fi 1055 | } 1056 | 1057 | function getMachineDescription() 1058 | { 1059 | lsb_release -d -s 1060 | } 1061 | 1062 | function getMachineRelease() 1063 | { 1064 | lsb_release -r -s 1065 | } 1066 | 1067 | function is64BitSystem() 1068 | { 1069 | if [[ "$(isMachineHardware 'x86_64')" = 'true' || "$(isMachineHardware 'arm64')" = 'true' ]] 1070 | then 1071 | echo 'true' && return 0 1072 | fi 1073 | 1074 | echo 'false' && return 1 1075 | } 1076 | 1077 | function isAmazonLinuxDistributor() 1078 | { 1079 | isDistributor 'amzn' 1080 | } 1081 | 1082 | function isCentOSDistributor() 1083 | { 1084 | isDistributor 'centos' 1085 | } 1086 | 1087 | function isDistributor() 1088 | { 1089 | local -r distributor="${1}" 1090 | 1091 | local -r found="$(grep -F -i -o -s "${distributor}" '/proc/version')" 1092 | 1093 | if [[ "$(isEmptyString "${found}")" = 'true' ]] 1094 | then 1095 | echo 'false' && return 1 1096 | fi 1097 | 1098 | echo 'true' && return 0 1099 | } 1100 | 1101 | function isLinuxOperatingSystem() 1102 | { 1103 | isOperatingSystem 'Linux' 1104 | } 1105 | 1106 | function isMachineHardware() 1107 | { 1108 | local -r machineHardware="$(escapeGrepSearchPattern "${1}")" 1109 | 1110 | local -r found="$(uname -m | grep -E -i -o "^${machineHardware}$")" 1111 | 1112 | if [[ "$(isEmptyString "${found}")" = 'true' ]] 1113 | then 1114 | echo 'false' && return 1 1115 | fi 1116 | 1117 | echo 'true' && return 0 1118 | } 1119 | 1120 | function isMacOperatingSystem() 1121 | { 1122 | isOperatingSystem 'Darwin' 1123 | } 1124 | 1125 | function isOperatingSystem() 1126 | { 1127 | local -r operatingSystem="$(escapeGrepSearchPattern "${1}")" 1128 | 1129 | local -r found="$(uname -s | grep -E -i -o "^${operatingSystem}$")" 1130 | 1131 | if [[ "$(isEmptyString "${found}")" = 'true' ]] 1132 | then 1133 | echo 'false' && return 1 1134 | fi 1135 | 1136 | echo 'true' && return 0 1137 | } 1138 | 1139 | function isRedHatDistributor() 1140 | { 1141 | isDistributor 'redhat' 1142 | } 1143 | 1144 | function isRockyLinuxDistributor() 1145 | { 1146 | isDistributor 'rockylinux' 1147 | } 1148 | 1149 | function isUbuntuDistributor() 1150 | { 1151 | isDistributor 'ubuntu' 1152 | } 1153 | 1154 | ##################### 1155 | # PACKAGE UTILITIES # 1156 | ##################### 1157 | 1158 | function getLastAptGetUpdate() 1159 | { 1160 | if [[ "$(isUbuntuDistributor)" = 'true' ]] 1161 | then 1162 | local -r aptDate="$(stat -c %Y '/var/cache/apt')" 1163 | local -r nowDate="$(date +'%s')" 1164 | 1165 | echo $((nowDate - aptDate)) 1166 | fi 1167 | } 1168 | 1169 | function installBuildEssential() 1170 | { 1171 | if [[ "$(isUbuntuDistributor)" = 'true' ]] 1172 | then 1173 | installPackages 'g++' 'build-essential' 1174 | elif [[ "$(isAmazonLinuxDistributor)" = 'true' || "$(isCentOSDistributor)" = 'true' || "$(isRedHatDistributor)" = 'true' || "$(isRockyLinuxDistributor)" = 'true' ]] 1175 | then 1176 | installPackages 'gcc' 'gcc-c++' 'kernel-devel' 'make' 'openssl-devel' 1177 | else 1178 | fatal '\nFATAL : only support Amazon-Linux, CentOS, RedHat, or Ubuntu OS' 1179 | fi 1180 | } 1181 | 1182 | function installBZip2Command() 1183 | { 1184 | local -r commandPackage=('bzip2' 'bzip2') 1185 | 1186 | installCommands "${commandPackage[@]}" 1187 | } 1188 | 1189 | function installCleanUp() 1190 | { 1191 | header 'CLEANING UP INSTALLATION' 1192 | 1193 | if [[ "$(isUbuntuDistributor)" = 'true' ]] 1194 | then 1195 | DEBIAN_FRONTEND='noninteractive' apt-get --fix-missing -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' autoremove 1196 | DEBIAN_FRONTEND='noninteractive' apt-get --fix-missing -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' clean 1197 | DEBIAN_FRONTEND='noninteractive' apt-get --fix-missing -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' autoclean 1198 | elif [[ "$(isAmazonLinuxDistributor)" = 'true' || "$(isCentOSDistributor)" = 'true' || "$(isRedHatDistributor)" = 'true' || "$(isRockyLinuxDistributor)" = 'true' ]] 1199 | then 1200 | yum clean all 1201 | else 1202 | fatal '\nFATAL : only support Amazon-Linux, CentOS, RedHat, or Ubuntu OS' 1203 | fi 1204 | } 1205 | 1206 | function installCommands() 1207 | { 1208 | local -r commandPackageData=("${@}") 1209 | 1210 | if [[ "$(isUbuntuDistributor)" = 'true' ]] 1211 | then 1212 | runAptGetUpdate '' 1213 | fi 1214 | 1215 | local i=0 1216 | 1217 | for ((i = 0; i < ${#commandPackageData[@]}; i = i + 2)) 1218 | do 1219 | local command="${commandPackageData[${i}]}" 1220 | local package="${commandPackageData[${i} + 1]}" 1221 | 1222 | checkNonEmptyString "${command}" 'undefined command' 1223 | checkNonEmptyString "${package}" 'undefined package' 1224 | 1225 | if [[ "$(existCommand "${command}")" = 'false' ]] 1226 | then 1227 | installPackages "${package}" 1228 | fi 1229 | done 1230 | } 1231 | 1232 | function installCURLCommand() 1233 | { 1234 | local -r commandPackage=('curl' 'curl') 1235 | 1236 | installCommands "${commandPackage[@]}" 1237 | } 1238 | 1239 | function installPackage() 1240 | { 1241 | local -r aptPackage="${1}" 1242 | local -r rpmPackage="${2}" 1243 | 1244 | if [[ "$(isUbuntuDistributor)" = 'true' ]] 1245 | then 1246 | if [[ "$(isEmptyString "${aptPackage}")" = 'false' ]] 1247 | then 1248 | if [[ "$(isAptGetPackageInstall "${aptPackage}")" = 'true' ]] 1249 | then 1250 | debug "\nApt-Get Package '${aptPackage}' has already been installed" 1251 | else 1252 | echo -e "\033[1;35m\nInstalling Apt-Get Package '${aptPackage}'\033[0m" 1253 | DEBIAN_FRONTEND='noninteractive' apt-get install "${aptPackage}" --fix-missing -y || 1254 | (DEBIAN_FRONTEND='noninteractive' apt-get install --fix-missing --yes -f -y && DEBIAN_FRONTEND='noninteractive' apt-get install "${aptPackage}" --fix-missing -y) 1255 | fi 1256 | fi 1257 | elif [[ "$(isAmazonLinuxDistributor)" = 'true' || "$(isCentOSDistributor)" = 'true' || "$(isRedHatDistributor)" = 'true' || "$(isRockyLinuxDistributor)" = 'true' ]] 1258 | then 1259 | if [[ "$(isEmptyString "${rpmPackage}")" = 'false' ]] 1260 | then 1261 | yum install -y "${rpmPackage}" 1262 | fi 1263 | else 1264 | fatal '\nFATAL : only support Amazon-Linux, CentOS, RedHat, or Ubuntu OS' 1265 | fi 1266 | } 1267 | 1268 | function installPackages() 1269 | { 1270 | local -r packages=("${@}") 1271 | 1272 | if [[ "$(isUbuntuDistributor)" = 'true' ]] 1273 | then 1274 | runAptGetUpdate '' 1275 | fi 1276 | 1277 | local package='' 1278 | 1279 | for package in "${packages[@]}" 1280 | do 1281 | if [[ "$(isUbuntuDistributor)" = 'true' ]] 1282 | then 1283 | installPackage "${package}" 1284 | elif [[ "$(isAmazonLinuxDistributor)" = 'true' || "$(isCentOSDistributor)" = 'true' || "$(isRedHatDistributor)" = 'true' || "$(isRockyLinuxDistributor)" = 'true' ]] 1285 | then 1286 | installPackage '' "${package}" 1287 | else 1288 | fatal '\nFATAL : only support Amazon-Linux, CentOS, RedHat, or Ubuntu OS' 1289 | fi 1290 | done 1291 | } 1292 | 1293 | function installPIPCommand() 1294 | { 1295 | local -r commandPackage=('pip' 'python-pip') 1296 | 1297 | installCommands "${commandPackage[@]}" 1298 | } 1299 | 1300 | function installPIPPackage() 1301 | { 1302 | local -r package="${1}" 1303 | 1304 | if [[ "$(isPIPPackageInstall "${package}")" = 'true' ]] 1305 | then 1306 | debug "PIP Package '${package}' found" 1307 | else 1308 | echo -e "\033[1;35m\nInstalling PIP package '${package}'\033[0m" 1309 | pip install "${package}" 1310 | fi 1311 | } 1312 | 1313 | function installUnzipCommand() 1314 | { 1315 | local -r commandPackage=('unzip' 'unzip') 1316 | 1317 | installCommands "${commandPackage[@]}" 1318 | } 1319 | 1320 | function isAptGetPackageInstall() 1321 | { 1322 | local -r package="$(escapeGrepSearchPattern "${1}")" 1323 | 1324 | local -r found="$(dpkg --get-selections | grep -E -o "^${package}(:amd64)*\s+install$")" 1325 | 1326 | if [[ "$(isEmptyString "${found}")" = 'true' ]] 1327 | then 1328 | echo 'false' && return 1 1329 | fi 1330 | 1331 | echo 'true' && return 0 1332 | } 1333 | 1334 | function isPIPPackageInstall() 1335 | { 1336 | local -r package="$(escapeGrepSearchPattern "${1}")" 1337 | 1338 | # Install PIP 1339 | 1340 | installPIPCommand > '/dev/null' 1341 | 1342 | # Check Command 1343 | 1344 | if [[ "$(existCommand 'pip')" = 'false' ]] 1345 | then 1346 | fatal 'FATAL : command python-pip not found' 1347 | fi 1348 | 1349 | local -r found="$(pip list | grep -E -o "^${package}\s+\(.*\)$")" 1350 | 1351 | if [[ "$(isEmptyString "${found}")" = 'true' ]] 1352 | then 1353 | echo 'false' && return 1 1354 | fi 1355 | 1356 | echo 'true' && return 0 1357 | } 1358 | 1359 | function runAptGetUpdate() 1360 | { 1361 | local updateInterval="${1}" 1362 | 1363 | if [[ "$(isUbuntuDistributor)" = 'true' ]] 1364 | then 1365 | local -r lastAptGetUpdate="$(getLastAptGetUpdate)" 1366 | 1367 | if [[ "$(isEmptyString "${updateInterval}")" = 'true' ]] 1368 | then 1369 | # Default To 24 hours 1370 | updateInterval="$((24 * 60 * 60))" 1371 | fi 1372 | 1373 | if [[ "${lastAptGetUpdate}" -gt "${updateInterval}" ]] 1374 | then 1375 | info 'apt-get update' 1376 | apt-get update -m 1377 | else 1378 | local -r lastUpdate="$(date -u -d @"${lastAptGetUpdate}" +'%-Hh %-Mm %-Ss')" 1379 | 1380 | info "\nSkip apt-get update because its last run was '${lastUpdate}' ago" 1381 | fi 1382 | fi 1383 | } 1384 | 1385 | function runUpgrade() 1386 | { 1387 | header 'UPGRADING SYSTEM' 1388 | 1389 | if [[ "$(isUbuntuDistributor)" = 'true' ]] 1390 | then 1391 | runAptGetUpdate '' 1392 | 1393 | info '\napt-get upgrade' 1394 | DEBIAN_FRONTEND='noninteractive' apt-get --fix-missing -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' upgrade 1395 | 1396 | info '\napt-get dist-upgrade' 1397 | DEBIAN_FRONTEND='noninteractive' apt-get --fix-missing -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' dist-upgrade 1398 | 1399 | info '\napt-get autoremove' 1400 | DEBIAN_FRONTEND='noninteractive' apt-get --fix-missing -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' autoremove 1401 | 1402 | info '\napt-get clean' 1403 | DEBIAN_FRONTEND='noninteractive' apt-get --fix-missing -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' clean 1404 | 1405 | info '\napt-get autoclean' 1406 | DEBIAN_FRONTEND='noninteractive' apt-get --fix-missing -y -o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold' autoclean 1407 | elif [[ "$(isAmazonLinuxDistributor)" = 'true' || "$(isCentOSDistributor)" = 'true' || "$(isRedHatDistributor)" = 'true' || "$(isRockyLinuxDistributor)" = 'true' ]] 1408 | then 1409 | yum -y --security update 1410 | yum -y update --nogpgcheck --skip-broken 1411 | fi 1412 | } 1413 | 1414 | function upgradePIPPackage() 1415 | { 1416 | local -r package="${1}" 1417 | 1418 | if [[ "$(isPIPPackageInstall "${package}")" = 'true' ]] 1419 | then 1420 | echo -e "\033[1;35mUpgrading PIP package '${package}'\033[0m" 1421 | pip install --upgrade "${package}" 1422 | else 1423 | debug "PIP Package '${package}' not found" 1424 | fi 1425 | } 1426 | 1427 | ##################### 1428 | # SERVICE UTILITIES # 1429 | ##################### 1430 | 1431 | function disableService() 1432 | { 1433 | local -r serviceName="${1}" 1434 | 1435 | checkNonEmptyString "${serviceName}" 'undefined service name' 1436 | 1437 | if [[ "$(existCommand 'systemctl')" = 'true' ]] 1438 | then 1439 | header "DISABLE SYSTEMD ${serviceName}" 1440 | 1441 | systemctl daemon-reload 1442 | systemctl disable "${serviceName}" 1443 | systemctl stop "${serviceName}" || true 1444 | else 1445 | header "DISABLE SERVICE ${serviceName}" 1446 | 1447 | chkconfig "${serviceName}" off 1448 | service "${serviceName}" stop || true 1449 | fi 1450 | 1451 | statusService "${serviceName}" 1452 | } 1453 | 1454 | function enableService() 1455 | { 1456 | local -r serviceName="${1}" 1457 | 1458 | checkNonEmptyString "${serviceName}" 'undefined service name' 1459 | 1460 | if [[ "$(existCommand 'systemctl')" = 'true' ]] 1461 | then 1462 | header "ENABLE SYSTEMD ${serviceName}" 1463 | 1464 | systemctl daemon-reload 1465 | systemctl enable "${serviceName}" || true 1466 | else 1467 | header "ENABLE SERVICE ${serviceName}" 1468 | 1469 | chkconfig "${serviceName}" on 1470 | fi 1471 | 1472 | statusService "${serviceName}" 1473 | } 1474 | 1475 | function restartService() 1476 | { 1477 | local -r serviceName="${1}" 1478 | 1479 | checkNonEmptyString "${serviceName}" 'undefined service name' 1480 | 1481 | stopService "${serviceName}" 1482 | startService "${serviceName}" 1483 | } 1484 | 1485 | function startService() 1486 | { 1487 | local -r serviceName="${1}" 1488 | 1489 | checkNonEmptyString "${serviceName}" 'undefined service name' 1490 | 1491 | if [[ "$(existCommand 'systemctl')" = 'true' ]] 1492 | then 1493 | header "STARTING SYSTEMD ${serviceName}" 1494 | 1495 | systemctl daemon-reload 1496 | systemctl enable "${serviceName}" || true 1497 | systemctl start "${serviceName}" 1498 | else 1499 | header "STARTING SERVICE ${serviceName}" 1500 | 1501 | chkconfig "${serviceName}" on 1502 | service "${serviceName}" start 1503 | fi 1504 | 1505 | statusService "${serviceName}" 1506 | } 1507 | 1508 | function statusService() 1509 | { 1510 | local -r serviceName="${1}" 1511 | 1512 | checkNonEmptyString "${serviceName}" 'undefined service name' 1513 | 1514 | if [[ "$(existCommand 'systemctl')" = 'true' ]] 1515 | then 1516 | header "STATUS SYSTEMD ${serviceName}" 1517 | 1518 | systemctl status "${serviceName}" --full --no-pager || true 1519 | else 1520 | header "STATUS SERVICE ${serviceName}" 1521 | 1522 | service "${serviceName}" status || true 1523 | fi 1524 | } 1525 | 1526 | function stopService() 1527 | { 1528 | local -r serviceName="${1}" 1529 | 1530 | checkNonEmptyString "${serviceName}" 'undefined service name' 1531 | 1532 | if [[ "$(existCommand 'systemctl')" = 'true' ]] 1533 | then 1534 | header "STOPPING SYSTEMD ${serviceName}" 1535 | 1536 | systemctl daemon-reload 1537 | systemctl stop "${serviceName}" || true 1538 | else 1539 | header "STOPPING SERVICE ${serviceName}" 1540 | 1541 | service "${serviceName}" stop || true 1542 | fi 1543 | 1544 | statusService "${serviceName}" 1545 | } 1546 | 1547 | #################### 1548 | # STRING UTILITIES # 1549 | #################### 1550 | 1551 | function checkNonEmptyString() 1552 | { 1553 | local -r string="${1}" 1554 | local -r errorMessage="${2}" 1555 | 1556 | if [[ "$(isEmptyString "${string}")" = 'true' ]] 1557 | then 1558 | if [[ "$(isEmptyString "${errorMessage}")" = 'true' ]] 1559 | then 1560 | fatal '\nFATAL : empty value detected' 1561 | fi 1562 | 1563 | fatal "\nFATAL : ${errorMessage}" 1564 | fi 1565 | } 1566 | 1567 | function checkTrueFalseString() 1568 | { 1569 | local -r string="${1}" 1570 | local -r errorMessage="${2}" 1571 | 1572 | if [[ "${string}" != 'true' && "${string}" != 'false' ]] 1573 | then 1574 | if [[ "$(isEmptyString "${errorMessage}")" = 'true' ]] 1575 | then 1576 | fatal "\nFATAL : '${string}' is not 'true' or 'false'" 1577 | fi 1578 | 1579 | fatal "\nFATAL : ${errorMessage}" 1580 | fi 1581 | } 1582 | 1583 | function debug() 1584 | { 1585 | local -r message="${1}" 1586 | 1587 | if [[ "$(isEmptyString "${message}")" = 'false' ]] 1588 | then 1589 | echo -e "\033[1;34m${message}\033[0m" 2>&1 1590 | fi 1591 | } 1592 | 1593 | function deleteSpaces() 1594 | { 1595 | local -r content="${1}" 1596 | 1597 | replaceString "${content}" ' ' '' 1598 | } 1599 | 1600 | function displayVersion() 1601 | { 1602 | local -r message="${1}" 1603 | local -r applicationName="${2}" 1604 | 1605 | if [[ "$(isEmptyString "${applicationName}")" = 'true' ]] 1606 | then 1607 | header 'DISPLAYING VERSION' 1608 | else 1609 | header "DISPLAYING ${applicationName} VERSION" 1610 | fi 1611 | 1612 | info "${message}" 1613 | } 1614 | 1615 | function encodeURL() 1616 | { 1617 | local -r url="${1}" 1618 | 1619 | local i=0 1620 | 1621 | for ((i = 0; i < ${#url}; i++)) 1622 | do 1623 | local walker='' 1624 | walker="${url:i:1}" 1625 | 1626 | case "${walker}" in 1627 | [a-zA-Z0-9.~_-]) 1628 | printf '%s' "${walker}" 1629 | ;; 1630 | ' ') 1631 | printf + 1632 | ;; 1633 | *) 1634 | printf '%%%X' "'${walker}" 1635 | ;; 1636 | esac 1637 | done 1638 | } 1639 | 1640 | function error() 1641 | { 1642 | local -r message="${1}" 1643 | 1644 | if [[ "$(isEmptyString "${message}")" = 'false' ]] 1645 | then 1646 | echo -e "\033[1;31m${message}\033[0m" 1>&2 1647 | fi 1648 | } 1649 | 1650 | function escapeGrepSearchPattern() 1651 | { 1652 | local -r searchPattern="${1}" 1653 | 1654 | sed 's/[]\.|$(){}?+*^]/\\&/g' <<< "${searchPattern}" 1655 | } 1656 | 1657 | function escapeSearchPattern() 1658 | { 1659 | local -r searchPattern="${1}" 1660 | 1661 | sed -e "s@\@@\\\\\\@@g" -e "s@\[@\\\\[@g" -e "s@\*@\\\\*@g" -e "s@\%@\\\\%@g" <<< "${searchPattern}" 1662 | } 1663 | 1664 | function fatal() 1665 | { 1666 | local -r message="${1}" 1667 | 1668 | error "${message}" 1669 | exit 1 1670 | } 1671 | 1672 | function formatPath() 1673 | { 1674 | local path="${1}" 1675 | 1676 | while [[ "$(grep -F '//' <<< "${path}")" != '' ]] 1677 | do 1678 | path="$(sed -e 's/\/\/*/\//g' <<< "${path}")" 1679 | done 1680 | 1681 | sed -e 's/\/$//g' <<< "${path}" 1682 | } 1683 | 1684 | function header() 1685 | { 1686 | local -r title="${1}" 1687 | 1688 | if [[ "$(isEmptyString "${title}")" = 'false' ]] 1689 | then 1690 | echo -e "\n\033[1;33m>>>>>>>>>> \033[1;4;35m${title}\033[0m \033[1;33m<<<<<<<<<<\033[0m\n" 1691 | fi 1692 | } 1693 | 1694 | function indentString() 1695 | { 1696 | local -r indentString="$(escapeSearchPattern "${1}")" 1697 | local -r string="$(escapeSearchPattern "${2}")" 1698 | 1699 | sed "s@^@${indentString}@g" <<< "${string}" 1700 | } 1701 | 1702 | function info() 1703 | { 1704 | local -r message="${1}" 1705 | 1706 | if [[ "$(isEmptyString "${message}")" = 'false' ]] 1707 | then 1708 | echo -e "\033[1;36m${message}\033[0m" 2>&1 1709 | fi 1710 | } 1711 | 1712 | function invertTrueFalseString() 1713 | { 1714 | local -r string="${1}" 1715 | 1716 | checkTrueFalseString "${string}" 1717 | 1718 | if [[ "${string}" = 'true' ]] 1719 | then 1720 | echo 'false' && return 1 1721 | fi 1722 | 1723 | echo 'true' && return 0 1724 | } 1725 | 1726 | function isEmptyString() 1727 | { 1728 | local -r string="${1}" 1729 | 1730 | if [[ "$(trimString "${string}")" = '' ]] 1731 | then 1732 | echo 'true' && return 0 1733 | fi 1734 | 1735 | echo 'false' && return 1 1736 | } 1737 | 1738 | function postUpMessage() 1739 | { 1740 | echo -e "\n\033[1;32m¯\_(ツ)_/¯\033[0m" 1741 | } 1742 | 1743 | function printTable() 1744 | { 1745 | local -r delimiter="${1}" 1746 | local -r tableData="$(removeEmptyLines "${2}")" 1747 | local -r colorHeader="${3}" 1748 | local -r displayTotalCount="${4}" 1749 | 1750 | if [[ "${delimiter}" != '' && "$(isEmptyString "${tableData}")" = 'false' ]] 1751 | then 1752 | local -r numberOfLines="$(trimString "$(wc -l <<< "${tableData}")")" 1753 | 1754 | if [[ "${numberOfLines}" -gt '0' ]] 1755 | then 1756 | local table='' 1757 | local i=1 1758 | 1759 | for ((i = 1; i <= "${numberOfLines}"; i = i + 1)) 1760 | do 1761 | local line='' 1762 | line="$(sed "${i}q;d" <<< "${tableData}")" 1763 | 1764 | local numberOfColumns=0 1765 | numberOfColumns="$(awk -F "${delimiter}" '{print NF}' <<< "${line}")" 1766 | 1767 | # Add Line Delimiter 1768 | 1769 | if [[ "${i}" -eq '1' ]] 1770 | then 1771 | table="${table}$(printf '%s#+' "$(repeatString '#+' "${numberOfColumns}")")" 1772 | fi 1773 | 1774 | # Add Header Or Body 1775 | 1776 | table="${table}\n" 1777 | 1778 | local j=1 1779 | 1780 | for ((j = 1; j <= "${numberOfColumns}"; j = j + 1)) 1781 | do 1782 | table="${table}$(printf '#| %s' "$(cut -d "${delimiter}" -f "${j}" <<< "${line}")")" 1783 | done 1784 | 1785 | table="${table}#|\n" 1786 | 1787 | # Add Line Delimiter 1788 | 1789 | if [[ "${i}" -eq '1' ]] || [[ "${numberOfLines}" -gt '1' && "${i}" -eq "${numberOfLines}" ]] 1790 | then 1791 | table="${table}$(printf '%s#+' "$(repeatString '#+' "${numberOfColumns}")")" 1792 | fi 1793 | done 1794 | 1795 | if [[ "$(isEmptyString "${table}")" = 'false' ]] 1796 | then 1797 | local output='' 1798 | output="$(echo -e "${table}" | column -s '#' -t | awk '/^ *\+/{gsub(" ", "-", $0)}1' | sed 's/^--\|^ / /g')" 1799 | 1800 | if [[ "${colorHeader}" = 'true' ]] 1801 | then 1802 | echo -e "\033[1;32m$(head -n 3 <<< "${output}")\033[0m" 1803 | tail -n +4 <<< "${output}" 1804 | else 1805 | echo "${output}" 1806 | fi 1807 | fi 1808 | fi 1809 | 1810 | if [[ "${displayTotalCount}" = 'true' && "${numberOfLines}" -ge '0' ]] 1811 | then 1812 | if [[ "${colorHeader}" = 'true' ]] 1813 | then 1814 | echo -e "\n\033[1;36mTOTAL ROWS : $((numberOfLines - 1))\033[0m" 1815 | else 1816 | echo -e "\nTOTAL ROWS : $((numberOfLines - 1))" 1817 | fi 1818 | fi 1819 | fi 1820 | } 1821 | 1822 | function removeEmptyLines() 1823 | { 1824 | local -r content="${1}" 1825 | 1826 | echo -e "${content}" | sed '/^\s*$/d' 1827 | } 1828 | 1829 | function repeatString() 1830 | { 1831 | local -r string="${1}" 1832 | local -r numberToRepeat="${2}" 1833 | 1834 | if [[ "${string}" != '' && "$(isPositiveInteger "${numberToRepeat}")" = 'true' ]] 1835 | then 1836 | local -r result="$(printf "%${numberToRepeat}s")" 1837 | echo -e "${result// /${string}}" 1838 | fi 1839 | } 1840 | 1841 | function replaceString() 1842 | { 1843 | local -r content="${1}" 1844 | local -r oldValue="$(escapeSearchPattern "${2}")" 1845 | local -r newValue="$(escapeSearchPattern "${3}")" 1846 | 1847 | sed "s@${oldValue}@${newValue}@g" <<< "${content}" 1848 | } 1849 | 1850 | function stringToNumber() 1851 | { 1852 | local -r string="${1}" 1853 | 1854 | checkNonEmptyString "${string}" 'undefined string' 1855 | 1856 | if [[ "$(existCommand 'md5')" = 'true' ]] 1857 | then 1858 | md5 <<< "${string}" | tr -cd '0-9' 1859 | elif [[ "$(existCommand 'md5sum')" = 'true' ]] 1860 | then 1861 | md5sum <<< "${string}" | tr -cd '0-9' 1862 | else 1863 | fatal '\nFATAL : md5 or md5sum command not found' 1864 | fi 1865 | } 1866 | 1867 | function stringToSearchPattern() 1868 | { 1869 | local -r string="$(trimString "${1}")" 1870 | 1871 | if [[ "$(isEmptyString "${string}")" = 'true' ]] 1872 | then 1873 | echo "${string}" 1874 | else 1875 | echo "^\s*$(sed -e 's/\s\+/\\s+/g' <<< "$(escapeSearchPattern "${string}")")\s*$" 1876 | fi 1877 | } 1878 | 1879 | function trimString() 1880 | { 1881 | local -r string="${1}" 1882 | 1883 | sed 's,^[[:blank:]]*,,' <<< "${string}" | sed 's,[[:blank:]]*$,,' 1884 | } 1885 | 1886 | function warn() 1887 | { 1888 | local -r message="${1}" 1889 | 1890 | if [[ "$(isEmptyString "${message}")" = 'false' ]] 1891 | then 1892 | echo -e "\033[1;33m${message}\033[0m" 1>&2 1893 | fi 1894 | } 1895 | 1896 | #################### 1897 | # SYSTEM UTILITIES # 1898 | #################### 1899 | 1900 | function addSwapSpace() 1901 | { 1902 | local swapSize="${1}" 1903 | local swapFile="${2}" 1904 | 1905 | header 'ADDING SWAP SPACE' 1906 | 1907 | # Set Default Values 1908 | 1909 | if [[ "$(isEmptyString "${swapSize}")" = 'true' ]] 1910 | then 1911 | swapSize='1024000' 1912 | fi 1913 | 1914 | if [[ "$(isEmptyString "${swapFile}")" = 'true' ]] 1915 | then 1916 | swapFile='/mnt/swapfile' 1917 | fi 1918 | 1919 | if [[ -f "${swapFile}" ]] 1920 | then 1921 | swapoff "${swapFile}" 1922 | fi 1923 | 1924 | rm -f "${swapFile}" 1925 | touch "${swapFile}" 1926 | 1927 | # Create Swap File 1928 | 1929 | dd if=/dev/zero of="${swapFile}" bs=1024 count="${swapSize}" 1930 | mkswap "${swapFile}" 1931 | chmod 600 "${swapFile}" 1932 | swapon "${swapFile}" 1933 | 1934 | # Config Swap File System 1935 | 1936 | local -r fstabConfig="${swapFile} swap swap defaults 0 0" 1937 | 1938 | appendToFileIfNotFound '/etc/fstab' "$(stringToSearchPattern "${fstabConfig}")" "${fstabConfig}" 'true' 'false' 'true' 1939 | 1940 | # Display Swap Status 1941 | 1942 | free -m 1943 | } 1944 | 1945 | function checkExistCommand() 1946 | { 1947 | local -r command="${1}" 1948 | local -r errorMessage="${2}" 1949 | 1950 | if [[ "$(existCommand "${command}")" = 'false' ]] 1951 | then 1952 | if [[ "$(isEmptyString "${errorMessage}")" = 'true' ]] 1953 | then 1954 | fatal "\nFATAL : command '${command}' not found" 1955 | fi 1956 | 1957 | fatal "\nFATAL : ${errorMessage}" 1958 | fi 1959 | } 1960 | 1961 | function checkRequirePorts() 1962 | { 1963 | local -r ports=("${@}") 1964 | 1965 | installPackages 'lsof' 1966 | 1967 | local -r headerRegex='^COMMAND\s\+PID\s\+USER\s\+FD\s\+TYPE\s\+DEVICE\s\+SIZE\/OFF\s\+NODE\s\+NAME$' 1968 | local -r status="$(lsof -i -n -P | grep "\( (LISTEN)$\)\|\(${headerRegex}\)")" 1969 | 1970 | local open='' 1971 | local port='' 1972 | 1973 | for port in "${ports[@]}" 1974 | do 1975 | local found='' 1976 | found="$(grep -i ":${port} (LISTEN)$" <<< "${status}" || echo)" 1977 | 1978 | if [[ "$(isEmptyString "${found}")" = 'false' ]] 1979 | then 1980 | open="${open}\n${found}" 1981 | fi 1982 | done 1983 | 1984 | if [[ "$(isEmptyString "${open}")" = 'false' ]] 1985 | then 1986 | echo -e "\033[1;31mFollowing ports are still opened. Make sure you uninstall or stop them before a new installation!\033[0m" 1987 | echo -e -n "\033[1;34m\n$(grep "${headerRegex}" <<< "${status}")\033[0m" 1988 | echo -e "\033[1;36m${open}\033[0m\n" 1989 | 1990 | exit 1 1991 | fi 1992 | } 1993 | 1994 | function displayOpenPorts() 1995 | { 1996 | local -r sleepTimeInSecond="${1}" 1997 | 1998 | installPackages 'lsof' 1999 | 2000 | header 'DISPLAYING OPEN PORTS' 2001 | 2002 | if [[ "$(isEmptyString "${sleepTimeInSecond}")" = 'false' ]] 2003 | then 2004 | sleep "${sleepTimeInSecond}" 2005 | fi 2006 | 2007 | lsof -i -n -P | grep -i ' (LISTEN)$' | sort -f 2008 | } 2009 | 2010 | function existCommand() 2011 | { 2012 | local -r command="${1}" 2013 | 2014 | if [[ "$(which "${command}" 2> '/dev/null')" = '' ]] 2015 | then 2016 | echo 'false' && return 1 2017 | fi 2018 | 2019 | echo 'true' && return 0 2020 | } 2021 | 2022 | function existDisk() 2023 | { 2024 | local -r disk="${1}" 2025 | 2026 | local -r foundDisk="$(fdisk -l "${disk}" 2> '/dev/null' | grep -E -i -o "^Disk\s+$(escapeGrepSearchPattern "${disk}"): ")" 2027 | 2028 | if [[ "$(isEmptyString "${disk}")" = 'false' && "$(isEmptyString "${foundDisk}")" = 'false' ]] 2029 | then 2030 | echo 'true' && return 0 2031 | fi 2032 | 2033 | echo 'false' && return 1 2034 | } 2035 | 2036 | function existDiskMount() 2037 | { 2038 | local -r disk="$(escapeGrepSearchPattern "${1}")" 2039 | local -r mountOn="$(escapeGrepSearchPattern "${2}")" 2040 | 2041 | local -r foundMount="$(df | grep -E "^${disk}\s+.*\s+${mountOn}$")" 2042 | 2043 | if [[ "$(isEmptyString "${foundMount}")" = 'true' ]] 2044 | then 2045 | echo 'false' && return 1 2046 | fi 2047 | 2048 | echo 'true' && return 0 2049 | } 2050 | 2051 | function existModule() 2052 | { 2053 | local -r module="${1}" 2054 | 2055 | checkNonEmptyString "${module}" 'undefined module' 2056 | 2057 | if [[ "$(lsmod | awk '{ print $1 }' | grep -F -o "${module}")" = '' ]] 2058 | then 2059 | echo 'false' && return 1 2060 | fi 2061 | 2062 | echo 'true' && return 0 2063 | } 2064 | 2065 | function existMount() 2066 | { 2067 | local -r mountOn="$(escapeGrepSearchPattern "${1}")" 2068 | 2069 | local -r foundMount="$(df | grep -E ".*\s+${mountOn}$")" 2070 | 2071 | if [[ "$(isEmptyString "${foundMount}")" = 'true' ]] 2072 | then 2073 | echo 'false' && return 1 2074 | fi 2075 | 2076 | echo 'true' && return 0 2077 | } 2078 | 2079 | function flushFirewall() 2080 | { 2081 | header 'FLUSHING FIREWALL' 2082 | 2083 | iptables -P INPUT ACCEPT 2084 | iptables -P FORWARD ACCEPT 2085 | iptables -P OUTPUT ACCEPT 2086 | 2087 | iptables -t nat -F 2088 | iptables -t mangle -F 2089 | iptables -F 2090 | iptables -X 2091 | 2092 | iptables --list 2093 | 2094 | saveFirewall 2095 | } 2096 | 2097 | function isPortOpen() 2098 | { 2099 | local -r port="$(escapeGrepSearchPattern "${1}")" 2100 | 2101 | checkNonEmptyString "${port}" 'undefined port' 2102 | 2103 | if [[ "$(isAmazonLinuxDistributor)" = 'true' || "$(isRedHatDistributor)" = 'true' || "$(isRockyLinuxDistributor)" = 'true' || "$(isUbuntuDistributor)" = 'true' ]] 2104 | then 2105 | local -r process="$(netstat -l -n -t -u | grep -E ":${port}\s+" | head -1)" 2106 | elif [[ "$(isCentOSDistributor)" = 'true' || "$(isMacOperatingSystem)" = 'true' ]] 2107 | then 2108 | if [[ "$(isCentOSDistributor)" = 'true' ]] 2109 | then 2110 | installPackages 'lsof' 2111 | fi 2112 | 2113 | local -r process="$(lsof -i -n -P | grep -E -i ":${port}\s+\(LISTEN\)$" | head -1)" 2114 | else 2115 | fatal '\nFATAL : only support Amazon-Linux, CentOS, Mac, RedHat, or Ubuntu OS' 2116 | fi 2117 | 2118 | if [[ "$(isEmptyString "${process}")" = 'true' ]] 2119 | then 2120 | echo 'false' && return 1 2121 | fi 2122 | 2123 | echo 'true' && return 0 2124 | } 2125 | 2126 | function redirectJDKTMPDir() 2127 | { 2128 | local -r option="_JAVA_OPTIONS='-Djava.io.tmpdir=/var/tmp'" 2129 | 2130 | appendToFileIfNotFound '/etc/environment' "${option}" "${option}" 'false' 'false' 'true' 2131 | appendToFileIfNotFound '/etc/profile' "${option}" "${option}" 'false' 'false' 'true' 2132 | } 2133 | 2134 | function remountTMP() 2135 | { 2136 | header 'RE-MOUNTING TMP' 2137 | 2138 | if [[ "$(existMount '/tmp')" = 'true' ]] 2139 | then 2140 | mount -o 'remount,rw,exec,nosuid' -v '/tmp' 2141 | else 2142 | warn 'WARN : mount /tmp not found' 2143 | fi 2144 | } 2145 | 2146 | function saveFirewall() 2147 | { 2148 | header 'SAVING FIREWALL' 2149 | 2150 | local ruleFile='' 2151 | 2152 | for ruleFile in '/etc/iptables/rules.v4' '/etc/iptables/rules.v6' '/etc/sysconfig/iptables' '/etc/sysconfig/ip6tables' 2153 | do 2154 | if [[ -f "${ruleFile}" ]] 2155 | then 2156 | if [[ "$(grep -F '6' <<< "${ruleFile}")" = '' ]] 2157 | then 2158 | iptables-save > "${ruleFile}" 2159 | else 2160 | ip6tables-save > "${ruleFile}" 2161 | fi 2162 | 2163 | info "${ruleFile}" 2164 | cat "${ruleFile}" 2165 | echo 2166 | fi 2167 | done 2168 | } 2169 | 2170 | ############################ 2171 | # USER AND GROUP UTILITIES # 2172 | ############################ 2173 | 2174 | function addUser() 2175 | { 2176 | local -r userLogin="${1}" 2177 | local -r groupName="${2}" 2178 | local -r createHome="${3}" 2179 | local -r systemAccount="${4}" 2180 | local -r allowLogin="${5}" 2181 | 2182 | checkNonEmptyString "${userLogin}" 'undefined user login' 2183 | checkNonEmptyString "${groupName}" 'undefined group name' 2184 | 2185 | # Options 2186 | 2187 | if [[ "${createHome}" = 'true' ]] 2188 | then 2189 | local -r createHomeOption=('-m') 2190 | else 2191 | local -r createHomeOption=('-M') 2192 | fi 2193 | 2194 | if [[ "${allowLogin}" = 'true' ]] 2195 | then 2196 | local -r allowLoginOption=('-s' '/bin/bash') 2197 | else 2198 | local -r allowLoginOption=('-s' '/bin/false') 2199 | fi 2200 | 2201 | # Add Group 2202 | 2203 | groupadd -f -r "${groupName}" 2204 | 2205 | # Add User 2206 | 2207 | if [[ "$(existUserLogin "${userLogin}")" = 'true' ]] 2208 | then 2209 | if [[ "$(isUserLoginInGroupName "${userLogin}" "${groupName}")" = 'false' ]] 2210 | then 2211 | usermod -a -G "${groupName}" "${userLogin}" 2212 | fi 2213 | 2214 | # Not Exist Home 2215 | 2216 | if [[ "${createHome}" = 'true' ]] 2217 | then 2218 | local -r userHome="$(getUserHomeFolder "${userLogin}")" 2219 | 2220 | if [[ "$(isEmptyString "${userHome}")" = 'true' || ! -d "${userHome}" ]] 2221 | then 2222 | mkdir -m 700 -p "/home/${userLogin}" 2223 | chown -R "${userLogin}:${groupName}" "/home/${userLogin}" 2224 | fi 2225 | fi 2226 | else 2227 | if [[ "${systemAccount}" = 'true' ]] 2228 | then 2229 | useradd "${createHomeOption[@]}" -r "${allowLoginOption[@]}" -g "${groupName}" "${userLogin}" 2230 | else 2231 | useradd "${createHomeOption[@]}" "${allowLoginOption[@]}" -g "${groupName}" "${userLogin}" 2232 | fi 2233 | fi 2234 | } 2235 | 2236 | function addUserAuthorizedKey() 2237 | { 2238 | local -r userLogin="${1}" 2239 | local -r groupName="${2}" 2240 | local -r sshRSA="${3}" 2241 | 2242 | configUserSSH "${userLogin}" "${groupName}" "${sshRSA}" 'authorized_keys' 2243 | } 2244 | 2245 | function addUserSSHKnownHost() 2246 | { 2247 | local -r userLogin="${1}" 2248 | local -r groupName="${2}" 2249 | local -r sshRSA="${3}" 2250 | 2251 | configUserSSH "${userLogin}" "${groupName}" "${sshRSA}" 'known_hosts' 2252 | } 2253 | 2254 | function addUserToSudoWithoutPassword() 2255 | { 2256 | local -r userLogin="${1}" 2257 | 2258 | echo "${userLogin} ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/${userLogin}" 2259 | chmod 440 "/etc/sudoers.d/${userLogin}" 2260 | } 2261 | 2262 | function checkExistGroupName() 2263 | { 2264 | local -r groupName="${1}" 2265 | 2266 | if [[ "$(existGroupName "${groupName}")" = 'false' ]] 2267 | then 2268 | fatal "\nFATAL : group name '${groupName}' not found" 2269 | fi 2270 | } 2271 | 2272 | function checkExistUserLogin() 2273 | { 2274 | local -r userLogin="${1}" 2275 | 2276 | if [[ "$(existUserLogin "${userLogin}")" = 'false' ]] 2277 | then 2278 | fatal "\nFATAL : user login '${userLogin}' not found" 2279 | fi 2280 | } 2281 | 2282 | function checkRequireNonRootUser() 2283 | { 2284 | if [[ "$(whoami)" = 'root' ]] 2285 | then 2286 | fatal '\nFATAL : non root login required' 2287 | fi 2288 | } 2289 | 2290 | function checkRequireRootUser() 2291 | { 2292 | checkRequireUserLogin 'root' 2293 | } 2294 | 2295 | function checkRequireUserLogin() 2296 | { 2297 | local -r userLogin="${1}" 2298 | 2299 | if [[ "$(whoami)" != "${userLogin}" ]] 2300 | then 2301 | fatal "\nFATAL : user login '${userLogin}' required" 2302 | fi 2303 | } 2304 | 2305 | function configUserGIT() 2306 | { 2307 | local -r userLogin="${1}" 2308 | local -r gitUserName="${2}" 2309 | local -r gitUserEmail="${3}" 2310 | 2311 | header "CONFIGURING GIT FOR USER ${userLogin}" 2312 | 2313 | checkExistUserLogin "${userLogin}" 2314 | checkNonEmptyString "${gitUserName}" 'undefined git user name' 2315 | checkNonEmptyString "${gitUserEmail}" 'undefined git user email' 2316 | 2317 | su -l "${userLogin}" -c 'git config --global pull.rebase false' 2318 | su -l "${userLogin}" -c 'git config --global push.default simple' 2319 | su -l "${userLogin}" -c "git config --global user.email '${gitUserEmail}'" 2320 | su -l "${userLogin}" -c "git config --global user.name '${gitUserName}'" 2321 | 2322 | info "$(su -l "${userLogin}" -c 'git config --list')" 2323 | } 2324 | 2325 | function configUserSSH() 2326 | { 2327 | local -r userLogin="${1}" 2328 | local -r groupName="${2}" 2329 | local -r sshRSA="${3}" 2330 | local -r configFileName="${4}" 2331 | 2332 | header "CONFIGURING ${configFileName} FOR USER ${userLogin}" 2333 | 2334 | checkExistUserLogin "${userLogin}" 2335 | checkExistGroupName "${groupName}" 2336 | checkNonEmptyString "${sshRSA}" 'undefined SSH-RSA' 2337 | checkNonEmptyString "${configFileName}" 'undefined config file' 2338 | 2339 | local -r userHome="$(getUserHomeFolder "${userLogin}")" 2340 | 2341 | checkExistFolder "${userHome}" 2342 | 2343 | mkdir -m 700 -p "${userHome}/.ssh" 2344 | touch "${userHome}/.ssh/${configFileName}" 2345 | appendToFileIfNotFound "${userHome}/.ssh/${configFileName}" "${sshRSA}" "${sshRSA}" 'false' 'false' 'false' 2346 | chmod 600 "${userHome}/.ssh/${configFileName}" 2347 | 2348 | chown -R "${userLogin}:${groupName}" "${userHome}/.ssh" 2349 | 2350 | cat "${userHome}/.ssh/${configFileName}" 2351 | } 2352 | 2353 | function deleteUser() 2354 | { 2355 | local -r userLogin="${1}" 2356 | 2357 | if [[ "$(existUserLogin "${userLogin}")" = 'true' ]] 2358 | then 2359 | userdel -f -r "${userLogin}" 2> '/dev/null' || true 2360 | fi 2361 | } 2362 | 2363 | function existGroupName() 2364 | { 2365 | local -r group="${1}" 2366 | 2367 | if [[ "$(grep -E -o "^${group}:" '/etc/group')" = '' ]] 2368 | then 2369 | echo 'false' && return 1 2370 | fi 2371 | 2372 | echo 'true' && return 0 2373 | } 2374 | 2375 | function existUserLogin() 2376 | { 2377 | local -r user="${1}" 2378 | 2379 | if ( id -u "${user}" > '/dev/null' 2>&1 ) 2380 | then 2381 | echo 'true' && return 0 2382 | fi 2383 | 2384 | echo 'false' && return 1 2385 | } 2386 | 2387 | function generateSSHPublicKeyFromPrivateKey() 2388 | { 2389 | local -r userLogin="${1}" 2390 | local groupName="${2}" 2391 | 2392 | # Set Default 2393 | 2394 | if [[ "$(isEmptyString "${groupName}")" = 'true' ]] 2395 | then 2396 | groupName="${userLogin}" 2397 | fi 2398 | 2399 | # Validate Input 2400 | 2401 | checkExistUserLogin "${userLogin}" 2402 | checkExistGroupName "${groupName}" 2403 | 2404 | local -r userHome="$(getUserHomeFolder "${userLogin}")" 2405 | 2406 | checkExistFile "${userHome}/.ssh/id_rsa" 2407 | 2408 | # Generate SSH Public Key 2409 | 2410 | header "GENERATING SSH PUBLIC KEY FOR USER '${userLogin}' FROM PRIVATE KEY" 2411 | 2412 | rm -f "${userHome}/.ssh/id_rsa.pub" 2413 | su -l "${userLogin}" -c "ssh-keygen -f '${userHome}/.ssh/id_rsa' -y > '${userHome}/.ssh/id_rsa.pub'" 2414 | chmod 600 "${userHome}/.ssh/id_rsa.pub" 2415 | chown "${userLogin}:${groupName}" "${userHome}/.ssh/id_rsa.pub" 2416 | 2417 | cat "${userHome}/.ssh/id_rsa.pub" 2418 | } 2419 | 2420 | function generateUserSSHKey() 2421 | { 2422 | local -r userLogin="${1}" 2423 | local groupName="${2}" 2424 | 2425 | # Set Default 2426 | 2427 | if [[ "$(isEmptyString "${groupName}")" = 'true' ]] 2428 | then 2429 | groupName="${userLogin}" 2430 | fi 2431 | 2432 | # Validate Input 2433 | 2434 | checkExistUserLogin "${userLogin}" 2435 | checkExistGroupName "${groupName}" 2436 | 2437 | local -r userHome="$(getUserHomeFolder "${userLogin}")" 2438 | 2439 | checkExistFolder "${userHome}" 2440 | 2441 | # Generate SSH Key 2442 | 2443 | header "GENERATING SSH KEY FOR USER '${userLogin}'" 2444 | 2445 | rm -f "${userHome}/.ssh/id_rsa" "${userHome}/.ssh/id_rsa.pub" 2446 | mkdir -m 700 -p "${userHome}/.ssh" 2447 | chown "${userLogin}:${groupName}" "${userHome}/.ssh" 2448 | 2449 | su -l "${userLogin}" -c "ssh-keygen -q -t rsa -N '' -f '${userHome}/.ssh/id_rsa'" 2450 | chmod 600 "${userHome}/.ssh/id_rsa" "${userHome}/.ssh/id_rsa.pub" 2451 | chown "${userLogin}:${groupName}" "${userHome}/.ssh/id_rsa" "${userHome}/.ssh/id_rsa.pub" 2452 | 2453 | cat "${userHome}/.ssh/id_rsa.pub" 2454 | } 2455 | 2456 | function getCurrentUserHomeFolder() 2457 | { 2458 | getUserHomeFolder "$(whoami)" 2459 | } 2460 | 2461 | function getProfileFilePath() 2462 | { 2463 | local -r user="${1}" 2464 | 2465 | local -r userHome="$(getUserHomeFolder "${user}")" 2466 | 2467 | if [[ "$(isEmptyString "${userHome}")" = 'false' && -d "${userHome}" ]] 2468 | then 2469 | local -r bashProfileFilePath="${userHome}/.bash_profile" 2470 | local -r profileFilePath="${userHome}/.profile" 2471 | 2472 | if [[ ! -f "${bashProfileFilePath}" && -f "${profileFilePath}" ]] 2473 | then 2474 | echo "${profileFilePath}" 2475 | else 2476 | echo "${bashProfileFilePath}" 2477 | fi 2478 | fi 2479 | } 2480 | 2481 | function getUserGroupName() 2482 | { 2483 | local -r userLogin="${1}" 2484 | 2485 | checkExistUserLogin "${userLogin}" 2486 | 2487 | id -g -n "${userLogin}" 2488 | } 2489 | 2490 | function getUserHomeFolder() 2491 | { 2492 | local -r user="${1}" 2493 | 2494 | if [[ "$(isEmptyString "${user}")" = 'false' ]] 2495 | then 2496 | local -r homeFolder="$(eval "echo ~${user}")" 2497 | 2498 | if [[ "${homeFolder}" = "\~${user}" ]] 2499 | then 2500 | echo 2501 | else 2502 | echo "${homeFolder}" 2503 | fi 2504 | else 2505 | echo 2506 | fi 2507 | } 2508 | 2509 | function isUserLoginInGroupName() 2510 | { 2511 | local -r userLogin="${1}" 2512 | local -r groupName="${2}" 2513 | 2514 | checkNonEmptyString "${userLogin}" 'undefined user login' 2515 | checkNonEmptyString "${groupName}" 'undefined group name' 2516 | 2517 | if [[ "$(existUserLogin "${userLogin}")" = 'true' ]] && [[ "$(groups "${userLogin}" | grep "\b${groupName}\b")" != '' ]] 2518 | then 2519 | echo 'true' && return 0 2520 | fi 2521 | 2522 | echo 'false' && return 1 2523 | } -------------------------------------------------------------------------------- /tunnel.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | function displayUsage() 4 | { 5 | local -r scriptName="$(basename "${BASH_SOURCE[0]}")" 6 | 7 | echo -e "\033[1;33m" 8 | echo "SYNOPSIS :" 9 | echo " ${scriptName}" 10 | echo " --help" 11 | echo " --configure" 12 | echo " --local-port " 13 | echo " --remote-port " 14 | echo " --local-to-remote" 15 | echo " --remote-to-local" 16 | echo " --remote-user " 17 | echo " --remote-host " 18 | echo " --identity-file " 19 | echo -e "\033[1;35m" 20 | echo "DESCRIPTION :" 21 | echo " --help Help page" 22 | echo " --configure Config remote server to support forwarding (optional)" 23 | echo " This option will require arguments '--remote-user' and '--remote-host'" 24 | echo " --local-port Local port number (require)" 25 | echo " --remote-port Remote port number (require)" 26 | echo " --local-to-remote Forward request from local machine to remote machine" 27 | echo " Either '--local-to-remote' or '--remote-to-local' must be specified" 28 | echo " --remote-to-local Forward request from remote machine to local machine (require)" 29 | echo " Either '--local-to-remote' or '--remote-to-local' must be specified" 30 | echo " --remote-user Remote user (require)" 31 | echo " --remote-host Remote host (require)" 32 | echo " --identity-file Path to private key (*.ppk, *.pem) to access remote server (optional)" 33 | echo -e "\033[1;36m" 34 | echo "EXAMPLES :" 35 | echo " ./${scriptName} --help" 36 | echo 37 | echo " ./${scriptName} --configure --remote-user 'root' --remote-host 'my-server.com'" 38 | echo " ./${scriptName} --configure --remote-user 'root' --remote-host 'my-server.com' --identity-file '/keys/my-server/key.ppk'" 39 | echo 40 | echo " ./${scriptName} --local-port 8080 --remote-port 9090 --local-to-remote --remote-user 'root' --remote-host 'my-server.com'" 41 | echo " ./${scriptName} --local-port 8080 --remote-port 9090 --local-to-remote --remote-user 'root' --remote-host 'my-server.com' --identity-file '/keys/my-server/key.ppk'" 42 | echo 43 | echo " ./${scriptName} --local-port 8080 --remote-port 9090 --remote-to-local --remote-user 'root' --remote-host 'my-server.com'" 44 | echo " ./${scriptName} --local-port 8080 --remote-port 9090 --remote-to-local --remote-user 'root' --remote-host 'my-server.com' --identity-file '/keys/my-server/key.ppk'" 45 | echo -e "\033[0m" 46 | 47 | exit ${1} 48 | } 49 | 50 | function getIdentityFileOption() 51 | { 52 | local identityFile="${1}" 53 | 54 | if [[ "$(isEmptyString "${identityFile}")" = 'false' && -f "${identityFile}" ]] 55 | then 56 | echo "-i "${identityFile}"" 57 | else 58 | echo 59 | fi 60 | } 61 | 62 | function configure() 63 | { 64 | local remoteUser="${1}" 65 | local remoteHost="${2}" 66 | local identityFile="${3}" 67 | 68 | local identityOption='' 69 | identityOption="$(getIdentityFileOption "${identityFile}")" 70 | 71 | local commands='' 72 | commands="$(cat "${utilPath}") 73 | checkRequireRootUser 74 | appendToFileIfNotFound \ 75 | '${sshdConfigFile}' \ 76 | '${tcpForwardConfigPattern}' \ 77 | '\nAllowTcpForwarding yes' \ 78 | 'true' \ 79 | 'true' \ 80 | 'true' 81 | appendToFileIfNotFound \ 82 | '${sshdConfigFile}' \ 83 | '${gatewayConfigPattern}' \ 84 | 'GatewayPorts yes' \ 85 | 'true' \ 86 | 'true' \ 87 | 'true' 88 | service ssh restart" 89 | 90 | ssh ${identityOption} -n "${remoteUser}@${remoteHost}" "${commands}" 91 | } 92 | 93 | function verifyPort() 94 | { 95 | local port="${1}" 96 | local mustExist="${2}" 97 | local remoteUser="${3}" 98 | local remoteHost="${4}" 99 | local identityOption="${5}" 100 | 101 | if [[ "$(isEmptyString "${remoteUser}")" = 'true' || "$(isEmptyString "${remoteHost}")" = 'true' ]] 102 | then 103 | local isProcessRunning='' 104 | isProcessRunning="$(isPortOpen "${port}")" 105 | 106 | local machineLocation='local' 107 | else 108 | local commands='' 109 | commands="$(cat "${utilPath}") 110 | isPortOpen '${port}'" 111 | 112 | local isProcessRunning='' 113 | isProcessRunning="$(ssh ${identityOption} -n "${remoteUser}@${remoteHost}" "${commands}")" 114 | 115 | local machineLocation="${remoteHost}" 116 | fi 117 | 118 | if [[ "${mustExist}" = 'true' && "${isProcessRunning}" = 'false' ]] 119 | then 120 | error "\nFATAL :" 121 | error " - There is not a process listening to port ${port} on the '${machineLocation}' machine." 122 | fatal " - Please make sure your process is listening to the port ${port} before trying to tunnel.\n" 123 | elif [[ "${mustExist}" = 'false' && "${isProcessRunning}" = 'true' ]] 124 | then 125 | error "\nFATAL :" 126 | error " - There is a process listening to port ${port} on the '${machineLocation}' machine." 127 | fatal " - Please make sure your process is not listening to the port ${port} before trying to tunnel.\n" 128 | fi 129 | } 130 | 131 | function tunnel() 132 | { 133 | local localPort="${1}" 134 | local remotePort="${2}" 135 | local tunnelDirection="${3}" 136 | local remoteUser="${4}" 137 | local remoteHost="${5}" 138 | local identityFile="${6}" 139 | 140 | # Get Identity File Option 141 | 142 | local identityOption='' 143 | identityOption="$(getIdentityFileOption "${identityFile}")" 144 | 145 | # Verify Ports 146 | 147 | if [[ "${tunnelDirection}" = 'local-to-remote' ]] 148 | then 149 | verifyPort "${localPort}" 'false' 150 | verifyPort "${remotePort}" 'true' "${remoteUser}" "${remoteHost}" "${identityOption}" 151 | elif [[ "${tunnelDirection}" = 'remote-to-local' ]] 152 | then 153 | verifyPort "${localPort}" 'true' 154 | verifyPort "${remotePort}" 'false' "${remoteUser}" "${remoteHost}" "${identityOption}" 155 | else 156 | fatal "\nFATAL : invalid tunnel direction '${tunnelDirection}'" 157 | fi 158 | 159 | # Verify Remote Config 160 | 161 | local tcpForwardConfigFound='' 162 | tcpForwardConfigFound="$(ssh ${identityOption} -n "${remoteUser}@${remoteHost}" grep -E -o "'${tcpForwardConfigPattern}'" "'${sshdConfigFile}'")" 163 | 164 | local gatewayConfigFound='' 165 | gatewayConfigFound="$(ssh ${identityOption} -n "${remoteUser}@${remoteHost}" grep -E -o "'${gatewayConfigPattern}'" "'${sshdConfigFile}'")" 166 | 167 | if [[ "$(isEmptyString "${tcpForwardConfigFound}")" = 'true' || "$(isEmptyString "${gatewayConfigFound}")" = 'true' ]] 168 | then 169 | error "\nWARNING :" 170 | error " - Your remote host '${remoteHost}' is NOT yet configured for tunneling." 171 | echo -e " \033[1;31m- Run '\033[1;33m--configure\033[1;31m' to set it up!\033[0m" 172 | error " - Will continue tunneling but it might NOT work for you!" 173 | sleep 5 174 | fi 175 | 176 | # Start Forwarding 177 | 178 | if [[ "${tunnelDirection}" = 'local-to-remote' ]] 179 | then 180 | doTunnel 'localhost' "${localPort}" "${remoteHost}" "${remotePort}" '-L' "${remoteUser}" "${remoteHost}" "${identityOption}" 181 | else 182 | doTunnel "${remoteHost}" "${remotePort}" 'localhost' "${localPort}" '-R' "${remoteUser}" "${remoteHost}" "${identityOption}" 183 | fi 184 | } 185 | 186 | function doTunnel() 187 | { 188 | local sourceHost="${1}" 189 | local sourcePort="${2}" 190 | local destinationHost="${3}" 191 | local destinationPort="${4}" 192 | local directionOption="${5}" 193 | local remoteUser="${6}" 194 | local remoteHost="${7}" 195 | local identityOption="${8}" 196 | 197 | echo -e "\n\033[1;35m${sourceHost}:${sourcePort} \033[1;36mforwards to \033[1;32m${destinationHost}:${destinationPort}\033[0m\n" 198 | 199 | ssh -c '3des-cbc' \ 200 | -C \ 201 | -g \ 202 | -N \ 203 | -p 22 \ 204 | -v \ 205 | ${identityOption} \ 206 | "${directionOption}" "${sourcePort}:localhost:${destinationPort}" \ 207 | "${remoteUser}@${remoteHost}" 208 | } 209 | 210 | function main() 211 | { 212 | local appPath='' 213 | appPath="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 214 | 215 | local optCount=${#} 216 | utilPath="${appPath}/libraries/util.bash" 217 | 218 | source "${utilPath}" 219 | 220 | while [[ ${#} -gt 0 ]] 221 | do 222 | case "${1}" in 223 | --help) 224 | displayUsage 0 225 | ;; 226 | 227 | --configure) 228 | shift 229 | 230 | local configure='true' 231 | ;; 232 | 233 | --local-port) 234 | shift 235 | 236 | if [[ ${#} -gt 0 ]] 237 | then 238 | local localPort='' 239 | localPort="$(trimString "${1}")" 240 | fi 241 | 242 | ;; 243 | 244 | --remote-port) 245 | shift 246 | 247 | if [[ ${#} -gt 0 ]] 248 | then 249 | local remotePort='' 250 | remotePort="$(trimString "${1}")" 251 | fi 252 | 253 | ;; 254 | 255 | --local-to-remote) 256 | shift 257 | 258 | local tunnelDirection='local-to-remote' 259 | ;; 260 | 261 | --remote-to-local) 262 | shift 263 | 264 | local tunnelDirection='remote-to-local' 265 | ;; 266 | 267 | --remote-user) 268 | shift 269 | 270 | if [[ ${#} -gt 0 ]] 271 | then 272 | local remoteUser='' 273 | remoteUser="$(trimString "${1}")" 274 | fi 275 | 276 | ;; 277 | 278 | --remote-host) 279 | shift 280 | 281 | if [[ ${#} -gt 0 ]] 282 | then 283 | local remoteHost='' 284 | remoteHost="$(trimString "${1}")" 285 | fi 286 | 287 | ;; 288 | 289 | --identity-file) 290 | shift 291 | 292 | if [[ ${#} -gt 0 ]] 293 | then 294 | local identityFile='' 295 | identityFile="$(formatPath "${1}")" 296 | fi 297 | 298 | ;; 299 | 300 | *) 301 | shift 302 | ;; 303 | esac 304 | done 305 | 306 | # Global Config 307 | 308 | sshdConfigFile='/etc/ssh/sshd_config' 309 | tcpForwardConfigPattern='^\s*AllowTcpForwarding\s+yes\s*$' 310 | gatewayConfigPattern='^\s*GatewayPorts\s+yes\s*$' 311 | 312 | # Validate Identity File Input 313 | 314 | if [[ "$(isEmptyString "${identityFile}")" = 'false' && ! -f "${identityFile}" ]] 315 | then 316 | fatal "\nFATAL : identity file '${identityFile}' not found!" 317 | fi 318 | 319 | # Action 320 | 321 | if [[ "${configure}" = 'true' ]] 322 | then 323 | if [[ "$(isEmptyString "${remoteUser}")" = 'true' || "$(isEmptyString "${remoteHost}")" = 'true' ]] 324 | then 325 | error '\nERROR : remoteUser or remoteHost argument not found!' 326 | displayUsage 1 327 | fi 328 | 329 | configure "${remoteUser}" "${remoteHost}" "${identityFile}" 330 | else 331 | if [[ "$(isEmptyString "${localPort}")" = 'true' || "$(isEmptyString "${remotePort}")" = 'true' || 332 | "$(isEmptyString "${tunnelDirection}")" = 'true' || 333 | "$(isEmptyString "${remoteUser}")" = 'true' || "$(isEmptyString "${remoteHost}")" = 'true' ]] 334 | then 335 | if [[ ${optCount} -gt 0 ]] 336 | then 337 | error '\nERROR : localPort, remotePort, tunnelDirection, remoteUser, or remoteHost argument not found!' 338 | displayUsage 1 339 | fi 340 | 341 | displayUsage 0 342 | fi 343 | 344 | tunnel "${localPort}" "${remotePort}" "${tunnelDirection}" "${remoteUser}" "${remoteHost}" "${identityFile}" 345 | fi 346 | } 347 | 348 | main "${@}" --------------------------------------------------------------------------------