#!/usr/bin/env bash # docker-finance | modern accounting for the power-user # # Copyright (C) 2021-2024 Aaron Fiore (Founder, Evergreen Crypto LLC) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . [ -z "$DOCKER_FINANCE_CLIENT_REPO" ] && exit 1 # # "Libraries" # source "${DOCKER_FINANCE_CLIENT_REPO}/container/src/finance/lib/internal/lib_utils.bash" || exit 1 # # Implementation # if [ $UID -lt 1000 ]; then lib_utils::die_fatal "Do not run as root or system user!" fi # IMPORTANT: keep umask for security umask o-rwx if ! docker compose version 1>/dev/null; then lib_utils::die_fatal "Docker compose plugin not installed" fi if [ -z "$EDITOR" ]; then editors=("vim" "vi" "emacs" "nano") for editor in "${editors[@]}"; do hash "$editor" &>/dev/null && export EDITOR="$editor" && break done if [ $? -ne 0 ]; then lib_utils::die_fatal "Shell EDITOR is not set, export EDITOR in your shell" fi fi # # "Constructor" for environment generation # # 1. Sets client-side environment with defaults or use existing environment # 2. If configured, resets to alternative environment configuration after bootstrap # # NOTE: some bootstrapped defaults are ignored by environment file (as seen below) # function lib_gen::gen() { [ -z "$DOCKER_FINANCE_CLIENT_REPO" ] && lib_utils::die_fatal # NOTE: global_* *MUST* be reset after sourcing new env file # # Generate `docker-finance` version # global_repo_manifest="${DOCKER_FINANCE_CLIENT_REPO}/client/docker-finance.yaml" declare -g global_repo_manifest global_client_version="$(grep '^version: ' $global_repo_manifest | sed -e 's/version: "//' -e 's/"//g')" # shellcheck disable=SC2034 # used during env gen declare -g global_client_version # # If empty environment: # # 1. Poke at possible (default) location of end-user environment file # a. If found, point to new location and read/reset from there # # 2. If file not found, bootstrap with defaults # # Environment (end-user) global_conf_filename="${USER}@$(uname -n)" declare -g global_conf_filename # Environment (end-user) tag dir (not full path) [ -z "$global_tag" ] && lib_utils::die_fatal global_tag_dir="client/$(uname -s)-$(uname -m)/${global_platform}/${global_tag}" declare -g global_tag_dir local _env_dir="/home/${USER}/.config/docker-finance.d/${global_tag_dir}/env" # NOTE: keep aligned with gen.bash local _env_file="${_env_dir}/${global_conf_filename}" # shellcheck source=/dev/null [ -f "$_env_file" ] && source "$_env_file" if [ -z "$DOCKER_FINANCE_CLIENT_CONF" ]; then # shellcheck source=/dev/null source "${DOCKER_FINANCE_CLIENT_REPO}/client/docker-finance.d/client/env/gen.bash" fi [ -z "$DOCKER_FINANCE_CLIENT_CONF" ] \ && lib_utils::die_fatal "Defaults not generated! (${global_repo_env_file})" lib_gen::__set_client_globals # # Reset environment with user-provided (user-defined) existing file (if available) # _env_dir="${DOCKER_FINANCE_CLIENT_CONF}/${global_tag_dir}/env" _env_file="${_env_dir}/${global_conf_filename}" if [ -f "$_env_file" ]; then if [ -s "$_env_file" ]; then # Re-bootstrap with (new) environment lib_utils::print_debug "Environment found! Using '${_env_file}'" lib_gen::__read_env "$_env_file" lib_gen::__set_client_globals else lib_utils::print_warning \ "Client environment '${_env_file}' is empty! Writing defaults" lib_gen::__write_env "$_env_file" fi else [ -z "$global_command" ] && lib_utils::die_fatal [ -z "$global_basename" ] && lib_utils::die_fatal if [[ -z "$global_command" || "$global_command" != "gen" ]]; then lib_utils::die_fatal \ "Client environment not found! Run gen command'" fi lib_utils::print_info \ "Client environment not found, writing defaults to '${_env_file}'" lib_gen::__write_env "$_env_file" fi } # # Set client globals from environment # function lib_gen::__set_client_globals() { lib_utils::print_debug "Setting (or resetting) client globals" # # Repository env # [ -z "$DOCKER_FINANCE_CLIENT_REPO" ] && lib_utils::die_fatal # Generate `docker-finance` version global_repo_manifest="${DOCKER_FINANCE_CLIENT_REPO}/client/docker-finance.yaml" declare -g global_repo_manifest lib_utils::print_debug "global_repo_manifest=${global_repo_manifest}" global_client_version="$(grep '^version: ' $global_repo_manifest | sed -e 's/version: "//' -e 's/"//g')" # shellcheck disable=SC2034 # used during env gen declare -g global_client_version lib_utils::print_debug "global_client_version=${global_client_version}" # This does not reset when reading env; export again export DOCKER_FINANCE_VERSION="$global_client_version" lib_utils::print_debug "DOCKER_FINANCE_VERSION=${DOCKER_FINANCE_VERSION}" # Repository-provided (not user-defined) default environment global_repo_conf_dir="${DOCKER_FINANCE_CLIENT_REPO}/client/docker-finance.d" declare -g global_repo_conf_dir lib_utils::print_debug "global_repo_conf_dir=${global_repo_conf_dir}" # Environment (repo) declare -g global_repo_env_file="${global_repo_conf_dir}/client/env/gen.bash" [ ! -f "$global_repo_env_file" ] \ && lib_utils::die_fatal "Missing environment defaults! ($global_repo_env_file)" lib_utils::print_debug "global_repo_env_file=${global_repo_env_file}" # # Repository env (Dockerfiles) # # Set image type [ -z "$global_platform" ] && lib_utils::die_fatal case "$global_platform" in archlinux | ubuntu) global_platform_image="finance" ;; dev-tools) global_platform_image="dev-tools" ;; *) lib_utils::die_fatal "unsupported platform" ;; esac declare -g global_platform_image lib_utils::print_debug "global_platform_image=${global_platform_image}" # Base location of Docker-related files (.in and final out files) global_repo_dockerfiles="${DOCKER_FINANCE_CLIENT_REPO}/client/Dockerfiles/${global_platform_image}" # shellcheck disable=SC2034 # used in lib_docker declare -g global_repo_dockerfiles lib_utils::print_debug "global_repo_dockerfiles=${global_repo_dockerfiles}" # Base custom end-user .in Dockerfile (to be appended to final Dockerfile after installation) declare -g global_repo_custom_dockerfile="${global_repo_conf_dir}/client/Dockerfiles/${global_platform_image}/Dockerfile.${global_platform}.in" [ ! -f "$global_repo_custom_dockerfile" ] \ && lib_utils::die_fatal "Missing default custom Dockerfile '${global_repo_custom_dockerfile}'" lib_utils::print_debug "global_repo_custom_dockerfile=${global_repo_custom_dockerfile}" # # Client-side env # [ -z "$global_tag_dir" ] && lib_utils::die_fatal # Environment (end-user) format [ -z "$DOCKER_FINANCE_USER" ] && lib_utils::die_fatal global_conf_filename="${DOCKER_FINANCE_USER}@$(uname -n)" declare -g global_conf_filename lib_utils::print_debug "global_conf_filename=${global_conf_filename}" # Environment file (if available) local _client_env_dir="${DOCKER_FINANCE_CLIENT_CONF}/${global_tag_dir}/env" [ ! -d "$_client_env_dir" ] && mkdir -p "$_client_env_dir" global_env_file="${_client_env_dir}/${global_conf_filename}" lib_utils::print_debug "global_env_file=${global_env_file}" # Custom Dockerfile (if available) local _client_dockerfile_dir="${DOCKER_FINANCE_CLIENT_CONF}/${global_tag_dir}/Dockerfiles" [ ! -d "$_client_dockerfile_dir" ] && mkdir -p "$_client_dockerfile_dir" global_custom_dockerfile="${_client_dockerfile_dir}/${global_conf_filename}" lib_utils::print_debug "global_custom_dockerfile=${global_custom_dockerfile}" # NOTE: # # Client env tag format is avoided because: # # - We copy over static bash_aliases in Dockerfile, # and superscript must be referenced by that static path # # - The needed dynamicness appears to not be satisfied via docker-compose local _client_shell_dir="${DOCKER_FINANCE_CLIENT_CONF}/container/shell" [ ! -d "$_client_shell_dir" ] && mkdir -p "$_client_shell_dir" global_shell_file="${_client_shell_dir}/superscript.bash" lib_utils::print_debug "global_shell_file=${global_shell_file}" # Client view of client portion of repository global_repo_client="${DOCKER_FINANCE_CLIENT_REPO}/client" [ ! -d "$global_repo_client" ] && lib_utils::die_fatal "Repository '${global_repo_client}' not found!" lib_utils::print_debug "global_repo_client=${global_repo_client}" # Backup-file extension # TODO: make configurable global_suffix="$(date +%Y-%m-%d_%H:%M:%S)" lib_utils::print_debug "global_suffix=${global_suffix}" } # # Get/Set client-side environment with given file # function lib_gen::__read_env() { local _file="$1" # Get environment local _env=() while read _line; do _env+=("$_line") done < <(printenv | grep -Eo "^DOCKER_FINANCE[^=]+") # Reset environment for _line in "${_env[@]}"; do lib_utils::print_debug "Unsetting $_line" unset "$_line" done # Set, if a script that generates env if [[ "$(head -n1 $_file)" =~ (bin|env|sh|bash) ]]; then # shellcheck source=/dev/null source "$global_repo_env_file" return $? fi # Set, if env file format (docker / bash) while read _line; do # Ignore comments if [[ ! "$_line" =~ ^# ]]; then # Don't allow manipulating version via file if [[ "$_line" =~ ^DOCKER_FINANCE_VERSION ]]; then continue fi # Export valid line export "${_line?}" # SC2163 lib_utils::print_debug "$_line" fi done <"$_file" } # # Write client-side environment to given file # function lib_gen::__write_env() { lib_utils::print_debug "Writing environment" unset DOCKER_FINANCE_VERSION # Must be generated internally printenv | grep -E "DOCKER_FINANCE" | sort >"$1" } # # Generate new configurations # function lib_gen::generate() { lib_utils::print_custom "\n" lib_utils::print_info "Generating client/container environment" # WARNING: client generation MUST be done before container generation lib_gen::__gen_client if [ $? -eq 0 ]; then if [ "$global_platform" != "dev-tools" ]; then lib_gen::__gen_container fi fi if [ $? -eq 0 ]; then lib_utils::print_info "Congratulations, environment sucessfully generated!" lib_utils::print_custom "\n" else lib_utils::die_fatal "Environment not fully generated! Try again" fi } function lib_gen::__gen_client() { lib_utils::print_debug "Generating client" # # Client-side environment file # # Backup existing file if [ -f "$global_env_file" ]; then lib_utils::print_custom " \e[32m│\e[0m\n" lib_utils::print_custom " \e[32m├─\e[34;1m Client-side environment found, backup then generate new one? [Y/n] \e[0m" read -p "" _read local _confirm="${_read:-y}" if [[ "$_confirm" == [yY] ]]; then cp -a "$global_env_file" "${global_env_file}_${global_suffix}" \ || lib_utils::die_fatal # Get/Set with repository defaults lib_utils::print_debug "Reading $global_repo_env_file" lib_gen::__read_env "$global_repo_env_file" lib_utils::print_debug "Writing $global_env_file" lib_gen::__write_env "$global_env_file" fi fi lib_utils::print_custom " \e[32m│ └─\e[34m Edit (new) environment now? [Y/n] \e[0m" read -p "" _read local _confirm="${_read:-y}" [[ "$_confirm" == [yY] ]] && $EDITOR "$global_env_file" # Get/Set new (edited) environment variables lib_gen::__read_env "$global_env_file" lib_gen::__set_client_globals # # Custom (optional) Dockerfile # # Backup existing custom Dockerfile if [ -f "$global_custom_dockerfile" ]; then lib_utils::print_custom " \e[32m│\e[0m\n" lib_utils::print_custom " \e[32m├─\e[34;1m Custom (optional) Dockerfile found, backup then generate new one? [Y/n] \e[0m" read -p "" _read local _confirm="${_read:-y}" if [[ "$_confirm" == [yY] ]]; then cp -a "$global_custom_dockerfile" "${global_custom_dockerfile}_${global_suffix}" || lib_utils::die_fatal cp -fa "$global_repo_custom_dockerfile" "$global_custom_dockerfile" || lib_utils::die_fatal fi else lib_utils::print_custom " \e[32m│\e[0m\n" lib_utils::print_custom " \e[32m├─\e[34;1m Generating new custom (optional) Dockerfile\e[0m\n" lib_utils::print_debug "$global_repo_custom_dockerfile" lib_utils::print_debug "$global_custom_dockerfile" cp -a "$global_repo_custom_dockerfile" "$global_custom_dockerfile" fi lib_utils::print_custom " \e[32m│ └─\e[34m Edit (new) custom Dockerfile now? [Y/n] \e[0m" read -p "" _read local _confirm="${_read:-y}" [[ "$_confirm" == [yY] ]] && $EDITOR "$global_custom_dockerfile" || return 0 } function lib_gen::__gen_container() { lib_utils::print_debug "Generating container" # # Generate plugins (custom) # [ -z "$DOCKER_FINANCE_CLIENT_PLUGINS" ] && lib_utils::die_fatal if [ ! -d "$DOCKER_FINANCE_CLIENT_PLUGINS" ]; then mkdir -p "$DOCKER_FINANCE_CLIENT_PLUGINS" || lib_utils::die_fatal fi lib_gen::__gen_plugins # # Generate flow # lib_utils::print_custom " \e[32m│\e[0m\n" lib_utils::print_custom " \e[32m├─\e[34;1m Generate (or update) container flow (profiles, etc.)? [Y/n] \e[0m" read -p "" _read local _confirm="${_read:-y}" if [[ "$_confirm" != [yY] ]]; then lib_utils::print_custom " \e[32m│\e[0m\n" lib_utils::print_normal "" return 0 fi [ -z "$DOCKER_FINANCE_CLIENT_FLOW" ] && lib_utils::die_fatal if [ ! -d "$DOCKER_FINANCE_CLIENT_FLOW" ]; then mkdir -p "$DOCKER_FINANCE_CLIENT_FLOW" || lib_utils::die_fatal fi lib_gen::__gen_times lib_gen::__gen_profile } # # Generate plugins (custom): # # - Provides: # - A layout to drop-in custom plugins # * Underlying impl expects this layout # function lib_gen::__gen_plugins() { lib_utils::print_debug "Generating custom plugins layout" local -r _client="${DOCKER_FINANCE_CLIENT_PLUGINS}/client" if [ ! -d "${_client}/docker" ]; then mkdir -p "${_client}/docker" || lib_utils::die_fatal fi local -r _container="${DOCKER_FINANCE_CLIENT_PLUGINS}/container" if [ ! -d "${_container}/finance" ]; then mkdir -p "${_container}/finance" || lib_utils::die_fatal fi if [ ! -d "${_container}/root" ]; then mkdir -p "${_container}/root" || lib_utils::die_fatal fi } # # Generate flow: times # # - Provides a place for: # * timew database # * Anything time-related (among all profiles) # function lib_gen::__gen_times() { lib_utils::print_debug "Generating times" local -r _times="${DOCKER_FINANCE_CLIENT_FLOW}/times" if [ ! -d "$_times" ]; then mkdir -p "$_times" || lib_utils::die_fatal fi # NOTE: timew database will be created upon first call } # # Generate flow: profile/subprofile # function lib_gen::__gen_profile() { lib_utils::print_debug "Generating profiles" local -r _profiles="${DOCKER_FINANCE_CLIENT_FLOW}/profiles" if [ ! -d "$_profiles" ]; then mkdir -p "$_profiles" || lib_utils::die_fatal fi # # Profile: development setup # lib_utils::print_custom " \e[32m│ │\e[0m\n" lib_utils::print_custom " \e[32m│ ├─\e[34;1m Will this profile be used for development and/or demonstration? [N/y] \e[0m" read -p "" _read local _is_testing="${_read:-false}" if [[ "$_read" == [yY] ]]; then _is_testing=true fi lib_utils::print_debug "_is_testing=${_is_testing}" # # Profile: construct full path to subprofile # lib_utils::print_custom " \e[32m│ │ │\e[0m\n" lib_utils::print_custom " \e[32m│ │ ├─\e[34m Enter profile name (e.g., family in 'family/alice'): \e[0m" read -p "" _read local -r _profile="${_read:-default}" lib_utils::print_custom " \e[32m│ │ │ └─\e[34m Using profile:\e[0m ${_profile}\n" lib_utils::print_custom " \e[32m│ │ │\e[0m\n" lib_utils::print_custom " \e[32m│ │ └─\e[34m Enter subprofile name (e.g., alice in 'family/alice'): \e[0m" read -p "" _read local -r _subprofile="${_read:-${global_user}}" lib_utils::print_custom " \e[32m│ │ └─\e[34m Using subprofile:\e[0m ${_subprofile}\n" if [ -d "${_profiles}/${_profile}/${_subprofile}" ]; then lib_utils::print_custom " \e[32m│ │ └─\e[34m Subprofile exists! Continue with backup and generation? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" if [[ "$_confirm" != [yY] ]]; then lib_utils::print_custom " \e[32m│\e[0m\n" lib_utils::print_normal "" return 0 fi fi # # Profile: execute profile-specific # lib_utils::print_custom " \e[32m│ │\e[0m\n" lib_utils::print_custom " \e[32m│ ├─\e[34;1m Generate (or update) joint client/container shell script (superscript)? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" if [[ "$_confirm" == [yY] ]]; then local _gen_path="\${DOCKER_FINANCE_CONTAINER_FLOW}/profiles/${_profile}/${_subprofile}" # \$ for container view local _gen_conf_path="${_gen_path}/docker-finance.d" lib_utils::print_debug "_gen_path=${_gen_path}" lib_utils::print_debug "_gen_conf_path=${_gen_conf_path}" lib_gen::__gen_subprofile_shell "$_gen_path" "$_gen_conf_path" fi lib_utils::print_custom " \e[32m│ │\e[0m\n" lib_utils::print_custom " \e[32m│ ├─\e[34;1m Generate (or update) hledger configuration file? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" if [[ "$_confirm" == [yY] ]]; then local _gen_path="${_profiles}/${_profile}/${_subprofile}" # client's view local _gen_conf_path="${_gen_path}/docker-finance.d" lib_utils::print_debug "_gen_path=${_gen_path}" lib_utils::print_debug "_gen_conf_path=${_gen_conf_path}" lib_gen::__gen_subprofile_hledger "$_gen_path" "$_gen_conf_path" fi lib_utils::print_custom " \e[32m│ │\e[0m\n" lib_utils::print_custom " \e[32m│ ├─\e[34;1m Generate (or update) container flow configs and/or accounts? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" if [[ "$_confirm" == [yY] ]]; then local _gen_path="${_profiles}/${_profile}/${_subprofile}" local _gen_conf_path="${_gen_path}/docker-finance.d" lib_utils::print_debug "_gen_path=${_gen_path}" lib_utils::print_debug "_gen_conf_path=${_gen_conf_path}" lib_gen::__gen_subprofile_flow "$_gen_path" "$_gen_conf_path" fi } # # Subprofile: generate client-side container superscript # TODO: separate superscript gen from subprofile append # function lib_gen::__gen_subprofile_shell() { local -r _gen_path="$1" local -r _gen_conf_path="$2" # Subprofile source added to superscript local -r _source="source \"${_gen_conf_path}/shell/subprofile.bash\"" local _file="$global_shell_file" if [ -f "$_file" ]; then # Append subprofile source to existing file lib_utils::print_custom " \e[32m│ │ └─\e[34m Shell superscript found, append new subprofile to existing file? [Y/n] \e[0m" read -p "" _read local _confirm="${_read:-y}" if [[ "$_confirm" == [yY] ]]; then grep "$_source" "$_file" >&/dev/null || sed -i "$(wc -l <$_file)i\\$_source\\" "$_file" fi # Or generate new file lib_utils::print_custom " \e[32m│ │ └─\e[34m Backup existing superscript and generate a new one? [N/y] \e[0m" read -p "" _read local _confirm="${_read:-n}" if [[ "$_confirm" == [yY] ]]; then # Backup local -r _backup=("cp" "-a" "$_file" "${_file}_${global_suffix}") lib_utils::print_debug "${_backup[@]}" "${_backup[@]}" || lib_utils::die_fatal # Write lib_gen::__gen_subprofile_shell_write fi local _print_custom=" \e[32m│ │ └─\e[34m Edit (new) superscript now? [Y/n] \e[0m" else # Generate new default file lib_gen::__gen_subprofile_shell_write local _print_custom=" \e[32m│ │ └─\e[34m Edit (new) superscript now? [Y/n] \e[0m" fi lib_utils::print_custom "$_print_custom" read -p "" _read local _confirm="${_read:-y}" [[ "$_confirm" == [yY] ]] && $EDITOR "$_file" } function lib_gen::__gen_subprofile_shell_write() { [ -z "$global_repo_conf_dir" ] && lib_utils::die_fatal lib_utils::print_debug "global_repo_conf_dir=${global_repo_conf_dir}" [ -z "$global_client_version" ] && lib_utils::die_fatal lib_utils::print_debug "global_client_version=${global_client_version}" sed \ -e "s:@DOCKER_FINANCE_SUBPROFILE_SOURCE@:${_source}:g" \ -e "s:@DOCKER_FINANCE_VERSION@:${global_client_version}:g" \ "${global_repo_conf_dir}/container/shell/superscript.bash.in" >"$_file" } # # Subprofile: generate hledger configuration file # function lib_gen::__gen_subprofile_hledger() { local -r _gen_path="$1" local -r _gen_conf_path="$2" local _dir="${_gen_conf_path}/hledger" [ ! -d "$_dir" ] && mkdir -p "$_dir" local _file="${_dir}/hledger.conf" if [ -f "$_file" ]; then lib_utils::print_custom " \e[32m│ │ │ └─\e[34m hledger configuration found, backup then generate new one? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" if [[ "$_confirm" == [yY] ]]; then cp -a "$_file" "${_file}_${global_suffix}" || lib_utils::die_fatal lib_gen::__gen_subprofile_hledger_write "$_file" local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) hledger configuration now? [Y/n] \e[0m" fi else lib_gen::__gen_subprofile_hledger_write "$_file" local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) hledger configuration now? [Y/n] \e[0m" fi lib_utils::print_custom "$_print_custom" read -p "" _read _confirm="${_read:-y}" [[ "$_confirm" == [yY] ]] && $EDITOR "$_file" } function lib_gen::__gen_subprofile_hledger_write() { local _file="$1" [ -z "$_file" ] && lib_utils::die_fatal [ -z "$global_repo_conf_dir" ] && lib_utils::die_fatal lib_utils::print_debug "global_repo_conf_dir=${global_repo_conf_dir}" [ -z "$global_client_version" ] && lib_utils::die_fatal lib_utils::print_debug "global_client_version=${global_client_version}" sed \ -e "s:@DOCKER_FINANCE_VERSION@:${global_client_version}:g" \ "${global_repo_conf_dir}/container/hledger/hledger.conf.in" >"$_file" } # # Subprofile: generate flow # function lib_gen::__gen_subprofile_flow() { local -r _gen_path="$1" local -r _gen_conf_path="$2" lib_utils::print_custom " \e[32m│ │ │\e[0m\n" lib_utils::print_custom " \e[32m│ │ ├─\e[34;1m Generate (or update) subprofile's shell script? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" [[ "$_confirm" == [yY] ]] && lib_gen::__gen_subprofile_flow_shell lib_utils::print_custom " \e[32m│ │ │\e[0m\n" lib_utils::print_custom " \e[32m│ │ ├─\e[34;1m Generate (or update) subprofile's fetch configuration? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" [[ "$_confirm" == [yY] ]] && lib_gen::__gen_subprofile_flow_fetch lib_utils::print_custom " \e[32m│ │ │\e[0m\n" lib_utils::print_custom " \e[32m│ │ ├─\e[34;1m Generate (or update) subprofile's financial metadata? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" [[ "$_confirm" == [yY] ]] && lib_gen::__gen_subprofile_flow_meta lib_utils::print_custom " \e[32m│ │ │\e[0m\n" lib_utils::print_custom " \e[32m│ │ ├─\e[34;1m Generate (or update) subprofile's hledger-flow accounts? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" [[ "$_confirm" == [yY] ]] && lib_gen::__gen_subprofile_flow_accounts lib_utils::print_custom " \e[32m│\e[0m\n" # Placeholder connector between hledger-flow code and end-user's hledger-flow profiles # NOTE: entrypoint will (MUST) update this on every container run (yes, looks different than entrypoint) lib_utils::print_custom " \e[32m├─\e[34;1m Connecting flow sources \e[0m\n" [ -z "$DOCKER_FINANCE_CONTAINER_REPO" ] && lib_utils::die_fatal ln -f -s "${DOCKER_FINANCE_CONTAINER_REPO}/src/hledger-flow" "${DOCKER_FINANCE_CLIENT_FLOW}/src" lib_utils::print_custom " \e[32m│\e[0m\n" } # # Subprofile: flow: generate subprofile script # function lib_gen::__gen_subprofile_flow_shell() { local _dir="${_gen_conf_path}/shell" [ ! -d "$_dir" ] && mkdir -p "$_dir" local _file="${_dir}/subprofile.bash" if [ -f "$_file" ]; then lib_utils::print_custom " \e[32m│ │ │ └─\e[34m Subprofile script found, backup then generate new one? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" if [[ "$_confirm" == [yY] ]]; then cp -a "$_file" "${_file}_${global_suffix}" || lib_utils::die_fatal lib_gen::__gen_subprofile_flow_shell_write local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile script now? [Y/n] \e[0m" fi else lib_gen::__gen_subprofile_flow_shell_write local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile script now? [Y/n] \e[0m" fi lib_utils::print_custom "$_print_custom" read -p "" _read _confirm="${_read:-y}" [[ "$_confirm" == [yY] ]] && $EDITOR "$_file" } function lib_gen::__gen_subprofile_flow_shell_write() { [ -z "$DOCKER_FINANCE_CONTAINER_CMD" ] && lib_utils::die_fatal sed \ -e "s:@DOCKER_FINANCE_CONTAINER_CMD@:${DOCKER_FINANCE_CONTAINER_CMD}:g" \ -e "s:@DOCKER_FINANCE_CONTAINER_REPO@:${DOCKER_FINANCE_CONTAINER_REPO}:g" \ -e "s:@DOCKER_FINANCE_VERSION@:${global_client_version}:g" \ -e "s:@DOCKER_FINANCE_PROFILE@:${_profile}:g" \ -e "s:@DOCKER_FINANCE_SUBPROFILE@:${_subprofile}:g" \ "${global_repo_conf_dir}/container/shell/subprofile.bash.in" >"$_file" } # # Subprofile: flow: generate fetch config # function lib_gen::__gen_subprofile_flow_fetch() { local _dir="${_gen_conf_path}/fetch" [ ! -d "$_dir" ] && mkdir -p "$_dir" local _file="${_dir}/fetch.yaml" if [ -f "$_file" ]; then lib_utils::print_custom " \e[32m│ │ │ └─\e[34m Subprofile's fetch configuration found, backup then generate new one? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" if [[ "$_confirm" == [yY] ]]; then cp -a "$_file" "${_file}_${global_suffix}" || lib_utils::die_fatal lib_gen::__gen_subprofile_flow_fetch_write local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile's fetch configuration now? [Y/n] \e[0m" fi else lib_gen::__gen_subprofile_flow_fetch_write local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile's fetch configuration now? [Y/n] \e[0m" fi lib_utils::print_custom "$_print_custom" read -p "" _read _confirm="${_read:-y}" [[ "$_confirm" == [yY] ]] && $EDITOR "$_file" } function lib_gen::__gen_subprofile_flow_fetch_write() { [ -z "$_profile" ] && lib_utils::die_fatal [ -z "$_subprofile" ] && lib_utils::die_fatal [ -z "$global_repo_conf_dir" ] && lib_utils::die_fatal [ -z "$_file" ] && lib_utils::die_fatal [ -z "$global_client_version" ] && lib_utils::die_fatal sed \ -e "s:@DOCKER_FINANCE_VERSION@:${global_client_version}:g" \ -e "s:@DOCKER_FINANCE_PROFILE@:${_profile}:g" \ -e "s:@DOCKER_FINANCE_SUBPROFILE@:${_subprofile}:g" \ "${global_repo_conf_dir}/container/fetch/fetch.yaml.in" >"$_file" } # # Subprofile: flow: generate financial metadata # function lib_gen::__gen_subprofile_flow_meta() { local _dir="${_gen_conf_path}/meta" [ ! -d "$_dir" ] && mkdir -p "$_dir" local _file="${_dir}/meta.csv" if [ -f "$_file" ]; then lib_utils::print_custom " \e[32m│ │ │ └─\e[34m Subprofile's financial metadata found, backup then generate new one? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" if [[ "$_confirm" == [yY] ]]; then cp -a "$_file" "${_file}_${global_suffix}" || lib_utils::die_fatal lib_gen::__gen_subprofile_flow_meta_write local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile's financial metadata now? [Y/n] \e[0m" fi else lib_gen::__gen_subprofile_flow_meta_write local _print_custom=" \e[32m│ │ │ └─\e[34m Edit (new) subprofile's financial metadata now? [Y/n] \e[0m" fi lib_utils::print_custom "$_print_custom" read -p "" _read _confirm="${_read:-y}" [[ "$_confirm" == [yY] ]] && $EDITOR "$_file" } function lib_gen::__gen_subprofile_flow_meta_write() { # Deletes default comments or else ROOT meta sample won't work out-of-the-box sed \ -e "/\/\/\\!/d" \ -e "s:@DOCKER_FINANCE_VERSION@:${global_client_version}:g" \ "${global_repo_conf_dir}/container/meta/meta.csv.in" >"$_file" } # # Subprofile: flow: generate accounts # function lib_gen::__gen_subprofile_flow_accounts() { lib_utils::print_debug "Generating accounts" local _subprofile_path="${_gen_path}/import/${_subprofile}" lib_utils::print_debug "_subprofile_path=${_subprofile_path}" if [ -d "$_subprofile_path" ]; then lib_utils::print_custom " \e[32m│ │ │ ├─\e[34m Subprofile import path exists! Continue with account generation (files will be backed up)? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" [[ "$_confirm" == [yY] ]] || return 0 else mkdir -p "$_subprofile_path" fi # docker-finance templates local _templates_paths mapfile -t _templates_paths < <(find ${DOCKER_FINANCE_CLIENT_REPO}/container/src/hledger-flow/accounts -name template | sort) declare -r _templates_paths lib_utils::print_debug "_templates_paths=${_templates_paths[*]}" lib_utils::print_custom " \e[32m│ │ │ │\e[0m\n" lib_utils::print_custom " \e[32m│ │ │ ├─\e[34;1m Generate individual subprofile accounts instead of generating them all at once? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" # Cycle through all available docker-finance templates and populate subprofile if [[ $_confirm == [yY] ]]; then for _template_path in "${_templates_paths[@]}"; do lib_utils::print_debug "_template_path=${_template_path}" lib_utils::print_custom " \e[32m│ │ │ │ │\e[0m\n" lib_utils::print_custom " \e[32m│ │ │ │ ├─\e[34m\e[1m Generate $(echo $_template_path | rev | cut -d/ -f2 | rev) ? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" [[ "$_confirm" == [yY] ]] \ && lib_gen::__gen_subprofile_flow_accounts_populate "$_subprofile_path" "$_template_path" "$_is_testing" done else for _template_path in "${_templates_paths[@]}"; do lib_utils::print_debug "$_subprofile_path $_template_path $_is_testing" lib_gen::__gen_subprofile_flow_accounts_populate "$_subprofile_path" "$_template_path" "$_is_testing" done fi } function lib_gen::__gen_subprofile_flow_accounts_populate() { [ -z "$1" ] && lib_utils::die_fatal [ -z "$2" ] && lib_utils::die_fatal [ -z "$3" ] && lib_utils::die_fatal local _subprofile_path="$1" local _template_path="$2" local _is_testing="$3" # Continue if hledger-flow account exists local _continue _continue=true local _account_path _account_path="${_subprofile_path}/$(echo $_template_path | rev | cut -d/ -f2 | rev)" lib_utils::print_debug "_account_path=${_account_path}" # TODO: doesn't check for blockchain explorer shared rules/bash if [ -d "$_account_path" ]; then lib_utils::print_custom " \e[32m│ │ │ │ │ └─\e[34m Account exists! Continue with backup and generation? [Y/n] \e[0m" read -p "" _read _confirm="${_read:-y}" [[ "$_confirm" == [yY] ]] || _continue=false fi # Populate with template if [[ $_continue == true ]]; then # Populates account and blockchain explorer's shared rules/preprocess # (at top-level, along with all the account's shared rules/preprocess) lib_utils::print_debug "Copying '${_template_path}/*' to '${_subprofile_path}/'" cp -a -R --backup --suffix="_${global_suffix}" \ "${_template_path}"/* "$_subprofile_path/" || lib_utils::die_fatal # If not blockchain explorers (since those exist as subaccounts) if [[ ! "$_template_path" =~ "blockchain-explorers" ]]; then # Add year to 1-in while read _in_path; do local _year_path _year_path="${_in_path}/$(date +%Y)" lib_utils::print_debug "Making year path '${_year_path}'" mkdir -p "$_year_path" # Appropriate test data if testing local _mockup="${_in_path}/mockup" lib_utils::print_debug "Getting mockup '${_mockup}'" if [[ $_is_testing == true ]]; then lib_utils::print_debug "Copying mockup to '${_in_path}'" cp -a -R --backup --suffix="_${global_suffix}" "${_mockup}"/* "${_in_path}/" || lib_utils::die_fatal fi # Always remove original test data rm -fr "$_mockup" done < <(find "$_account_path" -name 1-in) fi fi } # vim: sw=2 sts=2 si ai et