├── .gitignore ├── CHANGELOG.markdown ├── LICENSE.markdown ├── README.markdown ├── dev ├── examples ├── postgresql-ruby.sh ├── python-apache.sh ├── requirements.txt └── sublime.sh ├── src ├── ib-action.sh ├── ib-apt-cyg.sh ├── ib-apt.sh ├── ib-assert.sh ├── ib-git.sh ├── ib-jinja2.sh ├── ib-os.sh ├── ib-pip.sh ├── ib-postgresql.sh └── ib-service.sh └── test ├── ib-action.sh ├── ib-apt-cyg.sh ├── ib-apt.sh ├── ib-git.sh ├── ib-jinja2.sh ├── ib-os.sh ├── ib-pip.sh ├── ib-postgresql.sh ├── ib-service.sh ├── run.sh └── test.sql /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime-* 2 | /dist/idempotent-bash.sh 3 | /tmp.sh 4 | -------------------------------------------------------------------------------- /CHANGELOG.markdown: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog] and this project adheres to [Semantic Versioning]. 6 | 7 | [keep a changelog]: http://keepachangelog.com/en/1.0.0/ 8 | [semantic versioning]: http://semver.org/spec/v2.0.0.html 9 | 10 | --- 11 | 12 | [unreleased]: https://github.com/metaist/idempotent-bash/compare/1.1.4...HEAD 13 | 14 | --- 15 | 16 | [@davidfetter]: https://github.com/davidfetter 17 | [@luolong]: https://github.com/luolong 18 | [#39]: https://github.com/metaist/idempotent-bash/issues/39 19 | [#40]: https://github.com/metaist/idempotent-bash/issues/40 20 | [#44]: https://github.com/metaist/idempotent-bash/pull/44 21 | [#45]: https://github.com/metaist/idempotent-bash/pull/45 22 | [1.1.4]: https://github.com/metaist/idempotent-bash/compare/1.1.3...1.1..4 23 | 24 | ## [1.1.4] - 2022-01-07 25 | 26 | **Changed** 27 | 28 | - [#40]: replaced `build.sh` with `dev` 29 | - [#44]: improved `psql` connectivity test (thanks [@davidfetter]!) 30 | 31 | **Fixed** 32 | 33 | - [#39]: `ib-apt-update` only called when `ib-apt-install` actually needs it 34 | - [#45]: `idempotent-bash.sh --help` preserves newlines (thanks [@luolong]!) 35 | 36 | --- 37 | 38 | [#32]: https://github.com/metaist/idempotent-bash/issues/32 39 | [#33]: https://github.com/metaist/idempotent-bash/issues/33 40 | [#34]: https://github.com/metaist/idempotent-bash/issues/34 41 | [#36]: https://github.com/metaist/idempotent-bash/issues/36 42 | [#37]: https://github.com/metaist/idempotent-bash/issues/37 43 | [#38]: https://github.com/metaist/idempotent-bash/issues/38 44 | [1.1.3]: https://github.com/metaist/idempotent-bash/compare/1.1.2...1.1.3 45 | 46 | ## [1.1.3] - 2018-11-07 47 | 48 | **Added** 49 | 50 | - [#32]: `ib-quiet` to suppress output 51 | - [#34]: `ib-in?` to check if item is in array 52 | 53 | **Changed** 54 | 55 | - [#33]: `ib-os-append` uses the first line of a multiline text to pass to `grep` 56 | - [#37]: changelog to follow [Keep a Changelog] format 57 | 58 | **Fixed** 59 | 60 | - [#36]: only run `ib-apt-update` if there's a need to install anything 61 | 62 | --- 63 | 64 | [#19]: https://github.com/metaist/idempotent-bash/issues/19 65 | [#20]: https://github.com/metaist/idempotent-bash/issues/20 66 | [#21]: https://github.com/metaist/idempotent-bash/issues/21 67 | [#22]: https://github.com/metaist/idempotent-bash/issues/22 68 | [#23]: https://github.com/metaist/idempotent-bash/issues/23 69 | [#24]: https://github.com/metaist/idempotent-bash/issues/24 70 | [#25]: https://github.com/metaist/idempotent-bash/issues/25 71 | [#26]: https://github.com/metaist/idempotent-bash/issues/26 72 | [#27]: https://github.com/metaist/idempotent-bash/issues/27 73 | [#28]: https://github.com/metaist/idempotent-bash/issues/28 74 | [#29]: https://github.com/metaist/idempotent-bash/issues/29 75 | [#30]: https://github.com/metaist/idempotent-bash/issues/30 76 | [#31]: https://github.com/metaist/idempotent-bash/issues/31 77 | [1.1.2]: https://github.com/metaist/duil.js/compare/1.1.1...1.1.2 78 | 79 | ## [1.1.2] - 2017-07-27 80 | 81 | **Added** 82 | 83 | - [#24]: [git] functions to manage repositories 84 | - [#25]: `ib-postgresql-sql` to conditionally execute a single command 85 | - [#28]: common examples 86 | - [#29]: `--user` flag to handle different user 87 | 88 | **Changed** 89 | 90 | - [#26]: requirement that scripts run as `sudo` (see also [#29]) 91 | 92 | **Fixed** 93 | 94 | - [#19]: tests on Cygwin 95 | - [#20]: extra whitespace in `ib-pip-install` output 96 | - [#21]: documentation for `ib-postgresql-file` 97 | - [#22]: `ib-os-link` to prevent recursive removal of incorrect links 98 | - [#23]: package iteration in `ib-apt-install` and `ib-apt-cyg-install` 99 | - [#27]: dry run indicator when using global `IB_DRY_RUN` 100 | - [#30]: `ib-postgresql-ok?` to return boolean 101 | - [#31]: character escaping in `ib-os-append` 102 | 103 | [git]: https://git-scm.com/ 104 | 105 | --- 106 | 107 | [#12]: https://github.com/metaist/idempotent-bash/issues/12 108 | [#13]: https://github.com/metaist/idempotent-bash/issues/13 109 | [#14]: https://github.com/metaist/idempotent-bash/issues/14 110 | [#15]: https://github.com/metaist/idempotent-bash/issues/15 111 | [#16]: https://github.com/metaist/idempotent-bash/issues/16 112 | [#17]: https://github.com/metaist/idempotent-bash/issues/17 113 | [#18]: https://github.com/metaist/idempotent-bash/issues/18 114 | [1.1.1]: https://github.com/metaist/duil.js/compare/1.1.0...1.1.1 115 | 116 | ## [1.1.1] - 2016-04-17 117 | 118 | **Added** 119 | 120 | - [#12]: documentation to public-facing functions 121 | - [#13]: `ib-os-copy-link` to toggle copying or linking directories 122 | - [#14]: `ib-parse-args` to parse label and quiet args 123 | - [#15]: dry run options (global and per-action) 124 | - [#18]: multiple line blocks with `ib-os-append` 125 | 126 | **Fixed** 127 | 128 | - [#16]: unbound variable in `ib-pip-install` 129 | - [#17]: using grep with PCRE 130 | 131 | --- 132 | 133 | [#5]: https://github.com/metaist/idempotent-bash/issues/5 134 | [#7]: https://github.com/metaist/idempotent-bash/issues/7 135 | [#9]: https://github.com/metaist/idempotent-bash/issues/9 136 | [#10]: https://github.com/metaist/idempotent-bash/issues/10 137 | [#11]: https://github.com/metaist/idempotent-bash/issues/11 138 | [1.1.0]: https://github.com/metaist/duil.js/compare/1.0.0...1.1.0 139 | 140 | ## [1.1.0] - 2016-03-31 141 | 142 | **Added** 143 | 144 | - [#5]: basic functions for installing and starting/stopping services 145 | - [#7]: [apt-cyg] functions to install [Cygwin] packages 146 | - [#9]: quick start to README 147 | - [#11]: `ib-command?` to check if a command exists 148 | 149 | **Fixed** 150 | 151 | - [#10]: stat of `apt-get` package cache if missing 152 | 153 | [apt-cyg]: https://github.com/transcode-open/apt-cyg 154 | [cygwin]: https://cygwin.com 155 | 156 | --- 157 | 158 | [#1]: https://github.com/metaist/idempotent-bash/issues/1 159 | [#2]: https://github.com/metaist/idempotent-bash/issues/2 160 | [#3]: https://github.com/metaist/idempotent-bash/issues/3 161 | [#4]: https://github.com/metaist/idempotent-bash/issues/4 162 | [#6]: https://github.com/metaist/idempotent-bash/issues/6 163 | [#8]: https://github.com/metaist/idempotent-bash/issues/8 164 | [1.0.0]: https://github.com/metaist/idempotent-bash/tree/1.0.0 165 | 166 | ## [1.0.0] - 2016-03-28 167 | 168 | - Initial release. 169 | 170 | **Added** 171 | 172 | - [#1]: base action, testing functions 173 | - [#2]: basic OS functions to create directories, check/change permissions 174 | - [#3]: basic [Apt] functions to install Linux packages 175 | - [#4]: basic [pip] functions to install python packages 176 | - [#6]: [jinja2-cli] functions to generate configuration files 177 | - [#8]: [PostgreSQL] functions to execute SQL files 178 | 179 | [apt]: https://wiki.debian.org/Apt 180 | [pip]: https://pip.pypa.io/en/stable/ 181 | [postgresql]: http://www.postgresql.org/ 182 | [jinja2-cli]: https://github.com/mattrobenolt/jinja2-cli 183 | -------------------------------------------------------------------------------- /LICENSE.markdown: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Metaist LLC. 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. 22 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # idempotent-bash 2 | _Idempotent [Bash] setup scripts made easy._ 3 | 4 | ## Examples 5 | See the [Example directory](./examples). 6 | 7 | ## What does idempotent mean? 8 | It means that applying the same function multiple times is the same as applying it once. In other words, don't run code if the effect of the code is already present. 9 | 10 | For setup scripts, this means that when you re-run your setup script, it only runs the commands that whose conditions are not yet satisfied thereby saving time on potentially long-running actions. 11 | 12 | ## What about other projects? 13 | This project was inspired by [Ansible] and [Bash Booster]. I like the idea of idempotent setup scripts, but I needed more reporting of status to log files so I can debug particularly tricky environments. 14 | 15 | [Puppet] and [Chef] are other popular alternatives for writing setup scripts, but I haven't used them extensively. 16 | 17 | ## License 18 | Licensed under the MIT License. 19 | 20 | [Ansible]: https://www.ansible.com/ 21 | [Bash Booster]: http://www.bashbooster.net/ 22 | [Bash]: https://www.gnu.org/software/bash/ 23 | [Chef]: https://www.chef.io/chef/ 24 | [Puppet]: https://puppetlabs.com/ 25 | -------------------------------------------------------------------------------- /dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Bash script to run developer commands. 4 | 5 | # Use "strict mode" in bash to help debug your scripts. 6 | set -uo pipefail 7 | IFS=$'\n\t' 8 | 9 | # Find the location of this script. 10 | SCRIPT_PATH=$(readlink -f ${BASH_SOURCE[0]}) 11 | SCRIPT_DIR=$(dirname $SCRIPT_PATH) 12 | 13 | #### END OF STANDARD DEV HEADER #### 14 | 15 | FILE_DIST="$SCRIPT_DIR/dist/idempotent-bash.sh" 16 | 17 | dev_all() { # clean, test, and build 18 | dev_clean 19 | dev_test 20 | dev_build 21 | } 22 | 23 | dev_clean() { # remove generated files 24 | echo '==> clean' 25 | rm -rf "$FILE_DIST" 26 | } 27 | 28 | dev_test() { # run tests 29 | echo '==> test' 30 | bash "test/run.sh" 31 | } 32 | 33 | dev_build() { 34 | echo '==> build' 35 | rm -f "$FILE_DIST" 36 | cat src/*.sh >> "$FILE_DIST" 37 | chmod +x "$FILE_DIST" 38 | } 39 | 40 | 41 | #### START OF STANDARD DEV FOOTER #### 42 | 43 | dev_help() { # list possible actions and exit 44 | local regex='^dev[_-]([^(]*)\(\) *\{( *# *)?(.*)' 45 | printf "usage: ./$(basename "$SCRIPT_PATH") ACTION [ACTION...] \n" 46 | printf "\nPossible actions:\n" 47 | for fn in $(grep -oP $regex $SCRIPT_PATH); do 48 | if [[ $fn =~ $regex ]]; then 49 | printf " ${BASH_REMATCH[1]} - ${BASH_REMATCH[3]}\n"; 50 | fi 51 | done 52 | } 53 | 54 | main() { 55 | local arg=${1:-''} 56 | if [[ "$arg" == "" ]]; then dev_all; exit; fi 57 | if [[ "$arg" =~ ^(-h|--help)$ ]]; then dev_help; exit; fi 58 | while [[ "$#" > 0 ]]; do dev_$1; shift 1; done 59 | } 60 | 61 | main $@ 62 | -------------------------------------------------------------------------------- /examples/postgresql-ruby.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Use "strict mode" in bash to help debug your scripts. 4 | set -uo pipefail 5 | IFS=$'\n\t' 6 | 7 | 8 | # Find the location of this script and use it to find idempotent-bash. 9 | SETUP_SOURCE=$(readlink -f ${BASH_SOURCE[0]}) 10 | SETUP_DIR=$(dirname $SETUP_SOURCE) 11 | 12 | # Include idempotent-bash and set the log file so you can follow along using 13 | # tail -f $IB_LOG 14 | source "$SETUP_DIR/../dist/idempotent-bash.sh" 15 | 16 | # Set the log path and delete any existing logs. 17 | IB_LOG="/tmp/$(basename ${BASH_SOURCE[0]}).log" 18 | rm -f $IB_LOG &> /dev/null 19 | printf "Follow the log by running:\n$ tail -f $IB_LOG\n" 20 | 21 | # See: https://www.postgresql.org/download/linux/ubuntu/ 22 | setup_postgresql() { 23 | local version="9.6" 24 | local need_update=false 25 | local need_restart=false 26 | local LINUX_NAME=$(lsb_release -cs) 27 | printf "\n=== PostgreSQL ($version) ===\n" 28 | 29 | local keyid="ACCC4CF8" 30 | local url="https://www.postgresql.org/media/keys/ACCC4CF8.asc" 31 | ib-apt-add-key "$keyid" "$url" 32 | if ib-changed?; then need_update=true; fi 33 | # apt key added 34 | 35 | local dest="/etc/apt/sources.list.d/pgdg.list" 36 | local line="deb http://apt.postgresql.org/pub/repos/apt/ $LINUX_NAME-pgdg main" 37 | ib-os-append -u root -l "[apt] add repo" "$dest" "$line" 38 | if ib-changed?; then need_update=true; fi 39 | # apt source added 40 | 41 | if $need_update; then ib-apt-update; fi 42 | ib-apt-install "postgresql-$version" \ 43 | "postgresql-server-dev-$version" \ 44 | "postgresql-contrib-$version" 45 | if ib-changed?; then need_restart=true; fi 46 | # software installed 47 | 48 | local state="start" 49 | if $need_restart; then state="restart"; fi 50 | ib-service-state postgresql $state 51 | # service is running 52 | 53 | local check="SELECT 1 FROM pg_roles WHERE rolname='$USER';" 54 | local sql="CREATE USER \"$USER\" WITH SUPERUSER;" 55 | ib-postgresql-sql "$check" "$sql" 56 | # user is created 57 | } 58 | 59 | # See: https://www.digitalocean.com/community/tutorials/how-to-install-ruby-on-rails-with-rbenv-on-ubuntu-16-04 60 | setup_ruby() { 61 | local version="2.4.1" 62 | local skip 63 | printf "\n=== Ruby ($version) ===\n" 64 | 65 | ib-apt-install autoconf bison build-essential libssl-dev libyaml-dev \ 66 | libreadline6-dev zlib1g-dev libncurses5-dev libffi-dev libgdbm3 libgdbm-dev 67 | # software installed 68 | 69 | ib-git-clone "https://github.com/rbenv/rbenv.git" "~/.rbenv" 70 | ib-git-clone "https://github.com/rbenv/ruby-build.git" \ 71 | "~/.rbenv/plugins/ruby-build" 72 | ib-os-append "~/.bashrc" 'export PATH="$HOME/.rbenv/bin:$PATH"' 73 | export PATH="$HOME/.rbenv/bin:$PATH" 74 | # rbenv installed 75 | 76 | local installed=$(rbenv global) 77 | skip=$(ib-ok? [[ \"$installed\" == \"$version\" ]]) 78 | ib-action -s "$skip" -- rbenv install -f "$version" 79 | ib-action -s "$skip" -- rbenv global "$version" 80 | ib-os-append "~/.bashrc" 'eval "$(rbenv init -)"' 81 | eval "$(rbenv init -)" 82 | # ruby installed 83 | 84 | skip=$(ib-ok? gem which bundler) 85 | ib-os-append "~/.gemrc" "gem: --no-document" 86 | ib-action -s "$skip" -- gem install bundler 87 | # bundler installed 88 | } 89 | 90 | # run all functions named "setup_* in this file 91 | for fn in $(grep -oP '^(setup[_-])[^(]*' $SETUP_SOURCE); do $fn; done 92 | -------------------------------------------------------------------------------- /examples/python-apache.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Use "strict mode" in bash to help debug your scripts. 4 | set -uo pipefail 5 | IFS=$'\n\t' 6 | 7 | 8 | # Find the location of this script and use it to find idempotent-bash. 9 | SETUP_SOURCE=$(readlink -f ${BASH_SOURCE[0]}) 10 | SETUP_DIR=$(dirname $SETUP_SOURCE) 11 | 12 | # Include idempotent-bash and set the log file so you can follow along using 13 | # tail -f $IB_LOG 14 | source "$SETUP_DIR/../dist/idempotent-bash.sh" 15 | 16 | # Set the log path and delete any existing logs. 17 | IB_LOG="/tmp/$(basename ${BASH_SOURCE[0]}).log" 18 | rm -f $IB_LOG &> /dev/null 19 | printf "Follow the log by running:\n$ tail -f $IB_LOG\n" 20 | 21 | # This example demonstrates the basics organizing principles. 22 | 23 | setup_python() { 24 | printf "\n=== Python ===\n" 25 | ib-apt-install python-dev python-setuptools 26 | } 27 | 28 | setup_pip() { 29 | printf "\n=== Pip ===\n" 30 | local label="[python] install pip" 31 | local skip=$(command -v pip) 32 | local url="https://bootstrap.pypa.io/get-pip.py" 33 | ib-action -l "$label" -s "$skip" -- wget --quiet -O - $url \| sudo python 34 | 35 | ib-pip-install jinja2-cli 36 | ib-pip-install -r "requirements.txt" 37 | } 38 | 39 | setup_apache() { 40 | printf "\n=== Apache ===\n" 41 | local need_restart=false 42 | 43 | ib-apt-install apache2 44 | if ib-changed?; then need_restart=true; fi 45 | 46 | local state="start" 47 | if $need_restart; then state="restart"; fi 48 | ib-service-state apache2 $state 49 | } 50 | 51 | # run all functions named "setup_* in this file 52 | for fn in $(grep -oP '^(setup[_-])[^(]*' $SETUP_SOURCE); do $fn; done 53 | -------------------------------------------------------------------------------- /examples/requirements.txt: -------------------------------------------------------------------------------- 1 | pyyaml 2 | -------------------------------------------------------------------------------- /examples/sublime.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Use "strict mode" in bash to help debug your scripts. 4 | set -uo pipefail 5 | IFS=$'\n\t' 6 | 7 | 8 | # Find the location of this script and use it to find idempotent-bash. 9 | SETUP_SOURCE=$(readlink -f ${BASH_SOURCE[0]}) 10 | SETUP_DIR=$(dirname $SETUP_SOURCE) 11 | 12 | # Include idempotent-bash and set the log file so you can follow along using 13 | # tail -f $IB_LOG 14 | source "$SETUP_DIR/../dist/idempotent-bash.sh" 15 | 16 | # Set the log path and delete any existing logs. 17 | IB_LOG="/tmp/$(basename ${BASH_SOURCE[0]}).log" 18 | rm -f $IB_LOG &> /dev/null 19 | printf "Follow the log by running:\n$ tail -f $IB_LOG\n" 20 | 21 | # Organize modules using functions so you can include them in other files. 22 | 23 | 24 | # This example demonstrates the basics of the ib-changed? function which 25 | # returns true if the previous ib-* command had an effect. 26 | setup_sublime() { 27 | printf "\n=== SublimeText ===\n" 28 | local need_update=false 29 | 30 | local keyid="8A8F901A" 31 | local url="https://download.sublimetext.com/sublimehq-pub.gpg" 32 | ib-apt-add-key "$keyid" "$url" 33 | if ib-changed?; then need_update=true; fi 34 | # apt key added 35 | 36 | local dest="/etc/apt/sources.list.d/sublime-text.list" 37 | local line="deb https://download.sublimetext.com/ apt/stable/" 38 | ib-os-append -u root -l "[apt] add sublime repository" "$dest" "$line" 39 | if ib-changed?; then need_update=true; fi 40 | # apt source added 41 | 42 | if $need_update; then ib-apt-update; fi 43 | ib-apt-install sublime-text 44 | # software installed 45 | } 46 | 47 | # run all functions named "setup_* in this file 48 | for fn in $(grep -oP '^(setup[_-])[^(]*' $SETUP_SOURCE); do $fn; done 49 | -------------------------------------------------------------------------------- /src/ib-action.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2022 Metaist LLC 4 | # MIT License 5 | 6 | # bash strict mode 7 | set -uo pipefail 8 | IFS=$'\n\t' 9 | 10 | # script information 11 | IB_SCRIPT_NAME=${0:-""} 12 | IB_SCRIPT_VERSION="1.1.4" 13 | 14 | # script usage information 15 | IB_USAGE="\ 16 | Usage: $IB_SCRIPT_NAME [args] 17 | 18 | Keyword Arguments: 19 | -h, --help show usage and exit 20 | --version show version and exit 21 | -l, --label string to display (default: command to run) 22 | -s, --skip whether to skip this task (default: false) 23 | -q, --quiet whether to hide the label (default: false) 24 | -u, --user which user to run the command as (default: current) 25 | -e, -- command to execute 26 | 27 | Note that -e (or --) must preceed the bash command to execute. 28 | 29 | Example: 30 | $IB_SCRIPT_NAME --label '[bash] make a directory' -- mkdir -p foo 31 | " 32 | 33 | # terminal colors 34 | if [[ $(command -v tput) ]]; then 35 | IB_COLOR_RED=$(tput setaf 1) 36 | IB_COLOR_GREEN=$(tput setaf 2) 37 | IB_COLOR_NORMAL=$(tput sgr0) 38 | else 39 | IB_COLOR_RED="\e[31m" 40 | IB_COLOR_GREEN="\e[32m" 41 | IB_COLOR_NORMAL="\e[0m" 42 | fi 43 | 44 | # arguments to an IB command 45 | IB_ARGS=() 46 | 47 | # path to log file 48 | IB_LOG="/dev/null" 49 | 50 | # path to screen out 51 | IB_STDOUT="/dev/stdout" 52 | 53 | # whether to skip executing actions 54 | IB_DRY_RUN=false 55 | 56 | # last action executed 57 | IB_LAST_ACTION="" 58 | 59 | # Did the action cause a change? 60 | IB_CHANGED=false 61 | ib-changed?() { $IB_CHANGED; } 62 | 63 | # Evaluate a command and echo "true" or "false" depending on the return code. 64 | # Args: 65 | # *: command to execute 66 | ib-ok?() { 67 | eval "$@" > /dev/null && echo "true" || echo "false" 68 | } 69 | 70 | # Suppress output. Always returns 0. 71 | ib-quiet() { 72 | $@ 2&> /dev/null 73 | return 0 74 | } 75 | 76 | # Is this non-empty or truthy? 77 | # Args: 78 | # 1: item (str, boolean) 79 | ib-truthy?() { 80 | local item=${1:-''} 81 | [[ "$item" != "" && "$item" != false ]] 82 | } 83 | 84 | # Is this empty or falsy? 85 | # Args: 86 | # 1: item (str, boolean) 87 | ib-falsy?() { 88 | local item=${1:-''} 89 | [[ "$item" == "" || "$item" == false ]] 90 | } 91 | 92 | # Is this item in the array? 93 | # Args: 94 | # 1: needle 95 | # *: haystack 96 | ib-in?() { 97 | local item 98 | for item in "${@:2}"; do [[ "$item" == "$1" ]] && return 0; done 99 | return 1 100 | } 101 | 102 | # Is this command valid? 103 | # Args: 104 | # 1: item (str) 105 | # 2: warning (str) 106 | ib-command?() { 107 | local item=${1:-''} 108 | local warning=${2:-"WARNING: $item not installed"} 109 | if ib-falsy? $(command -v $item); then 110 | if ib-truthy? $warning; then 111 | echo "$warning" |& tee -a $IB_LOG 112 | fi 113 | return 1 114 | fi 115 | return 0 116 | } 117 | 118 | # Join an array with a separator. 119 | # Args: 120 | # 1: separator 121 | # *: array to join 122 | ib-join() { 123 | local IFS="$1" 124 | shift 125 | echo "$*" 126 | } 127 | 128 | # Parse an array of arguments. 129 | # Args: 130 | # -l, --label (str) 131 | # -q, --quiet 132 | # -u, --user (str) 133 | # *: remaining args 134 | ib-parse-args() { 135 | IB_ARGS=('' '' '') 136 | while [[ "$#" > 0 ]]; do 137 | case ${1:-""} in 138 | -l|--label) IB_ARGS[0]=${2:-''}; shift 2;; 139 | -q|--quiet) IB_ARGS[1]="-q"; shift 1;; 140 | -u|--user) IB_ARGS[2]=${2:-''}; shift 2;; 141 | *) IB_ARGS+=( "${1:-''}" ); shift 1;; 142 | esac 143 | done 144 | } 145 | 146 | # Print the start of an action. 147 | # Args: 148 | # 1: label (str) 149 | ib-action-start() { 150 | local label=${1:-"[action] run"} 151 | printf "$IB_COLOR_NORMAL[ .. ] ==> $label\r" >> $IB_STDOUT 152 | } 153 | 154 | # Print the status of an action. 155 | # Args: 156 | # 1: label (str) 157 | # 2: tried (bool) 158 | # 3: value (int) 159 | ib-action-stop() { 160 | local label=${1:-"[action] run"} 161 | local tried=${2:-""} 162 | local value=${3:-""} 163 | 164 | IB_CHANGED=false 165 | if [[ "$tried" == false ]]; then # already done 166 | printf "$IB_COLOR_GREEN[ OK ]$IB_COLOR_NORMAL ==> $label\n" >> $IB_STDOUT 167 | elif [[ "$tried" == true && "$value" == 0 ]]; then # changed 168 | IB_CHANGED=true 169 | printf "$IB_COLOR_GREEN[DONE] ==> $label$IB_COLOR_NORMAL\n" >> $IB_STDOUT 170 | else # failed 171 | printf "$IB_COLOR_RED[FAIL] ==> $label$IB_COLOR_NORMAL\n" >> $IB_STDOUT 172 | if [[ "$IB_LOG" != "/dev/null" ]]; then # there is a log file 173 | printf "\n== LOG OUTPUT ==\n" >> $IB_STDOUT 174 | cat $IB_LOG >> $IB_STDOUT 175 | fi 176 | exit $value 177 | fi 178 | return $value 179 | } 180 | 181 | # Perform an idempotent action. 182 | # Keyword Arguments: 183 | # -h, --help show usage and exit 184 | # --version show version and exit 185 | # -l, --label string to display (default: command to run) 186 | # -s, --skip whether to skip this task (default: false) 187 | # -q, --quiet whether to hide the label (default: false) 188 | # -u, --user which user to run the command as (default: current) 189 | # -e, -- command to execute 190 | # 191 | # Note that `-e` (or `--`) must preceed the bash command to execute. 192 | # 193 | # Example: 194 | # ib-action --label "[bash] make a directory" -- mkdir -p foo 195 | ib-action() { 196 | local label="" 197 | local skip=false 198 | local quiet=false 199 | local dryrun=false 200 | local user="" 201 | 202 | local tried=false 203 | local value=0 204 | 205 | while [[ "$#" > 0 ]]; do 206 | case ${1:-""} in 207 | -h|--help) echo "$IB_USAGE"; exit; break;; 208 | --version) 209 | echo "$(basename ${IB_SCRIPT_NAME%.*}) v$IB_SCRIPT_VERSION" 210 | exit 0 211 | break;; 212 | -l|--label) label=${2:-""}; shift 2;; 213 | -n|--dry-run) dryrun=true; shift 1;; 214 | -q|--quiet) quiet=true; shift 1;; 215 | -s|--skip) skip=${2:-""}; shift 2;; 216 | -u|--user) user=${2:-""}; shift 2;; 217 | -e|--) shift 1; break;; 218 | *) printf "Unknown paramter: $1\n$IB_USAGE"; exit 1; break;; 219 | esac 220 | done 221 | 222 | if [[ "$label" == "" ]]; then label="[bash] $(ib-join ' ' $@)"; fi 223 | if ib-truthy? $IB_DRY_RUN || ib-truthy? "$dryrun"; then 224 | label="[DRY RUN] $label" 225 | fi 226 | 227 | if ib-falsy? "$quiet"; then ib-action-start "$label"; fi 228 | if ib-falsy? "$skip"; then 229 | tried=true 230 | if ib-falsy? $IB_DRY_RUN && ib-falsy? "$dryrun"; then 231 | IB_LAST_ACTION="$@" 232 | if [[ "$user" != "" ]]; then 233 | IB_LAST_ACTION="sudo -u $user $IB_LAST_ACTION" 234 | fi 235 | echo -e "\n\$ $IB_LAST_ACTION" >> $IB_LOG 236 | eval "$IB_LAST_ACTION" &>> $IB_LOG 237 | else 238 | echo -e "\n[DRY RUN]\n\$ $IB_LAST_ACTION\n" >> $IB_LOG 239 | fi 240 | value=$? 241 | fi 242 | if ib-falsy? "$quiet"; then ib-action-stop "$label" "$tried" "$value"; fi 243 | 244 | return $value 245 | } 246 | 247 | # DEPRECATED: Will be removed in next major release. 248 | # Run an idempotent bash function. 249 | # Args: 250 | # 1: action 251 | # *: paramters to ib-${action} function 252 | # 253 | # Keyword Args: 254 | # -l, --label 255 | # -q, --quiet 256 | ib() { 257 | local action=${1:-''} 258 | shift 1; 259 | 260 | local label="" 261 | local quiet="" 262 | 263 | while [[ "$#" > 0 ]]; do 264 | case ${1:-""} in 265 | -l|--label) label=${2:-""}; shift 2;; 266 | -q|--quiet) quiet="-q"; shift 1;; 267 | --) shift 1; break;; 268 | *) break;; 269 | esac 270 | done 271 | 272 | echo "WARNING: ib() is deprecated; use ib-$action() instead." 273 | ib-$action "$label" "$quiet" $@ 274 | } 275 | 276 | if [[ "${BASH_SOURCE[0]}" == "${IB_SCRIPT_NAME}" ]]; then 277 | ib-action $@ 278 | exit 279 | fi 280 | -------------------------------------------------------------------------------- /src/ib-apt-cyg.sh: -------------------------------------------------------------------------------- 1 | 2 | ### apt-cyg - manage Cygwin packages 3 | 4 | # Install packages using apt-cyg. 5 | # 6 | # Usage: 7 | # ib-apt-cyg-install [-l LABEL] [-q] [-u USER] PACKAGE [PACKAGE...] 8 | # 9 | # Args: 10 | # -l LABEL, --label LABEL 11 | # label to display for progress on this task 12 | # -q, --quiet 13 | # suppress progress display 14 | # -u USER, --user USER 15 | # user to run as 16 | # PACKAGE name of package to install 17 | ib-apt-cyg-install() { 18 | if ! ib-command? apt-cyg; then return 1; fi 19 | 20 | ib-parse-args "$@" 21 | local label=${IB_ARGS[0]:-'[apt-cyg] apt-cyg install'} 22 | local quiet=${IB_ARGS[1]:-''} 23 | local user=${IB_ARGS[2]:-''} 24 | local packages="${IB_ARGS[@]:3}" 25 | local item 26 | local skip 27 | 28 | readarray -t packages <<< "$packages" 29 | for item in "${packages[@]}"; do 30 | skip=$(ib-ok? apt-cyg list \| grep -qsPe \"^$item\$\") 31 | ib-action -l "$label $item" -s "$skip" -u "$user" $quiet -- \ 32 | apt-cyg install "$item" 33 | done 34 | } 35 | -------------------------------------------------------------------------------- /src/ib-apt.sh: -------------------------------------------------------------------------------- 1 | 2 | ### apt - os package management 3 | 4 | # path to the apt cache file 5 | IB_APT_CACHE_PATH="/var/cache/apt/pkgcache.bin" 6 | 7 | # maximum age of apt cache in seconds 8 | IB_APT_CACHE_MAX=3600 9 | 10 | # prevent apt-key from yelling at us (see #38) 11 | export APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=1 12 | 13 | # Add a signing key to apt. 14 | # Usage: 15 | # ib-apt-add-key [-l LABEL] [-q] [-u USER] KEYID URL 16 | # 17 | # Args: 18 | # -l LABEL, --label LABEL 19 | # label to display for progress on this task 20 | # -q, --quiet 21 | # suppress progress display 22 | # -u USER, --user USER 23 | # user to run as 24 | # KEYID signing key id as it appears in `apt-key adv --list-public-keys` 25 | # URL location of the signing key to download 26 | ib-apt-add-key() { 27 | if ! ib-command? apt-key; then return 1; fi 28 | if ! ib-command? wget; then return 1; fi 29 | 30 | ib-parse-args "$@" 31 | local quiet=${IB_ARGS[1]:-''} 32 | local user=${IB_ARGS[2]:-'root'} 33 | local keyid=${IB_ARGS[3]:-''} 34 | local url=${IB_ARGS[4]:-''} 35 | local label=${IB_ARGS[0]:-"[apt] sudo apt-key add $keyid from $url"} 36 | local skip=$(ib-ok? apt-key adv --list-public-keys \| grep -qsPe \"$keyid\") 37 | 38 | ib-action -l "$label" -s "$skip" -u "$user" $quiet -- \ 39 | wget --quiet -O - $url \| sudo -u "$user" apt-key add - 40 | } 41 | 42 | # Update apt repository. 43 | # Usage: 44 | # ib-apt-update [-l LABEL] [-q] [-u USER] [MAXAGE] 45 | # 46 | # Args: 47 | # -l LABEL, --label LABEL 48 | # label to display for progress on this task 49 | # -q, --quiet 50 | # suppress progress display 51 | # -u USER, --user USER 52 | # user to run as 53 | # MAXAGE only run if apt cache is older than this number of seconds 54 | ib-apt-update() { 55 | if ! ib-command? apt-get; then return 1; fi 56 | 57 | ib-parse-args "$@" 58 | local label=${IB_ARGS[0]:-'[apt] sudo apt-get update'} 59 | local quiet=${IB_ARGS[1]:-''} 60 | local user=${IB_ARGS[2]:-'root'} 61 | local age_max=${IB_ARGS[3]:-''} 62 | local age_now=$(date '+%s') 63 | local skip=false 64 | local status 65 | local tried=false 66 | local value=0 67 | 68 | if [[ -e "$IB_APT_CACHE_PATH" ]]; then 69 | age_now=$(( age_now - $(stat -c '%Z' "$IB_APT_CACHE_PATH") )) 70 | fi 71 | 72 | if ib-falsy? "$quiet"; then ib-action-start "$label"; fi 73 | if [[ "$age_max" > 0 ]]; then 74 | skip=$(ib-ok? [[ "$age_now" -le "$age_max" ]]) 75 | fi 76 | 77 | if ib-falsy? $skip; then 78 | tried=true 79 | echo -e "\n\$ sudo apt-get update" >> $IB_LOG 80 | status=$(sudo -u $user apt-get update 2>&1) 81 | echo "$status" >> $IB_LOG 82 | echo "$status" | grep -qsPe '^[WE]:' 83 | value="$(( ! $? ))" 84 | fi 85 | if ib-falsy? "$quiet"; then ib-action-stop "$label" "$tried" "$value"; fi 86 | 87 | return $value 88 | } 89 | 90 | # Install packages using apt-get. 91 | # Usage: 92 | # ib-apt-install [-l LABEL] [-q] [-u USER] PACKAGE [PACKAGE...] 93 | # 94 | # Args: 95 | # -l LABEL, --label LABEL 96 | # label to display for progress on this task 97 | # -q, --quiet 98 | # suppress progress display 99 | # -u USER, --user USER 100 | # user to run as 101 | # PACKAGE name of package to install 102 | ib-apt-install() { 103 | if ! ib-command? apt-get; then return 1; fi 104 | if ! ib-command? dpkg; then return 1; fi 105 | 106 | ib-parse-args "$@" 107 | local label=${IB_ARGS[0]:-'[apt] sudo apt-get install -y'} 108 | local quiet=${IB_ARGS[1]:-''} 109 | local user=${IB_ARGS[2]:-'root'} 110 | local packages="${IB_ARGS[@]:3}" 111 | local item 112 | local skip 113 | local updated=false 114 | local regex_installed='"^Status.+installed"' 115 | 116 | readarray -t packages <<< "$packages" 117 | for item in "${packages[@]}"; do 118 | skip=$(ib-ok? dpkg -s $item 2>> /dev/null \| grep -sPe $regex_installed) 119 | if ! [[ "$updated" || "$skip" ]]; then 120 | ib-apt-update -u "$user" $quiet "$IB_APT_CACHE_MAX" 121 | updated=true 122 | fi 123 | 124 | ib-action -l "$label $item" -s "$skip" -u "$user" $quiet -- \ 125 | apt-get install -y $item 126 | done 127 | } 128 | -------------------------------------------------------------------------------- /src/ib-assert.sh: -------------------------------------------------------------------------------- 1 | 2 | ### assert - unit test functions 3 | 4 | # Location of the test directory. Must end in a slash. 5 | IB_RUN_DIR=$(dirname "$(readlink -f .)")/ 6 | 7 | IB_ASSERT_COUNT=0 8 | IB_ASSERT_PASS=0 9 | IB_ASSERT_FAIL=0 10 | 11 | IB_ASSERT_STATUS="" 12 | IB_ASSERT_FAILURES="" 13 | 14 | # Are the two args equal? 15 | # Args: 16 | # 1: x (any) first item to compare 17 | # 2: y (any) second item to compare 18 | # 3: origin (str, optional) 19 | ib-assert-eq() { 20 | local x=${1:-''} 21 | local y=${2:-''} 22 | local origin=${3:-''} 23 | if [[ "$origin" == "" ]]; then 24 | origin="${BASH_SOURCE[1]#$IB_RUN_DIR}:${BASH_LINENO}" 25 | fi 26 | 27 | ((IB_ASSERT_COUNT++)) 28 | if [[ "$x" == "$y" ]]; then 29 | ((IB_ASSERT_PASS++)) 30 | IB_ASSERT_STATUS+="." 31 | else 32 | ((IB_ASSERT_FAIL++)) 33 | IB_ASSERT_STATUS+="F" 34 | IB_ASSERT_FAILURES+="(#$IB_ASSERT_COUNT) $origin '$x' != '$y' 35 | " 36 | fi 37 | } 38 | 39 | ib-assert-true() { 40 | ib-assert-eq $(ib-ok? "$@") "true" \ 41 | "${BASH_SOURCE[1]#$IB_RUN_DIR}:${BASH_LINENO}" 42 | } 43 | 44 | ib-assert-false() { 45 | ib-assert-eq $(ib-ok? "$@") "false" \ 46 | "${BASH_SOURCE[1]#$IB_RUN_DIR}:${BASH_LINENO}" 47 | } 48 | 49 | ib-assert-stats() { 50 | printf "$IB_ASSERT_STATUS\n" 51 | printf "Total: $IB_ASSERT_COUNT, " 52 | printf "Pass: $IB_ASSERT_PASS, " 53 | printf "Fail: $IB_ASSERT_FAIL\n" 54 | if [[ "$IB_ASSERT_FAILURES" != "" ]]; then 55 | printf "\nFAILURES\n$IB_ASSERT_FAILURES" 56 | fi 57 | } 58 | -------------------------------------------------------------------------------- /src/ib-git.sh: -------------------------------------------------------------------------------- 1 | 2 | ### git - basic version control functions 3 | 4 | # Clone a repository if the folder doesn't exist. 5 | # Usage: 6 | # ib-git-clone [-l LABEL] [-q] [-u USER] URL [FLAGS...] 7 | # 8 | # Args: 9 | # -l LABEL, --label LABEL 10 | # label to display for progress on this task 11 | # -q, --quiet 12 | # suppress progress display 13 | # -u USER, --user USER 14 | # URL URL to the repository 15 | # DEST location to clone into 16 | # FLAGS additional parameters to git 17 | ib-git-clone() { 18 | if ! ib-command? git; then return 1; fi 19 | 20 | ib-parse-args "$@" 21 | local label=${IB_ARGS[0]:-''} 22 | local quiet=${IB_ARGS[1]:-''} 23 | local user=${IB_ARGS[2]:-''} 24 | local url=${IB_ARGS[3]:-''} 25 | local dest=${IB_ARGS[4]:-$(basename ${url%.*})} 26 | local flags="${IB_ARGS[@]:5}" 27 | local skip=$(ib-ok? [[ -d "$dest" ]]) 28 | ib-action -l "$label" -s "$skip" $quiet -- \ 29 | git clone "$url" "$dest" ${flags[@]} 30 | } 31 | -------------------------------------------------------------------------------- /src/ib-jinja2.sh: -------------------------------------------------------------------------------- 1 | 2 | ### jinja2 - simple templates 3 | 4 | # NOTE: This requires jinja2-cli 5 | # $ pip install jinja2-cli 6 | 7 | # Render a template into a file. 8 | # Usage: 9 | # ib-jinja2 [-l LABEL] [-q] [-u USER] SRC DATA DEST [FLAGS...] 10 | # 11 | # Args: 12 | # -l LABEL, --label LABEL 13 | # label to display for progress on this task 14 | # -q, --quiet 15 | # suppress progress display 16 | # -u USER, --user USER 17 | # SRC Jinja2 template file 18 | # DATA JSON data file 19 | # DEST destination file 20 | # FLAGS additional parameters to jinja2 21 | ib-jinja2() { 22 | if ! ib-command? jinja2; then return 1; fi 23 | 24 | ib-parse-args "$@" 25 | local label=${IB_ARGS[0]:-''} 26 | local quiet=${IB_ARGS[1]:-''} 27 | local user=${IB_ARGS[2]:-''} 28 | local src=${IB_ARGS[3]:-''} 29 | local data=${IB_ARGS[4]:-''} 30 | local dest=${IB_ARGS[5]:-''} 31 | local flags="${IB_ARGS[@]:6}" 32 | local content=$(jinja2 "$src" "$data" ${flags[@]}) 33 | 34 | echo "$content" | cmp --quiet "$dest" - >> $IB_LOG 35 | local skip=$(ib-ok? [[ $? == 0 ]]) 36 | ib-action -l "$label" -s "$skip" -u "$user" $quiet -- tee "$dest" <> $IB_LOG) 77 | local skip=$(ib-ok? [[ "$rwx" == "$current" ]]) 78 | ib-action -l "$label" -s "$skip" -u "$user" $quiet -- \ 79 | chmod -R ${flags[@]} "$rwx" "$dest" 80 | } 81 | 82 | # Change ownership (recursively) of a path. 83 | # Usage: 84 | # ib-os-chown [-l LABEL] [-q] [-u USER] DEST OWNER [FLAGS...] 85 | # 86 | # Args: 87 | # -l LABEL, --label LABEL 88 | # label to display for progress on this task 89 | # -q, --quiet 90 | # suppress progress display 91 | # -u USER, --user USER 92 | # user to run as 93 | # DEST target path 94 | # OWNER string formmated as "user:group" 95 | # FLAGS additional parameters to chown 96 | ib-os-chown() { 97 | ib-parse-args "$@" 98 | local label=${IB_ARGS[0]:-''} 99 | local quiet=${IB_ARGS[1]:-''} 100 | local user=${IB_ARGS[2]:-''} 101 | local dest=${IB_ARGS[3]:-''} 102 | local owner=${IB_ARGS[4]:-''} 103 | local flags="${IB_ARGS[@]:5}" 104 | local current=$(stat --printf='%U:%G' $dest 2>> $IB_LOG) 105 | local skip=$(ib-ok? [[ "$owner" == "$current" ]]) 106 | ib-action -l "$label" -s "$skip" -u "$user" $quiet -- \ 107 | chown -R ${flags[@]} "$owner" "$dest" 108 | } 109 | 110 | # Copy a path (recursively). 111 | # Usage: 112 | # ib-os-copy [-l LABEL] [-q] [-u USER] SRC DEST [FLAGS...] 113 | # 114 | # Args: 115 | # -l LABEL, --label LABEL 116 | # label to display for progress on this task 117 | # -q, --quiet 118 | # suppress progress display 119 | # -u USER, --user USER 120 | # user to run as 121 | # SRC source path 122 | # DEST target path 123 | # FLAGS additional parameters to cp 124 | ib-os-copy() { 125 | ib-parse-args "$@" 126 | local label=${IB_ARGS[0]:-''} 127 | local quiet=${IB_ARGS[1]:-''} 128 | local user=${IB_ARGS[2]:-''} 129 | local src=${IB_ARGS[3]:-''} 130 | local dest=${IB_ARGS[4]:-''} 131 | local flags="${IB_ARGS[@]:5}" 132 | local skip=$(ib-ok? [[ -e "$dest" ]]) 133 | ib-action -l "$label" -s "$skip" -u "$user" $quiet -- \ 134 | cp -ur $flags "$src" "$dest" 135 | } 136 | 137 | # Create a symlink. 138 | # Usage: 139 | # ib-os-link [-l LABEL] [-q] [-u USER] SRC DEST [FLAGS...] 140 | # 141 | # Args: 142 | # -l LABEL, --label LABEL 143 | # label to display for progress on this task 144 | # -q, --quiet 145 | # suppress progress display 146 | # -u USER, --user USER 147 | # user to run as 148 | # SRC source path 149 | # DEST target path 150 | # FLAGS additional parameters to ln 151 | ib-os-link() { 152 | ib-parse-args "$@" 153 | local label=${IB_ARGS[0]:-''} 154 | local quiet=${IB_ARGS[1]:-''} 155 | local user=${IB_ARGS[2]:-''} 156 | local src=${IB_ARGS[3]:-''} 157 | local dest=${IB_ARGS[4]:-''} 158 | local flags="${IB_ARGS[@]:5}" 159 | local target=$(readlink -f $dest) 160 | local skip 161 | 162 | if [[ (-L "$dest" || -e "$dest") && "$target" != "$src" ]]; then 163 | echo "INFO: wrong target - $target != $src" &>> $IB_LOG 164 | ib-action -l "" -s "false" -u "$user" $quiet -- \ 165 | rm -f "$dest" 166 | fi # remove incorrect target 167 | 168 | skip=$(ib-ok? [[ -L "$dest" ]] && [[ "$target" == "$src" ]] ) 169 | ib-action -l "$label" -s "$skip" -u "$user" $quiet -- \ 170 | ln -s $flags "$src" "$dest" 171 | } 172 | 173 | # Copy or link (default) depending on a parameter. 174 | # Usage: 175 | # ib-os-copy-link [-l LABEL] [-q] [-u USER] SRC DEST [TOGGLE] 176 | # 177 | # Args: 178 | # -l LABEL, --label LABEL 179 | # label to display for progress on this task 180 | # -q, --quiet 181 | # suppress progress display 182 | # -u USER, --user USER 183 | # user to run as 184 | # SRC source path 185 | # DEST target path 186 | # TOGGLE if truthy, copy SRC to DEST; otherwise (default), link DEST to SRC 187 | ib-os-copy-link() { 188 | ib-parse-args "$@" 189 | local label=${IB_ARGS[0]:-''} 190 | local quiet=${IB_ARGS[1]:-''} 191 | local user=${IB_ARGS[2]:-''} 192 | local src=${IB_ARGS[3]:-''} 193 | local dest=${IB_ARGS[4]:-''} 194 | local toggle=${IB_ARGS[5]:-''} 195 | IB_ARGS[5]='' 196 | if ib-truthy? $toggle; then 197 | ib-os-copy -l "$label" -u "$user" "$quiet" "$src" "$dest" 198 | else 199 | ib-os-link -l "$label" -u "$user" "$quiet" "$src" "$dest" 200 | fi 201 | } 202 | 203 | # Add a single line to a file. 204 | # Usage: 205 | # ib-os-append [-l LABEL] [-q] [-u USER] DEST LINE 206 | # 207 | # Args: 208 | # -l LABEL, --label LABEL 209 | # label to display for progress on this task 210 | # -q, --quiet 211 | # suppress progress display 212 | # -u USER, --user USER 213 | # user to run as 214 | # DEST target path 215 | # LINE line to add to file; note the first line is used for matching 216 | ib-os-append() { 217 | ib-parse-args "$@" 218 | local quiet=${IB_ARGS[1]:-''} 219 | local user=${IB_ARGS[2]:-''} 220 | local dest=${IB_ARGS[3]:-''} 221 | local label=${IB_ARGS[0]:-"[os] append to $dest"} 222 | local line="${IB_ARGS[@]:4}" 223 | local first=(${line[@]}) 224 | local pattern=$(printf '^%q$' ${first:-''}) 225 | local skip=$(ib-ok? grep -qsPe \'$pattern\' "$dest") 226 | ib-action -l "$label" -s "$skip" -u "$user" $quiet -- \ 227 | tee --append "$dest" <<< "$line" 228 | } 229 | -------------------------------------------------------------------------------- /src/ib-pip.sh: -------------------------------------------------------------------------------- 1 | 2 | ### pip - python package management 3 | 4 | # Install packages using pip. 5 | # 6 | # WARNING: This only works properly for pinned versions of packages. 7 | # 8 | # Usage: 9 | # ib-pip-install [-l LABEL] [-q] [-u USER] PACKAGE [PACKAGE...] 10 | # 11 | # Args: 12 | # -l LABEL, --label LABEL 13 | # label to display for progress on this task 14 | # -q, --quiet 15 | # suppress progress display 16 | # -u USER, --user USER 17 | # user to run as 18 | # PACKAGE name of package to install 19 | ib-pip-install() { 20 | if ! ib-command? pip; then return 1; fi 21 | 22 | ib-parse-args "$@" 23 | local label=${IB_ARGS[0]:-'[pip] pip install'} 24 | local quiet=${IB_ARGS[1]:-''} 25 | local user=${IB_ARGS[2]:-''} 26 | local packages=("${IB_ARGS[@]:3}") 27 | local pkgcount=${#packages[@]} 28 | local skip 29 | local item='' 30 | local items='' 31 | local pattern='' 32 | local existing=$(pip freeze 2>> $IB_LOG) 33 | 34 | for ((index=0; index <= ${pkgcount}; index++)); do 35 | if [[ "${packages[index]:-''}" == "-r" ]]; then 36 | ((index++)) 37 | item=`python - 2>> $IB_LOG <&1) 18 | ib-ok? grep -qPe \"\([^0]\d* rows?\)\" <<< "$status" 19 | } 20 | 21 | # Run a SQL script. 22 | # Usage: 23 | # ib-postgresql-file [-l LABEL] [-q] [-u USER] SQL SRC 24 | # 25 | # Args: 26 | # -l LABEL, --label LABEL 27 | # label to display for progress on this task 28 | # -q, --quiet 29 | # suppress progress display 30 | # -u USER, --user USER 31 | # user to run as 32 | # SQL command to execute to check if SRC should be loaded 33 | # SRC file with commands to execute 34 | ib-postgresql-file() { 35 | if ! ib-command? psql; then return 1; fi 36 | 37 | ib-parse-args "$@" 38 | local label=${IB_ARGS[0]:-''} 39 | local quiet=${IB_ARGS[1]:-''} 40 | local user=${IB_ARGS[2]:-"$IB_POSTGRESQL_USER"} 41 | local sql=${IB_ARGS[3]:-''} 42 | local src=$(readlink -f ${IB_ARGS[4]:-''}) 43 | local skip=$(ib-postgresql-ok? "$sql") 44 | ib-action -l "$label" -s "$skip" -u "$user" $quiet -- \ 45 | psql -f - \< $src 46 | } 47 | 48 | # Run a SQL command. 49 | # Usage: 50 | # ib-postgresql-sql [-l LABEL] [-q] [-u USER] CHECK SQL 51 | # 52 | # Args: 53 | # -l LABEL, --label LABEL 54 | # label to display for progress on this task 55 | # -q, --quiet 56 | # suppress progress display 57 | # -u USER, --user USER 58 | # user to run as 59 | # CHECK command to execute to check if SQL should be executed 60 | # SQL command to execute 61 | ib-postgresql-sql() { 62 | if ! ib-command? psql; then return 1; fi 63 | 64 | ib-parse-args "$@" 65 | local label=${IB_ARGS[0]:-''} 66 | local quiet=${IB_ARGS[1]:-''} 67 | local user=${IB_ARGS[2]:-"$IB_POSTGRESQL_USER"} 68 | local check=${IB_ARGS[3]:-''} 69 | local sql=${IB_ARGS[4]:-''} 70 | local skip=$(ib-postgresql-ok? "$check") 71 | ib-action -l "$label" -s "$skip" -u "$user" $quiet -- \ 72 | psql -c \"$sql\" 73 | } 74 | -------------------------------------------------------------------------------- /src/ib-service.sh: -------------------------------------------------------------------------------- 1 | 2 | ### service - manage background services 3 | 4 | # Install a service. 5 | # Usage: 6 | # ib-service-install [-l LABEL] [-q] [-u USER] NAME 7 | # 8 | # Args: 9 | # -l LABEL, --label LABEL 10 | # label to display for progress on this task 11 | # -q, --quiet 12 | # suppress progress display 13 | # -u USER, --user USER 14 | # user to run as 15 | # NAME service name 16 | ib-service-install() { 17 | if ib-command? update-rc.d; then true; else return 1; fi 18 | 19 | ib-parse-args "$@" 20 | local label=${IB_ARGS[0]:-''} 21 | local quiet=${IB_ARGS[1]:-''} 22 | local user=${IB_ARGS[2]:-'root'} 23 | local name=${IB_ARGS[3]:-''} 24 | local skip=$(ib-ok? stat -t /etc/rc*/???$name 2>> /dev/null) 25 | ib-action -l "$label" -s "$skip" -u "$user" $quiet -- \ 26 | update-rc.d "$name" defaults 98 02 27 | } 28 | 29 | # Set the service state. 30 | # Usage: 31 | # ib-service-install [-l LABEL] [-q] [-u USER] NAME STATE 32 | # 33 | # Args: 34 | # -l LABEL, --label LABEL 35 | # label to display for progress on this task 36 | # -q, --quiet 37 | # suppress progress display 38 | # -u USER, --user USER 39 | # user to run as 40 | # NAME service name 41 | # STATE one of "start", "stop", or "restart" 42 | ib-service-state() { 43 | if ib-command? service; then true; else return 1; fi 44 | 45 | ib-parse-args "$@" 46 | local quiet=${IB_ARGS[1]:-''} 47 | local user=${IB_ARGS[2]:-'root'} 48 | local name=${IB_ARGS[3]:-''} 49 | local state=${IB_ARGS[4]:-''} 50 | local label=${IB_ARGS[0]:-"[service] sudo service $name $state"} 51 | local status= 52 | local skip=false 53 | local tried=false 54 | local value=0 55 | 56 | if ib-falsy? "$quiet"; then ib-action-start "$label"; fi 57 | 58 | service $name status 2&>/dev/null 59 | status=$? 60 | case "$state" in 61 | start) skip=$(ib-ok? [[ $status == 0 ]]);; 62 | stop) skip=$(ib-ok? [[ $status != 0 ]]);; 63 | restart) skip= 64 | esac 65 | 66 | if ib-falsy? $skip; then 67 | tried=true 68 | IB_LAST_ACTION="service $name $state" 69 | if [[ "$user" != "" ]]; then 70 | IB_LAST_ACTION="sudo -u $user $IB_LAST_ACTION" 71 | fi 72 | echo -e "\n\$ $IB_LAST_ACTION" >> $IB_LOG 73 | eval "$IB_LAST_ACTION" &>> $IB_LOG 74 | sleep 0.3 75 | service $name status 2&> /dev/null 76 | value=$? 77 | if [[ "$state" == "stop" ]]; then 78 | value=$([[ "$value" != 0 ]] && echo 0); 79 | fi 80 | fi 81 | 82 | if ib-falsy? "$quiet"; then ib-action-stop "$label" "$tried" "$value"; fi 83 | } 84 | -------------------------------------------------------------------------------- /test/ib-action.sh: -------------------------------------------------------------------------------- 1 | if [[ -z "${IB_SCRIPT_VERSION:-''}" ]]; then 2 | source "../src/$(basename ${BASH_SOURCE[0]})" 3 | fi # only included if needed 4 | 5 | setup() { true; } 6 | 7 | teardown() { true; } 8 | 9 | test-ib-ok() { 10 | ib-assert-eq $(ib-ok? grep -qse "foobar" "foobar.txt") "false" 11 | ib-assert-eq $(ib-ok? grep -qse "foobar" "ib-action.sh") "true" 12 | ib-assert-eq $(ib-ok? [[ -e "ib-action.sh" ]]) "true" 13 | ib-assert-true $(ib-ok? command -v wget) 14 | } 15 | 16 | test-ib-truthy() { 17 | ib-assert-true $(ib-ok? ib-truthy? "true") 18 | ib-assert-true $(ib-ok? ib-truthy? "something") 19 | ib-assert-false $(ib-ok? ib-truthy? "false") 20 | ib-assert-false $(ib-ok? ib-truthy? "") 21 | } 22 | 23 | test-ib-falsy() { 24 | ib-assert-true $(ib-ok? ib-falsy? "false") 25 | ib-assert-true $(ib-ok? ib-falsy? "") 26 | ib-assert-false $(ib-ok? ib-falsy? "true") 27 | ib-assert-false $(ib-ok? ib-falsy? "something") 28 | } 29 | 30 | test-ib-command() { 31 | ib-assert-true $(ib-ok? ib-command? "wget") 32 | ib-assert-false $(ib-ok? ib-command? "fake" "false") 33 | } 34 | 35 | test-ib-join() { 36 | ib-assert-eq $(ib-join "" "a" "b" "c") "abc" 37 | ib-assert-eq $(ib-join "+" "a" "b" "c") "a+b+c" 38 | ib-assert-eq $(ib-join "-" "a" "b" "c") "a-b-c" 39 | } 40 | 41 | test-ib-action() { 42 | local TEST_LINE='example line of text' 43 | ib-action -q -- echo $TEST_LINE 44 | ib-assert-eq "$IB_LAST_ACTION" "echo $TEST_LINE" 45 | 46 | IB_LAST_ACTION='' 47 | ib-action -q --dry-run -- echo $TEST_LINE 48 | ib-assert-eq "$IB_LAST_ACTION" "" 49 | } 50 | -------------------------------------------------------------------------------- /test/ib-apt-cyg.sh: -------------------------------------------------------------------------------- 1 | source "../src/$(basename ${BASH_SOURCE[0]})" 2 | 3 | setup() { true; } 4 | 5 | teardown() { true; } 6 | 7 | test-ib-apt-cyg-install() { 8 | if ib-command? apt-cyg; then true; else return 1; fi 9 | 10 | ib-apt-cyg-install -q nano 11 | ib-assert-true apt-cyg list \| grep -sqe "^nano\$" 12 | } 13 | -------------------------------------------------------------------------------- /test/ib-apt.sh: -------------------------------------------------------------------------------- 1 | source "../src/$(basename ${BASH_SOURCE[0]})" 2 | 3 | setup() { true; } 4 | 5 | teardown() { true; } 6 | 7 | test-ib-apt-add-key() { 8 | if ib-command? apt-key; then true; else return 1; fi 9 | 10 | local keyid=ACCC4CF8 11 | local url=https://www.postgresql.org/media/keys/ACCC4CF8.asc 12 | ib-apt-add-key -q "$keyid" "$url" 13 | apt-key adv --list-public-keys | grep -qPe "$keyid" 14 | ib-assert-true [[ "$?" == "0" ]] 15 | } 16 | 17 | test-ib-apt-update() { 18 | if ib-command? apt-get; then true; else return 1; fi 19 | 20 | ib-apt-update -q 21 | ib-assert-true [[ "$?" == "0" ]] 22 | } 23 | 24 | test-ib-apt-install() { 25 | if ib-command? dpkg; then true; else return 1; fi 26 | 27 | ib-apt-install -q python ruby 28 | 29 | dpkg -s python3 | grep -qPe installed 30 | ib-assert-true [[ "$?" == "0" ]] 31 | } 32 | -------------------------------------------------------------------------------- /test/ib-git.sh: -------------------------------------------------------------------------------- 1 | source "../src/$(basename ${BASH_SOURCE[0]})" 2 | 3 | TEST_URI="https://github.com/metaist/idempotent-bash.git" 4 | TEST_DIR="/tmp/idempotent-bash-alt" 5 | 6 | setup() { 7 | rm -rf $TEST_DIR 8 | } 9 | 10 | teardown() { 11 | rm -rf $TEST_DIR 12 | } 13 | 14 | test-ib-git-clone() { 15 | if ib-command? git; then true; else return 1; fi 16 | 17 | ib-git-clone -q "$TEST_URI" "$TEST_DIR" 18 | ib-assert-eq "$IB_LAST_ACTION" "git clone $TEST_URI $TEST_DIR" 19 | ib-assert-true [[ -d $TEST_DIR/.git ]] 20 | } 21 | -------------------------------------------------------------------------------- /test/ib-jinja2.sh: -------------------------------------------------------------------------------- 1 | source "../src/$(basename ${BASH_SOURCE[0]})" 2 | 3 | TEST_TMPL='/tmp/ib-test.tmpl' 4 | TEST_DATA='/tmp/ib-test.json' 5 | TEST_DEST='/tmp/ib-test.txt' 6 | 7 | setup() { 8 | echo 'This is the {{key}} {{other}}' >> $TEST_TMPL 9 | echo '{"key": "thing that"}' >> $TEST_DATA 10 | rm -rf "$TEST_DEST" 11 | } 12 | 13 | teardown() { 14 | rm -rf "$TEST_TMPL" 15 | rm -rf "$TEST_DATA" 16 | rm -rf "$TEST_DEST" 17 | } 18 | 19 | test-ib-jinja2() { 20 | ib-jinja2 -q "$TEST_TMPL" "$TEST_DATA" "$TEST_DEST" -D other=works 21 | ib-assert-eq "$(<$TEST_DEST)" "This is the thing that works" 22 | } 23 | -------------------------------------------------------------------------------- /test/ib-os.sh: -------------------------------------------------------------------------------- 1 | source "../src/$(basename ${BASH_SOURCE[0]})" 2 | 3 | TEST_FILE="/tmp/ib-file-test.txt" 4 | TEST_DIR="/tmp/ib-dir-test" 5 | 6 | setup() { 7 | rm -rf "$TEST_FILE" 8 | rm -rf "$TEST_DIR" 9 | } 10 | 11 | teardown() { 12 | rm -rf "$TEST_FILE" 13 | rm -rf "$TEST_DIR" 14 | } 15 | 16 | test-ib-os-mkdir() { 17 | ib-os-mkdir -q "$TEST_DIR" -v 18 | ib-assert-eq "$IB_LAST_ACTION" "mkdir -p -v $TEST_DIR" 19 | ib-assert-true [[ -d $TEST_DIR ]] 20 | } 21 | 22 | test-ib-os-remove-file() { 23 | touch "$TEST_FILE" 24 | ib-os-remove -q "$TEST_FILE" 25 | ib-assert-false [[ -e "$TEST_FILE" ]] 26 | } 27 | 28 | test-ib-os-remove-dir() { 29 | mkdir "$TEST_DIR" 30 | ib-os-remove -q "$TEST_DIR" 31 | ib-assert-false [[ -e "$TEST_FILE" ]] 32 | } 33 | 34 | test-ib-os-chmod() { 35 | umask 0022 36 | touch "$TEST_FILE" 37 | ib-assert-eq "$(stat --printf='%a' $TEST_FILE)" "644" 38 | ib-os-chmod -q "$TEST_FILE" 777 39 | ib-assert-eq "$(stat --printf='%a' $TEST_FILE)" "777" 40 | } 41 | 42 | # NOTE: It is very difficult to test this function properly. 43 | test-ib-os-chown() { true; } 44 | 45 | test-ib-os-copy() { 46 | local TEST_FILE_2="${TEST_FILE}.bak" 47 | printf "THIS\nIS\nA\nTEST\n" >> "$TEST_FILE" 48 | ib-assert-false [[ -e "$TEST_FILE_2" ]] 49 | ib-os-copy -q "$TEST_FILE" "$TEST_FILE_2" 50 | ib-assert-true [[ -e "$TEST_FILE_2" ]] 51 | ib-assert-eq "$(<$TEST_FILE)" "$(<$TEST_FILE_2)" 52 | rm -rf "$TEST_FILE_2" 53 | } 54 | 55 | test-ib-os-link() { 56 | local TEST_LINK="${TEST_DIR}-link" 57 | local TEST_DIR_2="${TEST_DIR}-else" 58 | 59 | ib-assert-false [[ -e "$TEST_LINK" ]] 60 | mkdir "$TEST_DIR" 61 | 62 | ib-os-link -q "$TEST_DIR" "$TEST_LINK" 63 | ib-assert-true [[ -L "$TEST_LINK" ]] 64 | ib-assert-eq "$(readlink -f $TEST_DIR)" "$(readlink -f $TEST_LINK)" 65 | 66 | mkdir "$TEST_DIR_2" 67 | ib-os-link -q "$TEST_DIR_2" "$TEST_LINK" 68 | ib-assert-true [[ -L "$TEST_LINK" ]] 69 | ib-assert-eq "$(readlink -f $TEST_DIR_2)" "$(readlink -f $TEST_LINK)" 70 | 71 | rm -rf "$TEST_LINK" 72 | rm -rf "$TEST_DIR_2" 73 | } 74 | 75 | test-ib-os-copy-link() { 76 | local TEST_DIR_2="${TEST_DIR}-link" 77 | ib-assert-false [[ -e "$TEST_DIR_2" ]] 78 | mkdir "$TEST_DIR" 79 | ib-os-copy-link -q "$TEST_DIR" "$TEST_DIR_2" 80 | ib-assert-true [[ -L "$TEST_DIR_2" ]] 81 | ib-assert-eq "$(readlink -f $TEST_DIR)" "$(readlink -f $TEST_DIR_2)" 82 | rm -rf "$TEST_DIR_2" 83 | 84 | ib-os-copy-link -q "$TEST_DIR" "$TEST_DIR_2" "copy" 85 | ib-assert-true [[ -d "$TEST_DIR_2" ]] 86 | rm -rf "$TEST_DIR_2" 87 | } 88 | 89 | test-ib-os-append() { 90 | local TEST_LINE="this is a test line" 91 | ib-os-append -q "$TEST_FILE" "$TEST_LINE" 92 | ib-assert-eq "$(<$TEST_FILE)" "$TEST_LINE" 93 | rm -rf "$TEST_FILE" 94 | 95 | TEST_LINE="line 1 96 | line 2" 97 | ib-os-append -q "$TEST_FILE" "$TEST_LINE" 98 | ib-assert-eq "$(<$TEST_FILE)" "$TEST_LINE" 99 | 100 | # make sure that it doesn't add it again 101 | ib-os-append -q "$TEST_FILE" "$TEST_LINE" 102 | ib-assert-eq "$(<$TEST_FILE)" "$TEST_LINE" 103 | } 104 | -------------------------------------------------------------------------------- /test/ib-pip.sh: -------------------------------------------------------------------------------- 1 | source "../src/$(basename ${BASH_SOURCE[0]})" 2 | 3 | setup() { true; } 4 | 5 | teardown() { true; } 6 | 7 | test-ib-pip-install() { 8 | ib-pip-install -q jinja2-cli argh 9 | local pattern="^jinja2-cli" 10 | local existing=$(pip freeze 2>> /dev/null) 11 | local installed="$(grep -i "$pattern" <<< "$existing")" 12 | ib-assert-true [[ "$installed" =~ "$pattern" ]] 13 | } 14 | -------------------------------------------------------------------------------- /test/ib-postgresql.sh: -------------------------------------------------------------------------------- 1 | source "../src/$(basename ${BASH_SOURCE[0]})" 2 | DIR_TEST=$(dirname ${BASH_SOURCE[0]}) 3 | 4 | setup() { true; } 5 | 6 | teardown() { true; } 7 | 8 | test-ib-postgresql-file() { 9 | ib-postgresql-file -q "SELECT 1 LIMIT 0;" "$DIR_TEST/test.sql" 10 | ib-assert-eq "$IB_LAST_ACTION" \ 11 | "sudo -u postgres psql -f - < $DIR_TEST/test.sql" 12 | } 13 | 14 | test-ib-postgres-sql() { 15 | ib-postgresql-sql -q "SELECT 1 LIMIT 0;" "SELECT NOW();" 16 | ib-assert-eq "$IB_LAST_ACTION" \ 17 | 'sudo -u postgres psql -c "SELECT NOW();"' 18 | } 19 | -------------------------------------------------------------------------------- /test/ib-service.sh: -------------------------------------------------------------------------------- 1 | source "../src/$(basename ${BASH_SOURCE[0]})" 2 | 3 | setup() { true; } 4 | 5 | teardown() { true; } 6 | 7 | # TODO: add service install test 8 | test-ib-service-install() { true; } 9 | 10 | test-ib-service-state() { 11 | local status 12 | local name="urandom" 13 | 14 | ib-service-state -q "$name" "stop" 15 | service $name status 2&> /dev/null 16 | ib-assert-true [[ "$?" != "0" ]] 17 | 18 | ib-service-state -q "$name" "start" 19 | service $name status 2&> /dev/null 20 | ib-assert-true [[ "$?" == "0" ]] 21 | } 22 | -------------------------------------------------------------------------------- /test/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2018 Metaist LLC 4 | # MIT License 5 | 6 | # Test harness for idempotent-bash. 7 | 8 | # bash strict mode 9 | set -uo pipefail 10 | IFS=$'\n\t' 11 | 12 | IB_TEST_SCRIPT_NAME=$(readlink -f ${BASH_SOURCE[0]}) 13 | IB_TEST_SCRIPT_ARGS=$@ 14 | 15 | cd "$(dirname $IB_TEST_SCRIPT_NAME)" 16 | 17 | run-file() { 18 | local fn 19 | for fn in $(grep -oP '^(test[_-])[^(]*' ${1:-''}); do 20 | setup 21 | $fn 22 | teardown 23 | done 24 | } 25 | 26 | run-tests() { 27 | local name 28 | while [[ "$#" > 0 ]]; do 29 | name=$(readlink -f ${1:-''}) 30 | shift 1 31 | if [[ "$name" == "$IB_TEST_SCRIPT_NAME" ]]; then continue; fi 32 | 33 | source "$name" 34 | run-file "$name" 35 | printf "$IB_ASSERT_STATUS\r" 36 | done 37 | } 38 | 39 | source "../src/ib-action.sh" 40 | source "../src/ib-assert.sh" 41 | 42 | IB_LOG="/tmp/ib.log" 43 | rm -f $IB_LOG &> /dev/null 44 | 45 | if [[ ${1:-""} == "" ]]; then 46 | run-tests $(readlink -f *.sh) 47 | else 48 | run-tests $@ 49 | fi 50 | 51 | ib-assert-stats 52 | -------------------------------------------------------------------------------- /test/test.sql: -------------------------------------------------------------------------------- 1 | select now(); 2 | --------------------------------------------------------------------------------