├── CHANGELOG ├── LICENSE ├── README.md ├── add.png ├── bashible ├── bashible.edit.ble ├── bashible.net.ble ├── bashible.template.ble ├── bashible.timeout.ble ├── docs ├── @.md ├── absolute_path.md ├── add_line.md ├── and_when.md ├── append_line.md ├── base_dir.md ├── bashible_version.md ├── comment_lines_matching.md ├── dash1.md ├── dash2.md ├── dash3.md ├── delayed.md ├── evaluate.md ├── fail.md ├── finish.md ├── halt.md ├── ignore_errors.md ├── in_timeout.md ├── is_empty_dir.md ├── is_empty_output.md ├── is_empty_var.md ├── is_toplevel.md ├── not.md ├── or_when.md ├── orig_dir.md ├── output_to_file.md ├── output_to_var.md ├── prepend_line.md ├── print_error.md ├── print_info.md ├── print_warn.md ├── quiet.md ├── remove_lines_matching.md ├── replace_lines_matching.md ├── replace_matching.md ├── reset_base_dir.md ├── result.md ├── run.md ├── template.md ├── uncomment_lines_matching.md ├── unless_already.md ├── use.md ├── wait_for_tcp.md └── when.md ├── example.png └── examples ├── editing_files ├── README ├── add.ble ├── del.ble ├── generate_sshd_config.ble └── sshd_config.tpl ├── generating_html_from_a_template ├── README ├── generate.ble ├── index.tpl ├── js │ ├── bar.js │ └── foo.js └── parts │ ├── body.tpl │ └── head.tpl ├── killing_process ├── README ├── child.ble ├── loop.ble └── main.ble └── result ├── README └── result.ble /CHANGELOG: -------------------------------------------------------------------------------- 1 | 0.3.1 2 | - output_to_file accepts --tee (to duplicate output to the file and on the terminal) 3 | 4 | 0.3.0 5 | - release 6 | 7 | 0.2.1 8 | - bashible.edit.ble module fixed (pull request "use whole line matching for add_line") 9 | 10 | 0.2.0 11 | - system of conditions has changed 12 | 13 | The previous way with @ in the beginning was ugly: 14 | 15 | @ when test -f /etc/passwd 16 | @ and test -f /etc/shadow 17 | @ and test -f /etc/group 18 | @ Everything is installed 19 | - do foo 20 | - do bar 21 | 22 | From now on, put conditions right after the @ block: 23 | 24 | @ Everything is installed 25 | when test -f /etc/passwd 26 | and test -f /etc/shadow 27 | and test -f /etc/group 28 | - do foo 29 | - do bar 30 | 31 | The block won't print itself unless conditions match. 32 | 33 | 34 | 0.1.1 35 | - bug in is_empty_dir (worked the other way round) 36 | 37 | - added AND / OR conditions 38 | 39 | Now you can use multiple conditions of a block. (Just don't mix them :-) 40 | 41 | @ when test -f /etc/passwd 42 | @ and test -f /etc/shadow 43 | @ and test -f /etc/group 44 | @ Everything is installed 45 | - foo 46 | - bar 47 | 48 | @ when which php7 49 | @ or which php6 50 | @ or which php5 51 | @ A PHP version is installed 52 | - foo 53 | - bar 54 | 55 | It is also possible to write 'and when' and 'or when'. 56 | 57 | If all rows begin with 'when', it means AND. 58 | 59 | - added sugar 60 | 61 | Now you can also write 62 | 63 | @ when is not empty_dir /tmp 64 | 65 | instead of 66 | 67 | @ when not is_empty_dir /tmp 68 | 69 | 0.1.0 70 | - versioning changed to follow semver.org's rules 71 | 72 | - default script extension is .ble 73 | 74 | - conditional block execution has been changed 75 | 76 | use '@ when' before a block starts (or multiple when to achieve AND) 77 | 78 | @ when true 79 | @ Doing something... 80 | - ls / 81 | 82 | @ when false 83 | @ Not doing anything... 84 | - ls /tmp 85 | 86 | @ Doing always... 87 | - ls /home 88 | 89 | @ when foo 90 | @ when bar 91 | @ Executed if foo and bar matches 92 | - baz 93 | 94 | In previous versions of bashible, 'when' was used inside a block. 95 | Therefore even if it didn't pass, the block was printed 96 | (and it was not obvious what is going on). 97 | 98 | - set_var/fill_var 99 | 100 | There were two functions in previous versions, set_var and fill_var. 101 | The first did just foo=bar, the second ran it's arguments (command) and 102 | set a variable to the command's stdout. 103 | 104 | Now it is more straightforward: 105 | 106 | @ Setting some fixed variables if this block passes 107 | - foo = bar 108 | - abc = xyz 109 | 110 | @ Storing stdout of commands to variables 111 | - output_to_var user_homes ls /home 112 | - output_to_var myip evaluate "host foo.com | grep has address" 113 | 114 | It also takes some options: --stdout, --stderr 115 | 116 | @ Storing only stderr of the command 117 | - output_to_var error_message --stderr ignore_errors find /nonexistent 118 | # or the same 119 | - output_to_var error_message -2 ignore_errors find /nonexistent 120 | - echo $error_message 121 | 122 | - output_to_file 123 | 124 | Similar function for storing an output of a command into a file. 125 | Moreover it accepts --append option to append data at the end of the file. 126 | 127 | - to register both exitcode and stdout of a command use a combination 128 | of 'result' and 'output_to_var' (just remember that the 'result' must preceede 'output_to_var' 129 | because it runs a sub-process, otherwise the variable won't get propagated to the parent process) 130 | 131 | @ Installing nginx 132 | - result installed output_to_var log apt-get install nginx 133 | - echo STORED OUTPUT: $log 134 | 135 | @ when installed 136 | @ Installation succeeded 137 | 138 | @ when not installed 139 | @ Error happened while installation, mailing the output 140 | - mail me@me.com <<< $log 141 | 142 | - renamed functions: 143 | 144 | may_fail => ignore_errors 145 | toplevel => is_toplevel 146 | var_empty => is_empty_var 147 | empty => is_empty_output 148 | dir_empty => is_empty_dir 149 | 150 | - shopt -s inherit_errexit by default 151 | 152 | - there were some other suggestions on modules (how to make them like ansible's) 153 | 154 | It will happen in a next version of bashible. 155 | 156 | 157 | 0.3 158 | - first release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jan Molic 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BASHIBLE 2 | 3 | Bashible is a deployment/automation tool written in Bash (DSL). Inspired by Ansible. Simplifies things and prevents usual mistakes. 4 | 5 | Features: 6 | 7 | - improved readability 8 | - unhandled failures prevention 9 | - skipping already done tasks 10 | - command chaining 11 | - working directory always as expected 12 | - variable checking 13 | - dependencies; calling sub-scripts 14 | - delayed tasks executed on finish 15 | - child termination handler; no processes left running 16 | - modules: template engine, config editing, etc. 17 | - nice output 18 | 19 | At the moment, bashible has been used on Arch linux. It may not be compatible with other platforms, because it internally uses GNU/sed, grep, etc. 20 | 21 | Suggestions and bugfixes are welcome! :-) 22 | 23 | 24 | 25 | ## Example script 26 | 27 | `@` represents a task (block of commands), `-` represents a command. Both `@` and `-` are just bash functions with arguments. Each block may have multiple AND or OR conditions. 28 | 29 | The working directory is automatically set to the script's. Execution will stop immediately on failure, unless you prefix a command by `ignore_errors` (or register it's exitcode like in this example). 30 | 31 | ```bash 32 | #!/usr/local/bin/bashible 33 | 34 | @ Synchronizing files 35 | - register exitcode as 'synced' of rsync -av /foo /bar 36 | 37 | @ Shutting down the machine after successful synchronization 38 | when synced 39 | and test -f /etc/do-shutdown 40 | - shutdown -h now 41 | 42 | @ Error happened, sending an e-mail 43 | when not synced 44 | - mail me@me.com <<< "synchronzation failed" 45 | ``` 46 | 47 | ![output of the example](example.png) 48 | 49 | Rewritten into pure Bash, the example above may look like this, 50 | 51 | ```bash 52 | #!/bin/bash 53 | 54 | cd `dirname $0` 55 | set -eux -o pipefail 56 | 57 | echo Synchronizing files 58 | if rsync -av /foo /bar; then 59 | echo Shutting down the machine after successful synchronization 60 | if test -f /etc/do-shutdown; then 61 | shutdown -h now 62 | fi 63 | else 64 | echo Error happened, sending an e-mail 65 | mail me@me.com <<< "synchronzation failed" 66 | fi 67 | ``` 68 | 69 | ## Another example 70 | 71 | In this example, we are going to set two variables and store the output of `ls` command in them. 72 | Moreover, the output has to be something, otherwise the execution stops. 73 | 74 | ```bash 75 | #!/usr/local/bin/bashible 76 | 77 | @ Loading lists 78 | - output_to_var HOMES is not empty_output of ls -1 /home 79 | - output_to_var VHOSTS is not empty_output of ls -1 /etc/nginx/vhosts.d 80 | 81 | @ Rsyncing data and saving error messages into a file 82 | - quiet output_to_file errlog.txt -2 rsync /foo /bar 83 | ``` 84 | 85 | Both functions `output_to_var` and `output_to_file` accept options: -1|--stdout, -2|--stderr (or both). The `output_to_file` can also --append to it. 86 | 87 | By prefixing a command with `quiet`, no messages will be written on terminal. 88 | 89 | (`is` and `of` are just sugar words, they do actually nothing, but improve readability) 90 | 91 | ## Another example 92 | 93 | A module `template` is loaded. The module is a sourceable file expected to be in the same directory as `bashible` is. It adds some more functions. 94 | 95 | In this example, the script expects two arguments passed from the commandline ($1, $2), they should not be empty. Also an environment variable HOME has to be something. 96 | 97 | The `template` function is very powerful even it has just 18 lines of code. You can generate dynamic html with it, see examples/template directory. 98 | 99 | ```bash 100 | #!/usr/local/bin/bashible 101 | 102 | use template 103 | 104 | @ Doing some checks and setting variables 105 | - output_to_var HOST is not empty_output of echo $1 106 | - output_to_var PORT is not empty_output of echo $2 107 | - is not empty_var HOME 108 | - is empty_dir /home/$HOME 109 | 110 | @ Copying default files 111 | - cp -av /mnt/defaults /home/$HOME 112 | 113 | @ Creating .bashrc from a template 114 | # the template needs two variables to be set, HOST and PORT 115 | # these are set by arguments of this script ($1 and $2) 116 | - cd /home/$HOME/ 117 | - output_to_file .bashrc.tmp template /mnt/templates/bashrc.tpl 118 | - mv .bashrc.tmp .bashrc 119 | ``` 120 | 121 | The `is` is a sugar word. It actually does nothing and `empty_dir` is an alias for `is_empty_dir`. It's up to you, what you prefer. 122 | 123 | If you use `cd` within a block, it works as expected, but next block will have it's working directory back. On each block start, bashible does chdir to the base directory. You can change it by `base_dir`. 124 | 125 | ## Install & usage 126 | 127 | Install bashible and it's modules (sourceable functions - here I am going to install just one module, `edit`). Copy everything to the same directory, for instance /usr/local/bin. 128 | 129 | ```bash 130 | wget https://raw.githubusercontent.com/mig1984/bashible/master/bashible 131 | wget https://raw.githubusercontent.com/mig1984/bashible/master/bashible.edit.ble 132 | chmod 755 bashible 133 | chmod 755 bashible.edit.ble 134 | mv bashible /usr/local/bin 135 | mv bashible.edit.ble /usr/local/bin 136 | ``` 137 | 138 | Then run the script 139 | 140 | ```bash 141 | bashible my-script.ble ARG1 ARG2 ... 142 | ``` 143 | 144 | or put a she-bang in the beginning of the script and run it as a command 145 | 146 | ```bash 147 | #!/usr/local/bin/bashible 148 | ``` 149 | 150 | ```bash 151 | ./my-script.ble ARG1 ARG2 ... 152 | ``` 153 | 154 | ## Functions 155 | 156 | ### core functions 157 | 158 | [@ MESSAGE](docs/@.md) 159 | [when](docs/when.md) 160 | [and when](docs/and_when.md) 161 | [or when](docs/or_when.md) 162 | [- COMMAND ARGS ...](docs/dash1.md) 163 | [- VARIABLE = VALUE](docs/dash2.md) 164 | [- && (conditional loop)](docs/dash3.md) 165 | [absolute_path PATH](docs/absolute_path.md) 166 | [bashible_version](docs/bashible_version.md) 167 | [base_dir PATH](docs/base_dir.md) 168 | [delayed COMMAND ARGS ...](docs/delayed.md) 169 | [evaluate STRING](docs/evaluate.md) 170 | [fail MESSAGE](docs/fail.md) 171 | [finish MESSAGE](docs/finish.md) 172 | [halt MESSAGE](docs/halt.md) 173 | [not COMMAND ARGS ...](docs/not.md) 174 | [ignore_errors COMMAND ARGS ...](docs/ignore_errors.md) 175 | [is_toplevel](docs/is_toplevel.md) 176 | [is_empty_dir PATH](docs/is_empty_dir.md) 177 | [is_empty_output COMMAND ARGS ...](docs/is_empty_output.md) 178 | [is_empty_var VAR](docs/is_empty_var.md) 179 | [output_to_file DEST OPTS COMMAND ARGS ...](docs/output_to_file.md) 180 | [output_to_var NAME OPTS COMMAND ARGS ...](docs/output_to_var.md) 181 | [orig_dir](docs/orig_dir.md) 182 | [print_error MSG](docs/print_error.md) 183 | [print_info MSG](docs/print_info.md) 184 | [print_warn MSG](docs/print_warn.md) 185 | [quiet COMMAND ARGS ...](docs/quiet.md) 186 | [reset_base_dir](docs/reset_base_dir.md) 187 | [result NAME COMMAND ARGS ...](docs/result.md) 188 | [run PATH ARGS ...](docs/run.md) 189 | [unless_already COMMAND ARGS ...](docs/unless_already.md) 190 | [use FEATURES ...](docs/use.md) 191 | 192 | ### sugar 193 | 194 | For better readability, there are some more ways to do the same. 195 | 196 | when not is_empty_dir /home 197 | when is not empty_dir /home 198 | 199 | when not is_empty_var HOSTNAME 200 | when is not empty_var HOSTNAME 201 | 202 | when not is_empty_output ls /home 203 | when is not empty_output of ls /home 204 | 205 | result synced rsync /foo /bar 206 | register exitcode as 'synced' of rsync /foo /bar 207 | 208 | (The `is` and `of` words actually do nothing. `empty_dir` is an alias for `is_empty_dir`. The `register (exitcode (as))` is an alias for `result`.) 209 | 210 | ### file-editing functions - found in bashible.edit module 211 | 212 | [add_line LINE PATH](docs/add_line.md) 213 | [append_line LINE PATH](docs/append_line.md) 214 | [comment_lines_matching REGEXP PATH](docs/comment_lines_matching.md) 215 | [prepend_line LINE PATH](docs/prepend_line.md) 216 | [remove_lines_matching REGEX PATH](docs/remove_lines_matching.md) 217 | [replace_lines_matching REGEXP STRING PATH](docs/replace_lines_matching.md) 218 | [replace_matching REGEXP STRING PATH](docs/replace_matching.md) 219 | [uncomment_lines_matching REGEXP PATH](docs/uncomment_lines_matching.md) 220 | 221 | ### template engine - found in bashible.template module 222 | 223 | [template TEMPLATE_PATH RESULT_PATH](docs/template.md) 224 | 225 | ### timeout - found in bashible.timeout module 226 | 227 | [in_timeout SECS COMMAND ARGS ...](docs/in_timeout.md) 228 | 229 | ### network-oriented functions - found in bashible.net module 230 | 231 | [wait_for_tcp MATCH up|down](docs/wait_for_tcp.md) 232 | 233 | 234 | ## TODO 235 | 236 | Write more docs and examples. 237 | 238 | Modularize. The bashible core in the version 1.0 should contain only necessary functions and should not ever change. For instance, the [delayed](docs/delayed.md) and [unless_already COMMAND ARGS ...](docs/unless_already.md) functions now need two temporary files. These files are created on every bashible startup. These functions should go into optional modules instead. 239 | 240 | Create tests. Bashible uses GNU/grep, GNU/sed and other programs which may not work properly on all platforms. 241 | 242 | Make bashible multiplatform. 243 | 244 | Create more modules and/or integrate existing Bash libraries. 245 | -------------------------------------------------------------------------------- /add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mig1984/bashible/330e71e49b31afd72fcb8cd30b01447964e92527/add.png -------------------------------------------------------------------------------- /bashible: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # bashible 4 | # (c) Jan Molic 2020 5 | # https://github.com/mig1984/bashible 6 | 7 | ble_version() { 8 | echo 0.3.2 9 | } 10 | 11 | ble_help() { 12 | echo "bashible version $(ble_version)" 13 | echo 14 | echo "usage: bashible ./script.ble" 15 | echo 16 | echo "see also: https://github.com/mig1984/bashible" 17 | echo 18 | echo "special exitcodes:" 19 | echo " 42 already handled failure" 20 | echo " 43 halt" 21 | echo " 143 terminated by TERM, QUIT, INT or HUP" 22 | echo 23 | } 24 | 25 | ble_conditions_passed() { 26 | if ((_BLE_ANDOR==0 && _BLE_ANDSKIP==0)) || 27 | ((_BLE_ANDOR==1 && _BLE_ORSKIP==0)); then 28 | return 0; 29 | fi 30 | return 1; 31 | } 32 | 33 | ble_print_start() { 34 | echo -e "\n\033[1;37;45m ${_BLE_NAV} \033[1;37;44m START \033[0m\n" >&2 35 | } 36 | 37 | ble_print_block() { 38 | if ble_conditions_passed && ((${#_BLE_BLOCK_MESSAGE}>0)); then 39 | echo -e "\n\033[1;37;45m ${_BLE_NAV} \033[1;37;44m ${_BLE_BLOCK_MESSAGE} \033[0m\n" >&2 40 | _BLE_BLOCK_MESSAGE='' 41 | fi 42 | } 43 | 44 | ble_print_task() { 45 | echo -e "\033[37m - $* \033[0m" >&2 46 | } 47 | 48 | ble_print_fail() { 49 | echo -e "\n\033[1;37;45m ${_BLE_NAV} \033[1;37;41m $* \033[0m\n" >&2 50 | } 51 | 52 | ble_print_finish() { 53 | echo -e "\n\033[1;37;45m ${_BLE_NAV} \033[1;37;44m FINISH \033[0m\n" >&2 54 | } 55 | 56 | ble_print_continues() { 57 | echo -e "\033[1;37;45m ${_BLE_NAV} \033[1;37;44m ... \033[0m\n" >&2 58 | } 59 | 60 | ble_cd_basedir() { 61 | cd "${_BASE_DIR}" || fail "can't enter base dir '${_BASE_DIR}'" 62 | } 63 | 64 | ble_set_top_variables() { 65 | set +u 66 | is_toplevel || fail "use 'run' to run child bashible scripts instead of running them directly!" 67 | set -u 68 | export _BLE_LEVEL=0 69 | export _BLE_NAV='' 70 | export _BLE_SUPPRESS_ERRORS=0 71 | export _BLE=` absolute_path "$1" ` # the bashible script itself 72 | test ` basename "${_BLE}" ` = bashible || fail "the 'basename' command is probably not present" 73 | export _BLE_DELAYED=$(mktemp) # store commands run at the end 74 | test ${#_BLE_DELAYED} || fail "_BLE_DELAYED tempfile can not be created, is the 'mktemp' command present?" 75 | export _BLE_ALREADY=$(mktemp) # store already run commands 76 | test ${#_BLE_ALREADY} || fail "_BLE_ALREADY tempfile can not be created, is the 'mktemp' command present?" 77 | _BLE_SUBSHELL_PID=0 78 | } 79 | 80 | ble_set_variables() { 81 | _BLE_ORIG_DIR=` pwd ` 82 | _BLE_DIR=` absolute_path $( dirname "${_BLE}" ) ` # bashible modules reside there 83 | _BLE_SCRIPT_PATH=`absolute_path "$1"` 84 | base_dir "`dirname "${_BLE_SCRIPT_PATH}"`" 85 | _BLE_ORIG_BASE_DIR=${_BASE_DIR} 86 | let _BLE_LEVEL=${_BLE_LEVEL}+1 87 | if test ${#_BLE_NAV} -eq 0 ; then 88 | _BLE_NAV="`basename ${_BLE_SCRIPT_PATH} | sed "s/\.ble\$//" `" 89 | else 90 | _BLE_NAV="${_BLE_NAV} -> `basename ${_BLE_SCRIPT_PATH} | sed "s/\.ble\$//" `" 91 | fi 92 | _BLE_ANDOR=0 # main conditional mode (0=AND 1=OR) 93 | _BLE_ANDSKIP=0 # skipping mode for AND 94 | _BLE_ORSKIP=1 # skipping mode for OR 95 | _BLE_BLOCK_MESSAGE='' 96 | _BLE_FINISHED=0 # to detect unhandled exits (pipefail, errexit) 97 | export _BLE_NAV 98 | export _BLE_LEVEL 99 | } 100 | 101 | ble_exit() { 102 | _BLE_FINISHED=1 103 | exit $1 104 | } 105 | 106 | ble_do_finish() { 107 | if (($1==0 || $1==43)); then 108 | ble_print_finish 109 | else 110 | if ((_BLE_FINISHED==0 && $1!=42)); then 111 | ble_print_fail "Unhandled failure, exit code $1 (PID=$BASHPID, CWD is $PWD)" 112 | exit 42 # convert to handled 113 | fi 114 | fi 115 | } 116 | 117 | ble_trap_top_exit() { 118 | trap "rm \"\${_BLE_ALREADY}\" \"\${_BLE_DELAYED}\"" EXIT 119 | } 120 | 121 | ble_trap_exit() { 122 | trap "ble_do_finish \$?" EXIT 123 | } 124 | 125 | ble_trap_termination(){ 126 | trap " 127 | if is_toplevel && ((\$$==\$BASHPID)); then 128 | # in the toplevel script and not it's subshell 129 | if ((_BLE_SUBSHELL_PID)); then 130 | ble_print_fail \"process terminated, sending TERM to all processes in the process group \${_BLE_SUBSHELL_PID}\" 131 | trap - SIGHUP SIGINT SIGQUIT SIGTERM 132 | kill -TERM -\${_BLE_SUBSHELL_PID} 133 | fi 134 | fi 135 | ble_exit 42 136 | " SIGHUP SIGINT SIGQUIT SIGTERM 137 | } 138 | 139 | ble_check_cmd() { 140 | quiet type "$1" || fail "command '$@' not found (CWD is $PWD)" 141 | } 142 | 143 | ble_run_script() { 144 | local path=$1; shift 145 | ble_print_start 146 | _BLE_FINISHED=0 147 | source "$path" 148 | # unless it gets here, unhandled exit happened -> the exit handler will react on it because _BLE_FINISHED will be still set to 0 149 | _BLE_FINISHED=1 150 | } 151 | 152 | ble_handle_exitstatus() { 153 | local ex=$1; shift 154 | if ((ex==43)); then 155 | ble_exit 43 # 43 == halt 156 | elif ((ex>0 && ex!=42 && _BLE_SUPPRESS_ERRORS==0)); then 157 | ble_print_fail "command '$@' failed with exit code $ex (CWD is $PWD)" 158 | return 42 # 42 == error handled from now on (no more error messages in parents) 159 | else 160 | return $ex # 0 or another status when errors output is SUPPRESSed 161 | fi 162 | } 163 | 164 | ble_serialize_command() { 165 | oldIFS=$IFS 166 | IFS=$'\t'; echo -e "$PWD\t$*" 167 | IFS=$oldIFS 168 | } 169 | 170 | ### CORE FUNCTIONS ### 171 | 172 | @() { 173 | ble_print_block # command did not print the last block? do it now 174 | _BLE_ANDSKIP=0 175 | _BLE_ORSKIP=1 176 | _BLE_ANDOR=0 177 | _BLE_BLOCK_MESSAGE=$* # store the message, will be printed on first command or on next block 178 | ble_cd_basedir 179 | } 180 | 181 | # when|and when => AND mode (will enable _BLE_ANDSKIP unless command passes) 182 | and() { 183 | test "$1" = 'when' && shift # 'and when' 184 | 185 | _BLE_ANDOR=0 186 | 187 | if ((_BLE_ANDSKIP==0)); then 188 | local ex=0 189 | "$@" || ex=$? # || bypasses errexit 190 | if ((ex!=0)); then # command didn't pass, from now on AND mode will skip 191 | _BLE_ANDSKIP=1 192 | fi 193 | if ((ex==0)); then # command passed, from now on the OR mode will not skip 194 | _BLE_ORSKIP=0 195 | fi 196 | fi 197 | } 198 | 199 | when() { and "$@"; } 200 | 201 | # or|or when => OR mode (will disable _BLE_ORSKIP if a command passes) 202 | or() { 203 | test "$1" = 'when' && shift # 'or when' 204 | 205 | _BLE_ANDOR=1 206 | 207 | if ((_BLE_ORSKIP==1)); then 208 | local ex=0 209 | "$@" || ex=$? # || bypasses errexit 210 | if ((ex==0)); then # command passed, from now on the OR mode will not skip 211 | _BLE_ORSKIP=0 212 | fi 213 | fi 214 | } 215 | 216 | -() { 217 | 218 | if ble_conditions_passed; then 219 | 220 | ble_print_block 221 | 222 | # -&& for ... 223 | # -&& while ... 224 | # -&& continue 225 | if test $# -eq 0; then 226 | print_info "-&& in action" 227 | return 0 228 | fi 229 | 230 | # - foovar = barvalue 231 | if test ${#} -gt 1 && test "$2" = "="; then 232 | ble_print_task "$*" 233 | var=$1 234 | shift 235 | shift 236 | eval "$var=\$*" 237 | return 238 | fi 239 | 240 | # - command 241 | ble_print_task "$*" 242 | ble_check_cmd "$1" 243 | local ex=0 244 | "$@" || ex=$? 245 | # explicitly do exit, do not rely on set -e which is disabled in subshells (via 'run') 246 | ble_handle_exitstatus $ex "$@" || ble_exit $? 247 | 248 | else 249 | 250 | # -&& for ... (skipping) 251 | # -&& while ... (skipping) 252 | # -&& continue (skipping) 253 | if test $# -eq 0; then 254 | return 1 255 | fi 256 | 257 | fi 258 | } 259 | 260 | absolute_path() { 261 | local path 262 | if egrep -e '^\s*\//' <<< $1; then 263 | # $1 is an absolute path or a command (without a path) 264 | path=$1 265 | else 266 | # $1 is a relative path 267 | path="$(cd $(dirname $1); pwd)/$(basename $1)" 268 | fi 269 | test ${#path} -gt 0 || fail "absolute_path empty on '$1'" 270 | echo "$path" 271 | } 272 | 273 | base_dir() { 274 | _BASE_DIR=`absolute_path "$1"` 275 | ble_cd_basedir 276 | } 277 | 278 | run() { 279 | ( 280 | ble_set_variables "$1" 281 | ble_trap_termination 282 | ble_trap_exit 283 | set -eu -o pipefail +m 284 | shopt -s inherit_errexit 285 | ble_run_script "$@" 286 | ) 287 | local ex=$? 288 | if ((ex==0)); then 289 | test ${#_BLE_NAV} -gt 0 && ble_print_continues 290 | return 0 291 | else 292 | ble_handle_exitstatus $ex "$@" 293 | fi 294 | } 295 | 296 | delayed() { 297 | ble_serialize_command "$@" >> "${_BLE_DELAYED}" || fail "can't write to _BLE_DELAYED file (${_BLE_DELAYED})" 298 | } 299 | 300 | 301 | evaluate() { 302 | eval "$@" || return $? # || bypasses errexit 303 | } 304 | 305 | fail() { 306 | if test $# -eq 0; then 307 | ble_print_fail "execution failed" 308 | else 309 | ble_print_fail $* 310 | fi 311 | ble_exit 42 312 | } 313 | 314 | finish() { 315 | if test $# -eq 0; then 316 | print_info "finishing execution of the script" 317 | else 318 | print_info $* 319 | fi 320 | ble_exit 0 321 | } 322 | 323 | 324 | halt() { 325 | if test $# -eq 0; then 326 | print_info "halting execution of this script and all parent scripts" 327 | else 328 | print_info $* 329 | fi 330 | ble_exit 43 331 | } 332 | 333 | # but still fails if the command does not exist 334 | ignore_errors() { 335 | ble_check_cmd "$1" 336 | local old=${_BLE_SUPPRESS_ERRORS} 337 | export _BLE_SUPPRESS_ERRORS=1 338 | "$@" || ble_handle_exitstatus $? "$@" || true 339 | export _BLE_SUPPRESS_ERRORS=$old 340 | } 341 | 342 | is_toplevel() { 343 | ((_BLE_LEVEL==0)) 344 | } 345 | 346 | is_empty_dir() { 347 | local buf 348 | if buf=` ls -A $1 | wc -l `; then 349 | ((buf==0)) 350 | else 351 | ble_handle_exitstatus $? "$@" 352 | fi 353 | } 354 | 355 | is_empty_output() { 356 | ble_check_cmd "$1" 357 | local buf 358 | if buf=` "$@" `; then 359 | echo "$buf" 360 | test ${#buf} -eq 0 361 | else 362 | ble_handle_exitstatus $? "$@" 363 | fi 364 | } 365 | 366 | is_empty_var() { 367 | local var=$1 368 | eval "test \${#$var} -eq 0" 369 | } 370 | 371 | not() { 372 | ble_check_cmd "$1" 373 | if ! "$@"; then 374 | local ex=$? 375 | if ((ex==42 || ex==43)); then 376 | return $ex 377 | fi 378 | return 0 379 | else 380 | return 1 381 | fi 382 | } 383 | 384 | # output_to_file foo.txt -1 -2 --append ls /home 385 | output_to_file() { 386 | local stdout=0 387 | local stderr=0 388 | local append=0 389 | local tee=0 390 | local cnt=0 391 | 392 | local dest=$1; shift 393 | test "${#dest}" -gt 0 || fail "output_to_file: no destination file specified" 394 | 395 | for i in "$@"; do 396 | case "$1" in 397 | -1|--stdout) 398 | stdout=1 399 | let cnt=cnt+1 400 | shift 401 | ;; 402 | -2|--stderr) 403 | stderr=1 404 | let cnt=cnt+1 405 | shift 406 | ;; 407 | -a|--append) 408 | append=1 409 | let cnt=cnt+1 410 | shift 411 | ;; 412 | -t|--tee) 413 | tee=1 414 | let cnt=cnt+1 415 | shift 416 | ;; 417 | *) # a command 418 | 419 | ((cnt==0)) && stdout=1 # default - when no opts 420 | 421 | local s='' 422 | local ap='' 423 | 424 | if ((tee==1)); then 425 | ((stderr==1)) && s="$s 2>&1" 426 | ((stdout==0)) && s="$s 1>/dev/null" 427 | ((append==1)) && ap="-a" 428 | s="$s | tee $ap $dest" 429 | else 430 | ((append==1)) && ap=">>" || ap=">" 431 | ((stdout==1)) && s="$s 1$ap\$dest" 432 | ((stderr==1)) && s="$s 2$ap\$dest" 433 | fi 434 | 435 | ble_check_cmd "$1" 436 | local ex=0 437 | eval "\"\$@\" $s" || ex=$? 438 | ble_handle_exitstatus $ex "$@" 439 | return $? # of the handler 440 | ;; 441 | esac 442 | done 443 | } 444 | 445 | # output_to_var foobar -1 -2 ignore_errors ls /home 446 | output_to_var() { 447 | local stdout=0 448 | local stderr=0 449 | local cnt=0 450 | local var=$1; shift 451 | 452 | for i in "$@"; do 453 | case "$1" in 454 | -1|--stdout) 455 | stdout=1 456 | let cnt=cnt+1 457 | shift 458 | ;; 459 | -2|--stderr) 460 | stderr=1 461 | let cnt=cnt+1 462 | shift 463 | ;; 464 | *) # a command 465 | 466 | ((cnt==0)) && stdout=1 # default if no opts 467 | local s='' 468 | ((stdout==1 && stderr==1)) && s="2>&1" 469 | ((stdout==0)) && s="$s 1>/dev/null" 470 | 471 | eval "$var=''" # initialize the variable to prevent unbound variable error if the command fails 472 | local ex=0 473 | eval "$var=\` \"\$@\" $s \`" || ex=$? 474 | eval "print_info \"\$var=\$$var\"" 475 | ble_handle_exitstatus $ex "$@" 476 | 477 | return $? # of the handler 478 | ;; 479 | esac 480 | done 481 | } 482 | 483 | orig_dir() { 484 | cd "${_BLE_ORIG_DIR}" 485 | } 486 | 487 | print_error() { 488 | echo -e " \033[31m($*)\033[0m" >&2 489 | } 490 | 491 | print_warn() { 492 | echo -e " \033[31m($*)\033[0m" >&2 493 | } 494 | 495 | print_info() { 496 | echo -e " \033[32m($*)\033[0m" >&2 497 | } 498 | 499 | quiet() { 500 | "$@" >/dev/null 2>&1 || return $? 501 | } 502 | 503 | reset_base_dir() { 504 | base_dir "${_BLE_ORIG_BASE_DIR}" 505 | } 506 | 507 | result() { 508 | local name=$1; shift 509 | ble_check_cmd "$1" 510 | local old=${_BLE_SUPPRESS_ERRORS} 511 | export _BLE_SUPPRESS_ERRORS=1 512 | local ex=0 513 | "$@" || ble_handle_exitstatus $? "$@" || ex=$? 514 | export _BLE_SUPPRESS_ERRORS=$old 515 | eval " 516 | $name() { 517 | if test \$# -gt 0; then 518 | test $ex \"\$@\"; # with arguments - to test exitcode 519 | else 520 | return $ex # without - to test boolean 521 | fi 522 | } 523 | " 524 | return 0 525 | } 526 | 527 | 528 | unless_already() { 529 | local cmd=$1; shift 530 | local path=`absolute_path "$cmd"` 531 | local scmd=`ble_serialize_command "$path" "$@"` 532 | if ! grep -q "$scmd" < ${_BLE_ALREADY}; then 533 | echo "$scmd" >> "${_BLE_ALREADY}" || fail "can't write to _BLE_ALREADY file (${_BLE_ALREADY})" 534 | if ! "$path" "$@"; then 535 | ble_handle_exitstatus $? "$@" 536 | fi 537 | fi 538 | } 539 | 540 | use() { 541 | for i in "$@"; do 542 | source "${_BLE_DIR}/bashible.$i.ble" || fail "use: can't load ${_BLE_DIR}/bashible.$i.ble" 543 | done 544 | } 545 | 546 | ### SUGAR ### 547 | 548 | # when not is_empty_dir 549 | # when is not empty_dir 550 | 551 | # result installed which nginx 552 | # register exitcode as 'installed' of which nginx 553 | 554 | is() { "$@"; } 555 | of() { "$@"; } 556 | as() { "$@"; } 557 | register() { test "$1" = 'exitcode' -o "$1" = 'result' && shift; test "$1" = 'as' && shift; result "$@"; } 558 | empty_dir() { is_empty_dir "$@";} 559 | empty_var() { is_empty_var "$@";} 560 | empty_file() { is_empty_file "$@";} 561 | empty_output() { test "$1" = 'of' && shift; is_empty_output "$@";} 562 | 563 | ################ 564 | #### START ##### 565 | ################ 566 | 567 | # help? 568 | if test "$1" = -h -o "$1" = --help ; then 569 | ble_help 570 | exit 0 571 | fi 572 | 573 | # no script passed? 574 | if test ${#1} -eq 0; then 575 | ble_help 576 | exit 1 577 | fi 578 | 579 | # be strict 580 | set -eu -o pipefail 581 | 582 | # otherwise set -e is disabled in subprocesses 583 | shopt -s inherit_errexit 584 | 585 | # use a new process group 586 | set -m 587 | 588 | ble_set_top_variables "$0" 589 | ble_trap_termination 590 | ble_trap_top_exit 591 | 592 | # the toplevel script will run on the background (signals will break the "wait", therefore the 593 | # top process can react immediately) 594 | ( 595 | # keep the same process group 596 | set +m 597 | 598 | ble_set_variables "$1" 599 | ble_trap_termination 600 | ble_trap_exit 601 | ble_cd_basedir 602 | 603 | shift 604 | ble_run_script "${_BLE_SCRIPT_PATH}" "$@" 605 | 606 | test $? -ne 0 && ble_exit $? # do not use || on the line above, it would disable errexit (still it works well only in the top script) 607 | 608 | ble_print_block # print the last block unless already 609 | 610 | # run delayed commands 611 | # (they will exit immediately on failure unless prefixed by "may_fail") 612 | if test -s "${_BLE_DELAYED}"; then 613 | export _BLE_NAV='DELAYED' 614 | ble_print_start 615 | IFS=$'\n'; while read line; do 616 | readarray -d $'\t' -t ary <<< $line 617 | cd "${ary[0]}" # original CWD 618 | ary=("${ary[@]:1}") # shift 619 | - "${ary[@]}" # run the command with arguments via '-' 620 | done < "${_BLE_DELAYED}" 621 | ble_print_finish 622 | fi 623 | 624 | ) & 625 | _BLE_SUBSHELL_PID=$! 626 | wait -n 627 | _BLE_SUBSHELL_PID=0 628 | ble_exit $? 629 | -------------------------------------------------------------------------------- /bashible.edit.ble: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | _DEL=` printf '\x1' ` # regexp delimiter 4 | 5 | add_line() { 6 | local line=$1 7 | local path=$2 8 | quiet head -n 1 "$path" || { print_error "add_line: can't read file '$path'"; return 1; } 9 | if ! quiet grep -Fx "$line" "$path"; then 10 | echo "$line" >> "$path" || { print_error "add_line: can't write to file '$path'"; return 1; } 11 | else 12 | print_info "already" 13 | fi 14 | } 15 | 16 | append_line() { 17 | local line=$1 18 | local path=$2 19 | quiet tail -n 1 "$path" || { print_error "append_line: can't read file '$path'"; return 1; } 20 | if test "` tail -n 1 "$path" `" != "$line" ; then 21 | echo "$line" >> "$path" || { print_error "append_line: can't write to file '$path'"; return 1; } 22 | else 23 | print_info "already" 24 | fi 25 | } 26 | 27 | comment_lines_matching() { 28 | local match=$1 29 | local path=$2 30 | test ${#match} -eq 0 && { print_error "comment_line_matching: empty regexp is not allowed!"; return 1; } 31 | test ! -f "$path" && { print_error "comment_line_matching: file '$path' not found"; return 1; } 32 | sed -i -r 's'${_DEL}'^([^#]*'"$match"'.*)$'${_DEL}'#\1'${_DEL} "$path" 33 | } 34 | 35 | prepend_line() { 36 | local line=$1 37 | local path=$2 38 | quiet head -n 1 "$path" || { print_error "prepend_line: can't read file '$path'"; return 1; } 39 | if test "` head -n 1 "$path" `" != "$line" ; then 40 | cat - "$path" <<< "$line" > "$path.tmp" 41 | mv "$path.tmp" "$path" 42 | #sed -i "1i \\ $line" "$path" || { print_error "prepend_line: can't edit file '$path'"; return 1; } 43 | else 44 | print_info "already" 45 | fi 46 | } 47 | 48 | remove_lines_matching() { 49 | local match=$1 50 | local path=$2 51 | test ${#match} -eq 0 && { print_error "remove_line_matching: empty regexp is not allowed!"; return 1; } 52 | test ! -f "$path" && { print_error "remove_line_matching: file '$path' not found"; return 1; } 53 | sed -i "\\${_DEL}$match${_DEL}d" "$path" || { print_error "remove_line_matching: can't edit file '$path'"; return 1; } 54 | } 55 | 56 | replace_matching() { 57 | local match=$1 58 | local replace=$2 59 | local path=$3 60 | test ${#match} -eq 0 && { print_error "replace_matching: empty regexp is not allowed!"; return 1; } 61 | test ! -f "$path" && { print_error "replace_matching: file '$path' not found"; return 1; } 62 | sed -i -r "s${_DEL}$match${_DEL}$replace${_DEL}g" "$path" 63 | } 64 | 65 | replace_lines_matching() { 66 | local match=$1 67 | local replace=$2 68 | local path=$3 69 | test ${#match} -eq 0 && { print_error "replace_line_matching: empty regexp is not allowed!"; return 1; } 70 | test ! -f "$path" && { print_error "replace_line_matching: file '$path' not found"; return 1; } 71 | sed -i -r "s${_DEL}.*$match.*${_DEL}$replace${_DEL}g" "$path" 72 | } 73 | 74 | uncomment_lines_matching() { 75 | local match=$1 76 | local path=$2 77 | test ${#match} -eq 0 && { print_error "uncomment_line_matching: empty regexp is not allowed!"; return 1; } 78 | test ! -f "$path" && { print_error "uncomment_line_matching: file '$path' not found"; return 1; } 79 | sed -i -r 's'${_DEL}'^ *#(.*'"$match"'.*)$'${_DEL}'\1'${_DEL} "$path" 80 | } 81 | -------------------------------------------------------------------------------- /bashible.net.ble: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wait_for_tcp() { 4 | local what=$1 5 | local up=$2 6 | if test "$up" = up ; then 7 | while ! netstat -lant | grep "$what" | grep -q 'LISTEN'; do sleep 1; done 8 | elif test "$up" = down ; then 9 | while netstat -lant | grep "$what" | grep -q 'LISTEN'; do sleep 1; done 10 | else 11 | fail "wait for '$up'? (expected up|down)" 12 | fi 13 | return 0 14 | } 15 | 16 | -------------------------------------------------------------------------------- /bashible.template.ble: -------------------------------------------------------------------------------- 1 | template() { 2 | local path=$1 3 | local res 4 | test -f "$path" || fail "template does not exist" 5 | local orig_nav=${_BLE_NAV} 6 | ( 7 | export _BLE_NAV="${_BLE_NAV} (in $path)" 8 | exec 3>&1 9 | set -u 10 | stderr=` eval " 11 | cat <&1 1>&3 14 | ` 15 | test "${#stderr}" -ne 0 && { echo $stderr>&2; return 1; } 16 | return 0 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /bashible.timeout.ble: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | timeout() { 4 | local time=$1; shift 5 | ( "$@" ) & pid=$! 6 | ( sleep $time && kill -HUP $pid ) 2>/dev/null & killer=$! 7 | if ! wait $pid; then 8 | print_error "timeout expired" 9 | return 1 10 | else 11 | pkill -HUP -P $killer 12 | wait $killer 13 | return 0 14 | fi 15 | } 16 | -------------------------------------------------------------------------------- /docs/@.md: -------------------------------------------------------------------------------- 1 | #### @ MESSAGE OR CONDITION 2 | 3 | Represents a block of tasks. 4 | 5 | The block sets the current working directory to the base dir (the default base dir is the same directory as the script resides in). 6 | 7 | ##### Example 8 | 9 | ```bash 10 | @ Installing nginx 11 | - yum_install nginx 12 | ``` 13 | 14 | With conditions 15 | 16 | ```bash 17 | @ Installing nginx 18 | when not which nginx 19 | - yum_install nginx 20 | ``` 21 | 22 | With multiple conditions (OR) 23 | 24 | ```bash 25 | @ Installing nginx 26 | when not which nginx 27 | or not test -d /etc/nginx 28 | - yum_install nginx 29 | ``` 30 | 31 | With multiple conditions (AND) 32 | 33 | ```bash 34 | @ Probably on linux system 35 | when test -d /sys 36 | and test -d /dev 37 | and test -d /proc 38 | - foo 39 | - bar 40 | ``` 41 | 42 | You can write also 'or when' and 'and when'. 43 | 44 | If there are multiple 'when' in the beginning, it means AND. 45 | 46 | 47 | Loops: 48 | 49 | Sometimes you need to use a loop. The loop should be executed only if the condition of the block passes. 50 | In this case, you have to use "-&&" (i.e. "- &&") which will do the job. 51 | 52 | ```bash 53 | 54 | output_to_var DBS cat databases_to_install.txt 55 | 56 | @ Installing databases 57 | when is not empty_var DBS 58 | -&& for db in $DBS; do yum_install $db; done 59 | ``` 60 | 61 | 62 | ##### See also 63 | 64 | [and when](and_when.md) 65 | [or when](and_when.md) 66 | [base_dir](base_dir.md) 67 | [reset_base_dir](reset_base_dir.md) 68 | [result](result.md) 69 | [not](not.md) 70 | -------------------------------------------------------------------------------- /docs/absolute_path.md: -------------------------------------------------------------------------------- 1 | #### absolute_path PATH 2 | 3 | Get an absolute path of specified PATH. 4 | 5 | Example: 6 | 7 | ```bash 8 | 9 | - output_to_var path absolute_path ./this_file.txt 10 | - echo $path # /foo/bar/baz/this_file.txt 11 | 12 | ``` 13 | -------------------------------------------------------------------------------- /docs/add_line.md: -------------------------------------------------------------------------------- 1 | ##### add_line LINE PATH 2 | 3 | Adds a line to a file unless aleady found elsewhere in the file. 4 | 5 | ```bash 6 | 7 | use edit 8 | 9 | @ Editing sshd_config 10 | - add_line 'UseDNS no;' /etc/ssh/sshd_config 11 | ``` 12 | 13 | ##### See also 14 | 15 | [append_line](append_line.md) 16 | [prepend_line](prepend_line.md) 17 | -------------------------------------------------------------------------------- /docs/and_when.md: -------------------------------------------------------------------------------- 1 | #### and when COMMAND 2 | 3 | Represents a condition AND. Should be used right after @ block. 4 | 5 | ##### Example 6 | 7 | ```bash 8 | @ Installing nginx 9 | when not which nginx 10 | - yum_install nginx 11 | ``` 12 | 13 | ('when' is an alias for 'and when') 14 | 15 | With multiple conditions: 16 | 17 | ```bash 18 | @ Probably on linux system 19 | when test -d /sys 20 | and test -d /dev 21 | and test -d /proc 22 | - do foo 23 | - do bar 24 | ``` 25 | 26 | You can write 'and when test...' or just 'and test...' 27 | 28 | ##### See also 29 | 30 | [and when](and_when.md) 31 | [or when](and_when.md) 32 | [@](@.md) 33 | -------------------------------------------------------------------------------- /docs/append_line.md: -------------------------------------------------------------------------------- 1 | ##### append_line LINE PATH 2 | 3 | Appends a line to a file unless already found at the end of the file. 4 | 5 | ```bash 6 | 7 | use edit 8 | 9 | @ Editing sshd_config 10 | - append_line 'UseDNS no;' /etc/ssh/sshd_config 11 | ``` 12 | 13 | ##### See also 14 | 15 | [add_line](add_line.md) 16 | [prepend_line](prepend_line.md) 17 | 18 | -------------------------------------------------------------------------------- /docs/base_dir.md: -------------------------------------------------------------------------------- 1 | #### base_dir PATH 2 | 3 | Sets a base directory. The current working directory will be reset to this base dir on every block start. 4 | The default base directory is the same directory as where the script itself resides in. 5 | 6 | Example: 7 | 8 | ```bash 9 | 10 | @ Now the working directory is the same as of the script 11 | - pwd 12 | - ls 13 | 14 | base_dir '/etc' 15 | 16 | @ Now we are working in /etc 17 | - cp passwd passwd.bak 18 | - cp shadow shadow.bak 19 | - cd / 20 | - cd /foo/bar 21 | 22 | @ We are still working in /etc 23 | - cp group group.bak 24 | 25 | reset_base_dir 26 | 27 | @ Now the working directory is again the one where the script is 28 | - pwd 29 | - ls 30 | 31 | ``` 32 | 33 | ##### See also 34 | 35 | [reset_base_dir](reset_base_dir.md) 36 | [orig_dir](orig_dir.md) 37 | 38 | -------------------------------------------------------------------------------- /docs/bashible_version.md: -------------------------------------------------------------------------------- 1 | #### bashible_version 2 | 3 | Echoes version of the bashible. 4 | -------------------------------------------------------------------------------- /docs/comment_lines_matching.md: -------------------------------------------------------------------------------- 1 | ##### comment_line_matching REGEXP PATH 2 | 3 | Prefixes matching line(s) by '#' in a given file. 4 | 5 | ```bash 6 | 7 | use edit 8 | 9 | @ Editing sshd_config 10 | - comment_line_matching 'UseDNS' /etc/ssh/sshd_config 11 | ``` 12 | 13 | -------------------------------------------------------------------------------- /docs/dash1.md: -------------------------------------------------------------------------------- 1 | #### - COMMAND [ARG1] [ARG2] ... 2 | 3 | Does a command if conditions match. The command may be also a bash function. (Actually, the '-' itself is a bash function and everything that follows are it's arguments.) 4 | 5 | When the command exits nonzero, further execution of the script (and all caller scripts) is halted and an error is displayed. 6 | 7 | You can prefix it with [ignore_errors](ignore_errors.md) to avoid stopping execution, if the exitcode does not matter. 8 | 9 | ##### Example: 10 | 11 | ```bash 12 | @ Installing nginx 13 | when not test -f /usr/bin/nginx # set skipping mode if the test succeeds 14 | - yum_install nginx # skipped if already 15 | - systemctl start nginx # skipped if already 16 | 17 | @ Purging temporary files if any # now the skipping mode is off again 18 | - ignore_errors rm /tmp/temp1 19 | - ignore_errors rm /tmp/temp2 20 | ``` 21 | 22 | ##### See also 23 | 24 | [- VARIABLE = VALUE](dash2.md) 25 | [- && (conditional loop)](dash3.md) 26 | [and_when](and_when.md) 27 | [or_when](or_when.md) 28 | [@](@.md) 29 | [ignore_errors](ignore_errors.md) -------------------------------------------------------------------------------- /docs/dash2.md: -------------------------------------------------------------------------------- 1 | #### - VARIABLE = VALUE 2 | 3 | If you want to set a variable if conditions of a block match, do it like this: 4 | 5 | ##### Example: 6 | 7 | ```bash 8 | @ Checking for php presence 9 | when which php 10 | or which php7.2 11 | or which php6.4 12 | - present = 1 13 | 14 | echo $present # => 1 15 | 16 | ``` 17 | 18 | ##### See also 19 | 20 | [- COMMAND](dash1.md) 21 | [- && (conditional loop)](dash3.md) 22 | [and_when](and_when.md) 23 | [or_when](or_when.md) 24 | [@](@.md) 25 | [ignore_errors](ignore_errors.md) 26 | -------------------------------------------------------------------------------- /docs/dash3.md: -------------------------------------------------------------------------------- 1 | #### - && (conditional loop) 2 | 3 | Sometimes you need to use a loop. But the loop should be executed only if conditions of a block matche. 4 | 5 | To do so, use `- &&` magic: 6 | 7 | ```bash 8 | 9 | DBS='mysql pgsql' 10 | 11 | @ Installing databases 12 | when is not empty_var DBS 13 | - && for db in $DBS; do yum_install $db; done 14 | ``` 15 | 16 | Of course, the for loop will work also without the `- &&`, but then it will execute always, even if the block conditions wouldn't match. 17 | 18 | ##### See also 19 | 20 | [- COMMAND](dash1.md) 21 | [- VARIABLE = VALUE](dash2.md) 22 | [and_when](and_when.md) 23 | [or_when](or_when.md) 24 | [@](@.md) 25 | [ignore_errors](ignore_errors.md) -------------------------------------------------------------------------------- /docs/delayed.md: -------------------------------------------------------------------------------- 1 | ##### delayed COMMNAD ARGS ... 2 | 3 | Runs a command at the very end of the process. For instance, use it to reload a webserver after the work is done. 4 | Combine it with "unless_already" to prevent multiple reloads. 5 | 6 | You could also combine it with "may_fail" to avoid interruption of the process in the very end if a delayed command fails. 7 | 8 | In the following example, two virtual hosts will be generated, but nginx will reload only once afterwards. 9 | You can delay ./reload-nginx.bash also in subscripts (called from this parent; it would have more sense to put 10 | the webserver reload in the create-virtual-host.ble script). 11 | 12 | ```bash 13 | @ Creating virtual host example1.com 14 | - run ./create-virtual-host.bash example1.com 15 | - delayed unless_already ./reload-nginx.ble 16 | 17 | @ Creating virtual host example2.com 18 | - run ./create-virtual-host.bash example2.com 19 | - delayed unless_already ./reload-nginx.ble 20 | 21 | @ Cleanup at the end 22 | - delayed unless_already ignore_errors ./cleanup.ble 23 | 24 | ``` 25 | 26 | ##### See also 27 | 28 | [run](run.md) 29 | [ignore_errors](ignore_errors.md) 30 | [unless_already](unless_already.md) 31 | -------------------------------------------------------------------------------- /docs/evaluate.md: -------------------------------------------------------------------------------- 1 | ##### evaluate STRING 2 | 3 | Sometimes you need more complex things, for instance a pipeline or conditions. 4 | 5 | In the following example, users starting with the letter "g" will be loaded into a variable USERS (and it will also check whether it contains something): 6 | 7 | ```bash 8 | @ Loading some variables 9 | - output_to_var USERS is not empty_output evaluate 'cat /etc/passwd | egrep -e ^g' 10 | - echo $USERS # now you can be sure there is something 11 | ``` 12 | 13 | ##### See also 14 | 15 | [output_to_var](output_to_var.md) 16 | [is_empty_output](is_empty_output.md) -------------------------------------------------------------------------------- /docs/fail.md: -------------------------------------------------------------------------------- 1 | ##### fail [MESSAGE] 2 | 3 | Interrupts execution of the script with a message displayed on stderr. It interrupts the full process. 4 | 5 | It will exit with exitcode 42. 6 | 7 | ```bash 8 | 9 | @ Basic checks 10 | when not test -x /usr/sbin/nginx 11 | - fail "nginx must be installed first" 12 | 13 | ``` 14 | 15 | ##### See also 16 | 17 | [finish](finish.md) 18 | [halt](halt.md) 19 | [when](when.md) 20 | -------------------------------------------------------------------------------- /docs/finish.md: -------------------------------------------------------------------------------- 1 | ##### finish [MESSAGE] 2 | 3 | Stops the execution of the current bashible script. Parent (caller) scripts will continue if this script is a sub-script. 4 | 5 | ```bash 6 | @ Check for nginx 7 | when not test -f /usr/local/bin/nginx 8 | - finish "already installed" 9 | 10 | ``` 11 | 12 | ##### See also 13 | 14 | [fail](fail.md) 15 | [halt](halt.md) 16 | [when](when.md) 17 | -------------------------------------------------------------------------------- /docs/halt.md: -------------------------------------------------------------------------------- 1 | ##### halt [MESSAGE] 2 | 3 | Stops the process immediately. 4 | 5 | All possible parent (caller) scripts will stop immediately as well. 6 | 7 | It will exit with exitcode 43. 8 | 9 | ```bash 10 | @ Basic checks 11 | when not test -d /tmp 12 | - halt "there is no /tmp dir" 13 | ``` 14 | 15 | ##### See also 16 | 17 | [fail](fail.md) 18 | [finish](finish.md) 19 | [when](when.md) 20 | -------------------------------------------------------------------------------- /docs/ignore_errors.md: -------------------------------------------------------------------------------- 1 | ##### ignore_errors COMMAND [ARG1] [ARG2] ... 2 | 3 | Normally, if a command returns a nonzero exitcode, the execution of the script is halted. But sometimes the failing task doesn't matter. 4 | 5 | ```bash 6 | @ Cleaning up 7 | - ignore_errors rmdir /home 8 | - ignore_errors rmdir /var 9 | ``` 10 | -------------------------------------------------------------------------------- /docs/in_timeout.md: -------------------------------------------------------------------------------- 1 | ##### in_timeout SECS COMMAND [ARG1] [ARG2] ... 2 | 3 | Runs a command and fails if it lasts more than SECS seconds. 4 | 5 | ```bash 6 | 7 | use net timeout 8 | 9 | @ Start nginx service 10 | - systemd start nginx 11 | - in_timeout 20 wait_for_tcp 127.0.0.1:80 up 12 | 13 | @ when not in_timeout 20 wait_for_tcp 127.0.0.1:80 up 14 | @ Start nginx service and send me a mail unless successful 15 | - systemd start nginx 16 | - mail me@me.com <<< "nginx is not up" 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/is_empty_dir.md: -------------------------------------------------------------------------------- 1 | #### is_empty_dir PATH 2 | 3 | Succeeds if the specified directory is empty. Fails otherwise. 4 | 5 | Fails also if the directory does not exist. 6 | 7 | Example: 8 | 9 | ```bash 10 | 11 | @ Checking if foo is mounted 12 | - is not empty_dir /mnt/foo 13 | 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/is_empty_output.md: -------------------------------------------------------------------------------- 1 | ##### is_empty_output COMMAND [ARG1] [ARG2] ... 2 | 3 | Runs the command and returns true if it's output is empty. Combine it with "not" to negate it. 4 | 5 | ```bash 6 | @ Setting some variables and checking whether they contain something 7 | - output_to_var DOMAIN is not empty_output cat /etc/myapp/domain.txt 8 | - output_to_var FOO is not empty_output echo $BAR 9 | ``` 10 | 11 | ##### See also 12 | 13 | [not](not.md) 14 | [output_to_var](output_to_var.md) 15 | [is_empty_var](is_empty_var.md) 16 | 17 | -------------------------------------------------------------------------------- /docs/is_empty_var.md: -------------------------------------------------------------------------------- 1 | ##### is_empty_var VAR 2 | 3 | Return true if the variable is empty or unset. 4 | 5 | ```bash 6 | @ Getting list of databases to install 7 | - output_to_var DATABASES cat /etc/databases.txt 8 | 9 | @ when is not empty_var DATABASES 10 | @ Installing databases 11 | - && for db in $DATABASES; do 12 | - run ./install-db.bash "$db" 13 | done 14 | ``` 15 | 16 | ##### See also 17 | 18 | [output_to_var](output_to_var.md) 19 | -------------------------------------------------------------------------------- /docs/is_toplevel.md: -------------------------------------------------------------------------------- 1 | ##### is_toplevel 2 | 3 | Returns true if the current script is the top (not a child sub-script). 4 | 5 | In the following example, the script will halt if it is called directly; 6 | it has to be called from within another bashible script. 7 | 8 | ```bash 9 | @ Basic checks 10 | - not is_toplevel 11 | ``` 12 | 13 | -------------------------------------------------------------------------------- /docs/not.md: -------------------------------------------------------------------------------- 1 | ##### not COMMAND [ARG1] [ARG2] ... 2 | 3 | Runs a command and negates it's exit status. 4 | 5 | ```bash 6 | @ Basic checks 7 | - is not empty_output of echo $1 8 | - is not empty_output of cat /etc/passwd 9 | 10 | @ Installing cron.d files unless already 11 | when is not empty_dir /etc/cron.d/ 12 | - cp /shared/cron.d/* /etc/cron.d 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/or_when.md: -------------------------------------------------------------------------------- 1 | #### and when COMMAND 2 | 3 | Represents a condition OR. Should be used after @ block. 4 | 5 | ##### Example 6 | 7 | ```bash 8 | @ Probably on linux system 9 | when test -d /sys 10 | or test -d /dev 11 | or test -d /proc 12 | - do foo 13 | - do bar 14 | ``` 15 | 16 | You can write 'or when test...' or just 'or test...' 17 | 18 | ##### See also 19 | 20 | [and when](and_when.md) 21 | [or when](and_when.md) 22 | [@](@.md) 23 | -------------------------------------------------------------------------------- /docs/orig_dir.md: -------------------------------------------------------------------------------- 1 | #### orig_dir 2 | 3 | Temporarily switches into the directory from which the script has been executed. (Bashible automatically switches to the script's directory on start.) 4 | 5 | To switch into it, do 6 | 7 | ```bash 8 | #!/usr/local/bin/bashible 9 | 10 | echo I am in the script's directory now 11 | 12 | @ This is a first block 13 | - orig_dir 14 | - echo I am in the original directory now 15 | 16 | @ Another block 17 | - echo back in the actual base_dir (the script's directory again) 18 | 19 | ``` 20 | 21 | ##### See also 22 | 23 | [base_dir](base_dir.md) 24 | [reset_base_dir](reset_base_dir.md) 25 | -------------------------------------------------------------------------------- /docs/output_to_file.md: -------------------------------------------------------------------------------- 1 | ##### output_to_file VARIABLE OPTIONS COMMAND ARGS ... 2 | 3 | Write output of a command into a file. 4 | 5 | Options: 6 | 7 | -1|--stdout 8 | -2|--stderr 9 | -a|--append 10 | -t|--tee 11 | 12 | Unless --tee is used, it does a redirect: single '>' or double '>>' when --append is specified. 13 | 14 | When --tee is used, it duplicates the output to the file and on the terminal. 15 | 16 | ```bash 17 | 18 | @ Writing some files 19 | - output_to_file only_stdout.txt find -type d /tmp 20 | - output_to_file both.txt -1 -2 ignore_errors find -type d /home 21 | - output_to_file error_message.txt -2 ignore_errors ls /asdfasdfasdf 22 | - output_to_file growing_file.txt --append ls /home 23 | ``` 24 | 25 | If the command fails and no --append mode used, contents of the original file will be lost 26 | (overwritten by empty data). To prevent this, use a temporary file and then rename it: 27 | 28 | ```bash 29 | 30 | @ Writing some files 31 | - output_to_file list.tmp ls /home 32 | - mv list.tmp list.txt 33 | ``` 34 | 35 | ##### See also 36 | 37 | [output_to_var](output_to_var) 38 | -------------------------------------------------------------------------------- /docs/output_to_var.md: -------------------------------------------------------------------------------- 1 | ##### output_to_var VARIABLE OPTIONS COMMAND ARGS ... 2 | 3 | Store output of a command in a variable. 4 | 5 | Options: 6 | 7 | -1|--stdout 8 | -2|--stderr 9 | 10 | By default, only stdout is stored. 11 | 12 | ```bash 13 | 14 | @ Setting some variables 15 | # store only stdout 16 | - output_to_var tempdirs find -type d /tmp 17 | # store both stdout and stderr 18 | - output_to_var homedirs -1 -2 ignore_errors find -type d /home 19 | # store only stderr 20 | - output_to_var error_message -2 ignore_errors ls /asdfasdfasdf 21 | ``` 22 | 23 | You can use this to store and check presence of an argument 24 | 25 | ```bash 26 | 27 | @ Setting some variables 28 | - output_to_var arg1 is not empty_output echo $1 29 | ``` 30 | 31 | ##### See also 32 | 33 | [output_to_file](output_to_file) 34 | [is_empty_output](is_empty_output.md) -------------------------------------------------------------------------------- /docs/prepend_line.md: -------------------------------------------------------------------------------- 1 | ##### prepend_line LINE FILE 2 | 3 | Prepends a file with a line (unless already). 4 | 5 | ```bash 6 | 7 | use edit 8 | 9 | @ Editing sshd_config 10 | - prepend_line 'UseDNS no;' /etc/ssh/sshd_config 11 | ``` 12 | 13 | ##### See also 14 | 15 | [add_line](add_line.md) 16 | [append_line](append_line.md) 17 | -------------------------------------------------------------------------------- /docs/print_error.md: -------------------------------------------------------------------------------- 1 | ##### print_error MSG 2 | 3 | Prints an error message. 4 | 5 | ```bash 6 | print_error "This shouldn't happen." 7 | ``` 8 | 9 | ##### See also 10 | 11 | [print_info](print_info.md) 12 | [print_warn](print_warn.md) -------------------------------------------------------------------------------- /docs/print_info.md: -------------------------------------------------------------------------------- 1 | ##### print_info MSG 2 | 3 | Prints an info message. 4 | 5 | ```bash 6 | print_info "value is: $foobar" 7 | ``` 8 | 9 | ##### See also 10 | 11 | [print_error](print_error.md) 12 | [print_warn](print_warn.md) 13 | -------------------------------------------------------------------------------- /docs/print_warn.md: -------------------------------------------------------------------------------- 1 | ##### print_warn MSG 2 | 3 | Prints an warning message. 4 | 5 | ```bash 6 | print_warn "This is a warning." 7 | ``` 8 | 9 | ##### See also 10 | 11 | [print_error](print_error.md) 12 | [print_info](print_info.md) -------------------------------------------------------------------------------- /docs/quiet.md: -------------------------------------------------------------------------------- 1 | ##### quiet COMMAND [ARG1] [ARG2] ... 2 | 3 | Runs a command with it's output redirected to /dev/null. 4 | 5 | ```bash 6 | @ Installing nginx unless already 7 | when quiet grep centos /etc/hosts 8 | - yum_install nginx 9 | - quiet ignore_errors rm /foo/bar 10 | ``` 11 | -------------------------------------------------------------------------------- /docs/remove_lines_matching.md: -------------------------------------------------------------------------------- 1 | ##### remove_line_matching REGEX PATH 2 | 3 | Removes matching line(s) from a file. 4 | 5 | ```bash 6 | 7 | use edit 8 | 9 | @ Editing sshd_config 10 | - remove_line_matching 'UseDNS' /etc/ssh/sshd_config 11 | ``` 12 | 13 | ##### See also 14 | 15 | [replace_line_matching](replace_line_matching.md) 16 | [replace_matching](replace_matching.md) 17 | -------------------------------------------------------------------------------- /docs/replace_lines_matching.md: -------------------------------------------------------------------------------- 1 | ##### replace_line_matching REGEXP STRING PATH 2 | 3 | Replaces matching line(s) with a specified string. 4 | 5 | It replaces whole lines! 6 | 7 | ```bash 8 | 9 | use edit 10 | 11 | @ Editing sshd_config 12 | - replace_line_matching 'domain' 'domain=example.com' /etc/default/domain.cfg 13 | ``` 14 | 15 | ##### See also 16 | 17 | [replace_matching](replace_matching.md) 18 | [remove_line_matching](remove_line_matching.md) 19 | -------------------------------------------------------------------------------- /docs/replace_matching.md: -------------------------------------------------------------------------------- 1 | ##### replace_matching REGEXP STRING PATH 2 | 3 | Replaces matching regexps with a string. 4 | 5 | ```bash 6 | 7 | use edit 8 | 9 | @ Editing sshd_config 10 | - replace_matching 'enabled=0' 'enabled=1' /etc/default/foo.cfg 11 | ``` 12 | 13 | ##### See also 14 | 15 | [replace_line_matching](replace_line_matching.md) 16 | [remove_line_matching](remove_line_matching.md) 17 | -------------------------------------------------------------------------------- /docs/reset_base_dir.md: -------------------------------------------------------------------------------- 1 | #### reset_base_dir PATH 2 | 3 | Sets a base directory to the original base dir (i.e. the same directory as where the bashible script resides). 4 | 5 | Example: 6 | 7 | ```bash 8 | 9 | base_dir '/etc' 10 | 11 | @ Working in /etc 12 | - cp passwd passwd.bak 13 | - cp shadow shadow.bak 14 | 15 | @ Still working in /etc 16 | - cp group group.bak 17 | 18 | reset_base_dir 19 | 20 | @ Now working in the original directory 21 | - ls 22 | 23 | ``` 24 | 25 | ##### See also 26 | 27 | [base_dir](base_dir.md) 28 | [orig_dir](orig_dir.md) 29 | -------------------------------------------------------------------------------- /docs/result.md: -------------------------------------------------------------------------------- 1 | ##### result NAME COMMAND ARGS... 2 | 3 | Stores result of the executed command. The command may fail and it won't stop the execution but store it's error code. 4 | 5 | Internally it creates a new function which returns the stored exit status. This function is to be used with when [@](@.md). 6 | 7 | WARNING: use the 'result' as the first function in the chain. Some functions like [output_to_file](output_to_file.md) start another process to grab output of their commands. 8 | The resulting function would not be propagated to the parent. 9 | 10 | ```bash 11 | @ Try to synchronize files 12 | - result synced rsync /foo /bar 13 | 14 | @ Success, mailing it 15 | when synced 16 | - mail me@me.com <<< "files synced ok" 17 | 18 | @ Error happened, mailing it 19 | when not synced 20 | - mail me@me.com <<< "files not synced!!!" 21 | ``` 22 | 23 | It's possible to combine multiple checks into one result. Functions "true" and "false" return the appropriate exit code. 24 | 25 | ```bash 26 | 27 | result webserver_is_installed false 28 | 29 | @ Web stack is installed 30 | when which nginx # is nginx there? 31 | and which php # is php there? 32 | - result webserver_is_installed true 33 | ``` 34 | 35 | The newly created function accepts also arguments. You can check for a specific exit code: 36 | 37 | ```bash 38 | @ This will exit with code 45 39 | - result stored_res bash -c "exit 45" 40 | 41 | @ The exitcode was zero 42 | when stored_res == 0 43 | 44 | @ The exitcode was 1 45 | when stored_res == 1 46 | 47 | @ The exitcode was 2 48 | when stored_res == 2 49 | 50 | @ The exitcode was greater than 5 and lower or equal 50 51 | when stored_res -gt 5 52 | and stored_res -le 50 53 | 54 | @ The exitcode is either 4, 7 or 10 55 | when stored_res -eq 4 56 | or stored_res -eq 7 57 | or stored_res -eq 10 58 | 59 | ``` 60 | 61 | ##### See also 62 | 63 | [@](@.md) 64 | -------------------------------------------------------------------------------- /docs/run.md: -------------------------------------------------------------------------------- 1 | ##### run PATH [ARG1] [ARG2] ... 2 | 3 | Runs another bashible script using "source". Avoids multiple parsing of bashible itself. 4 | The sourced script runs in a new subprocess, therefore it won't affect the caller. 5 | 6 | Use [unless_already](unless_already.md) to prevent multiple runs. 7 | 8 | Use [delayed](delayed.md) to postpone run. Delayed tasks will run when the top-level script finishes. 9 | 10 | ```bash 11 | @ Prerequisities 12 | - run ./system_base.bash 13 | - run ./install-redis.bash 14 | - run ./install-nginx.bash 15 | 16 | @ Creating a virtual host 17 | - run ./create-virtual-host.bash example.com 18 | - delayed unless_already systemd nginx reload 19 | ``` 20 | 21 | ##### See also 22 | 23 | [unless_already](unless_already.md) 24 | [delayed](delayed.md) 25 | -------------------------------------------------------------------------------- /docs/template.md: -------------------------------------------------------------------------------- 1 | ##### template TEMPLATE_PATH 2 | 3 | Implements a very simple Bash templating engine. You can use it for substituting variables. 4 | 5 | Example: 6 | 7 | Let's have a mysql.conf.tpl which is actually a bash script: 8 | 9 | ``` 10 | ... 11 | ## Buffer settings 12 | key-buffer-size = $( echo $KEY_BUFFER_SIZE ) 13 | read-buffer-size = 2M 14 | sort-buffer-size = $( echo $SORT_BUFFER_SIZE ) 15 | max-join-size = 512M 16 | max-heap-table-size = 128M 17 | ... 18 | ``` 19 | 20 | Now execute the template in a bashible script: 21 | 22 | ```bash 23 | 24 | use template 25 | 26 | # set some variables for the template 27 | KEY_BUFFER_SIZE=1G 28 | SORT_BUFFER_SIZE=128M 29 | 30 | @ Creating mysql.conf 31 | - output_to_file /etc/mysql/mysql.conf.tmp template mysql.conf.tpl 32 | - mv /etc/mysql/mysql.conf.tmp /etc/mysql/mysql.conf 33 | ``` 34 | 35 | All variables must be set, otherwise the `template` function exits 1. 36 | 37 | --- 38 | 39 | Moreover you can use commands in templates the same way as in bashible scripts 40 | (prefix commands with '-' to stop execution on error) 41 | 42 | ``` 43 | ... 44 | ## Buffer settings 45 | key-buffer-size = $( - is not empty_output cat /settings/key-buffer-size.txt ) 46 | read-buffer-size = 2M 47 | sort-buffer-size = $( - is not empty_output cat /settings/sort-buffer-size.txt ) 48 | max-join-size = 512M 49 | max-heap-table-size = 128M 50 | ... 51 | ``` 52 | 53 | --- 54 | 55 | It is also possible to use loops inside brackets: 56 | 57 | ``` 58 | ... 59 | ## Buffer settings 60 | $( for db in $BINLOG_DO_DB; do echo "binlog-do-db = $db"; done ) 61 | key-buffer-size = $( echo $KEY_BUFFER_SIZE ) 62 | read-buffer-size = 2M 63 | sort-buffer-size = $( echo $SORT_BUFFER_SIZE ) 64 | max-join-size = 512M 65 | max-heap-table-size = 128M 66 | ... 67 | ``` 68 | 69 | --- 70 | 71 | Or you can call another templates from a template, 72 | 73 | ```html 74 | 75 | 76 | $( template head.tpl ) 77 | 78 | 79 | $( template body.tpl ) 80 | 81 | 82 | ``` 83 | -------------------------------------------------------------------------------- /docs/uncomment_lines_matching.md: -------------------------------------------------------------------------------- 1 | ##### uncomment_line_matching REGEXP PATH 2 | 3 | Removes '#' from the beginning of matching line(s). 4 | 5 | ```bash 6 | 7 | use edit 8 | 9 | @ Editing sshd_config 10 | - uncomment_line_matching 'UseDNS' /etc/ssh/sshd_config 11 | ``` 12 | 13 | ##### See also 14 | 15 | [comment_line_matching](comment_line_matching.md).. 16 | -------------------------------------------------------------------------------- /docs/unless_already.md: -------------------------------------------------------------------------------- 1 | #### unless_already COMMAND ARGS ... 2 | 3 | Execute the command unless already executed during the run of a bashible script(s). Internally it writes the command including it's arguments and working directory to a file, 4 | which has been created by the topmost parent. Then checks the file and does not run the command if it matches. 5 | 6 | Example: 7 | 8 | ```bash 9 | 10 | - unless_already systemctl reload nginx 11 | 12 | ``` 13 | -------------------------------------------------------------------------------- /docs/use.md: -------------------------------------------------------------------------------- 1 | ##### use FEATURES ... 2 | 3 | Import (source) modules. They must reside within the same directory as bashible. 4 | 5 | For instance here we import the template engine: 6 | 7 | It will always fail when the specified module can not be loaded. 8 | 9 | ```bash 10 | 11 | use template 12 | 13 | @ Generating my.cnf 14 | - output_to_file /etc/my.cnf.tmp template my.cnf.tpl 15 | - mv /etc/my.cnf.tmp /etc/my.cnf 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/wait_for_tcp.md: -------------------------------------------------------------------------------- 1 | ##### wait_for_tcp MATCH up|down 2 | 3 | Waits for a listening tcp service to be up or down. 4 | 5 | Internally uses "netstat -ltn". 6 | 7 | warning: may not work on all platforms! 8 | 9 | ```bash 10 | 11 | use net timeout 12 | 13 | @ Stop nginx service 14 | - service nginx stop 15 | - timeout 20 wait_for_tcp 127.0.0.1:80 down 16 | ``` 17 | -------------------------------------------------------------------------------- /docs/when.md: -------------------------------------------------------------------------------- 1 | #### when COMMAND 2 | 3 | Is an alias for [and when](and_when.md). 4 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mig1984/bashible/330e71e49b31afd72fcb8cd30b01447964e92527/example.png -------------------------------------------------------------------------------- /examples/editing_files/README: -------------------------------------------------------------------------------- 1 | expects bashible to be installed in /usr/local/bin (including it's modules) 2 | 3 | then run 4 | 5 | $ bashible ./add.ble 6 | 7 | or 8 | 9 | $ bashible ./del.ble 10 | 11 | they will first generate sshd_config and then edit it 12 | 13 | -------------------------------------------------------------------------------- /examples/editing_files/add.ble: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/bashible 2 | 3 | use edit 4 | 5 | - run generate_sshd_config.ble 23 127.0.0.1 6 | 7 | @ Uncommenting lines in the config 8 | - uncomment_lines_matching 'UseDNS' sshd_config 9 | - uncomment_lines_matching 'X11Forwarding no' sshd_config 10 | 11 | @ Adding lines to the end 12 | - add_line "This will be at the end" sshd_config 13 | 14 | @ Replacing whole lines 15 | - replace_lines_matching 'AllowTcpForwarding' 'AllowTcpForwarding yes' sshd_config 16 | -------------------------------------------------------------------------------- /examples/editing_files/del.ble: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/bashible 2 | 3 | use edit 4 | 5 | - run generate_sshd_config.ble 23 127.0.0.1 6 | 7 | @ Edit config 8 | - comment_lines_matching 'UseDNS' sshd_config 9 | - comment_lines_matching 'X11Forwarding no' sshd_config 10 | 11 | @ Removing lines 12 | - remove_lines_matching "This will be" sshd_config 13 | 14 | @ Replacing whole lines 15 | - replace_lines_matching 'AllowTcpForwarding' 'AllowTcpForwarding no' sshd_config -------------------------------------------------------------------------------- /examples/editing_files/generate_sshd_config.ble: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/bashible 2 | 3 | use template 4 | 5 | @ Setting up and checking variables needed by a template 6 | - output_to_var PORT is not empty_output of echo $1 7 | - output_to_var LISTEN is not empty_output of echo $2 8 | 9 | @ Generating sshd_config from a template unless already 10 | when not test -f sshd_config 11 | - output_to_file sshd_config.tmp template sshd_config.tpl 12 | - mv sshd_config.tmp sshd_config # on success 13 | -------------------------------------------------------------------------------- /examples/editing_files/sshd_config.tpl: -------------------------------------------------------------------------------- 1 | # This is the sshd server system-wide configuration file. See 2 | # sshd_config(5) for more information. 3 | 4 | # This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin 5 | 6 | # The strategy used for options in the default sshd_config shipped with 7 | # OpenSSH is to specify options with their default value where 8 | # possible, but leave them commented. Uncommented options override the 9 | # default value. 10 | 11 | Port $( echo $PORT ) 12 | #AddressFamily any 13 | ListenAddress $( echo $LISTEN ) 14 | #ListenAddress :: 15 | 16 | # The default requires explicit activation of protocol 1 17 | #Protocol 2 18 | 19 | # HostKey for protocol version 1 20 | #HostKey /etc/ssh/ssh_host_key 21 | # HostKeys for protocol version 2 22 | #HostKey /etc/ssh/ssh_host_rsa_key 23 | #HostKey /etc/ssh/ssh_host_dsa_key 24 | #HostKey /etc/ssh/ssh_host_ecdsa_key 25 | #HostKey /etc/ssh/ssh_host_ed25519_key 26 | 27 | # Lifetime and size of ephemeral version 1 server key 28 | #KeyRegenerationInterval 1h 29 | #ServerKeyBits 1024 30 | 31 | # Ciphers and keying 32 | #RekeyLimit default none 33 | 34 | # Logging 35 | # obsoletes QuietMode and FascistLogging 36 | #SyslogFacility AUTH 37 | #LogLevel INFO 38 | 39 | # Authentication: 40 | 41 | #LoginGraceTime 2m 42 | PermitRootLogin without-password 43 | #StrictModes yes 44 | #MaxAuthTries 6 45 | #MaxSessions 10 46 | 47 | #RSAAuthentication yes 48 | #PubkeyAuthentication yes 49 | 50 | # The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2 51 | # but this is overridden so installations will only check .ssh/authorized_keys 52 | AuthorizedKeysFile .ssh/authorized_keys 53 | 54 | #AuthorizedPrincipalsFile none 55 | 56 | #AuthorizedKeysCommand none 57 | #AuthorizedKeysCommandUser nobody 58 | 59 | # For this to work you will also need host keys in /etc/ssh/ssh_known_hosts 60 | #RhostsRSAAuthentication no 61 | # similar for protocol version 2 62 | #HostbasedAuthentication no 63 | # Change to yes if you don't trust ~/.ssh/known_hosts for 64 | # RhostsRSAAuthentication and HostbasedAuthentication 65 | #IgnoreUserKnownHosts no 66 | # Don't read the user's ~/.rhosts and ~/.shosts files 67 | #IgnoreRhosts yes 68 | 69 | # To disable tunneled clear text passwords, change to no here! 70 | #PasswordAuthentication yes 71 | #PermitEmptyPasswords no 72 | 73 | # Change to no to disable s/key passwords 74 | ChallengeResponseAuthentication no 75 | 76 | # Kerberos options 77 | #KerberosAuthentication no 78 | #KerberosOrLocalPasswd yes 79 | #KerberosTicketCleanup yes 80 | #KerberosGetAFSToken no 81 | 82 | # GSSAPI options 83 | #GSSAPIAuthentication no 84 | #GSSAPICleanupCredentials yes 85 | 86 | # Set this to 'yes' to enable PAM authentication, account processing, 87 | # and session processing. If this is enabled, PAM authentication will 88 | # be allowed through the ChallengeResponseAuthentication and 89 | # PasswordAuthentication. Depending on your PAM configuration, 90 | # PAM authentication via ChallengeResponseAuthentication may bypass 91 | # the setting of "PermitRootLogin without-password". 92 | # If you just want the PAM account and session checks to run without 93 | # PAM authentication, then enable this but set PasswordAuthentication 94 | # and ChallengeResponseAuthentication to 'no'. 95 | UsePAM yes 96 | 97 | #AllowAgentForwarding yes 98 | #AllowTcpForwarding yes 99 | #GatewayPorts no 100 | X11Forwarding no 101 | #X11DisplayOffset 10 102 | #X11UseLocalhost yes 103 | #PermitTTY yes 104 | PrintMotd no # pam does that 105 | #PrintLastLog yes 106 | #TCPKeepAlive yes 107 | #UseLogin no 108 | #UsePrivilegeSeparation sandbox 109 | #PermitUserEnvironment no 110 | #Compression delayed 111 | #ClientAliveInterval 0 112 | #ClientAliveCountMax 3 113 | UseDNS no 114 | #PidFile /run/sshd.pid 115 | #MaxStartups 10:30:100 116 | #PermitTunnel no 117 | #ChrootDirectory none 118 | #VersionAddendum none 119 | 120 | # no default banner path 121 | #Banner none 122 | 123 | # override default of no subsystems 124 | Subsystem sftp /usr/lib/ssh/sftp-server 125 | 126 | # Example of overriding settings on a per-user basis 127 | #Match User anoncvs 128 | X11Forwarding no 129 | AllowTcpForwarding yes 130 | # PermitTTY no 131 | # ForceCommand cvs server 132 | -------------------------------------------------------------------------------- /examples/generating_html_from_a_template/README: -------------------------------------------------------------------------------- 1 | expects bashible to be installed in /usr/local/bin (including it's modules) 2 | 3 | then run 4 | 5 | $ bashible generate.ble 6 | 7 | it will create index.html from a template. 8 | -------------------------------------------------------------------------------- /examples/generating_html_from_a_template/generate.ble: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/bashible 2 | 3 | use template 4 | 5 | # this variable is used to include either foo.js or bar.js in the HTML head (see parts/head.tpl template) 6 | FOO_OR_BAR=bar #foo 7 | 8 | @ Building the HTML from a template 9 | - output_to_file index.html.tmp template index.tpl 10 | - mv index.html.tmp index.html # on success 11 | -------------------------------------------------------------------------------- /examples/generating_html_from_a_template/index.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | $( template parts/head.tpl ) 4 | 5 | 6 | $( template parts/body.tpl ) 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/generating_html_from_a_template/js/bar.js: -------------------------------------------------------------------------------- 1 | function bar() { 2 | } 3 | -------------------------------------------------------------------------------- /examples/generating_html_from_a_template/js/foo.js: -------------------------------------------------------------------------------- 1 | function foo() { 2 | } 3 | -------------------------------------------------------------------------------- /examples/generating_html_from_a_template/parts/body.tpl: -------------------------------------------------------------------------------- 1 |

