first commit
This commit is contained in:
793
profiles/desktop/.local/bin/distrobox-assemble
Executable file
793
profiles/desktop/.local/bin/distrobox-assemble
Executable file
@@ -0,0 +1,793 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
#
|
||||
# This file is part of the distrobox project:
|
||||
# https://github.com/89luca89/distrobox
|
||||
#
|
||||
# Copyright (C) 2021 distrobox contributors
|
||||
#
|
||||
# distrobox is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU General Public License version 3
|
||||
# as published by the Free Software Foundation.
|
||||
#
|
||||
# distrobox 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 distrobox; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Ensure we have our env variables correctly set
|
||||
[ -z "${USER}" ] && USER="$(id -run)"
|
||||
[ -z "${HOME}" ] && HOME="$(getent passwd "${USER}" | cut -d':' -f6)"
|
||||
[ -z "${SHELL}" ] && SHELL="$(getent passwd "${USER}" | cut -d':' -f7)"
|
||||
|
||||
# POSIX
|
||||
#
|
||||
default_input_file="./distrobox.ini"
|
||||
delete=-1
|
||||
distrobox_path="$(dirname "${0}")"
|
||||
dryrun=0
|
||||
boxname=""
|
||||
input_file=""
|
||||
replace=0
|
||||
root_flag=""
|
||||
# tmpfile will be used as a little buffer to pass variables without messing up
|
||||
# quoting and escaping
|
||||
tmpfile="$(mktemp -u)"
|
||||
tmp_download_file="$(mktemp -u)"
|
||||
verbose=0
|
||||
version="1.8.2.2"
|
||||
# initializing block of variables used in the manifest
|
||||
additional_flags=""
|
||||
additional_packages=""
|
||||
entry=""
|
||||
home=""
|
||||
hostname=""
|
||||
image=""
|
||||
clone=""
|
||||
init=""
|
||||
init_hooks=""
|
||||
nvidia=""
|
||||
pre_init_hooks=""
|
||||
pull=""
|
||||
root=""
|
||||
start_now=""
|
||||
unshare_groups=""
|
||||
unshare_ipc=""
|
||||
unshare_netns=""
|
||||
unshare_process=""
|
||||
unshare_devsys=""
|
||||
unshare_all=""
|
||||
volume=""
|
||||
exported_apps=""
|
||||
exported_bins=""
|
||||
exported_bins_path="${HOME}/.local/bin"
|
||||
|
||||
# Cleanup tmpfiles on exit
|
||||
trap 'rm -f ${tmpfile} ${tmp_download_file}' EXIT
|
||||
|
||||
# Despite of running this script via SUDO/DOAS being not supported (the
|
||||
# script itself will call the appropriate tool when necessary), we still want
|
||||
# to allow people to run it as root, logged in in a shell, and create rootful
|
||||
# containers.
|
||||
#
|
||||
# SUDO_USER is a variable set by SUDO and can be used to check whether the script was called by it. Same thing for DOAS_USER, set by DOAS.
|
||||
if [ -n "${SUDO_USER}" ] || [ -n "${DOAS_USER}" ]; then
|
||||
printf >&2 "Running %s via SUDO/DOAS is not supported." "$(basename "${0}")"
|
||||
printf >&2 "Instead, please try using root=true property in the distrobox.ini file.\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Source configuration files, this is done in an hierarchy so local files have
|
||||
# priority over system defaults
|
||||
# leave priority to environment variables.
|
||||
#
|
||||
# On NixOS, for the distrobox derivation to pick up a static config file shipped
|
||||
# by the package maintainer the path must be relative to the script itself.
|
||||
self_dir="$(dirname "$(realpath "$0")")"
|
||||
nix_config_file="${self_dir}/../share/distrobox/distrobox.conf"
|
||||
|
||||
config_files="
|
||||
${nix_config_file}
|
||||
/usr/share/distrobox/distrobox.conf
|
||||
/usr/share/defaults/distrobox/distrobox.conf
|
||||
/usr/etc/distrobox/distrobox.conf
|
||||
/usr/local/share/distrobox/distrobox.conf
|
||||
/etc/distrobox/distrobox.conf
|
||||
${XDG_CONFIG_HOME:-"${HOME}/.config"}/distrobox/distrobox.conf
|
||||
${HOME}/.distroboxrc
|
||||
"
|
||||
for config_file in ${config_files}; do
|
||||
# Shellcheck will give error for sourcing a variable file as it cannot follow
|
||||
# it. We don't care so let's disable this linting for now.
|
||||
# shellcheck disable=SC1090
|
||||
[ -e "${config_file}" ] && . "$(realpath "${config_file}")"
|
||||
done
|
||||
|
||||
[ -n "${DBX_VERBOSE}" ] && verbose="${DBX_VERBOSE}"
|
||||
|
||||
# Fixup variable=[true|false], in case we find it in the config file(s)
|
||||
[ "${verbose}" = "true" ] && verbose=1
|
||||
[ "${verbose}" = "false" ] && verbose=0
|
||||
|
||||
# show_help will print usage to stdout.
|
||||
# Arguments:
|
||||
# None
|
||||
# Expected global variables:
|
||||
# version: string distrobox version
|
||||
# Expected env variables:
|
||||
# None
|
||||
# Outputs:
|
||||
# print usage with examples.
|
||||
show_help()
|
||||
{
|
||||
cat << EOF
|
||||
distrobox version: ${version}
|
||||
|
||||
Usage:
|
||||
|
||||
distrobox assemble create
|
||||
distrobox assemble rm
|
||||
distrobox assemble create --file /path/to/file.ini
|
||||
distrobox assemble rm --file /path/to/file.ini
|
||||
distrobox assemble create --replace --file /path/to/file.ini
|
||||
|
||||
Options:
|
||||
|
||||
--file: path or URL to the distrobox manifest/ini file
|
||||
--name/-n: run against a single entry in the manifest/ini file
|
||||
--replace/-R: replace already existing distroboxes with matching names
|
||||
--dry-run/-d: only print the container manager command generated
|
||||
--verbose/-v: show more verbosity
|
||||
--version/-V: show version
|
||||
EOF
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
while :; do
|
||||
case $1 in
|
||||
create)
|
||||
delete=0
|
||||
shift
|
||||
;;
|
||||
rm)
|
||||
delete=1
|
||||
shift
|
||||
;;
|
||||
--file)
|
||||
# Call a "show_help" function to display a synopsis, then exit.
|
||||
if [ -n "$2" ]; then
|
||||
input_file="${2}"
|
||||
shift
|
||||
shift
|
||||
fi
|
||||
;;
|
||||
-n | --name)
|
||||
# Call a "show_help" function to display a synopsis, then exit.
|
||||
if [ -n "$2" ]; then
|
||||
boxname="${2}"
|
||||
shift
|
||||
shift
|
||||
fi
|
||||
;;
|
||||
-h | --help)
|
||||
# Call a "show_help" function to display a synopsis, then exit.
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
-d | --dry-run)
|
||||
shift
|
||||
dryrun=1
|
||||
;;
|
||||
-v | --verbose)
|
||||
verbose=1
|
||||
shift
|
||||
;;
|
||||
-R | --replace)
|
||||
replace=1
|
||||
shift
|
||||
;;
|
||||
-V | --version)
|
||||
printf "distrobox: %s\n" "${version}"
|
||||
exit 0
|
||||
;;
|
||||
--) # End of all options.
|
||||
shift
|
||||
break
|
||||
;;
|
||||
-*) # Invalid options.
|
||||
printf >&2 "ERROR: Invalid flag '%s'\n\n" "$1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
*) # Default case: If no more options then break out of the loop.
|
||||
# If we have a flagless option and container_name is not specified
|
||||
# then let's accept argument as container_name
|
||||
if [ -n "$1" ]; then
|
||||
input_file="$1"
|
||||
shift
|
||||
else
|
||||
break
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
# set verbosity
|
||||
if [ "${verbose}" -ne 0 ]; then
|
||||
set -o xtrace
|
||||
fi
|
||||
|
||||
# check if we're getting the right inputs
|
||||
if [ "${delete}" -eq -1 ]; then
|
||||
printf >&2 "Please specify create or rm.\n"
|
||||
show_help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Fallback to distrobox.ini if no file is passed
|
||||
if [ -z "${input_file}" ]; then
|
||||
input_file="${default_input_file}"
|
||||
fi
|
||||
|
||||
# Check if file effectively exists
|
||||
if [ ! -e "${input_file}" ]; then
|
||||
|
||||
if command -v curl > /dev/null 2>&1; then
|
||||
download="curl --connect-timeout 3 --retry 1 -sLo"
|
||||
elif command -v wget > /dev/null 2>&1; then
|
||||
download="wget --timeout=3 --tries=1 -qO"
|
||||
fi
|
||||
|
||||
if ! ${download} - "${input_file}" > "${tmp_download_file}"; then
|
||||
printf >&2 "File %s does not exist.\n" "${input_file}"
|
||||
exit 1
|
||||
else
|
||||
input_file="${tmp_download_file}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# run_distrobox will create distrobox with parameters parsed from ini file.
|
||||
# Arguments:
|
||||
# name: name of the distrobox.
|
||||
# Expected global variables:
|
||||
# boxname: string name of the target container
|
||||
# tmpfile: string name of the tmpfile to read
|
||||
# delete: bool delete container
|
||||
# replace: bool replace container
|
||||
# dryrun: bool dryrun (only print, no execute)
|
||||
# verbose: bool verbose
|
||||
# Expected env variables:
|
||||
# None
|
||||
# Outputs:
|
||||
# execution of the proper distrobox-create command.
|
||||
run_distrobox()
|
||||
{
|
||||
name="${1}"
|
||||
additional_flags=""
|
||||
additional_packages=""
|
||||
entry=""
|
||||
home=""
|
||||
hostname=""
|
||||
image=""
|
||||
clone=""
|
||||
init=""
|
||||
init_hooks=""
|
||||
nvidia=""
|
||||
pre_init_hooks=""
|
||||
pull=""
|
||||
root=""
|
||||
start_now=""
|
||||
unshare_groups=""
|
||||
unshare_ipc=""
|
||||
unshare_netns=""
|
||||
unshare_process=""
|
||||
unshare_devsys=""
|
||||
unshare_all=""
|
||||
volume=""
|
||||
exported_apps=""
|
||||
exported_bins=""
|
||||
exported_bins_path="${HOME}/.local/bin"
|
||||
|
||||
# Skip item if --name used and no match is found
|
||||
if [ "${boxname}" != "" ] && [ "${boxname}" != "${name}" ]; then
|
||||
rm -f "${tmpfile}"
|
||||
return
|
||||
fi
|
||||
|
||||
# Source the current block variables
|
||||
if [ -e "${tmpfile}" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "${tmpfile}" && rm -f "${tmpfile}"
|
||||
fi
|
||||
|
||||
if [ -n "${root}" ] && [ "${root}" -eq 1 ]; then
|
||||
root_flag="--root"
|
||||
fi
|
||||
|
||||
# We're going to delete, not create!
|
||||
if [ "${delete}" -ne 0 ] || [ "${replace}" -ne 0 ]; then
|
||||
printf " - Deleting %s...\n" "${name}"
|
||||
|
||||
if [ "${dryrun}" -eq 0 ]; then
|
||||
# shellcheck disable=SC2086,2248
|
||||
"${distrobox_path}"/distrobox rm ${root_flag} -f "${name}" > /dev/null || :
|
||||
fi
|
||||
|
||||
if [ "${delete}" -ne 0 ]; then
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
# We're going to create!
|
||||
printf " - Creating %s...\n" "${name}"
|
||||
|
||||
# If distrobox already exist, and we have replace enabled, destroy the container
|
||||
# we have to recreate it.
|
||||
# shellcheck disable=SC2086,2248
|
||||
if "${distrobox_path}"/distrobox-list ${root_flag} | grep -qw " ${name} " && [ "${dryrun}" -eq 0 ]; then
|
||||
printf >&2 "%s already exists\n" "${name}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Now we dynamically generate the distrobox-create command based on the
|
||||
# declared flags.
|
||||
result_command="${distrobox_path}/distrobox-create --yes"
|
||||
if [ "${verbose}" -ne 0 ]; then
|
||||
result_command="${result_command} -v"
|
||||
fi
|
||||
if [ -n "${name}" ]; then
|
||||
result_command="${result_command} --name $(sanitize_variable "${name}")"
|
||||
fi
|
||||
if [ -n "${image}" ]; then
|
||||
result_command="${result_command} --image $(sanitize_variable "${image}")"
|
||||
fi
|
||||
if [ -n "${clone}" ]; then
|
||||
result_command="${result_command} --clone $(sanitize_variable "${clone}")"
|
||||
fi
|
||||
if [ -n "${init}" ] && [ "${init}" -eq 1 ]; then
|
||||
result_command="${result_command} --init"
|
||||
fi
|
||||
if [ -n "${root}" ] && [ "${root}" -eq 1 ]; then
|
||||
result_command="${result_command} --root"
|
||||
fi
|
||||
if [ -n "${pull}" ] && [ "${pull}" -eq 1 ]; then
|
||||
result_command="${result_command} --pull"
|
||||
fi
|
||||
if [ -n "${entry}" ] && [ "${entry}" -eq 0 ]; then
|
||||
result_command="${result_command} --no-entry"
|
||||
fi
|
||||
if [ -n "${nvidia}" ] && [ "${nvidia}" -eq 1 ]; then
|
||||
result_command="${result_command} --nvidia"
|
||||
fi
|
||||
if [ -n "${unshare_netns}" ] && [ "${unshare_netns}" -eq 1 ]; then
|
||||
result_command="${result_command} --unshare-netns"
|
||||
fi
|
||||
if [ -n "${unshare_groups}" ] && [ "${unshare_groups}" -eq 1 ]; then
|
||||
result_command="${result_command} --unshare-groups"
|
||||
fi
|
||||
if [ -n "${unshare_ipc}" ] && [ "${unshare_ipc}" -eq 1 ]; then
|
||||
result_command="${result_command} --unshare-ipc"
|
||||
fi
|
||||
if [ -n "${unshare_process}" ] && [ "${unshare_process}" -eq 1 ]; then
|
||||
result_command="${result_command} --unshare-process"
|
||||
fi
|
||||
if [ -n "${unshare_devsys}" ] && [ "${unshare_devsys}" -eq 1 ]; then
|
||||
result_command="${result_command} --unshare-devsys"
|
||||
fi
|
||||
if [ -n "${unshare_all}" ] && [ "${unshare_all}" -eq 1 ]; then
|
||||
result_command="${result_command} --unshare-all"
|
||||
fi
|
||||
if [ -n "${home}" ]; then
|
||||
result_command="${result_command} --home $(sanitize_variable "${home}")"
|
||||
fi
|
||||
if [ -n "${hostname}" ]; then
|
||||
result_command="${result_command} --hostname $(sanitize_variable "${hostname}")"
|
||||
fi
|
||||
if [ -n "${init_hooks}" ]; then
|
||||
IFS="¤"
|
||||
args=": ;"
|
||||
separator=""
|
||||
for arg in ${init_hooks}; do
|
||||
if [ -z "${arg}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Convert back from base64
|
||||
arg="$(echo "${arg}" | base64 -d)"
|
||||
args="${args} ${separator} ${arg}"
|
||||
|
||||
# Prepare for the next line, if we already have a ';' or '&&', do nothing
|
||||
# else prefer adding '&&'
|
||||
if ! echo "${arg}" | grep -qE ';[[:space:]]{0,1}$' &&
|
||||
! echo "${arg}" | grep -qE '&&[[:space:]]{0,1}$'; then
|
||||
separator="&&"
|
||||
else
|
||||
separator=""
|
||||
fi
|
||||
done
|
||||
result_command="${result_command} --init-hooks $(sanitize_variable "${args}")"
|
||||
fi
|
||||
# Replicable flags
|
||||
if [ -n "${pre_init_hooks}" ]; then
|
||||
IFS="¤"
|
||||
args=": ;"
|
||||
separator=""
|
||||
for arg in ${pre_init_hooks}; do
|
||||
if [ -z "${arg}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Convert back from base64
|
||||
arg="$(echo "${arg}" | base64 -d)"
|
||||
args="${args} ${separator} ${arg}"
|
||||
|
||||
# Prepare for the next line, if we already have a ';' or '&&', do nothing
|
||||
# else prefer adding '&&'
|
||||
if ! echo "${arg}" | grep -qE ';[[:space:]]{0,1}$' &&
|
||||
! echo "${arg}" | grep -qE '&&[[:space:]]{0,1}$'; then
|
||||
separator="&&"
|
||||
else
|
||||
separator=""
|
||||
fi
|
||||
done
|
||||
result_command="${result_command} --pre-init-hooks $(sanitize_variable "${args}")"
|
||||
fi
|
||||
if [ -n "${additional_packages}" ]; then
|
||||
IFS="¤"
|
||||
args=""
|
||||
for packages in ${additional_packages}; do
|
||||
if [ -z "${packages}" ]; then
|
||||
continue
|
||||
fi
|
||||
args="${args} ${packages}"
|
||||
done
|
||||
result_command="${result_command} --additional-packages $(sanitize_variable "${args}")"
|
||||
fi
|
||||
if [ -n "${volume}" ]; then
|
||||
IFS="¤"
|
||||
for vol in ${volume}; do
|
||||
if [ -z "${vol}" ]; then
|
||||
continue
|
||||
fi
|
||||
result_command="${result_command} --volume $(sanitize_variable "${vol}")"
|
||||
done
|
||||
fi
|
||||
if [ -n "${additional_flags}" ]; then
|
||||
IFS="¤"
|
||||
for flag in ${additional_flags}; do
|
||||
if [ -z "${flag}" ]; then
|
||||
continue
|
||||
fi
|
||||
result_command="${result_command} --additional-flags $(sanitize_variable "${flag}")"
|
||||
done
|
||||
fi
|
||||
|
||||
# Execute the distrobox-create command
|
||||
if [ "${dryrun}" -ne 0 ]; then
|
||||
echo "${result_command}"
|
||||
return
|
||||
fi
|
||||
eval "${result_command}"
|
||||
|
||||
# If we need to start immediately, do it, so that the container
|
||||
# is ready to be entered.
|
||||
if [ -n "${start_now}" ] && [ "${start_now}" -eq 1 ]; then
|
||||
# Here we execute the `distrobox enter` command with a `/dev/null` stdin.
|
||||
# This is due to the fact that this command is very likely to be executed inside
|
||||
# the read loop in the prse_file function. This way we avoid stdin to be swallowed by
|
||||
# this command execution. This is valid for all the `distrobox enter` calls from now on.
|
||||
#
|
||||
# shellcheck disable=SC2086,2248
|
||||
"${distrobox_path}"/distrobox enter ${root_flag} "${name}" -- touch /dev/null < /dev/null
|
||||
fi
|
||||
|
||||
# if there are exported bins and apps declared, let's export them
|
||||
if [ -n "${exported_apps}" ] || [ -n "${exported_bins}" ]; then
|
||||
# First we start the container
|
||||
# shellcheck disable=SC2086,2248
|
||||
"${distrobox_path}"/distrobox enter ${root_flag} "${name}" -- touch /dev/null < /dev/null
|
||||
|
||||
IFS="¤"
|
||||
for apps in ${exported_apps}; do
|
||||
# Split the string by spaces
|
||||
IFS=" "
|
||||
for app in ${apps}; do
|
||||
# Export the app
|
||||
# shellcheck disable=SC2086,2248
|
||||
"${distrobox_path}"/distrobox enter ${root_flag} "${name}" -- distrobox-export --app "${app}" < /dev/null
|
||||
done
|
||||
done
|
||||
|
||||
IFS="¤"
|
||||
for bins in ${exported_bins}; do
|
||||
# Split the string by spaces
|
||||
IFS=" "
|
||||
for bin in ${bins}; do
|
||||
# Export the bin
|
||||
# shellcheck disable=SC2086,2248
|
||||
"${distrobox_path}"/distrobox enter ${root_flag} "${name}" -- distrobox-export --bin "${bin}" \
|
||||
--export-path "${exported_bins_path}" < /dev/null
|
||||
done
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# encode_variable will encode an input in base64, removing surrounding single/double quotes.
|
||||
# Arguments:
|
||||
# variable: string
|
||||
# Expected global variables:
|
||||
# None
|
||||
# Expected env variables:
|
||||
# None
|
||||
# Outputs:
|
||||
# a value string encoded in base64
|
||||
encode_variable()
|
||||
{
|
||||
variable="${1}"
|
||||
# remove surrounding quotes possibly added by the user
|
||||
if echo "${variable}" | grep -qE '^"'; then
|
||||
variable="$(echo "${variable}" | sed -e 's/^"//' -e 's/"$//')"
|
||||
elif echo "${variable}" | grep -qE "^'"; then
|
||||
variable="$(echo "${variable}" | sed -e "s/^'//" -e "s/'$//")"
|
||||
fi
|
||||
|
||||
echo "${variable}" | base64 -w 0
|
||||
}
|
||||
|
||||
# sanitize_variable will sanitize an input, add single/double quotes and escapes
|
||||
# Arguments:
|
||||
# variable: string
|
||||
# Expected global variables:
|
||||
# None
|
||||
# Expected env variables:
|
||||
# None
|
||||
# Outputs:
|
||||
# a value string sanitized
|
||||
sanitize_variable()
|
||||
{
|
||||
variable="${1}"
|
||||
|
||||
# If there are spaces but no quotes, let's add them
|
||||
if echo "${variable}" | grep -q " " &&
|
||||
! echo "${variable}" | grep -Eq "^'|^\""; then
|
||||
|
||||
# if we have double quotes we should wrap the whole line in single quotes
|
||||
# in order to not "undo" them
|
||||
if echo "${variable}" | grep -q '"'; then
|
||||
variable="'${variable}'"
|
||||
else
|
||||
variable="\"${variable}\""
|
||||
fi
|
||||
fi
|
||||
|
||||
# Return
|
||||
echo "${variable}"
|
||||
}
|
||||
|
||||
# parse_file will read and parse input file and call distrobox-create accordingly
|
||||
# Arguments:
|
||||
# file: string path of the manifest file to parse
|
||||
# Expected global variables:
|
||||
# tmpfile: string name of the tmpfile to read
|
||||
# Expected env variables:
|
||||
# None
|
||||
# Outputs:
|
||||
# None
|
||||
parse_file()
|
||||
{
|
||||
file="${1}"
|
||||
name=""
|
||||
|
||||
IFS='
|
||||
'
|
||||
while read -r line; do
|
||||
# Remove comments and trailing spaces
|
||||
line="$(echo "${line}" |
|
||||
sed 's/\t/ /g' |
|
||||
sed 's/^#.*//g' |
|
||||
sed 's/].*#.*//g' |
|
||||
sed 's/ #.*//g' |
|
||||
sed 's/\s*$//g')"
|
||||
|
||||
if [ -z "${line}" ]; then
|
||||
# blank line, skip
|
||||
continue
|
||||
fi
|
||||
|
||||
# Detect start of new section
|
||||
if [ "$(echo "${line}" | cut -c 1)" = '[' ]; then
|
||||
# We're starting a new section
|
||||
if [ -n "${name}" ]; then
|
||||
# We've finished the previous section, so this is the time to
|
||||
# perform the distrobox command, before going to the new section.
|
||||
run_distrobox "${name}"
|
||||
fi
|
||||
|
||||
# Remove brackets and spaces
|
||||
name="$(echo "${line}" | tr -d '][ ')"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Get key-values from the file
|
||||
key="$(echo "${line}" | cut -d'=' -f1 | tr -d ' ')"
|
||||
value="$(echo "${line}" | cut -d'=' -f2-)"
|
||||
|
||||
# Normalize true|false to 0|1
|
||||
[ "${value}" = "true" ] && value=1
|
||||
[ "${value}" = "false" ] && value=0
|
||||
|
||||
# Sanitize value, by whitespaces, quotes and escapes
|
||||
if [ "${key}" = "init_hooks" ] || [ "${key}" = "pre_init_hooks" ]; then
|
||||
# in case of shell commands (so the hooks) we prefer to pass the variable
|
||||
# around encoded, so that we don't accidentally execute stuff
|
||||
# and, we will execute sanitize_variable on the final string flag at the
|
||||
# end, instead of key/value base.
|
||||
value="$(encode_variable "${value}")"
|
||||
else
|
||||
value="$(sanitize_variable "${value}")"
|
||||
fi
|
||||
|
||||
# Save options to tempfile, to source it later
|
||||
touch "${tmpfile}"
|
||||
if [ -n "${key}" ] && [ -n "${value}" ]; then
|
||||
if grep -q "^${key}=" "${tmpfile}"; then
|
||||
# make keys cumulative
|
||||
value="\${${key}}¤${value}"
|
||||
fi
|
||||
echo "${key}=${value}" >> "${tmpfile}"
|
||||
fi
|
||||
done < "${file}"
|
||||
# Execute now one last time for the last block
|
||||
run_distrobox "${name}"
|
||||
}
|
||||
|
||||
# read_section reads the content of a section from a TOML file.
|
||||
# Arguments:
|
||||
# section: Name of the section to find (string).
|
||||
# file: Path to the file to search into (string).
|
||||
# Expected global variables:
|
||||
# None.
|
||||
# Expected env variables:
|
||||
# None.
|
||||
# Outputs:
|
||||
# Writes the found section body to stdout.
|
||||
# Exit behavior / errors:
|
||||
# Does not exit non‑zero on missing section; caller should treat empty output as needed.
|
||||
read_section()
|
||||
{
|
||||
section="$1"
|
||||
file="$2"
|
||||
awk -v sec="[${section}]" '
|
||||
$0 == sec {show=1; next}
|
||||
show && /^\[/ {exit}
|
||||
show && NF {print}
|
||||
' "${file}"
|
||||
}
|
||||
|
||||
# resolve_includes Resolve 'include' keys in a manifest by inlining referenced sections.
|
||||
# Arguments:
|
||||
# input_file: Path to the original manifest file that may contain 'include' keys.
|
||||
# output_file: Path to the file where the resolved manifest will be written (if empty, a temp file is used).
|
||||
# Expected global variables:
|
||||
# read_section (function) - used to extract referenced section bodies from input_file.
|
||||
# replace_line (function) - used to replace include lines with extracted content.
|
||||
# Expected env variables:
|
||||
# TMPDIR (optional) - may influence mktemp location.
|
||||
# Outputs:
|
||||
# Prints the path to the output file to stdout when completed.
|
||||
# Exit behavior / errors:
|
||||
# Exits with status 1 and writes to stderr on missing definitions, circular includes, or helper failures.
|
||||
resolve_includes()
|
||||
{
|
||||
input_file="$1"
|
||||
output_file="$2"
|
||||
include_stack=""
|
||||
|
||||
# At the starting point, the output file is equal to input_file
|
||||
# Later on, the output file will be edited as includes will be resolved
|
||||
cat "${input_file}" > "${output_file}"
|
||||
|
||||
n=0
|
||||
while true; do
|
||||
total_lines=$(wc -l < "${output_file}")
|
||||
[ "${n}" -gt "${total_lines}" ] && break
|
||||
|
||||
line=$(sed -n "$((n + 1))p" "${output_file}")
|
||||
|
||||
# Detected begin of a section: clear the include stack and go on
|
||||
if [ "$(echo "${line}" | cut -c 1)" = '[' ]; then
|
||||
include_stack=""
|
||||
n=$((n + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Match key=value from the current line
|
||||
key="$(echo "${line}" | cut -d'=' -f1 | tr -d ' ')"
|
||||
value="$(echo "${line}" | cut -d'=' -f2-)"
|
||||
|
||||
if [ "${key}" = "include" ]; then
|
||||
# Detect circular references while including other distrobox definitions
|
||||
# The reference stack is handled as a string of shape [name].[name].[name]
|
||||
if expr "${include_stack}" : ".*\[${value}\]" > /dev/null; then
|
||||
printf >&2 "ERROR circular reference detected: including [%s] again after %s\n" "${value}" "${include_stack}"
|
||||
exit 1
|
||||
else
|
||||
include_stack="[${value}]¤${include_stack}"
|
||||
fi
|
||||
|
||||
# Read the definition for the distrobox to include from the original file
|
||||
# and replace the current line with the found lines.
|
||||
# The line counter is not incremented to allow recursive include resolution
|
||||
inc=$(read_section "$(echo "${value}" | tr -d '"')" "${input_file}")
|
||||
if [ -z "${inc}" ]; then
|
||||
printf >&2 "ERROR cannot include '%s': definition not found\n" "${value}"
|
||||
exit 1
|
||||
fi
|
||||
l=$((n + 1))
|
||||
replace_line "${l}" "${inc}" "${output_file}" "${output_file}" > /dev/null
|
||||
|
||||
continue
|
||||
fi
|
||||
|
||||
# Nothing to do, increment counter and go on
|
||||
n=$((n + 1))
|
||||
|
||||
done
|
||||
|
||||
echo "${output_file}"
|
||||
|
||||
}
|
||||
|
||||
# replace_line Replace a 1-based numbered line in a file with provided (possibly multiline) text.
|
||||
# Arguments:
|
||||
# line_number: 1-based index of the line to replace.
|
||||
# new_value: String to insert (may contain newlines).
|
||||
# input_file: Path to the original file to read from.
|
||||
# output_file: Path to write the resulting file (if empty, a temp file will be created).
|
||||
# Expected global variables:
|
||||
# None.
|
||||
# Expected env variables:
|
||||
# TMPDIR (optional) - may influence mktemp location.
|
||||
# Outputs:
|
||||
# Prints the path to the output file to stdout when complete.
|
||||
# Exit behavior / errors:
|
||||
# Exits with status 1 on fatal errors (e.g., mktemp failure).
|
||||
replace_line()
|
||||
{
|
||||
line_number="$1"
|
||||
new_value="$2"
|
||||
input_file="$3"
|
||||
output_file="$4"
|
||||
|
||||
# if no output file, use a temp file
|
||||
if [ -z "${output_file}" ]; then
|
||||
output_file=$(mktemp -u)
|
||||
fi
|
||||
|
||||
tmpfile=$(mktemp) || exit 1
|
||||
|
||||
# Split the file into two parts around the line to replace and combine with new_value
|
||||
# Print lines before line_number
|
||||
sed "$((line_number - 1))q" "${input_file}" > "${tmpfile}"
|
||||
|
||||
# Append the new_value
|
||||
printf "%s\n" "${new_value}" >> "${tmpfile}"
|
||||
|
||||
# Append lines after line_number
|
||||
sed -n "$((line_number + 1)),\$p" "${input_file}" >> "${tmpfile}"
|
||||
|
||||
# Replace original file with tmpfile
|
||||
mv "${tmpfile}" "${output_file}"
|
||||
|
||||
echo "${output_file}"
|
||||
}
|
||||
|
||||
# Exec
|
||||
expanded_file=$(mktemp -u)
|
||||
resolve_includes "${input_file}" "${expanded_file}"
|
||||
parse_file "${expanded_file}"
|
||||
Reference in New Issue
Block a user