diff --git a/README.md b/README.md index ebbf7f0c..4eab2a75 100644 --- a/README.md +++ b/README.md @@ -631,8 +631,8 @@ Templates](https://gitlab.com/BastilleBSD-Templates)? Bastille supports a templating system allowing you to apply files, pkgs and execute commands inside the container automatically. -Currently supported template hooks are: `LIMITS`, `INCLUDE`, `PRE`, `FSTAB`, -`PKG`, `OVERLAY`, `SYSRC`, `SERVICE`, `CMD`. +Currently supported template hooks are: `ARG`, `LIMITS`, `INCLUDE`, `PRE`, + `FSTAB`, `PKG`, `OVERLAY`, `SYSRC`, `SERVICE`, `CMD`, `RENDER`. Planned template hooks include: `PF`, `LOG` Templates are created in `${bastille_prefix}/templates` and can leverage any of @@ -655,17 +655,19 @@ echo "usr" > /usr/local/bastille/templates/username/base-template/OVERLAY Template hooks are executed in specific order and require specific syntax to work as expected. This table outlines that order and those requirements: -| SUPPORTED | format | example | -|-----------|---------------------|------------------------------------------------| -| LIMITS | resource value | memoryuse 1G | -| INCLUDE | template path/URL | http?://TEMPLATE_URL or username/base-template | -| PRE | /bin/sh command | mkdir -p /usr/local/path | -| FSTAB | fstab syntax | /host/path container/path nullfs ro 0 0 | -| PKG | port/pkg name(s) | vim-console zsh git-lite tree htop | -| OVERLAY | paths (one/line) | etc usr | -| SYSRC | sysrc command(s) | nginx_enable=YES | -| SERVICE | service command(s) | nginx restart | -| CMD | /bin/sh command | /usr/bin/chsh -s /usr/local/bin/zsh | +| SUPPORTED | format | example | +|-----------|-----------------------|------------------------------------------------| +| ARG | name=value (one/line) | domain=example.com (omit value for no default) | +| LIMITS | resource value | memoryuse 1G | +| INCLUDE | template path/URL | http?://TEMPLATE_URL or username/base-template | +| PRE | /bin/sh command | mkdir -p /usr/local/path | +| FSTAB | fstab syntax | /host/path container/path nullfs ro 0 0 | +| PKG | port/pkg name(s) | vim-console zsh git-lite tree htop | +| OVERLAY | paths (one/line) | etc usr | +| SYSRC | sysrc command(s) | nginx_enable=YES | +| SERVICE | service command(s) | nginx restart | +| CMD | /bin/sh command | /usr/bin/chsh -s /usr/local/bin/zsh | +| RENDER | paths (one/line) | /usr/local/etc/nginx | | PLANNED | format | example | |---------|------------------|----------------------------------------------------------------| @@ -674,6 +676,12 @@ work as expected. This table outlines that order and those requirements: Note: SYSRC requires NO quotes or that quotes (`"`) be escaped. ie; `\"`) +Any name provided in the ARG file can be used as a variable in the other hooks. +For example, `name=value` in the ARG file will cause instances of `${name}` +to be replaced with `value`. The `RENDER` hook can be used to specify files or +directories whose contents should have the variables replaced. Values can be specified +either through the command line when applying the template or as a default in the ARG file. + In addition to supporting template hooks, Bastille supports overlaying files into the container. This is done by placing the files in their full path, using the template directory as "/". @@ -705,12 +713,22 @@ create a `Bastillefile` inside the base template directory. Each line in the file should begin with an uppercase reference to a Bastille command followed by its arguments (omitting the target, which is deduced from the `template` arguments). Lines beginning with `#` are treated as comments. +Variables can also be defined using `ARG` with one `name=value` pair per +line. Subsequent references to `${name}` would be replaced by `value`. +Note that argument values are not available for use until after the point +at which they are defined in the file. Bastillefile example: ```shell LIMITS memoryuse 1G +# This value can be overridden when the template is applied. +ARG domain=example.com + +# Replace all argument variables inside the nginx config. +RENDER /usr/local/etc/nginx + # Install and start nginx. PKG nginx SYSRC nginx_enable=YES @@ -719,6 +737,9 @@ SERVICE nginx restart # Copy files to nginx. CP www/ usr/local/www/nginx-dist/ +# Use the "domain" arg to create a file on the server containing the domain. +CMD echo "${domain}" > /usr/local/www/nginx-dist/domain.txt + # Create a file on the server containing the jail's hostname. CMD hostname > /usr/local/www/nginx-dist/hostname.txt @@ -735,8 +756,12 @@ Bastille includes a `template` sub-command. This sub-command requires a target and a template name. As covered in the previous section, template names correspond to directory names in the `bastille/templates` directory. +To provide values for arguments defined by `ARG` in the template, pass the +optional `--arg` parameter as many times as needed. Alternatively, use +`--arg-file ` with one `name=value` pair per line. + ```shell -ishmael ~ # bastille template folsom username/base +ishmael ~ # bastille template folsom username/base --arg domain=example.com [folsom]: Copying files... Copy complete. diff --git a/usr/local/share/bastille/template.sh b/usr/local/share/bastille/template.sh index f00be62c..8ec25ca8 100644 --- a/usr/local/share/bastille/template.sh +++ b/usr/local/share/bastille/template.sh @@ -49,6 +49,62 @@ post_command_hook() { esac } +get_arg_name() { + echo "${1}" | sed -E 's/=.*//' +} + +parse_arg_value() { + # Parses the value after = and then escapes back/forward slashes and single quotes in it. -- cwells + echo "${1}" | sed -E 's/[^=]+=?//' | sed -e 's/\\/\\\\/g' -e 's/\//\\\//g' -e 's/'\''/'\''\\'\'\''/g' +} + +get_arg_value() { + _name_value_pair="${1}" + shift + _arg_name="$(get_arg_name "${_name_value_pair}")" + + # Remaining arguments in $@ are the script arguments, which take precedence. -- cwells + for _script_arg in "$@"; do + case ${_script_arg} in + --arg) + # Parse whatever is next. -- cwells + _next_arg='true' ;; + *) + if [ "${_next_arg}" = 'true' ]; then # This is the parameter after --arg. -- cwells + _next_arg='' + if [ "$(get_arg_name "${_script_arg}")" = "${_arg_name}" ]; then + parse_arg_value "${_script_arg}" + return + fi + fi + ;; + esac + done + + # Check the ARG_FILE if one was provided. --cwells + if [ -n "${ARG_FILE}" ]; then + # To prevent a false empty value, only parse the value if this argument exists in the file. -- cwells + if grep "^${_arg_name}=" "${ARG_FILE}" > /dev/null 2>&1; then + parse_arg_value "$(grep "^${_arg_name}=" "${ARG_FILE}")" + return + fi + fi + + # Return the default value, which may be empty, from the name=value pair. -- cwells + parse_arg_value "${_name_value_pair}" +} + +render() { + _file_path="${1}/${2}" + if [ -d "${_file_path}" ]; then # Recursively render every file in this directory. -- cwells + find "${_file_path}" \( -type d -name .git -prune \) -o -type f -print0 | $(eval "xargs -0 sed -i '' ${ARG_REPLACEMENTS}") + elif [ -f "${_file_path}" ]; then + eval "sed -i '' ${ARG_REPLACEMENTS} '${_file_path}'" + else + echo -e "${COLOR_YELLOW}Path not found for render: ${2}${COLOR_RESET}" + fi +} + # Handle special-case commands first. case "$1" in help|-h|--help) @@ -56,7 +112,7 @@ help|-h|--help) ;; esac -if [ $# -ne 1 ]; then +if [ $# -lt 1 ]; then bastille_usage fi @@ -87,7 +143,27 @@ if [ -z "${JAILS}" ]; then fi if [ -z "${HOOKS}" ]; then - HOOKS='LIMITS INCLUDE PRE FSTAB PF PKG OVERLAY CONFIG SYSRC SERVICE CMD' + HOOKS='LIMITS INCLUDE PRE FSTAB PF PKG OVERLAY CONFIG SYSRC SERVICE CMD RENDER' +fi + +# Check for an --arg-file parameter. -- cwells +for _script_arg in "$@"; do + case ${_script_arg} in + --arg-file) + # Parse whatever is next. -- cwells + _next_arg='true' ;; + *) + if [ "${_next_arg}" = 'true' ]; then # This is the parameter after --arg-file. -- cwells + _next_arg='' + ARG_FILE="${_script_arg}" + break + fi + ;; + esac +done + +if [ -n "${ARG_FILE}" ] && [ ! -f "${ARG_FILE}" ]; then + error_exit "File not found: ${ARG_FILE}" fi ## global variables @@ -113,6 +189,23 @@ for _jail in ${JAILS}; do fi fi + # This is parsed outside the HOOKS loop so an ARG file can be used with a Bastillefile. -- cwells + ARG_REPLACEMENTS='' + if [ -s "${bastille_template}/ARG" ]; then + while read _line; do + if [ -z "${_line}" ]; then + continue + fi + _arg_name=$(get_arg_name "${_line}") + _arg_value=$(get_arg_value "${_line}" "$@") + if [ -z "${_arg_value}" ]; then + echo -e "${COLOR_YELLOW}No value provided for arg: ${_arg_name}${COLOR_RESET}" + fi + # Build a list of sed commands like this: -e 's/${username}/root/g' -e 's/${domain}/example.com/g' + ARG_REPLACEMENTS="${ARG_REPLACEMENTS} -e 's/\${${_arg_name}}/${_arg_value}/g'" + done < "${bastille_template}/ARG" + fi + if [ -s "${bastille_template}/Bastillefile" ]; then # Ignore blank lines and comments. -- cwells SCRIPT=$(grep -v '^\s*$' "${bastille_template}/Bastillefile" | grep -v '^\s*#') @@ -121,19 +214,27 @@ for _jail in ${JAILS}; do ' set -f for _line in ${SCRIPT}; do + # First word converted to lowercase is the Bastille command. -- cwells _cmd=$(echo "${_line}" | awk '{print tolower($1);}') - _args=$(echo "${_line}" | awk '{$1=""; sub(/^ */, ""); print;}') + # Rest of the line with "arg" variables replaced will be the arguments. -- cwells + _args=$(echo "${_line}" | awk '{$1=""; sub(/^ */, ""); print;}' | eval "sed ${ARG_REPLACEMENTS}") # Apply overrides for commands/aliases and arguments. -- cwells case $_cmd in + arg) # This is a template argument definition. -- cwells + _arg_name=$(get_arg_name "${_args}") + _arg_value=$(get_arg_value "${_args}" "$@") + if [ -z "${_arg_value}" ]; then + echo -e "${COLOR_YELLOW}No value provided for arg: ${_arg_name}${COLOR_RESET}" + fi + # Build a list of sed commands like this: -e 's/${username}/root/g' -e 's/${domain}/example.com/g' + ARG_REPLACEMENTS="${ARG_REPLACEMENTS} -e 's/\${${_arg_name}}/${_arg_value}/g'" + continue + ;; cmd) # Allow redirection within the jail. -- cwells _args="sh -c '${_args}'" ;; - overlay) - _cmd='cp' - _args="${bastille_template}/${_args} /" - ;; cp|copy) _cmd='cp' # Convert relative "from" path into absolute path inside the template directory. -- cwells @@ -145,8 +246,16 @@ for _jail in ${JAILS}; do _cmd='mount' ;; include) _cmd='template' ;; + overlay) + _cmd='cp' + _args="${bastille_template}/${_args} /" + ;; pkg) _args="install -y ${_args}" ;; + render) # This is a path to one or more files needing arguments replaced by values. -- cwells + render "${bastille_jail_path}" "${_args}" + continue + ;; esac if ! eval "bastille ${_cmd} ${_jail} ${_args}"; then @@ -185,6 +294,10 @@ for _jail in ${JAILS}; do continue ;; PRE) _cmd='cmd' ;; + RENDER) # This is a path to one or more files needing arguments replaced by values. -- cwells + render "${bastille_jail_path}" "${_line}" + continue + ;; esac echo -e "${COLOR_GREEN}[${_jail}]:${_hook} -- START${COLOR_RESET}" @@ -198,6 +311,8 @@ for _jail in ${JAILS}; do if [ -z "${_line}" ]; then continue fi + # Replace "arg" variables in this line with the provided values. -- cwells + _line=$(echo "${_line}" | eval "sed ${ARG_REPLACEMENTS}") eval "_args=\"${_args_template}\"" bastille "${_cmd}" "${_jail}" ${_args} || exit 1 done < "${bastille_template}/${_hook}"