Now is $( date )

2 | -------------------------------------------------------------------------------- /examples/generating_html_from_a_template/parts/head.tpl: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /examples/killing_process/README: -------------------------------------------------------------------------------- 1 | expects bashible to be installed in /usr/local/bin (including it's modules) 2 | 3 | This example demonstrates killing parent process which will kill it's children. In pure bash, children processes stay alive unless you run the script in interactive mode. 4 | 5 | usage: 6 | 7 | bashible ./main.bash 8 | 9 | CTRL-C or send SIGTERM to the main process 10 | 11 | -------------------------------------------------------------------------------- /examples/killing_process/child.ble: -------------------------------------------------------------------------------- 1 | @ I am a child process with PID $BASHPID, Starting a loop 2 | - run loop.ble 3 | -------------------------------------------------------------------------------- /examples/killing_process/loop.ble: -------------------------------------------------------------------------------- 1 | @ I am a second child process doing the loop. Try to kill the topmost parent, it will terminate me as well. 2 | 3 | while true; do 4 | echo "x" 5 | sleep 1 6 | done 7 | -------------------------------------------------------------------------------- /examples/killing_process/main.ble: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/bashible 2 | 3 | @ I am main process with pid $$, going to run child.ble 4 | - run child.ble 5 | -------------------------------------------------------------------------------- /examples/result/README: -------------------------------------------------------------------------------- 1 | expects bashible to be installed in /usr/local/bin (including it's modules) 2 | 3 | usage: 4 | 5 | bashible ./result.ble 6 | 7 | -------------------------------------------------------------------------------- /examples/result/result.ble: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/bashible 2 | 3 | @ This will exit with exitcode 10 4 | - register exitcode as 'stored_res' of bash -c "exit 10" 5 | 6 | @ The exitcode was zero 7 | when stored_res == 0 8 | # - cmd1 9 | # - cmd2 10 | # ... 11 | 12 | @ The exitcode was 1 13 | when stored_res == 1 14 | # - cmd1 15 | # - cmd2 16 | # ... 17 | 18 | @ The exitcode was 2 19 | when stored_res == 2 20 | # - cmd1 21 | # - cmd2 22 | # ... 23 | 24 | @ The exitcode was greater than 5 and lower or equal 50 25 | when stored_res -gt 5 26 | and stored_res -le 50 27 | # - cmd1 28 | # - cmd2 29 | # ... 30 | 31 | @ The exitcode was either 4, 6 or 10. 32 | when stored_res -eq 4 33 | or stored_res -eq 6 34 | or stored_res -eq 10 35 | # - cmd1 36 | # - cmd2 37 | # ... 38 | --------------------------------------------------------------------------------