#!/usr/bin/env bash #============================================================================== # # (c) 2017 Konstantin Gredeskoul # MIT License, distributed as part of `sym` ruby gem. # https://github.com/kigster/sym # #============================================================================== # Purpuse of this script is to transparently edit application secrets in a # Rails app. It's a simple enough wrapper around sym. # # What the fuck? # # 1) This assumes you are storing application secrets in a file, say named, # RAILS_ROOT/config/special/secrets/production.yml.enc # # 2) You want to be able to easily and transparently edit it with sym, without # having to remember sym's CLI. # # 3) You may want to have a search paths to look for the file in... # # 4) You may want to override the file extension assumed (instead of .yml.enc). # # SO: here is what you do: # # export sym__ext="json.enc" # export sym__folder="config/special/secrets" # export sym__key="application-key" # # And then # # symit production # # ...and vola! You are editing the encrypted file with sym from the root of # your Rails application. Neat, no? # symit::init() { [[ -z "${sym__ext}" ]] && export sym__ext="yml.enc" [[ -z "${sym__folder}" ]] && export sym__folder="config/settings/secrets" export true=1 export false=0 export txtrst='\e[0m' # Text Reset export bldred='\e[1;31m' # Red export bldgrn='\e[1;32m' # Green export bldylw='\e[1;33m' # Yellow export bldblu='\e[1;34m' # Blue unset cli__opts } symit::usage() { printf "${bldblu}symit: ${txtrst}edit an encrypted file using configuration from environment\n\n" printf " Usage: ${bldgrn}symit ${bldylw}[file-spec] [options]${txtrst}\n\n" printf " Eg: To edit an encrypted file config/settings/secrets/development.yml.enc${txtrst}\n" printf " ${bldgrn}symit${bldylw} development${txtrst}\n\n" printf "options: \n" printf " -k | --key [key-spec] Pass an alternative key, other than ${sym__key}\n" printf " -x | --extension [extension] Use extension other than ${bldylw}${sym__ext}${txtrst}\n" printf " -l | --locations Print search locations for [file-spec]\n" printf " -i | --install Install the latest version of ${bldylw}sym${txtrst}\n" printf " -h | --help Show this help message\n" printf " -n | --dry-run Show the generated sym command\n\n" printf "configuration: export sym__ext=yml.enc export sym__folder=config/special/secrets export sym__key=my-encryption-key And then, eg from RAILS_ROOT of your app: ${bldgrn}symit production${txtrst}\n\n" } symit::error() { printf "${bldred}error: $* ${bldylw}\n\n" } symit::install() { if [[ -z "${_symit__installed}" ]]; then current_version=$(gem list | grep sym | awk '{print $2}' | sed 's/(//g;s/)//g') if [[ -z "${current_version}" ]]; then gem install sym else local help=$(sym -h 2>&1) unset SYM_ARGS remote_version=$(gem search sym | egrep '^sym \(' | awk '{print $2}' | sed 's/(//g;s/)//g') if [[ "${remote_version}" != "${current_version}" ]]; then printf "detected an older ${bldgrn}sym (${current_version}), installing ${bldgrn}sym (${remote_version})${txtrst}...\n" echo y | gem uninstall sym -a 2>/dev/null gem install sym export _symit__installed="yes" else printf "${bldgrn}sym${txtrst} is on the latest version ${remote_version} already\n" fi fi fi } symit::locs() { if [[ -n ${encrypted_file} ]]; then [[ -n ${cli__opts[extension]} ]] && export sym__ext=${cli__opts[extension]} declare -a locations=("${sym__folder}/${encrypted_file}.${sym__ext}" "${sym__folder}/${encrypted_file}" "${encrypted_file}") fi echo -n ${locations[*]} } symit::locs::print() { printf "Search locations:\n" for loc in ${locations[@]}; do if [[ -n "${loc}" ]] ; then printf "\t - ${bldblu}${loc}${txtrst}\n" fi done } symit::exit() { code=${1:-0} echo -n ${code} } symit::cleanup() { unset encrypted_file unset cli__opts unset locations } symit() { [[ -n "${1}" && "${1:0:1}" != "-" ]] && { export encrypted_file=$1 shift } symit::init declare -A cli__opts=( [verbose]=${true} [key]=${sym__key} [extension]=${sym__ext} [dry_run]=${false} ) [[ -z ${encrypted_file} && -z $* ]] && symit::usage while :; do case $1 in -h|-\?|--help) shift symit::usage symit::cleanup return $(symit::exit 0) ;; -k|--key) shift if [[ -z $1 ]]; then symit::error "-k/--key requires an argument" && return $(symit::exit 1) else cli__opts[key]=$1 shift fi ;; -x|--extension) shift if [[ -z $1 ]]; then symit::error "-x/--extension requires an argument" && return $(symit::exit 1) else cli__opts[extension]=${1} shift fi ;; -l|--locations) shift cli__opts[locations]=${true} ;; -i|--install) shift symit::install symit::cleanup return $(symit::exit 0) ;; -n|--dry-run) shift cli__opts[dry_run]=${true} ;; --) # End of all options. shift break ;; -?*) printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2 symit::cleanup return $(symit::exit 127) shift ;; *) # Default case: If no more options then break out of the loop. break shift esac done declare -a locations=$(symit::locs) if [[ ${cli__opts[locations]} == ${true} ]]; then if [[ -z ${encrypted_file} ]]; then symit::error "-l/--locations requires file-spec to be provided as the 1st argument" symit::cleanup return $(symit::exit 2) else symit::locs::print symit::cleanup return $(symit::exit 0) fi fi if [[ -z "${encrypted_file}" ]]; then symit::error "Missing 1st argument — file name to be loaded, eg 'production', etc." symit::cleanup return $(symit::exit 3) fi if [[ -z "${cli__opts[key]}" ]]; then symit::error "Key was not defined, pass it with ${bldblu}-k key-spec${bldred} or set it via ${bldgrn}\$sym__key${bldred} variable." symit::cleanup return $(symit::exit 4) fi file= for loc in ${locations[@]}; do if [[ -s "${loc}" ]] ; then file=${loc} break fi done [[ -z "${file}" ]] && { symit::error "${bldylw}${encrypted_file}${bldred} could not be found." symit::locs::print symit::cleanup return $(symit::exit 5) } command="sym -ck ${cli__opts[key]} -t ${file}" [[ ${cli_opts[dry_run]} ]] && printf "[dry_run] " printf "${bldgrn}${command}${txtrst}\n" [[ ${cli_opts[dry_run]} ]] || ${command} }