#!/bin/bash -
# ------------------------------------------------------------------------------
# setup-venv.sh
# =============
#
# Scope Native
# Copyright (C) 2025 by RaySoft, Zurich, Switzerland
# License GNU General Public License (GPL) 2.0
# https://www.gnu.org/licenses/gpl2.txt
#
# ------------------------------------------------------------------------------
set -o 'noglob' -o 'nounset' -o 'pipefail' # -o 'errexit' -o 'xtrace'
# ------------------------------------------------------------------------------
# Path to the global libraries
SV_DE_GLOBAL_LIB="${HOME}/dev/lib"
# Global libraries to be loaded
SV_GLOBAL_LIBS=('nf' 'os')
# Path to the development environment
SV_DE_DEV_PATH="${HOME}/dev"
# Path to the configuration file
SV_FE_CONFIG_PATH="${SV_DE_DEV_PATH}/setup-venv.yaml"
# Path to the 'venv-runner.sh' file
SV_FE_RUNNER_PATH="${SV_DE_DEV_PATH}/venv-runner.sh"
# ------------------------------------------------------------------------------
SV_X_LN=('/opt/homebrew/bin/gln' '--force')
SV_X_PYTHON=('/opt/homebrew/bin/python3')
SV_X_RM=('/opt/homebrew/bin/grm' '--recursive')
SV_X_YQ=('/opt/homebrew/bin/yq')
# ------------------------------------------------------------------------------
# Test if library path is available
if [[ ! -d "${SV_DE_GLOBAL_LIB}" ]]; then
echo "Error finding directory: ${SV_DE_GLOBAL_LIB}!"
exit 1
fi
# Load libraries
for lib in "${SV_GLOBAL_LIBS[@]}"; do
lib="${SV_DE_GLOBAL_LIB}/${lib}.sh"
if [[ ! -f "${lib}" ]]; then
echo "Error finding library: ${lib}!"
exit 1
fi
if ! source "${lib}"; then
exit 1
fi
done
# Test the defined variables
if ! os::test_env 'SV'; then
exit 1
fi
# Get the list of all projects from the configuration file
if ! mapfile -t 'projects' < <( \
"${SV_X_YQ[@]}" 'keys[]' <"${SV_FE_CONFIG_PATH}" 2>'/dev/null' \
)
then
nf::carp -e 'Error getting the list of projects!'
fi
for project in "${projects[@]}"; do
nf::print_heading '=' 'b' "${project}"
# Get the path to the project from the configuration file
if ! project_path="$( \
"${SV_X_YQ[@]}" ".\"${project}\".path" <"${SV_FE_CONFIG_PATH}" \
2>'/dev/null' \
)"
then
nf::carp -e "Error getting the project path: ${project}!"
fi
project_path+="/${project}"
# Get the 'requirements' boolean from the configuration file that defines
# whether the 'requirements.txt' file is written or not
if ! requ_bool="$( \
"${SV_X_YQ[@]}" ".\"${project}\".requirements" <"${SV_FE_CONFIG_PATH}" \
2>'/dev/null' \
)"
then
nf::carp -e "Error getting the 'requirements' boolean: ${project}!"
fi
requ_path="${project_path}/requirements.txt"
# Remove the 'requirements.txt' file if it is defined in the configuration
# file and the file already exists
if [[ "${requ_bool}" == 'true' && -f "${requ_path}" ]]; then
if ! "${SV_X_RM[@]}" "${requ_path}" >'/dev/null' 2>&1; then
nf::carp "Error removing file: ${requ_path}!"
fi
fi
venv_path="${project_path}/venv"
# Remove the virtual environment if it already exists
if [[ -d "${venv_path}" ]]; then
if ! "${SV_X_RM[@]}" "${venv_path}" >'/dev/null' 2>&1; then
nf::carp "Error removing directory: ${venv_path}!"
continue
fi
fi
# Make the 'venv' directory
if ! os::mkdir_ifn "${project_path}/venv"; then
continue
fi
# Get the 'runner' boolean from the configuration file that defines whether
# the ‘venv-runner.sh’ script is written or not
if ! runner_bool="$( \
"${SV_X_YQ[@]}" ".\"${project}\".runner" <"${SV_FE_CONFIG_PATH}" \
2>'/dev/null' \
)"
then
nf::carp -e "Error getting the 'runner' boolean: ${project}!"
fi
runner_file="${project_path}/src/${SV_FE_RUNNER_PATH##*/}"
# Make the 'src' directory and hard link the 'venv-runner.sh' script into the
# directory if it is defined in the configuration file
if [[ "${runner_bool}" == 'true' ]]; then
if ! os::mkdir_ifn "${project_path}/src"; then
continue
fi
if ! "${SV_X_LN[@]}" "${SV_FE_RUNNER_PATH}" "${runner_file}" \
>'/dev/null' 2>&1
then
nf::carp "Error linking file: ${runner_file}!"
fi
# Remove the 'venv-runner.sh' script from the project if it isn't defined in
# the configuration file and the script already exists
elif [[ "${runner_bool}" != 'true' && -f "${runner_file}" ]]; then
if ! "${SV_X_RM[@]}" "${runner_file}" >'/dev/null' 2>&1; then
nf::carp "Error removing file: ${runner_file}!"
fi
fi
# Create the virtual environment
if ! "${SV_X_PYTHON[@]}" -m 'venv' --prompt "venv '${project}'" \
--system-site-packages "${venv_path}" >'/dev/null' 2>&1
then
nf::carp "Error creating virtual environment: ${project}!"
continue
fi
# Activate the virtual environment
if ! source "${venv_path}/bin/activate"; then
nf::carp "Error activating virtual environment: ${project}!"
continue
fi
# NOTE: In the further part of this script, the binary 'python3' is not
# called with an absolute path, as it is to be used from the virtual
# environment.
# Get the list of project-related libraries from the configuration file
if ! mapfile -t 'libraries' < <( \
"${SV_X_YQ[@]}" ".\"${project}\".libraries[]" <"${SV_FE_CONFIG_PATH}" \
2>'/dev/null' \
)
then
# Deactivate the virtual environment
deactivate
nf::carp -e "Error getting the list of libraries: ${project}!"
fi
# Install the project-related libraries
for library in "${libraries[@]}"; do
nf::print_heading '-' 'b' "${library##*/}"
if ! python3 -m 'pip' install --upgrade "${library}"; then
# Deactivate the virtual environment
deactivate
nf::carp "Error installing library: ${library}!"
continue 2
fi
done
echo
# Verify the dependencies of the installed libraries
python3 -m 'pip' check
# Write the 'requirements.txt' file if it is defined in the configuration file
if [[ "${requ_bool}" == 'true' ]]; then
if ! python3 -m 'pip' freeze >"${requ_path}" 2>'/dev/null'; then
nf::carp "Error creating file: ${requ_path}!"
fi
fi
# Deactivate the virtual environment
deactivate
done
# ------------------------------------------------------------------------------
exit 0
Usage
- Create a configuration file
vi "${HOME}/dev/setup-venv.yaml"
atlassian-api:
path: /home/alex/dev
runner: true
requirements: true
libraries:
- atlassian-python-api
bw2kp:
path: /home/alex/dev
runner: false
requirements: false
libraries:
- git+https://codeberg.org/k3karthic/bitwarden-to-keepass
deploy_certs:
path: /home/alex/dev
runner: true
requirements: true
libraries:
- certbot
- certbot-dns-nsone
- Change the script's permissions
chmod 0700 "${HOME}/dev/setup-venv.sh"
- Run the script
"${HOME}/dev/setup-venv.sh"