first commit

This commit is contained in:
2026-02-13 04:20:30 +01:00
commit 2bd6b181f4
159 changed files with 194785 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
#!/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/>.
# POSIX
set -o errexit
set -o nounset
version="1.8.2.2"
# show_help will print usage to stdout.
# Arguments:
# None
# Expected global variables:
# version: distrobox version
# Expected env variables:
# None
# Outputs:
# print usage with examples.
show_help()
{
cat << EOF
distrobox version: ${version}
Choose one of the available commands:
assemble
create
enter
list | ls
rm
stop
upgrade
ephemeral
generate-entry
version
help
EOF
}
if [ $# -eq 0 ]; then
show_help
exit
fi
distrobox_path="$(dirname "${0}")"
distrobox_command="${1}"
shift
# Simple wrapper to the distrobox utilities.
# We just detect the 1st argument and launch the matching distrobox utility.
case "${distrobox_command}" in
assemble)
"${distrobox_path}"/distrobox-assemble "$@"
;;
create)
"${distrobox_path}"/distrobox-create "$@"
;;
enter)
"${distrobox_path}"/distrobox-enter "$@"
;;
ls | list)
"${distrobox_path}"/distrobox-list "$@"
;;
stop)
"${distrobox_path}"/distrobox-stop "$@"
;;
rm)
"${distrobox_path}"/distrobox-rm "$@"
;;
upgrade)
"${distrobox_path}"/distrobox-upgrade "$@"
;;
generate-entry)
"${distrobox_path}"/distrobox-generate-entry "$@"
;;
ephemeral)
"${distrobox_path}"/distrobox-ephemeral "$@"
;;
-V | --version | version)
printf "distrobox: %s\n" "${version}"
exit 0
;;
help | --help | -h)
show_help
exit 0
;;
*) # Default case: If no more options then break out of the loop.
printf >&2 "Error: invalid command\n"
show_help
exit 1
;;
esac

View 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 nonzero 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}"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,729 @@
#!/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/>.
# POSIX
# Expected env variables:
# HOME
# USER
# Optional env variables:
# DBX_CONTAINER_ALWAYS_PULL
# DBX_CONTAINER_CUSTOM_HOME
# DBX_CONTAINER_GENERATE_ENTRY
# DBX_CONTAINER_HOME_PREFIX
# DBX_CONTAINER_HOSTNAME
# DBX_CONTAINER_IMAGE
# DBX_CONTAINER_MANAGER
# DBX_CONTAINER_NAME
# DBX_CONTAINER_CLEAN_PATH
# DBX_NON_INTERACTIVE
# DBX_VERBOSE
# DBX_SKIP_WORKDIR
# DBX_SUDO_PROGRAM
# 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)"
app_cache_dir=${XDG_CACHE_HOME:-"${HOME}/.cache"}/distrobox
trap cleanup TERM INT HUP EXIT
# cleanup will remove fifo and temp files, and print to stdout
# container's logs in case of error and verbose.
# Arguments:
# None
# Expected global variables:
# container_manager: string container manager to use
# container_name: string container name
# app_cache_dir: string cache dire to write file into
# logs_pid: string pid of the podman/docker logs process
# verbose: bool verbose
# Expected env variables:
# None
# Outputs:
# None
cleanup()
{
rm -f "${app_cache_dir}/.${container_name}.fifo"
if [ -n "${logs_pid:-}" ]; then
kill "${logs_pid:-}" 2> /dev/null || :
fi
if [ "${verbose}" -eq 1 ]; then
${container_manager} logs "${container_name}"
fi
}
# 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}" ]
} && [ "$(id -ru)" -eq 0 ]; then
printf >&2 "Running %s via SUDO/DOAS is not supported. Instead, please try running:\n" "$(basename "${0}")"
printf >&2 " %s --root %s\n" "$(basename "${0}")" "$*"
exit 1
fi
# Defaults
# by default we use getent to get the login shell of the user and use that
container_custom_command=0
container_command_user="$(echo "${USER}" | sed 's|\\|\\\\|g')"
container_image_default="registry.fedoraproject.org/fedora-toolbox:latest"
container_manager="autodetect"
container_manager_additional_flags=""
container_name=""
container_name_default="my-distrobox"
non_interactive=0
# Use cd + dirname + pwd so that we do not have relative paths in mount points
# We're not using "realpath" here so that symlinks are not resolved this way
# "realpath" would break situations like Nix or similar symlink based package
# management.
distrobox_enter_path="$(cd "$(dirname "$0")" && pwd)/distrobox-enter"
dryrun=0
headless=0
# If the user runs this script as root in a login shell, set rootful=1.
# There's no need for them to pass the --root flag option in such cases.
[ "$(id -ru)" -eq 0 ] && rootful=1 || rootful=0
skip_workdir=0
verbose=0
clean_path=0
version="1.8.2.2"
# 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
# If we're running this script as root -- as in, logged in in the shell as root
# user, and not via SUDO/DOAS --, we don't need to set distrobox_sudo_program
# as it's meaningless for this use case.
if [ "$(id -ru)" -ne 0 ]; then
# If the DBX_SUDO_PROGRAM/distrobox_sudo_program variable was set by the
# user, use its value instead of "sudo". But only if not running the script
# as root (UID 0).
distrobox_sudo_program=${DBX_SUDO_PROGRAM:-${distrobox_sudo_program:-"sudo"}}
fi
[ -n "${DBX_CONTAINER_MANAGER}" ] && container_manager="${DBX_CONTAINER_MANAGER}"
[ -n "${DBX_CONTAINER_NAME}" ] && container_name="${DBX_CONTAINER_NAME}"
[ -n "${DBX_CONTAINER_CLEAN_PATH}" ] && clean_path=1
[ -n "${DBX_SKIP_WORKDIR}" ] && skip_workdir="${DBX_SKIP_WORKDIR}"
[ -n "${DBX_NON_INTERACTIVE}" ] && non_interactive="${DBX_NON_INTERACTIVE}"
[ -n "${DBX_VERBOSE}" ] && verbose="${DBX_VERBOSE}"
# Fixup variable=[true|false], in case we find it in the config file(s)
[ "${non_interactive}" = "true" ] && non_interactive=1
[ "${non_interactive}" = "false" ] && non_interactive=0
[ "${verbose}" = "true" ] && verbose=1
[ "${verbose}" = "false" ] && verbose=0
# show_help will print usage to stdout.
# Arguments:
# None
# Expected global variables:
# version: distrobox version
# Expected env variables:
# None
# Outputs:
# print usage with examples.
show_help()
{
cat << EOF
distrobox version: ${version}
Usage:
distrobox-enter --name fedora-39 -- bash -l
distrobox-enter my-alpine-container -- sh -l
distrobox-enter --additional-flags "--preserve-fds" --name test -- bash -l
distrobox-enter --additional-flags "--env MY_VAR=value" --name test -- bash -l
MY_VAR=value distrobox-enter --additional-flags "--preserve-fds" --name test -- bash -l
Options:
--name/-n: name for the distrobox default: my-distrobox
--/-e: end arguments execute the rest as command to execute at login default: default ${USER}'s shell
--clean-path: reset PATH inside container to FHS standard
--no-tty/-T: do not instantiate a tty
--no-workdir/-nw: always start the container from container's home directory
--additional-flags/-a: additional flags to pass to the container manager command
--help/-h: show this message
--root/-r: launch podman/docker/lilipod with root privileges. Note that if you need root this is the preferred
way over "sudo distrobox" (note: if using a program other than 'sudo' for root privileges is necessary,
specify it through the DBX_SUDO_PROGRAM env variable, or 'distrobox_sudo_program' config variable)
--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
-h | --help)
# Call a "show_help" function to display a synopsis, then exit.
show_help
exit 0
;;
-v | --verbose)
shift
verbose=1
;;
-T | -H | --no-tty)
shift
headless=1
;;
-r | --root)
shift
rootful=1
;;
-V | --version)
printf "distrobox: %s\n" "${version}"
exit 0
;;
-d | --dry-run)
shift
dryrun=1
;;
-nw | --no-workdir)
shift
skip_workdir=1
;;
-n | --name)
if [ -n "$2" ]; then
container_name="$2"
shift
shift
fi
;;
-a | --additional-flags)
if [ -n "$2" ]; then
if [ -z "${container_manager_additional_flags=}" ]; then
container_manager_additional_flags="$(echo "${2}" | sed -E "s/(--[a-zA-Z]+) ([^ ]+)/\1=\2/g" | sed 's/ --/\n--/g')"
else
container_manager_additional_flags="${container_manager_additional_flags}
$(echo "${2}" | sed -E "s/(--[a-zA-Z]+) ([^ ]+)/\1=\2/g" | sed 's/ --/\n--/g')"
fi
shift
shift
fi
;;
-Y | --yes)
non_interactive=1
shift
;;
-e | --exec | --)
container_custom_command=1
shift
# We pass the rest of arguments as $@ at the end
break
;;
--clean-path)
shift
clean_path=1
;;
-*) # 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
container_name="$1"
shift
else
break
fi
;;
esac
done
set -o errexit
set -o nounset
# set verbosity
if [ "${verbose}" -ne 0 ]; then
set -o xtrace
fi
if [ -z "${container_name}" ]; then
container_name="${container_name_default}"
fi
if [ ! -t 0 ] || [ ! -t 1 ]; then
headless=1
fi
# We depend on a container manager let's be sure we have it
# First we use podman, else docker, else lilipod
case "${container_manager}" in
autodetect)
if command -v podman > /dev/null; then
container_manager="podman"
elif command -v podman-launcher > /dev/null; then
container_manager="podman-launcher"
elif command -v docker > /dev/null; then
container_manager="docker"
elif command -v lilipod > /dev/null; then
container_manager="lilipod"
fi
;;
podman)
container_manager="podman"
;;
podman-launcher)
container_manager="podman-launcher"
;;
lilipod)
container_manager="lilipod"
;;
docker)
container_manager="docker"
;;
*)
printf >&2 "Invalid input %s.\n" "${container_manager}"
printf >&2 "The available choices are: 'autodetect', 'podman', 'docker', 'lilipod'\n"
;;
esac
# Be sure we have a container manager to work with.
if ! command -v "${container_manager}" > /dev/null && [ "${dryrun}" -eq 0 ]; then
# Error: we need at least one between docker, podman or lilipod.
printf >&2 "Missing dependency: we need a container manager.\n"
printf >&2 "Please install one of podman, docker or lilipod.\n"
printf >&2 "You can follow the documentation on:\n"
printf >&2 "\tman distrobox-compatibility\n"
printf >&2 "or:\n"
printf >&2 "\thttps://github.com/89luca89/distrobox/blob/main/docs/compatibility.md\n"
exit 127
fi
# add verbose if -v is specified
if [ "${verbose}" -ne 0 ]; then
container_manager="${container_manager} --log-level debug"
fi
# prepend sudo (or the specified sudo program) if we want our container manager to be rootful
if [ "${rootful}" -ne 0 ]; then
container_manager="${distrobox_sudo_program-} ${container_manager}"
fi
# generate_enter_command will produce a Podman, Docker or Lilipod command to execute to enter the container.
# Arguments:
# None
# Expected global variables:
# container_manager: string container manager to use
# container_name: string container name
# container_manager_additional_flags: string container manager additional flags to use
# container_home: string container's home path
# container_path: string container's default PATH variable
# headless: bool headless mode
# skip_workdir: bool skip workdir
# verbose: bool verbose
# unshare_groups
# distrobox_enter_path
# Expected env variables:
# PATH
# USER
# PWD
# XDG_DATA_DIRS
# XDG_CONFIG_DIRS
# Outputs:
# prints the podman, docker or lilipod command to enter the distrobox container
generate_enter_command()
{
result_command="exec"
result_command="${result_command}
--interactive"
result_command="${result_command}
--detach-keys="
# In case of initful systems or unshared groups, we don't enter directly
# as our user, but we instead enter as root, and then su $USER, in order
# to trigger a proper login
if [ "${unshare_groups:-0}" -eq 1 ]; then
result_command="${result_command}
--user=root"
else
result_command="${result_command}
--user=${USER}"
fi
# For some usage, like use in service, or launched by non-terminal
# eg. from desktop files, TTY can fail to instantiate, and fail to enter
# the container.
# To work around this, --headless let's you skip the --tty flag and make it
# work in tty-less situations.
# Disable tty also if we're NOT in a tty (test -t 0, test -t 1).
if [ "${headless}" -eq 0 ]; then
result_command="${result_command}
--tty"
fi
# Entering container using our user and workdir.
# Start container from working directory. Else default to home. Else do /.
# Since we are entering from host, drop at workdir through '/run/host'
# which represents host's root inside container. Any directory on host
# even if not explicitly mounted is bound to exist under /run/host.
# Since user $HOME is very likely present in container, enter there directly
# to avoid confusing the user about shifted paths.
# pass distrobox-enter path, it will be used in the distrobox-export tool.
if [ "${skip_workdir}" -eq 0 ]; then
workdir="${PWD:-${container_home:-"/"}}"
if [ -n "${workdir##*"${container_home}"*}" ]; then
workdir="/run/host${workdir}"
fi
else
# Skipping workdir we just enter $HOME of the container.
workdir="${container_home}"
fi
result_command="${result_command}
--workdir=${workdir}"
result_command="${result_command}
--env=CONTAINER_ID=${container_name}"
result_command="${result_command}
--env=DISTROBOX_ENTER_PATH=${distrobox_enter_path}"
# Loop through all the environment vars
# and export them to the container.
set +o xtrace
# disable logging for this snippet, or it will be too talkative.
# We filter the environment so that we do not have strange variables or
# multiline.
# We also NEED to ignore the HOME variable, as this is set at create time
# and needs to stay that way to use custom home dirs. or it will be too talkative.
result_command="${result_command}
$(printenv | grep '=' | grep -Ev '"|`|\$' |
grep -Ev '^(CONTAINER_ID|FPATH|HOST|HOSTNAME|HOME|PATH|PROFILEREAD|SHELL|XDG_SEAT|XDG_VTNR|XDG_.*_DIRS|^_)' |
sed 's/ /\ /g' | sed 's/^\(.*\)$/--env=\1/g')"
# Start with the $PATH set in the container's config
container_paths="${container_path:-""}"
# Ensure the standard FHS program paths are in PATH environment
standard_paths="/usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin"
if [ "${clean_path}" -eq 1 ]; then
# only add the standard paths
for standard_path in ${standard_paths}; do
if [ -z "${container_paths}" ]; then
container_paths="${standard_path}"
else
container_paths="${container_paths}:${standard_path}"
fi
done
else
# collect standard paths not existing from host PATH
for standard_path in ${standard_paths}; do
pattern="(:|^)${standard_path}(:|$)"
if ! echo "${PATH}" | grep -Eq "${pattern}"; then
if [ -z "${container_paths}" ]; then
container_paths="${standard_path}"
else
container_paths="${container_paths}:${standard_path}"
fi
fi
done
# append additional standard paths to host PATH to get final container_paths
if [ -n "${container_paths}" ]; then
container_paths="${PATH}:${container_paths}"
else
container_paths="${PATH}"
fi
fi
result_command="${result_command}
--env=PATH=${container_paths}"
# Ensure the standard FHS program paths are in XDG_DATA_DIRS environment
standard_paths="/usr/local/share /usr/share"
container_paths="${XDG_DATA_DIRS:-}"
# add to the XDG_DATA_DIRS only after the host's paths, and only if not already present.
for standard_path in ${standard_paths}; do
pattern="(:|^)${standard_path}(:|$)"
if [ -z "${container_paths}" ]; then
container_paths="${standard_path}"
elif ! echo "${container_paths}" | grep -Eq "${pattern}"; then
container_paths="${container_paths}:${standard_path}"
fi
done
result_command="${result_command}
--env=XDG_DATA_DIRS=${container_paths}"
# This correctly sets the XDG_* dirs to the container_home
# it will be $HOME if using regular home dirs
# if will be $container_home if using a custom home during create
result_command="${result_command}
--env=XDG_CACHE_HOME=${container_home}/.cache
--env=XDG_CONFIG_HOME=${container_home}/.config
--env=XDG_DATA_HOME=${container_home}/.local/share
--env=XDG_STATE_HOME=${container_home}/.local/state"
# Ensure the standard FHS program paths are in XDG_CONFIG_DIRS environment
standard_paths="/etc/xdg"
container_paths="${XDG_CONFIG_DIRS:-}"
# add to the XDG_CONFIG_DIRS only after the host's paths, and only if not already present.
for standard_path in ${standard_paths}; do
pattern="(:|^)${standard_path}(:|$)"
if [ -z "${container_paths}" ]; then
container_paths="${standard_path}"
elif ! echo "${container_paths}" | grep -Eq "${pattern}"; then
container_paths="${container_paths}:${standard_path}"
fi
done
result_command="${result_command}
--env=XDG_CONFIG_DIRS=${container_paths}"
# re-enable logging if it was enabled previously.
if [ "${verbose}" -ne 0 ]; then
set -o xtrace
fi
# Add additional flags
if [ -n "${container_manager_additional_flags}" ]; then
result_command="${result_command}
${container_manager_additional_flags}"
fi
# Run selected container with specified command.
result_command="${result_command}
${container_name}"
# Return generated command.
# here we remove tabs as an artifact of using indentation in code to improve
# readability
printf "%s\n" "${result_command}" | tr -d '\t'
}
container_home="${HOME}"
container_path="${PATH}"
unshare_groups=0
# Now inspect the container we're working with.
container_status="unknown"
eval "$(${container_manager} inspect --type container --format \
'container_status={{.State.Status}};
unshare_groups={{ index .Config.Labels "distrobox.unshare_groups" }};
{{range .Config.Env}}{{if and (ge (len .) 5) (eq (slice . 0 5) "HOME=")}}container_home={{slice . 5 | printf "%q"}}{{end}}{{end}};
{{range .Config.Env}}{{if and (ge (len .) 5) (eq (slice . 0 5) "PATH=")}}container_path={{slice . 5 | printf "%q"}}{{end}}{{end}}' \
"${container_name}")"
# dry run mode, just generate the command and print it. No execution.
if [ "${dryrun}" -ne 0 ]; then
cmd="$(generate_enter_command | sed 's/\t//g')"
printf "%s %s\n" "${cmd}" "$*"
exit 0
fi
# Check if the container is even there
if [ "${container_status}" = "unknown" ]; then
# If not, prompt to create it first
# If we're not-interactive, just don't ask questions
if [ "${non_interactive}" -eq 1 ]; then
response="yes"
else
printf >&2 "Create it now, out of image %s? [Y/n]: " "${container_image_default}"
read -r response
response="${response:-"Y"}"
fi
# Accept only y,Y,Yes,yes,n,N,No,no.
case "${response}" in
y | Y | Yes | yes | YES)
# Ok, let's create the container with just 'distrobox create $container_name
create_command="$(dirname "${0}")/distrobox-create"
if [ "${rootful}" -ne 0 ]; then
create_command="${create_command} --root"
fi
create_command="${create_command} --yes -i ${container_image_default} -n ${container_name}"
printf >&2 "Creating the container %s\n" "${container_name}"
if [ "${dryrun}" -ne 1 ]; then
${create_command}
fi
;;
n | N | No | no | NO)
printf >&2 "Ok. For creating it, run this command:\n"
printf >&2 "\tdistrobox create <name-of-container> --image <remote>/<docker>:<tag>\n"
exit 0
;;
*) # Default case: If no more options then break out of the loop.
printf >&2 "Invalid input.\n"
printf >&2 "The available choices are: y,Y,Yes,yes,YES or n,N,No,no,NO.\nExiting.\n"
exit 1
;;
esac
fi
# If the container is not already running, we need to start if first
if [ "${container_status}" != "running" ]; then
# If container is not running, start it first
#
# Here, we save the timestamp before launching the start command, so we can
# be sure we're working with this very same session of logs later.
log_timestamp="$(date -u +%FT%T).000000000+00:00"
${container_manager} start "${container_name}" > /dev/null
#
# Check if the container is going in error status earlier than the
# entrypoint
if [ "$(${container_manager} inspect \
--type container \
--format "{{.State.Status}}" "${container_name}")" != "running" ]; then
printf >&2 "\033[31m Error: could not start entrypoint.\n\033[0m"
container_manager_log="$(${container_manager} logs "${container_name}")"
printf >&2 "%s\n" "${container_manager_log}"
exit 1
fi
printf >&2 "%-40s\t" "Starting container..."
mkdir -p "${app_cache_dir}"
rm -f "${app_cache_dir}/.${container_name}.fifo"
mkfifo "${app_cache_dir}/.${container_name}.fifo"
while true; do
# Exit early in case of crashed/stopped container during setup
if [ "$(${container_manager} inspect --type container --format '{{.State.Status}}' "${container_name}")" != "running" ]; then
printf >&2 "\nContainer Setup Failure!\n"
exit 1
fi
# save starting loop timestamp in temp variable, we'll use it
# after to let logs command minimize possible holes
${container_manager} logs --since "${log_timestamp}" -f "${container_name}" \
> "${app_cache_dir}/.${container_name}.fifo" 2>&1 &
logs_pid="$!"
# read logs from log_timestamp to now, line by line
while IFS= read -r line; do
case "${line}" in
"+"*)
# Ignoring logging commands
;;
"Error:"*)
printf >&2 "\033[31m %s\n\033[0m" "${line}"
exit 1
;;
"Warning:"*)
printf >&2 "\n\033[33m %s\033[0m" "${line}"
;;
"distrobox:"*)
current_line="$(echo "${line}" | cut -d' ' -f2-)"
# Save current line in the status, to avoid printing the same line multiple times
printf >&2 "\033[32m [ OK ]\n\033[0m%-40s\t" "${current_line}"
;;
"container_setup_done"*)
printf >&2 "\033[32m [ OK ]\n\033[0m"
kill "${logs_pid}" > /dev/null 2>&1
break 2
;;
*) ;;
esac
done < "${app_cache_dir}/.${container_name}.fifo"
done
# cleanup fifo
rm -f "${app_cache_dir}/.${container_name}.fifo"
printf >&2 "\nContainer Setup Complete!\n"
fi
################################################################################
# Execution section, in this section we will manipulate the positional parameters
# in order to generate our long docker/podman/lilipod command to execute.
#
# We use positional parameters in order to have the shell manage escaping and spaces
# so we remove the problem of we having to handle them.
#
# 1 - handle absence of custom command, we will need to add a getent command to
# execute the right container's user's shell
# 2 - in case of unshared groups (or initful) we need to trigger a proper login
# using `su`, so we will need to manipulate these arguments accorodingly
# 3 - prepend our generated command
# to do this, we use `tac` so we reverse loop it and prepend each argument.
# 4 - now that we're done, we can prepend our container_command
# we will need to use `rev` to reverse it as we reverse loop and prepend each
# argument
################################################################################
#
# Setup default commands if none are specified
# execute a getent command using the /bin/sh shell
# to find out the default shell of the user, and
# do a login shell with it (eg: /bin/bash -l)
if [ "${container_custom_command}" -eq 0 ]; then
set - "$@" "/bin/sh" "-c" "\$(getent passwd '${container_command_user}' | cut -f 7 -d :) -l"
fi
# If we have a command and we're unsharing groups, we need to execute those
# command using su $container_command_user
# if we're in a tty, also allocate one
if [ "${unshare_groups:-0}" -eq 1 ]; then
# shellcheck disable=SC2089,SC2016
set -- "-c" '"$0" "$@"' -- "$@"
set -- "-s" "/bin/sh" "$@"
if [ "${headless}" -eq 0 ]; then
set -- "--pty" "$@"
fi
set -- "-m" "$@"
set -- "${container_command_user}" "$@"
set -- "su" "$@"
fi
# Generate the exec command and run it
cmd="$(generate_enter_command | awk '{a[i++]=$0} END {for (j=i-1; j>=0;) print a[j--]}')"
# Reverse it so we can reverse loop and prepend the command's arguments
# to our positional parameters
IFS='
'
for arg in ${cmd}; do
set - "${arg}" "$@"
done
# Prepend the container manager command
# reverse it first, so we can loop backward as we're prepending not appending
IFS=' '
for arg in $(echo "${container_manager}" | rev); do
arg="$(echo "${arg}" | rev)"
set - "${arg}" "$@"
done
exec "$@"

View File

@@ -0,0 +1,282 @@
#!/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/>.
# POSIX
# Optional env variables:
# DBX_CONTAINER_MANAGER
# DBX_CONTAINER_NAME
# DBX_VERBOSE
# DBX_SUDO_PROGRAM
# 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}" ]
} && [ "$(id -ru)" -eq 0 ]; then
printf >&2 "Running %s via SUDO/DOAS is not supported. Instead, please try running:\n" "$(basename "${0}")"
printf >&2 " %s --root %s\n" "$(basename "${0}")" "$*"
exit 1
fi
# 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)"
trap cleanup TERM INT HUP
name=$(mktemp -u distrobox-XXXXXXXXXX)
container_command=""
create_flags=""
distrobox_path="$(dirname "${0}")"
extra_flags=""
# If the user runs this script as root in a login shell, set rootful=1.
# There's no need for them to pass the --root flag option in such cases.
[ "$(id -ru)" -eq 0 ] && rootful=1 || rootful=0
verbose=0
version="1.8.2.2"
container_additional_packages=""
container_init_hook=" "
container_manager_additional_flags=""
container_pre_init_hook=" "
# 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: distrobox version
# Expected env variables:
# None
# Outputs:
# print usage with examples.
show_help()
{
cat << EOF
distrobox version: ${version}
Usage:
distrobox-ephemeral [--root/-r]
Options:
--root/-r: launch podman/docker/lilipod with root privileges. Note that if you need root this is the preferred
way over "sudo distrobox" (note: if using a program other than 'sudo' for root privileges is necessary,
specify it through the DBX_SUDO_PROGRAM env variable, or 'distrobox_sudo_program' config variable)
--verbose/-v: show more verbosity
--help/-h: show this message
--/-e: end arguments execute the rest as command to execute at login default: default ${USER}'s shell
--version/-V: show version
See also:
distrobox-ephemeral also inherits all the flags from distrobox-create:
EOF
}
# Parse arguments
while :; do
case $1 in
-h | --help)
# Call a "show_help" function to display a synopsis, then exit.
show_help
"${distrobox_path}"/distrobox-create --help | tail -n +2
exit 0
;;
-r | --root)
shift
rootful=1
;;
-v | --verbose)
verbose=1
shift
;;
-V | --version)
printf "distrobox: %s\n" "${version}"
exit 0
;;
-e | --exec | --)
shift
container_command="-- $*"
break
;;
-n | --name)
# Ignore --name on ephemeral
if [ -n "$2" ]; then
name="${2}"
shift
shift
fi
;;
-a | --additional-flags)
if [ -n "$2" ]; then
container_manager_additional_flags="${container_manager_additional_flags} ${2}"
shift
shift
fi
;;
-ap | --additional-packages)
if [ -n "$2" ]; then
container_additional_packages="${container_additional_packages} ${2}"
shift
shift
fi
;;
--init-hooks)
if [ -n "$2" ]; then
container_init_hook="$2"
shift
shift
fi
;;
--pre-init-hooks)
if [ -n "$2" ]; then
container_pre_init_hook="${2}"
shift
shift
fi
;;
*) # 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
create_flags="${create_flags} $1"
shift
else
break
fi
;;
esac
done
set -o nounset
# set verbosity
if [ "${verbose}" -ne 0 ]; then
set -o xtrace
extra_flags="${extra_flags} --verbose"
fi
# prepend sudo (or the specified sudo program) if we want our container manager to be rootful
if [ "${rootful}" -ne 0 ]; then
extra_flags="${extra_flags} --root"
fi
# generate_ephemeral_create_command will produce a distrobox-create command to execute.
# Arguments:
# None
# Expected global variables:
# distrobox_path = string distrobox path
# name = string container name
# extra_flags = string extra flags to inject
# create_flags = string create extra flags to inject
# Expected env variables:
# None
# Outputs:
# prints the distrobox-create command handling special flags
generate_ephemeral_create_command()
{
result_command="${distrobox_path}/distrobox-create"
if [ -n "${container_manager_additional_flags}" ]; then
result_command="${result_command} \
--additional-flags \"${container_manager_additional_flags}\""
fi
if [ -n "${container_additional_packages}" ]; then
result_command="${result_command} \
--additional-packages \"${container_additional_packages}\""
fi
if [ -n "${container_init_hook}" ]; then
result_command="${result_command} \
--init-hooks \"${container_init_hook}\""
fi
if [ -n "${container_pre_init_hook}" ]; then
result_command="${result_command} \
--pre-init-hooks \"${container_pre_init_hook}\""
fi
result_command="${result_command} \
${extra_flags} ${create_flags} --yes --name ${name}"
# Return generated command.
printf "%s" "${result_command}"
}
# cleanup will ensure we remove the ephemeral container
# Arguments:
# None
# Expected global variables:
# name: string the name of the container
# extra_flags: string extra flags to append to the distrobox command
# distrobox_path: string path to the distrobox script
# Expected env variables:
# None
# Outputs:
# None
cleanup()
{
trap - TERM INT HUP
# shellcheck disable=SC2086
"${distrobox_path}"/distrobox-rm ${extra_flags} --force "${name}" --yes
}
cmd="$(generate_ephemeral_create_command)"
# shellcheck disable=SC2086
eval ${cmd}
# shellcheck disable=SC2086
"${distrobox_path}"/distrobox-enter ${extra_flags} "${name}" ${container_command}
exit_code="$?"
cleanup
exit "${exit_code}"

View File

@@ -0,0 +1,711 @@
#!/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/>.
# POSIX
# Expected env variables:
# HOME
# USER
# DISTROBOX_ENTER_PATH
# DISTROBOX_HOST_HOME
# 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)"
# Defaults
container_name="${CONTAINER_ID:-}"
[ -z "${container_name}" ] && container_name="$(grep "name=" /run/.containerenv | cut -d'=' -f2- | tr -d '"')"
export_action=""
exported_app=""
exported_app_label=""
exported_bin=""
exported_delete=0
extra_flags=""
enter_flags=""
# Use DBX_HOST_HOME if defined, else fallback to HOME
# DBX_HOST_HOME is set in case container is created
# with custom --home directory
host_home="${DISTROBOX_HOST_HOME:-"${HOME}"}"
dest_path="${DISTROBOX_EXPORT_PATH:-${host_home}/.local/bin}"
is_sudo=0
rootful=""
sudo_prefix=""
verbose=0
version="1.8.2.2"
sudo_askpass_path="${dest_path}/distrobox_sudo_askpass"
sudo_askpass_script="#!/bin/sh
if command -v zenity 2>&1 > /dev/null; then
zenity --password
elif command -v kdialog 2>&1 > /dev/null; then
kdialog --password 'A password is required...'
else
exit 127
fi"
# We depend on some commands, let's be sure we have them
base_dependencies="basename find grep sed"
for dep in ${base_dependencies}; do
if ! command -v "${dep}" > /dev/null; then
printf >&2 "Missing dependency: %s\n" "${dep}"
exit 127
fi
done
# show_help will print usage to stdout.
# Arguments:
# None
# Expected global variables:
# version: distrobox version
# Expected env variables:
# None
# Outputs:
# print usage with examples.
show_help()
{
cat << EOF
distrobox version: ${version}
Usage:
distrobox-export --app mpv [--extra-flags "flags"] [--enter-flags "flags"] [--delete] [--sudo]
distrobox-export --bin /path/to/bin [--export-path ~/.local/bin] [--extra-flags "flags"] [--enter-flags "flags"] [--delete] [--sudo]
Options:
--app/-a: name of the application to export or absolute path to desktopfile to export
--bin/-b: absolute path of the binary to export
--list-apps: list applications exported from this container
--list-binaries: list binaries exported from this container, use -ep to specify custom paths to search
--delete/-d: delete exported application or binary
--export-label/-el: label to add to exported application name.
Use "none" to disable.
Defaults to (on \$container_name)
--export-path/-ep: path where to export the binary
--extra-flags/-ef: extra flags to add to the command
--enter-flags/-nf: flags to add to distrobox-enter
--sudo/-S: specify if the exported item should be run as sudo
--help/-h: show this message
--verbose/-v: show more verbosity
--version/-V: show version
EOF
}
# Parse arguments
while :; do
case $1 in
-h | --help)
# Call a "show_help" function to display a synopsis, then exit.
show_help
exit 0
;;
-v | --verbose)
shift
verbose=1
;;
-V | --version)
printf "distrobox: %s\n" "${version}"
exit 0
;;
-a | --app)
if [ -n "$2" ]; then
export_action="app"
exported_app="$2"
shift
shift
fi
;;
-b | --bin)
if [ -n "$2" ]; then
export_action="bin"
exported_bin="$2"
shift
shift
fi
;;
--list-apps)
export_action="list-apps"
exported_bin="null"
shift
;;
--list-binaries)
export_action="list-binaries"
exported_bin="null"
shift
;;
-S | --sudo)
is_sudo=1
shift
;;
-el | --export-label)
if [ -n "$2" ]; then
exported_app_label="$2"
shift
shift
fi
;;
-ep | --export-path)
if [ -n "$2" ]; then
dest_path="$2"
shift
shift
fi
;;
-ef | --extra-flags)
if [ -n "$2" ]; then
extra_flags="$2"
shift
shift
fi
;;
-nf | --enter-flags)
if [ -n "$2" ]; then
enter_flags="$2"
shift
shift
fi
;;
-d | --delete)
exported_delete=1
shift
;;
-*) # 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.
break ;;
esac
done
set -o errexit
set -o nounset
# set verbosity
if [ "${verbose}" -ne 0 ]; then
set -o xtrace
fi
# Ensure we can write stuff there
if [ ! -e "${dest_path}" ]; then
mkdir -p "${dest_path}"
fi
# Check we're running inside a container and not on the host.
if [ ! -f /run/.containerenv ] && [ ! -f /.dockerenv ] && [ -z "${container:-}" ]; then
printf >&2 "You must run %s inside a container!\n" " $(basename "$0")"
exit 126
fi
# Check if we're in a rootful or rootless container.
if grep -q "rootless=0" /run/.containerenv 2> /dev/null; then
rootful="--root"
# We need an askpass script for SUDO_ASKPASS, to launch graphical apps
# from the drawer
if [ ! -e "${sudo_askpass_path}" ]; then
echo "${sudo_askpass_script}" > "${sudo_askpass_path}"
chmod +x "${sudo_askpass_path}"
fi
fi
# We're working with HOME, so we must run as USER, not as root.
if [ "$(id -u)" -eq 0 ]; then
printf >&2 "You must not run %s as root!\n" " $(basename "$0")"
exit 1
fi
# Ensure we're not receiving more than one action at time.
if [ -n "${exported_app}" ] && [ -n "${exported_bin}" ]; then
printf >&2 "Error: Invalid arguments, choose only one action below.\n"
printf >&2 "Error: You can only export one thing at time.\n"
exit 2
fi
# Filter enter_flags and remove redundant options
if [ -n "${enter_flags}" ]; then
# shellcheck disable=SC2086
set -- ${enter_flags}
filtered_flags=""
# Inform user that certain flags are redundant
while [ $# -gt 0 ]; do
case "$1" in
--root | -r)
printf >&2 "Warning: %s argument will be set automatically and should be removed.\n" "${1}"
;;
--name | -n)
printf >&2 "Warning: %s argument will be set automatically and should be removed.\n" "${1}"
shift
;;
*)
filtered_flags="${filtered_flags} $1"
;;
esac
shift
done
enter_flags="${filtered_flags}"
fi
# Command to execute
container_command_suffix="'${exported_bin}' ${extra_flags} \"\$@\""
# Check if exported application/binary should be run with sudo privileges
if [ "${is_sudo}" -ne 0 ]; then
sudo_prefix="sudo -S"
if ! ${sudo_prefix} test > /dev/null 2>&1; then
sudo_prefix="sudo"
fi
# Edge case for systems with doas
if command -v doas > /dev/null >&1; then
sudo_prefix="doas"
container_command_suffix="sh -l -c \"'${exported_bin}' ${extra_flags} \$@\""
fi
# Edge case for systems without sudo
if command -v su-exec > /dev/null >&1; then
sudo_prefix="su-exec root"
container_command_suffix="sh -l -c \"'${exported_bin}' ${extra_flags} \$@\""
fi
fi
# Prefix to add to an existing command to work through the container
container_command_prefix="${DISTROBOX_ENTER_PATH:-"distrobox-enter"} ${rootful} -n ${container_name} ${enter_flags} -- ${sudo_prefix} "
if [ -n "${rootful}" ]; then
container_command_prefix="env SUDO_ASKPASS=\"${sudo_askpass_path}\" DBX_SUDO_PROGRAM=\"sudo --askpass\" ${container_command_prefix}"
fi
if [ -z "${exported_app_label}" ]; then
exported_app_label=" (on ${container_name})"
elif [ "${exported_app_label}" = "none" ]; then
exported_app_label=""
else
# Add a leading space so that we can have "NAME LABEL" in the entry
exported_app_label=" ${exported_app_label}"
fi
# generate_script will generate a script from template. This script will wrap the
# exported binary in order to ensure it will be executed in the right container.
# Arguments:
# None
# Expected global variables:
# CONTAINER_ID: id of the current container
# container_command_suffix: string to postpone to the command to launch
# container_name: string name of this container
# dest_path: string path where to export the binary
# enter_flags: string extra flags to append to the distrobox enter command
# exported_bin: string path to the binary to export
# exported_delete: bool delete the binary exported
# extra_flags: string extra flags to append to the exported app command
# rootful: bool if this is a rootful container
# sudo_prefix: string sudo command to prepend to the exported command
# Expected env variables:
# None
# Outputs:
# print generated script.
generate_script()
{
cat << EOF
#!/bin/sh
# distrobox_binary
# name: ${container_name}
if [ -z "\${CONTAINER_ID}" ]; then
exec "${DISTROBOX_ENTER_PATH:-"distrobox-enter"}" ${rootful} -n ${container_name} ${enter_flags} -- ${sudo_prefix} ${container_command_suffix}
elif [ -n "\${CONTAINER_ID}" ] && [ "\${CONTAINER_ID}" != "${container_name}" ]; then
exec distrobox-host-exec '${dest_path}/$(basename "${exported_bin}")' "\$@"
else
exec ${sudo_prefix} '${exported_bin}' "\$@"
fi
EOF
return $?
}
# export_binary will export selected binary to destination directory.
# the following function will use generate_script to create a shell script in
# dest_path that will execute the exported binary in the selected distrobox.
#
# Arguments:
# None
# Expected global variables:
# CONTAINER_ID: id of the current container
# container_name: string name of this container
# dest_path: string path where to export the binary
# exported_bin: string path to the binary to export
# exported_delete: bool delete the binary exported
# Expected env variables:
# None
# Outputs:
# a generated_script in dest_path
# or error code.
export_binary()
{
# Ensure the binary we're exporting is installed
if [ ! -f "${exported_bin}" ]; then
printf >&2 "Error: cannot find %s.\n" "${exported_bin}"
return 127
fi
# generate dest_file path
dest_file="${dest_path}/$(basename "${exported_bin}")"
# create the binary destination path if it doesn't exist
mkdir -p "${dest_path}"
# If we're deleting it, just do it and exit
if [ "${exported_delete}" -ne 0 ]; then
# ensure it's a distrobox exported binary
if ! grep -q "distrobox_binary" "${dest_file}"; then
printf >&2 "Error: %s is not exported.\n" "${exported_bin}"
return 1
fi
if rm -f "${dest_file}"; then
printf "%s from %s removed successfully from %s.\nOK!\n" \
"${exported_bin}" "${container_name}" "${dest_path}"
return 0
fi
return 1
fi
# test if we have writing rights on the file
if ! touch "${dest_file}"; then
printf >&2 "Error: cannot create destination file %s.\n" "${dest_file}"
return 1
fi
# create the script from template and write to file
if generate_script > "${dest_file}"; then
chmod +x "${dest_file}"
printf "%s from %s exported successfully in %s.\nOK!\n" \
"${exported_bin}" "${container_name}" "${dest_path}"
return 0
fi
# Unknown error.
return 3
}
# export_application will export input graphical application to the host.
# the following function will scan the distrobox for desktop and icon files for
# the selected application. It will then put the needed icons in the host's icons
# directory and create a new .desktop file that will execute the selected application
# in the distrobox.
#
# Arguments:
# None
# Expected global variables:
# CONTAINER_ID: id of the current container
# container_command_prefix: string to prepend to the command to launch
# container_name: string name of this container
# exported_app: string name of the app to export
# exported_app_label: string label to use to mark the exported app
# exported_delete: bool if we want to delete or not
# extra_flags: string extra flags to append to the exported app command
# host_home: home path ofr the host, where to search desktop files
# Expected env variables:
# None
# Outputs:
# needed icons in /run/host/$host_home/.local/share/icons
# needed desktop files in /run/host/$host_home/.local/share/applications
# or error code.
export_application()
{
canon_dirs=""
# In case we're explicitly going for a full desktopfile path
if [ -e "${exported_app}" ]; then
desktop_files="${exported_app}"
else
IFS=":"
if [ -n "${XDG_DATA_DIRS}" ]; then
for xdg_data_dir in ${XDG_DATA_DIRS}; do
[ -d "${xdg_data_dir}/applications" ] && canon_dirs="${canon_dirs} ${xdg_data_dir}/applications"
done
else
[ -d /usr/share/applications ] && canon_dirs="/usr/share/applications"
[ -d /usr/local/share/applications ] && canon_dirs="${canon_dirs} /usr/local/share/applications"
[ -d /var/lib/flatpak/exports/share/applications ] && canon_dirs="${canon_dirs} /var/lib/flatpak/exports/share/applications"
fi
if [ -n "${XDG_DATA_HOME}" ]; then
[ -d "${XDG_DATA_HOME}/applications" ] && canon_dirs="${canon_dirs} ${XDG_DATA_HOME}/applications"
else
[ -d "${HOME}/.local/share/applications" ] && canon_dirs="${canon_dirs} ${HOME}/.local/share/applications"
fi
unset IFS
# In this phase we search for applications to export.
# First find command will grep through all files in the canonical directories
# and only list files that contain the $exported_app, excluding those that
# already contains a distrobox-enter command. So skipping already exported apps.
# Second find will list all files that contain the name specified, so that
# it is possible to export an app not only by its executable name but also
# by its launcher name.
desktop_files=$(
# shellcheck disable=SC2086
find ${canon_dirs} -type f -print -o -type l -print | sed 's/./\\&/g' |
xargs -I{} grep -l -e "Exec=.*${exported_app}.*" -e "Name=.*${exported_app}.*" "{}" | sed 's/./\\&/g' |
xargs -I{} grep -L -e "Exec=.*${DISTROBOX_ENTER_PATH:-"distrobox.*enter"}.*" "{}" | sed 's/./\\&/g' |
xargs -I{} printf "%s¤" "{}"
)
fi
# Ensure the app we're exporting is installed
# Check that we found some desktop files first.
if [ -z "${desktop_files}" ]; then
printf >&2 "Error: cannot find any desktop files.\n"
printf >&2 "Error: trying to export a non-installed application.\n"
return 127
fi
# Find icons by using the Icon= specification. If it's only a name, we'll
# search for the file, if it's already a path, just grab it.
icon_files=""
IFS="¤"
for desktop_file in ${desktop_files}; do
if [ -z "${desktop_file}" ]; then
continue
fi
icon_name="$(grep Icon= "${desktop_file}" | cut -d'=' -f2- | paste -sd "¤" -)"
for icon in ${icon_name}; do
if case "${icon_name}" in "/"*) true ;; *) false ;; esac &&
[ -e "${icon_name}" ]; then
# In case it's an hard path, conserve it and continue
icon_files="${icon_files}¤${icon_name}"
else
# If it's not an hard path, find all files in the canonical paths.
icon_files="${icon_files}¤$(find \
/usr/share/icons \
/usr/share/pixmaps \
/var/lib/flatpak/exports/share/icons -iname "*${icon}*" \
-printf "%p¤" 2> /dev/null || :)"
fi
done
# remove leading delimiter
icon_files=${icon_files#¤}
done
# create applications dir if not existing
mkdir -p "/run/host${host_home}/.local/share/applications"
# copy icons in home directory
icon_file_absolute_path=""
IFS="¤"
for icon_file in ${icon_files}; do
if [ -z "${icon_file}" ]; then
continue
fi
# replace canonical paths with equivalent paths in HOME
icon_home_directory="$(dirname "${icon_file}" |
sed "s|/usr/share/|\/run\/host\/${host_home}/.local/share/|g" |
sed "s|/var/lib/flatpak/exports/share|\/run\/host\/${host_home}/.local/share/|g" |
sed "s|pixmaps|icons|g")"
# check if we're exporting an icon which is not in a canonical path
if [ "${icon_home_directory}" = "$(dirname "${icon_file}")" ]; then
icon_home_directory="${host_home}/.local/share/icons/"
icon_file_absolute_path="${icon_home_directory}$(basename "${icon_file}")"
fi
# check if we're exporting or deleting
if [ "${exported_delete}" -ne 0 ]; then
# we need to remove, not export
rm -rf "${icon_home_directory:?}"/"$(basename "${icon_file:?}")"
continue
fi
# we wanto to export the application's icons
mkdir -p "${icon_home_directory}"
if [ ! -e "${icon_home_directory}/$(basename "${icon_file}")" ] && [ -e "$(realpath "${icon_file}")" ]; then
cp -rf "$(realpath "${icon_file}")" "${icon_home_directory}"
fi
done
# create desktop files for the distrobox
IFS="¤"
for desktop_file in ${desktop_files}; do
if [ -z "${desktop_file}" ]; then
continue
fi
desktop_original_file="$(basename "${desktop_file}")"
desktop_home_file="${container_name}-$(basename "${desktop_file}")"
# check if we're exporting or deleting
if [ "${exported_delete}" -ne 0 ]; then
rm -f "/run/host${host_home}/.local/share/applications/${desktop_original_file}"
rm -f "/run/host${host_home}/.local/share/applications/${desktop_home_file}"
# we're done, go to next
continue
fi
# Add command_prefix
# Add extra flags
# Add closing quote
# If a TryExec is present, we have to fake it as it will not work
# through the container separation
sed "s|^Exec=\(.*\)|Exec=${container_command_prefix} \1 |g" "${desktop_file}" |
sed "s|\(%.*\)|${extra_flags} \1|g" |
sed "/^TryExec=.*/d" |
sed "/^DBusActivatable=true/d" |
sed "s|Name.*|&${exported_app_label}|g" \
> "/run/host${host_home}/.local/share/applications/${desktop_home_file}"
# in the end we add the final quote we've opened in the "container_command_prefix"
if ! grep -q "StartupWMClass" "/run/host${host_home}/.local/share/applications/${desktop_home_file}"; then
printf "StartupWMClass=%s\n" "${exported_app}" >> "\
/run/host${host_home}/.local/share/applications/${desktop_home_file}"
fi
# In case of an icon in a non canonical path, we need to replace the path
# in the desktop file.
if [ -n "${icon_file_absolute_path}" ]; then
sed -i "s|Icon=.*|Icon=${icon_file_absolute_path}|g" \
"/run/host${host_home}/.local/share/applications/${desktop_home_file}"
# we're done, go to next
continue
fi
# In case of an icon in a canonical path, but specified as an absolute
# we need to replace the path in the desktop file.
sed -i "s|Icon=/usr/share/|Icon=/run/host${host_home}/.local/share/|g" \
"/run/host${host_home}/.local/share/applications/${desktop_home_file}"
sed -i "s|pixmaps|icons|g" \
"/run/host${host_home}/.local/share/applications/${desktop_home_file}"
done
# Update the desktop files database to ensure exported applications will
# show up in the taskbar/desktop menu/whatnot right after running this
# script.
/usr/bin/distrobox-host-exec --yes update-desktop-database "${host_home}/.local/share/applications" > /dev/null 2>&1 || :
if [ "${exported_delete}" -ne 0 ]; then
printf "Application %s successfully un-exported.\nOK!\n" "${exported_app}"
printf "%s will disappear from your applications list in a few seconds.\n" "${exported_app}"
else
printf "Application %s successfully exported.\nOK!\n" "${exported_app}"
printf "%s will appear in your applications list in a few seconds.\n" "${exported_app}"
fi
}
# list_exported_applications will print all exported applications in canonical directories.
# the following function will list exported desktop files from this container.
#
# Arguments:
# None
# Expected global variables:
# host_home: home path ofr the host, where to search desktop files
# CONTAINER_ID: id of the current container
# Expected env variables:
# None
# Outputs:
# a list of exported apps
# or error code.
list_exported_applications()
{
# In this phase we search for applications exported.
# First find command will grep through all files in the canonical directories
# and only list files that contain the $DISTROBOX_ENTER_PATH.
desktop_files=$(
# shellcheck disable=SC2086
find "/run/host${host_home}/.local/share/applications" -type f -print -o -type l -print 2> /dev/null | sed 's/./\\&/g' |
xargs -I{} grep -l -e "Exec=.*${DISTROBOX_ENTER_PATH:-"distrobox.*enter"}.*" "{}" | sed 's/./\\&/g' |
xargs -I{} printf "%s¤" "{}"
)
# Then we try to pretty print them.
IFS="¤"
for i in ${desktop_files}; do
if [ -z "${i}" ]; then
continue
fi
# Get app name, and remove label
name="$(grep -Eo 'Name=.*' "${i}" | head -n 1 | cut -d'=' -f2- | sed 's|(.*)||g')"
# Print only stuff we exported from this box!
if echo "${i}" | grep -q "${CONTAINER_ID}"; then
printf "%-20s | %-30s\n" "${name}" "${i}"
fi
done
unset IFS
}
# list_exported_binaries will print all exported binaries.
# the following function will list exported desktop files from this container.
# If no export-path is specified, it searches in the default path.
#
# Arguments:
# None
# Expected global variables:
# dest_path: destination path where to search binaries
# CONTAINER_ID: id of the current container
# Expected env variables:
# None
# Outputs:
# a list of exported apps
# or error code.
list_exported_binaries()
{
# In this phase we search for binaries exported.
# First find command will grep through all files in the canonical directories
# and only list files that contain the comment "# distrobox_binary".
binary_files=$(
find "${dest_path}" -type f -print 2> /dev/null | sed 's/./\\&/g' |
xargs -I{} grep -l -e "^# distrobox_binary" "{}" | sed 's/./\\&/g' |
xargs -I{} printf "%s¤" "{}"
)
# Then we try to pretty print them.
IFS="¤"
for i in ${binary_files}; do
if [ -z "${i}" ]; then
continue
fi
# Get original binary name
name="$(grep -B1 "fi" "${i}" | grep exec | cut -d' ' -f2)"
# Print only stuff we exported from this box!
if grep "^# name:.*" "${i}" | grep -q "${CONTAINER_ID}"; then
printf "%-20s | %-30s\n" "${name}" "${i}"
fi
done
unset IFS
}
# Main routine
case "${export_action}" in
app)
export_application
;;
bin)
export_binary
;;
list-apps)
list_exported_applications
;;
list-binaries)
list_exported_binaries
;;
*)
printf >&2 "Invalid arguments, choose an action below.\n"
show_help
exit 2
;;
esac

View File

@@ -0,0 +1,378 @@
#!/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/>.
# POSIX
# Optional env variables:
# DBX_CONTAINER_MANAGER
# DBX_VERBOSE
# 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}" ]
} && [ "$(id -ru)" -eq 0 ]; then
printf >&2 "Running %s via SUDO/DOAS is not supported. Instead, please try running:\n" "$(basename "${0}")"
printf >&2 " %s --root %s\n" "$(basename "${0}")" "$*"
exit 1
fi
# 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)"
# If the user runs this script as root in a login shell, set rootful=1.
# There's no need for them to pass the --root flag option in such cases.
[ "$(id -ru)" -eq 0 ] && rootful=1 || rootful=0
extra_flags=""
all=0
container_manager="autodetect"
container_name_default="my-distrobox"
delete=0
icon="auto"
icon_default="${XDG_DATA_HOME:-${HOME}/.local/share}/icons/terminal-distrobox-icon.svg"
verbose=0
online=0
version="1.8.2.2"
# 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_CONTAINER_MANAGER}" ] && container_manager="${DBX_CONTAINER_MANAGER}"
[ -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: distrobox version
# Expected env variables:
# None
# Outputs:
# print usage with examples.
show_help()
{
cat << EOF
distrobox version: ${version}
Usage:
distrobox-generate-entry container-name [--delete] [--icon [auto,/path/to/icon]]
Options:
--help/-h: show this message
--all/-a: perform for all distroboxes
--delete/-d: delete the entry
--icon/-i: specify a custom icon [/path/to/icon] (default auto)
--root/-r: perform on rootful distroboxes
--verbose/-v: show more verbosity
--version/-V: show version
EOF
}
# Parse arguments
while :; do
case $1 in
-h | --help)
# Call a "show_help" function to display a synopsis, then exit.
show_help
exit 0
;;
-v | --verbose)
verbose=1
shift
;;
-V | --version)
printf "distrobox: %s\n" "${version}"
exit 0
;;
-d | --delete)
delete=1
shift
;;
-a | --all)
all=1
shift
;;
-i | --icon)
if [ -n "$2" ]; then
icon="$2"
shift
shift
fi
;;
-r | --root)
shift
rootful=1
;;
--) # 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
container_name="$1"
shift
else
break
fi
;;
esac
done
set -o errexit
set -o nounset
# set verbosity
if [ "${verbose}" -ne 0 ]; then
set -o xtrace
fi
if [ -z "${container_name:-}" ]; then
container_name="${container_name_default}"
fi
if [ "${all}" -ne 0 ]; then
container_names="$(distrobox list --no-color | tail -n +2 | cut -d'|' -f2 | tr -d ' ')"
for container_name in ${container_names}; do
if [ "${delete}" -ne 0 ]; then
"${0}" "${container_name}" --delete
continue
fi
"${0}" "${container_name}"
done
exit
fi
# If we delete, just ask confirmation and exit.
if [ "${delete}" -ne 0 ]; then
rm -f "${XDG_DATA_HOME:-${HOME}/.local/share}/applications/${container_name}.desktop"
exit
fi
if ! command -v curl > /dev/null && ! command -v wget > /dev/null; then
printf >&2 "Icon generation depends on either curl or wget\n"
printf >&2 "Fallbacking to default icon.\n"
download="null"
fi
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
# We depend on a container manager let's be sure we have it
# First we use podman, else docker, else lilipod
case "${container_manager}" in
autodetect)
if command -v podman > /dev/null; then
container_manager="podman"
container_manager_cp_command="podman cp"
elif command -v podman-launcher > /dev/null; then
container_manager="podman-launcher"
container_manager_cp_command="podman-launcher cp"
elif command -v docker > /dev/null; then
container_manager="docker"
container_manager_cp_command="docker cp -L"
elif command -v lilipod > /dev/null; then
container_manager="lilipod"
container_manager_cp_command="lilipod cp"
fi
;;
podman)
container_manager="podman"
container_manager_cp_command="podman cp"
;;
podman-launcher)
container_manager="podman-launcher"
container_manager_cp_command="podman-launcher cp"
;;
lilipod)
container_manager="lilipod"
container_manager_cp_command="lilipod cp"
;;
docker)
container_manager="docker"
container_manager_cp_command="docker cp -L"
;;
*)
printf >&2 "Invalid input %s.\n" "${container_manager}"
printf >&2 "The available choices are: 'autodetect', 'podman', 'docker', 'lilipod'\n"
;;
esac
# Be sure we have a container manager to work with.
if ! command -v "${container_manager}" > /dev/null; then
# Error: we need at least one between docker, podman or lilipod.
printf >&2 "Missing dependency: we need a container manager.\n"
printf >&2 "Please install one of podman, docker or lilipod.\n"
printf >&2 "You can follow the documentation on:\n"
printf >&2 "\tman distrobox-compatibility\n"
printf >&2 "or:\n"
printf >&2 "\thttps://github.com/89luca89/distrobox/blob/main/docs/compatibility.md\n"
exit 127
fi
# add verbose if -v is specified
if [ "${verbose}" -ne 0 ]; then
container_manager="${container_manager} --log-level debug"
container_manager_cp_command="${container_manager_cp_command} --log-level debug"
fi
# prepend sudo (or the specified sudo program) if we want our container manager to be rootful
if [ "${rootful}" -ne 0 ]; then
extra_flags="${extra_flags} --root"
container_manager="${distrobox_sudo_program:-"sudo"} ${container_manager}"
container_manager_cp_command="${distrobox_sudo_program:-"sudo"} ${container_manager_cp_command}"
fi
if ! ${container_manager} inspect --type container "${container_name}" > /dev/null; then
printf >&2 "Cannot find container %s. Please create it first.\n" "${container_name}"
exit 1
fi
# Ensure the destination dir exists.
mkdir -p "${XDG_DATA_HOME:-${HOME}/.local/share}/applications"
mkdir -p "${XDG_DATA_HOME:-${HOME}/.local/share}/icons/distrobox"
distrobox_path="$(dirname "$(realpath "${0}")")"
entry_name="$(echo "${container_name}" | cut -c1 | tr "[:lower:]" "[:upper:]")$(echo "${container_name}" | cut -c2-)"
if [ "${icon}" = "auto" ]; then
# Set icon to the generic terminal as a fallback.
icon="${icon_default}"
# This is a NON comprehensive list of logos of the most popular distributions. If you find logos for
# other supported distros, add it here.
DISTRO_ICON_MAP="
alma:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/alma-distrobox.png
alpine:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/alpine-distrobox.png
alt:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/alt-distrobox.png
arch:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/arch-distrobox.png
centos:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/centos-distrobox.png
clear--os:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/clear-distrobox.png
debian:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/debian-distrobox.png
deepin:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/deepin-distrobox.png
fedora:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/fedora-distrobox.png
gentoo:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/gentoo-distrobox.png
kali:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/kali-distrobox.png
kdeneon:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/kdeneon-distrobox.png
opensuse-leap:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/opensuse-distrobox.png
opensuse-tumbleweed:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/opensuse-distrobox.png
rhel:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/redhat-distrobox.png
rocky:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/rocky-distrobox.png
ubuntu:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/ubuntu-distrobox.png
vanilla:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/vanilla-distrobox.png
void:https://raw.githubusercontent.com/89luca89/distrobox/main/docs/assets/png/distros/void-distrobox.png
"
# Try to detect container's distribution by using /etc/os-release
${container_manager_cp_command} "${container_name}":/etc/os-release /tmp/"${container_name}".os-release
container_distro="$(grep "^ID=" /tmp/"${container_name}".os-release |
cut -d'=' -f2- |
sed "s|linux||g" |
tr -d ' ' |
tr -d '"')"
if [ "${rootful}" -ne 0 ]; then
${distrobox_sudo_program:-"sudo"} rm -f /tmp/"${container_name}".os-release
else
rm -f /tmp/"${container_name}".os-release
fi
icon_url="$(echo "${DISTRO_ICON_MAP}" | grep "${container_distro}:" | cut -d':' -f2-)"
# Distro not found in our map, fallback to generic icon
if [ -z "${icon_url}" ]; then
icon_url="https://raw.githubusercontent.com/89luca89/distrobox/main/icons/terminal-distrobox-icon.svg"
container_distro="terminal-distrobox-icon"
fi
if [ -n "${icon_url}" ] && [ "${download}" != "null" ]; then
icon_extension="${icon_url##*.}"
if [ "${online}" -lt 1 ] && ${download} - "${icon_url}" > "${XDG_DATA_HOME:-${HOME}/.local/share}/icons/distrobox/${container_distro}.${icon_extension}"; then
icon="${XDG_DATA_HOME:-${HOME}/.local/share}/icons/distrobox/${container_distro}.${icon_extension}"
else
# Wget failed for some reasons. Default to generic terminal icon as declared at the beginning.
printf >&2 "Warning: Failed to download icon. Defaulting to generic one.\n"
fi
else
# Distribution not found in the list. Default to generic terminal icon as declared at the beginning.
printf >&2 "Warning: Distribution not found in default icon set. Defaulting to generic one.\n"
fi
fi
cat << EOF > "${XDG_DATA_HOME:-${HOME}/.local/share}/applications/${container_name}.desktop"
[Desktop Entry]
Name=${entry_name}
GenericName=Terminal entering ${entry_name}
Comment=Terminal entering ${entry_name}
Categories=Distrobox;System;Utility
Exec=${distrobox_path}/distrobox enter ${extra_flags} ${container_name}
Icon=${icon}
Keywords=distrobox;
NoDisplay=false
Terminal=true
TryExec=${distrobox_path}/distrobox
Type=Application
Actions=Remove;
[Desktop Action Remove]
Name=Remove ${entry_name} from system
Exec=${distrobox_path}/distrobox rm ${extra_flags} ${container_name}
EOF

View File

@@ -0,0 +1,234 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-3.0-only
#
# This file is part of the distrobox project:
# https://github.com/89luca89/distrobox
#
# Copyright (C) 2022 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)"
# Defaults
host_command=""
non_interactive=0
# If we're in a non-interactive shell, let's act accordingly
if [ ! -t 1 ] ||
! tty > /dev/null 2>&1; then
non_interactive=1
fi
distrobox_host_exec_default_command="${SHELL:-/bin/sh}"
host_spawn_version="v1.6.0"
download_command=""
sudo_command=""
verbose=0
version="1.8.2.2"
# show_help will print usage to stdout.
# Arguments:
# None
# Expected global variables:
# version: distrobox version
# Expected env variables:
# None
# Outputs:
# print usage with examples.
show_help()
{
cat << EOF
distrobox version: ${version}
Usage:
distrobox-host-exec [command [arguments]]
distrobox-host-exec ls
distrobox-host-exec bash -l
distrobox-host-exec flatpak run org.mozilla.firefox
distrobox-host-exec podman ps -a
Options:
--help/-h: show this message
--verbose/-v: show more verbosity
--version/-V: show version
--yes/-Y: Automatically answer yes to prompt:
host-spawn will be installed on the guest system
if host-spawn is not detected.
This behaviour is default when running in a non-interactive shell.
EOF
}
# If we're a symlink to a command, use that as command to exec, and skip arg parsing.
if [ "$(basename "${0}")" != "distrobox-host-exec" ]; then
host_command="$(basename "${0}")"
fi
# Parse arguments
if [ -z "${host_command}" ]; then
# Skip argument parsing if we're a symlink
while :; do
case $1 in
-h | --help)
# Call a "show_help" function to display a synopsis, then exit.
show_help
exit 0
;;
-v | --verbose)
verbose=1
shift
;;
-V | --version)
printf "distrobox: %s\n" "${version}"
printf "host-spawn: %s\n" "${host_spawn_version}"
exit 0
;;
-Y | --yes)
non_interactive=1
shift
;;
--) # End of all options.
shift
;;
-*) # Invalid options.
printf >&2 "ERROR: Invalid flag '%s'\n\n" "$1"
show_help
exit 1
;;
*)
if [ -n "$1" ]; then
host_command=$1
shift
fi
break
;;
esac
done
fi
set -o errexit
set -o nounset
# set verbosity
if [ "${verbose}" -ne 0 ]; then
set -o xtrace
fi
# Check we're running inside a container and not on the host
if [ ! -f /run/.containerenv ] && [ ! -f /.dockerenv ] && [ -z "${container:-}" ]; then
printf >&2 "You must run %s inside a container!\n" " $(basename "$0")"
exit 126
fi
if [ -z "${host_command}" ]; then
host_command="${distrobox_host_exec_default_command}"
fi
if [ "$(id -ru)" -ne 0 ]; then
if command -v sudo 2> /dev/null > /dev/null; then
sudo_command="sudo"
else
sudo_command="su -l -c"
fi
fi
if command -v curl > /dev/null 2>&1; then
download_command="curl -sLfo"
elif command -v wget > /dev/null 2>&1; then
download_command="wget -qO"
fi
# Normalize architecture name to comply to golang/release naming
architecture="$(uname -m)"
if echo "${architecture}" | grep -q armv; then
architecture="$(echo "${architecture}" | grep -Eo "armv[0-9]+")"
fi
# Setup host-spawn as a way to execute commands back on the host
if ! command -v host-spawn > /dev/null ||
[ "$(printf "%s\n%s\n" "${host_spawn_version}" "$(host-spawn --version)" |
sort -V | head -n 1)" != "${host_spawn_version}" ]; then
# if non-interactive flag flag hasn't been set
if [ "${non_interactive}" -eq 0 ]; then
# Prompt to download it.
printf "Warning: host-spawn not found or version is too old!\n"
printf "Do you want to install host-spawn utility? [Y/n] "
read -r response
response=${response:-"Y"}
else
response="yes"
fi
# Accept only y,Y,Yes,yes,n,N,No,no.
case "${response}" in
y | Y | Yes | yes | YES)
# Download matching version with current distrobox
if ! ${download_command} /tmp/host-spawn \
"https://github.com/1player/host-spawn/releases/download/${host_spawn_version}/host-spawn-${architecture}"; then
printf "Error: Cannot download host-spawn\n"
exit 1
fi
if [ -e /tmp/host-spawn ]; then
${sudo_command} sh -c "mv /tmp/host-spawn /usr/bin/"
${sudo_command} sh -c "chmod +x /usr/bin/host-spawn"
fi
;;
n | N | No | no | NO)
printf "Installation aborted, please install host-spawn.\n"
exit 0
;;
*) # Default case: If no more options then break out of the loop.
printf >&2 "Invalid input.\n"
printf >&2 "The available choices are: y,Y,Yes,yes,YES or n,N,No,no,NO.\nExiting.\n"
exit 1
;;
esac
fi
# This makes host-spawn work on initful containers, where the dbus session is
# separate from the host, we point the dbus session straight to the host's socket
# in order to talk with the org.freedesktop.Flatpak.Development.HostCommand on the host
[ -z "${XDG_RUNTIME_DIR:-}" ] && XDG_RUNTIME_DIR="/run/user/$(id -ru)"
[ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ] && DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -ru)/bus"
XDG_RUNTIME_DIR="/run/host/${XDG_RUNTIME_DIR}"
DBUS_SESSION_BUS_ADDRESS="unix:path=/run/host/$(echo "${DBUS_SESSION_BUS_ADDRESS}" | cut -d '=' -f2-)"
###
# This workaround is needed because of a bug in gio (used by xdg-open) where
# a race condition happens when allocating a pty, leading to the command
# being killed before having time to be executed.
#
# https://gitlab.gnome.org/GNOME/glib/-/issues/2695
# https://github.com/1player/host-spawn/issues/7
#
# As an (ugly) workaround, we will not allocate a pty for those commands.
###
# Also, we don't initialize a pty, if we're not in a tty.
if [ "$(basename "${host_command}")" = "xdg-open" ] ||
[ "$(basename "${host_command}")" = "gio" ] ||
[ "$(basename "${host_command}")" = "flatpak" ] ||
[ ! -t 1 ] ||
! tty > /dev/null 2>&1; then
host-spawn --no-pty "${host_command}" "$@"
# Exit here, we don't continue execution
exit $?
fi
host-spawn "${host_command}" "$@"
# Exit here, we don't continue execution
exit $?

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,240 @@
#!/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/>.
# POSIX
next=0
no_color=0
verbose=0
version=1.8.2.2
# show_help will print usage to stdout.
# Arguments:
# None
# Outputs:
# print usage with examples.
show_help()
{
cat << EOF
install --prefix /usr/local
Options:
--prefix/-P: base bath where all files will be deployed (default /usr/local if root, ~/.local if not)
--next/-N: install latest development version from git, instead of the latest stable release.
--no-color: disable color formatting
--help/-h: show this message
-v: show more verbosity
EOF
}
# Parse arguments
while :; do
case $1 in
-h | --help)
# Call a "show_help" function to display a synopsis, then exit.
show_help
exit
;;
--no-color)
shift
no_color=1
;;
-v | --verbose)
shift
verbose=1
;;
-N | --next)
shift
next=1
;;
-P | --prefix)
if [ -n "$2" ]; then
prefix="$2"
shift
shift
fi
;;
*) # Default case: If no more options then break out of the loop.
break ;;
esac
done
# if we're in not a tty, don't use colors
BOLD_GREEN=""
BOLD_RED=""
CLEAR=""
if [ -t 0 ] && [ -t 1 ] && [ "${no_color}" -ne 1 ]; then
# we're in a tty, use colors
BOLD_GREEN='\033[1;32m'
BOLD_RED='\033[1;31m'
CLEAR='\033[0m'
fi
if [ -z "${prefix}" ]; then
prefix="/usr/local"
# in case we're not root, just default to the home directory
if [ "$(id -u)" -ne 0 ]; then
prefix="${HOME}/.local"
fi
fi
dest_path="${prefix}/bin"
man_dest_path="${prefix}/share/man/man1"
icon_dest_path="${prefix}/share/icons/hicolor"
completion_bash_dest_path="${prefix}/share/bash-completion/completions/"
completion_zsh_dest_path="${prefix}/share/zsh/site-functions/"
set -o errexit
set -o nounset
# set verbosity
if [ "${verbose}" -ne 0 ]; then
set -o xtrace
fi
# get current dir
curr_dir=$(dirname "$0")
cd "${curr_dir}" || exit 1
# if files are available, install files in dest directory
# else download targz and uncompress it
if [ -e "${curr_dir}/distrobox-enter" ]; then
if ! install -d "${dest_path}" "${man_dest_path}" "${completion_bash_dest_path}" \
"${completion_zsh_dest_path}" "${icon_dest_path}/scalable/apps"; then
printf >&2 "Do you have permission to write to %s?\n" "${prefix}"
exit 1
fi
for file in distrobox*; do
if ! install -m 0755 "${file}" "${dest_path}"; then
printf >&2 "Do you have permission to write to %s?\n" "${dest_path}"
exit 1
fi
done
if [ -e "man" ]; then
for file in man/man1/*; do
install -m 0644 "${file}" "${man_dest_path}"
done
fi
if [ -e "completions" ]; then
for file in completions/bash/*; do
install -m 0644 "${file}" "${completion_bash_dest_path}"
done
fi
if [ -e "completions" ]; then
for file in completions/zsh/*; do
install -m 0644 "${file}" "${completion_zsh_dest_path}"
done
fi
if [ -e icons/terminal-distrobox-icon.svg ]; then
install -m 0644 icons/terminal-distrobox-icon.svg "${icon_dest_path}/scalable/apps"
for sz in 16 22 24 32 36 48 64 72 96 128 256; do
install -d "${icon_dest_path}/${sz}x${sz}/apps"
install -m 0644 icons/hicolor/"${sz}x${sz}"/apps/terminal-distrobox-icon.png \
"${icon_dest_path}/${sz}x${sz}/apps"
done
fi
else
printf >&2 "%b Checking dependencies...\n%b" "${BOLD_GREEN}" "${CLEAR}"
# check that we have base dependencies
if ! {
command -v curl > /dev/null || command -v wget > /dev/null
} || ! command -v tar > /dev/null; then
printf >&2 "Online install depends on tar and either curl or wget\n"
printf >&2 "Ensure you have all dependencies installed.\n"
exit 1
fi
if command -v curl > /dev/null 2>&1; then
download="curl -sLo"
elif command -v wget > /dev/null 2>&1; then
download="wget -qO"
fi
printf >&2 "%b Downloading...\n%b" "${BOLD_RED}" "${CLEAR}"
if [ "${next}" -eq 0 ]; then
release_ver="89luca89/distrobox/archive/refs/tags/${version}.tar.gz"
release_name=$(basename "${release_ver}")
else
release_ver="89luca89/distrobox/archive/refs/heads/main.tar.gz"
release_name="main"
fi
# go in tmp
tmp_dir="$(mktemp -d)"
cd "${tmp_dir}"
# download our target
${download} "${release_name}" "https://github.com/${release_ver}"
# uncompress
printf >&2 "%b Unpacking...\n%b" "${BOLD_RED}" "${CLEAR}"
if [ "${verbose}" -ne 0 ]; then
tar xvf "${release_name}"
else
tar xf "${release_name}"
fi
# deploy our files
if ! install -d "${dest_path}" "${man_dest_path}" "${completion_bash_dest_path}" \
"${completion_zsh_dest_path}" "${icon_dest_path}/scalable/apps"; then
printf >&2 "Do you have permission to write to %s?\n" "${prefix}"
exit 1
fi
for file in "distrobox-$(echo "${release_name}" | sed 's/.tar.gz//g')"/distrobox*; do
if ! install -m 0755 "${file}" "${dest_path}"; then
printf >&2 "Do you have permission to write to %s?\n" "${dest_path}"
exit 1
fi
done
if [ -e "distrobox-$(echo "${release_name}" | sed 's/.tar.gz//g')/man/" ]; then
for file in "distrobox-$(echo "${release_name}" | sed 's/.tar.gz//g')"/man/man1/*; do
install -m 0644 "${file}" "${man_dest_path}"
done
fi
if [ -e "distrobox-$(echo "${release_name}" | sed 's/.tar.gz//g')/completions/bash/" ]; then
for file in "distrobox-$(echo "${release_name}" | sed 's/.tar.gz//g')"/completions/bash/*; do
install -m 0644 "${file}" "${completion_bash_dest_path}"
done
fi
if [ -e "distrobox-$(echo "${release_name}" | sed 's/.tar.gz//g')/completions/zsh/" ]; then
for file in "distrobox-$(echo "${release_name}" | sed 's/.tar.gz//g')"/completions/zsh/*; do
install -m 0644 "${file}" "${completion_zsh_dest_path}"
done
fi
if [ -e "distrobox-$(echo "${release_name}" | sed 's/.tar.gz//g')"/icons/terminal-distrobox-icon.svg ]; then
install -m 0644 "distrobox-$(echo "${release_name}" | sed 's/.tar.gz//g')"/icons/terminal-distrobox-icon.svg \
"${icon_dest_path}/scalable/apps"
for sz in 16 22 24 32 36 48 64 72 96 128 256; do
install -d "${icon_dest_path}/${sz}x${sz}/apps"
install -m 0644 "distrobox-$(echo "${release_name}" | sed 's/.tar.gz//g')/icons/hicolor/${sz}x${sz}/apps/terminal-distrobox-icon.png" \
"${icon_dest_path}/${sz}x${sz}/apps"
done
fi
# securely delete unneeded files
cd
if [ -n "${tmp_dir}" ] && [ -e "${tmp_dir}" ]; then
rm -rf "${tmp_dir}"
fi
fi
[ ! -w "${dest_path}" ] && printf >&2 "Cannot write into %s, permission denied.\n" "${dest_path}" && exit 1
[ ! -w "${man_dest_path}" ] && printf >&2 "Cannot write into %s, permission denied.\n" "${man_dest_path}" && exit 1
printf >&2 "%b Installation successful!\n%b" "${BOLD_GREEN}" "${CLEAR}"
printf >&2 "%b Shell scripts are located in %b%s\n%b" "${CLEAR}" "${BOLD_RED}" "${dest_path}" "${CLEAR}"
printf >&2 "%b Manpages are located in %b%s\n%b" "${CLEAR}" "${BOLD_RED}" "${man_dest_path}" "${CLEAR}"
if ! echo "${PATH}" | grep -q "${dest_path}"; then
printf >&2 "%b Be sure that %b%s%b is in your %b\$PATH%b environment variable to be able to use distrobox without specifying the full path.\n%s" "${CLEAR}" "${BOLD_RED}" "${dest_path}" "${CLEAR}" "${BOLD_RED}" "${CLEAR}" "${CLEAR}"
fi

View File

@@ -0,0 +1,276 @@
#!/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/>.
# POSIX
# Optional env variables:
# DBX_CONTAINER_MANAGER
# DBX_VERBOSE
# DBX_SUDO_PROGRAM
# 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}" ]
} && [ "$(id -ru)" -eq 0 ]; then
printf >&2 "Running %s via SUDO/DOAS is not supported. Instead, please try running:\n" "$(basename "${0}")"
printf >&2 " %s --root %s\n" "$(basename "${0}")" "$*"
exit 1
fi
# 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)"
# Defaults
no_color=0
# If the user runs this script as root in a login shell, set rootful=1.
# There's no need for them to pass the --root flag option in such cases.
[ "$(id -ru)" -eq 0 ] && rootful=1 || rootful=0
verbose=0
version="1.8.2.2"
container_manager="autodetect"
# 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
# If we're running this script as root -- as in logged in in the shell as root
# user, and not via SUDO/DOAS --, we don't need to set distrobox_sudo_program
# as it's meaningless for this use case.
if [ "$(id -ru)" -ne 0 ]; then
# If the DBX_SUDO_PROGRAM/distrobox_sudo_program variable was set by the
# user, use its value instead of "sudo". But only if not running the script
# as root (UID 0).
distrobox_sudo_program=${DBX_SUDO_PROGRAM:-${distrobox_sudo_program:-"sudo"}}
fi
[ -n "${DBX_CONTAINER_MANAGER}" ] && container_manager="${DBX_CONTAINER_MANAGER}"
# show_help will print usage to stdout.
# Arguments:
# None
# Expected global variables:
# version: distrobox version
# Expected env variables:
# None
# Outputs:
# print usage with examples.
show_help()
{
cat << EOF
distrobox version: ${version}
Usage:
distrobox-list
Options:
--help/-h: show this message
--no-color: disable color formatting
--root/-r: launch podman/docker/lilipod with root privileges. Note that if you need root this is the preferred
way over "sudo distrobox" (note: if using a program other than 'sudo' for root privileges is necessary,
specify it through the DBX_SUDO_PROGRAM env variable, or 'distrobox_sudo_program' config variable)
--verbose/-v: show more verbosity
--version/-V: show version
EOF
}
# Parse arguments
while :; do
case $1 in
-h | --help)
# Call a "show_help" function to display a synopsis, then exit.
show_help
exit 0
;;
--no-color)
shift
no_color=1
;;
-r | --root)
shift
rootful=1
;;
-v | --verbose)
verbose=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.
break ;;
esac
done
set -o errexit
set -o nounset
# set verbosity
if [ "${verbose}" -ne 0 ]; then
set -o xtrace
fi
# We depend on a container manager let's be sure we have it
# First we use podman, else docker, else lilipod
case "${container_manager}" in
autodetect)
if command -v podman > /dev/null; then
container_manager="podman"
elif command -v podman-launcher > /dev/null; then
container_manager="podman-launcher"
elif command -v docker > /dev/null; then
container_manager="docker"
elif command -v lilipod > /dev/null; then
container_manager="lilipod"
fi
;;
podman)
container_manager="podman"
;;
podman-launcher)
container_manager="podman-launcher"
;;
lilipod)
container_manager="lilipod"
;;
docker)
container_manager="docker"
;;
*)
printf >&2 "Invalid input %s.\n" "${container_manager}"
printf >&2 "The available choices are: 'autodetect', 'podman', 'docker', 'lilipod'\n"
;;
esac
# Be sure we have a container manager to work with.
if ! command -v "${container_manager}" > /dev/null; then
# Error: we need at least one between docker, podman or lilipod.
printf >&2 "Missing dependency: we need a container manager.\n"
printf >&2 "Please install one of podman, docker or lilipod.\n"
printf >&2 "You can follow the documentation on:\n"
printf >&2 "\tman distrobox-compatibility\n"
printf >&2 "or:\n"
printf >&2 "\thttps://github.com/89luca89/distrobox/blob/main/docs/compatibility.md\n"
exit 127
fi
# add verbose if -v is specified
if [ "${verbose}" -ne 0 ]; then
container_manager="${container_manager} --log-level debug"
fi
# prepend sudo (or the specified sudo program) if we want our container manager to be rootful
if [ "${rootful}" -ne 0 ]; then
container_manager="${distrobox_sudo_program-} ${container_manager}"
fi
# List containers using custom format that included MOUNTS
# we do this as we can detect the custom mounts done by distrobox to distringuish
# between a normal container and a distrobox one.
container_list=$(${container_manager} ps -a --no-trunc --format \
"{{.ID}}|{{.Image}}|{{.Names}}|{{.Status}}|{{.Labels}}{{.Mounts}}")
printf "%-12s | %-20s | %-18s | %-30s\n" \
"ID" "NAME" "STATUS" "IMAGE"
IFS='
'
# if we're in not a tty, don't use colors
GREEN=""
YELLOW=""
CLEAR=""
if [ -t 0 ] && [ -t 1 ] && [ "${no_color}" -ne 1 ]; then
# we're in a tty, use colors
GREEN='\033[32m'
YELLOW='\033[33m'
CLEAR='\033[0m'
fi
# Header of the output
for container in ${container_list}; do
# Check if the current container has a custom mount point for distrobox.
if [ -z "${container##*distrobox*}" ]; then
# Extract the information for the single container to pretty print it
container_id="$(printf "%s" "${container}" | cut -d'|' -f1 | cut -c1-12)"
container_image="$(printf "%s" "${container}" | cut -d'|' -f2)"
container_name="$(printf "%s" "${container}" | cut -d'|' -f3)"
container_status="$(printf "%s" "${container}" | cut -d'|' -f4)"
IFS=' '
# If the container is Up and Running, print it in green and go next.
if [ -z "${container_status##*Up*}" ] || [ -z "${container_status##*running*}" ]; then
# echo -e is not defined in posix, and printing with %s will not work
# for colors, so we're disabling this lint for color prints.
# shellcheck disable=SC2059
printf "${GREEN}"
else
# shellcheck disable=SC2059
printf "${YELLOW}"
fi
# print it in yellow if not Running
printf "%-12s | %-20s | %-18s | %-30s" \
"${container_id}" "${container_name}" "${container_status}" "${container_image}"
# shellcheck disable=SC2059
printf "${CLEAR}\n"
fi
done

View File

@@ -0,0 +1,464 @@
#!/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/>.
# POSIX
# Optional env variables:
# DBX_CONTAINER_MANAGER
# DBX_CONTAINER_NAME
# DBX_CONTAINER_RM_CUSTOM_HOME
# DBX_NON_INTERACTIVE
# DBX_VERBOSE
# DBX_SUDO_PROGRAM
# 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}" ]
} && [ "$(id -ru)" -eq 0 ]; then
printf >&2 "Running %s via SUDO/DOAS is not supported. Instead, please try running:\n" "$(basename "${0}")"
printf >&2 " %s --root %s\n" "$(basename "${0}")" "$*"
exit 1
fi
# 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)"
# Defaults
all=0
container_manager="autodetect"
distrobox_flags=""
distrobox_path="$(dirname "$(realpath "${0}")")"
force=0
force_flag=""
non_interactive=0
# If the user runs this script as root in a login shell, set rootful=1.
# There's no need for them to pass the --root flag option in such cases.
[ "$(id -ru)" -eq 0 ] && rootful=1 || rootful=0
verbose=0
rm_home=0
response_rm_home="N"
version="1.8.2.2"
# 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_CONTAINER_MANAGER}" ] && container_manager="${DBX_CONTAINER_MANAGER}"
[ -n "${DBX_CONTAINER_RM_CUSTOM_HOME}" ] && rm_home="${DBX_CONTAINER_RM_CUSTOM_HOME}"
[ -n "${DBX_NON_INTERACTIVE}" ] && non_interactive="${DBX_NON_INTERACTIVE}"
[ -n "${DBX_VERBOSE}" ] && verbose="${DBX_VERBOSE}"
# Fixup variable=[true|false], in case we find it in the config file(s)
[ "${non_interactive}" = "true" ] && non_interactive=1
[ "${non_interactive}" = "false" ] && non_interactive=0
[ "${verbose}" = "true" ] && verbose=1
[ "${verbose}" = "false" ] && verbose=0
# If we're running this script as root - as in logged in in the shell as root
# user, and not via SUDO/DOAS -, we don't need to set distrobox_sudo_program
# as it's meaningless for this use case.
if [ "$(id -ru)" -ne 0 ]; then
# If the DBX_SUDO_PROGRAM/distrobox_sudo_program variable was set by the
# user, use its value instead of "sudo". But only if not running the script
# as root (UID 0).
distrobox_sudo_program=${DBX_SUDO_PROGRAM:-${distrobox_sudo_program:-"sudo"}}
fi
# Declare it AFTER config sourcing because we do not want a default name set for rm.
container_name_default="my-distrobox"
container_name_list=""
# show_help will print usage to stdout.
# Arguments:
# None
# Expected global variables:
# version: distrobox version
# Expected env variables:
# None
# Outputs:
# print usage with examples.
show_help()
{
cat << EOF
distrobox version: ${version}
Usage:
distrobox-rm [-f/--force] container-name [container-name1 container-name2 ...]
Options:
--all/-a: delete all distroboxes
--force/-f: force deletion
--rm-home: remove the mounted home if it differs from the host user's one
--root/-r: launch podman/docker/lilipod with root privileges. Note that if you need root this is the preferred
way over "sudo distrobox" (note: if using a program other than 'sudo' for root privileges is necessary,
specify it through the DBX_SUDO_PROGRAM env variable, or 'distrobox_sudo_program' config variable)
--help/-h: show this message
--verbose/-v: show more verbosity
--version/-V: show version
EOF
}
# Parse arguments
while :; do
case $1 in
-h | --help)
# Call a "show_help" function to display a synopsis, then exit.
show_help
exit 0
;;
-a | --all)
shift
all=1
;;
-r | --root)
shift
rootful=1
;;
--rm-home)
shift
rm_home=1
;;
-v | --verbose)
verbose=1
shift
;;
-V | --version)
printf "distrobox: %s\n" "${version}"
exit 0
;;
-f | --force)
force=1
non_interactive=1
shift
;;
-Y | --yes)
non_interactive=1
shift
;;
--) # 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
container_name_list="${container_name_list} $1"
shift
else
break
fi
;;
esac
done
set -o errexit
set -o nounset
# set verbosity
if [ "${verbose}" -ne 0 ]; then
set -o xtrace
fi
# We depend on a container manager let's be sure we have it
# First we use podman, else docker, else lilipod
case "${container_manager}" in
autodetect)
if command -v podman > /dev/null; then
container_manager="podman"
elif command -v podman-launcher > /dev/null; then
container_manager="podman-launcher"
elif command -v docker > /dev/null; then
container_manager="docker"
elif command -v lilipod > /dev/null; then
container_manager="lilipod"
fi
;;
podman)
container_manager="podman"
;;
podman-launcher)
container_manager="podman-launcher"
;;
lilipod)
container_manager="lilipod"
;;
docker)
container_manager="docker"
;;
*)
printf >&2 "Invalid input %s.\n" "${container_manager}"
printf >&2 "The available choices are: 'autodetect', 'podman', 'docker', 'lilipod'\n"
;;
esac
# Be sure we have a container manager to work with.
if ! command -v "${container_manager}" > /dev/null; then
# Error: we need at least one between docker, podman or lilipod.
printf >&2 "Missing dependency: we need a container manager.\n"
printf >&2 "Please install one of podman, docker or lilipod.\n"
printf >&2 "You can follow the documentation on:\n"
printf >&2 "\tman distrobox-compatibility\n"
printf >&2 "or:\n"
printf >&2 "\thttps://github.com/89luca89/distrobox/blob/main/docs/compatibility.md\n"
exit 127
fi
# add verbose if -v is specified
if [ "${verbose}" -ne 0 ]; then
container_manager="${container_manager} --log-level debug"
fi
# add -f if force is specified
if [ "${force}" -ne 0 ]; then
force_flag="--force"
fi
# prepend sudo (or the specified sudo program) if we want our container manager to be rootful
if [ "${rootful}" -ne 0 ]; then
container_manager="${distrobox_sudo_program-} ${container_manager}"
distrobox_flags="--root"
fi
# If all, just set container_name to the list of names in distrobox-list
if [ "${all}" -ne 0 ]; then
# prepend sudo (or the specified sudo program) if we want our container manager to be rootful
# shellcheck disable=SC2086,2248
container_name_list="$("${distrobox_path}"/distrobox-list ${distrobox_flags} --no-color |
tail -n +2 | cut -d'|' -f2 | tr -d ' ' | tr '\n' ' ')"
fi
if [ -z "${container_name_list}" ] && [ "${all}" -ne 0 ]; then
printf >&2 "No containers found.\n"
exit 0
fi
# check if we have containers to delete
if [ -z "${container_name_list}" ]; then
container_name_list="${container_name_default}"
fi
# cleanup_exports will remove exported apps and bins for container to delete.
# Arguments:
# container_name: string container name
# Expected global variables:
# distrobox_flags: string additional distrobox flags to use
# Expected env variables:
# None
# Outputs:
# None
cleanup_exports()
{
container_name="$1"
IFS='¤'
printf "Removing exported binaries...\n"
binary_files="$(grep -rl "# distrobox_binary" "${HOME}/.local/bin" 2> /dev/null | sed 's/./\\&/g' |
xargs -I{} grep -le "# name: ${container_name}$" "{}" | sed 's/./\\&/g' |
xargs -I{} printf "%s¤" "{}" 2> /dev/null || :)"
for file in ${binary_files}; do
printf "Removing exported binary %s...\n" "${file}"
rm -f "${file}"
done
# Remove exported gui apps from this container in default path
# shellcheck disable=SC2086,SC2038
desktop_files="$(find "${HOME}/.local/share/applications/${container_name}"* -type f -o -type l 2> /dev/null | sed 's/./\\&/g' |
xargs -I{} grep -le "Exec=.*${container_name} " "{}" | sed 's/./\\&/g' |
xargs -I{} printf "%s¤" "{}" 2> /dev/null || :)"
for file in ${desktop_files}; do
if [ -e "${file}" ]; then
app="$(grep -Eo "Name=.*" "${file}" | head -n 1 | cut -d'=' -f2)"
icon="$(grep -Eo "Icon=.*" "${file}" | head -n 1 | cut -d'=' -f2)"
printf "Removing exported app %s...\n" "${app}"
rm -f "${file}"
find "${HOME}/.local/share/icons" -name "${icon}.*" -delete
fi
done
unset IFS
}
# delete_container will remove input container
# Arguments:
# container_name: string container name
# Expected global variables:
# container_manager: string container manager to use
# distrobox_flags: string distrobox additional flags
# non_interactive: bool non interactive mode
# force_flag: bool force mode
# rm_home: bool remove home
# verbose: bool verbose
# Expected env variables:
# None
# Outputs:
# None
delete_container()
{
container_name="$1"
# Inspect the container we're working with.
container_status="$(${container_manager} inspect --type container \
--format '{{.State.Status}}' "${container_name}" || :)"
# Does the container exist? check if inspect reported errors
if [ -z "${container_status}" ]; then
# If not, prompt to create it first
printf >&2 "Cannot find container %s.\n" "${container_name}"
return
fi
# Retrieve container's HOME, and check if it's different from host's one. In
# this case we prompt for deletion of the custom home.
container_home=$(${container_manager} inspect --type container --format \
'{{range .Config.Env}}{{if and (ge (len .) 5) (eq (slice . 0 5) "HOME=")}}{{slice . 5}}{{end}}{{end}}' "${container_name}")
# Prompt for confirmation
if [ "${container_home}" != "${HOME}" ]; then
if [ "${non_interactive}" -eq 0 ] &&
[ "${rm_home}" -eq 1 ]; then
printf "Do you want to remove custom home of container %s (%s)? [y/N]: " "${container_name}" "${container_home}"
read -r response_rm_home
response_rm_home="${response_rm_home:-"N"}"
fi
fi
# Validate home response
# Accept only y,Y,Yes,yes,n,N,No,no.
case "${response_rm_home}" in
y | Y | Yes | yes | YES)
rm_home_local=1
;;
n | N | No | no | NO)
rm_home_local=0
;;
*) # Default case: If no more options then break out of the loop.
printf >&2 "Invalid input.\n"
printf >&2 "The available choices are: y,Y,Yes,yes,YES or n,N,No,no,NO.\nExiting.\n"
exit 1
;;
esac
# Remove the container
printf "Removing container...\n"
# shellcheck disable=SC2086,SC2248
${container_manager} rm ${force_flag} --volumes "${container_name}"
# Remove exported apps and bins
cleanup_exports "${container_name}"
# We're going to delete the box, let's also delete the entry
verbose_arg=""
if [ "${verbose}" -ne 0 ]; then
verbose_arg="--verbose"
fi
"$(dirname "$(realpath "${0}")")/distrobox-generate-entry" "${container_name}" --delete "${verbose_arg}"
# Remove custom home
if [ "${rm_home_local}" -eq 1 ]; then
rm -r "${container_home}"
printf "Successfully removed %s\n" "${container_home}"
fi
}
# Prompt for confirmation
if [ "${non_interactive}" -eq 0 ] && [ "${force}" -eq 0 ]; then
printf "Do you really want to delete containers:%s? [Y/n]: " "${container_name_list}"
read -r response
response="${response:-"Y"}"
else
response="yes"
fi
for container in ${container_name_list}; do
if [ "$(${container_manager} inspect --type container --format '{{.State.Status}}' "${container}")" = "running" ]; then
if [ "${non_interactive}" -eq 0 ] && [ "${force}" -eq 0 ]; then
printf "Container %s running, do you want to force delete them? [Y/n]: " "${container_name_list}"
read -r response_force
response_force="${response_force:-"Y"}"
else
response_force="yes"
fi
fi
# Accept only y,Y,Yes,yes,n,N,No,no.
case "${response_force:-"N"}" in
y | Y | Yes | yes | YES)
force=1
force_flag="--force"
break
;;
n | N | No | no | NO) ;;
*) # Default case: If no more options then break out of the loop.
printf >&2 "Invalid input.\n"
printf >&2 "The available choices are: y,Y,Yes,yes,YES or n,N,No,no,NO.\nExiting.\n"
;;
esac
done
# Accept only y,Y,Yes,yes,n,N,No,no.
case "${response}" in
y | Y | Yes | yes | YES)
for container in ${container_name_list}; do
delete_container "${container}"
done
;;
n | N | No | no | NO)
printf "Aborted.\n"
exit 0
;;
*) # Default case: If no more options then break out of the loop.
printf >&2 "Invalid input.\n"
printf >&2 "The available choices are: y,Y,Yes,yes,YES or n,N,No,no,NO.\nExiting.\n"
exit 1
;;
esac

View File

@@ -0,0 +1,305 @@
#!/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/>.
# POSIX
# Optional env variables:
# DBX_CONTAINER_MANAGER
# DBX_CONTAINER_NAME
# DBX_NON_INTERACTIVE
# DBX_VERBOSE
# DBX_SUDO_PROGRAM
# 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}" ]
} && [ "$(id -ru)" -eq 0 ]; then
printf >&2 "Running %s via SUDO/DOAS is not supported. Instead, please try running:\n" "$(basename "${0}")"
printf >&2 " %s --root %s\n" "$(basename "${0}")" "$*"
exit 1
fi
# 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)"
# Defaults
all=0
container_manager="autodetect"
distrobox_flags=""
distrobox_path="$(dirname "$(realpath "${0}")")"
container_name=""
container_name_default="my-distrobox"
container_name_list=""
non_interactive=0
# If the user runs this script as root in a login shell, set rootful=1.
# There's no need for them to pass the --root flag option in such cases.
[ "$(id -ru)" -eq 0 ] && rootful=1 || rootful=0
verbose=0
version="1.8.2.2"
# 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_CONTAINER_MANAGER}" ] && container_manager="${DBX_CONTAINER_MANAGER}"
[ -n "${DBX_CONTAINER_NAME}" ] && container_name="${DBX_CONTAINER_NAME}"
[ -n "${DBX_NON_INTERACTIVE}" ] && non_interactive="${DBX_NON_INTERACTIVE}"
[ -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
[ "${non_interactive}" = "true" ] && non_interactive=1
[ "${non_interactive}" = "false" ] && non_interactive=0
# If we're running this script as root - as in logged in in the shell as root
# user, and not via SUDO/DOAS -, we don't need to set distrobox_sudo_program
# as it's meaningless for this use case.
if [ "$(id -ru)" -ne 0 ]; then
# If the DBX_SUDO_PROGRAM/distrobox_sudo_program variable was set by the
# user, use its value instead of "sudo". But only if not running the script
# as root (UID 0).
distrobox_sudo_program=${DBX_SUDO_PROGRAM:-${distrobox_sudo_program:-"sudo"}}
fi
[ -n "${DBX_SUDO_PROGRAM}" ] && distrobox_sudo_program="${DBX_SUDO_PROGRAM}"
# show_help will print usage to stdout.
# Arguments:
# None
# Expected global variables:
# version: distrobox version
# Expected env variables:
# None
# Outputs:
# print usage with examples.
show_help()
{
cat << EOF
distrobox version: ${version}
Usage:
distrobox-stop container-name
Options:
--all/-a: stop all distroboxes
--yes/-Y: non-interactive, stop without asking
--help/-h: show this message
--root/-r: launch podman/docker/lilipod with root privileges. Note that if you need root this is the preferred
way over "sudo distrobox" (note: if using a program other than 'sudo' for root privileges is necessary,
specify it through the DBX_SUDO_PROGRAM env variable, or 'distrobox_sudo_program' config variable)
--verbose/-v: show more verbosity
--version/-V: show version
EOF
}
# Parse arguments
while :; do
case $1 in
-h | --help)
# Call a "show_help" function to display a synopsis, then exit.
show_help
exit 0
;;
-a | --all)
shift
all=1
;;
-r | --root)
shift
rootful=1
;;
-v | --verbose)
verbose=1
shift
;;
-V | --version)
printf "distrobox: %s\n" "${version}"
exit 0
;;
-Y | --yes)
non_interactive=1
shift
;;
--) # 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
container_name_list="${container_name_list} $1"
shift
else
break
fi
;;
esac
done
set -o errexit
set -o nounset
# set verbosity
if [ "${verbose}" -ne 0 ]; then
set -o xtrace
fi
if [ -z "${container_name}" ]; then
container_name="${container_name_default}"
fi
# We depend on a container manager let's be sure we have it
# First we use podman, else docker, else lilipod
case "${container_manager}" in
autodetect)
if command -v podman > /dev/null; then
container_manager="podman"
elif command -v podman-launcher > /dev/null; then
container_manager="podman-launcher"
elif command -v docker > /dev/null; then
container_manager="docker"
elif command -v lilipod > /dev/null; then
container_manager="lilipod"
fi
;;
podman)
container_manager="podman"
;;
podman-launcher)
container_manager="podman-launcher"
;;
lilipod)
container_manager="lilipod"
;;
docker)
container_manager="docker"
;;
*)
printf >&2 "Invalid input %s.\n" "${container_manager}"
printf >&2 "The available choices are: 'autodetect', 'podman', 'docker', 'lilipod'\n"
;;
esac
# Be sure we have a container manager to work with.
if ! command -v "${container_manager}" > /dev/null; then
# Error: we need at least one between docker, podman or lilipod.
printf >&2 "Missing dependency: we need a container manager.\n"
printf >&2 "Please install one of podman, docker or lilipod.\n"
printf >&2 "You can follow the documentation on:\n"
printf >&2 "\tman distrobox-compatibility\n"
printf >&2 "or:\n"
printf >&2 "\thttps://github.com/89luca89/distrobox/blob/main/docs/compatibility.md\n"
exit 127
fi
# add verbose if -v is specified
if [ "${verbose}" -ne 0 ]; then
container_manager="${container_manager} --log-level debug"
fi
# prepend sudo (or the specified sudo program) if we want our container manager to be rootful
if [ "${rootful}" -ne 0 ]; then
container_manager="${distrobox_sudo_program-} ${container_manager}"
distrobox_flags="--root"
fi
# If all, just set container_name to the list of names in distrobox-list
if [ "${all}" -ne 0 ]; then
# prepend sudo (or the specified sudo program) if we want our container manager to be rootful
# shellcheck disable=SC2086,2248
container_name_list="$("${distrobox_path}"/distrobox-list ${distrobox_flags} --no-color |
tail -n +2 | cut -d'|' -f2 | tr -d ' ' | tr '\n' ' ')"
fi
if [ -z "${container_name_list}" ] && [ "${all}" -ne 0 ]; then
printf >&2 "No containers found.\n"
exit 0
fi
# check if we have containers to delete
if [ -z "${container_name_list}" ]; then
container_name_list="${container_name_default}"
else
# strip leading whitespace from container name
container_name_list="$(echo "${container_name_list}" | sed -E 's/^[[:space:]]+//')"
fi
if [ "${non_interactive}" -eq 0 ]; then
# Prompt to stop the container.
printf "Do you really want to stop %s? [Y/n]: " "${container_name_list}"
read -r response
response="${response:-"Y"}"
else
response="yes"
fi
# Accept only y,Y,Yes,yes,n,N,No,no.
case "${response}" in
y | Y | Yes | yes | YES)
# Stop the container
for container_name in ${container_name_list}; do
${container_manager} stop "${container_name}"
done
;;
n | N | No | no | NO)
printf "Aborted.\n"
exit 0
;;
*) # Default case: If no more options then break out of the loop.
printf >&2 "Invalid input.\n"
printf >&2 "The available choices are: y,Y,Yes,yes,YES or n,N,No,no,NO.\nExiting.\n"
exit 1
;;
esac

View File

@@ -0,0 +1,272 @@
#!/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/>.
# 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}" ]
} && [ "$(id -ru)" -eq 0 ]; then
printf >&2 "Running %s via SUDO/DOAS is not supported. Instead, please try running:\n" "$(basename "${0}")"
printf >&2 " %s --root %s\n" "$(basename "${0}")" "$*"
exit 1
fi
# 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)"
all=0
running=0
container_manager="autodetect"
distrobox_flags=""
distrobox_path="$(dirname "$(realpath "${0}")")"
rootful=0
verbose=0
version="1.8.2.2"
# 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_CONTAINER_MANAGER}" ] && container_manager="${DBX_CONTAINER_MANAGER}"
[ -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
# If we're running this script as root - as in logged in in the shell as root
# user, and not via SUDO/DOAS -, we don't need to set distrobox_sudo_program
# as it's meaningless for this use case.
if [ "$(id -ru)" -ne 0 ]; then
# If the DBX_SUDO_PROGRAM/distrobox_sudo_program variable was set by the
# user, use its value instead of "sudo". But only if not running the script
# as root (UID 0).
distrobox_sudo_program=${DBX_SUDO_PROGRAM:-${distrobox_sudo_program:-"sudo"}}
fi
# Declare it AFTER config sourcing because we do not want a default name set.
container_name=""
# show_help will print usage to stdout.
# Arguments:
# None
# Expected global variables:
# version: distrobox version
# Expected env variables:
# None
# Outputs:
# print usage with examples.
show_help()
{
cat << EOF
distrobox version: ${version}
Usage:
distrobox-upgrade container-name
distrobox-upgrade --all
Options:
--help/-h: show this message
--all/-a: perform for all distroboxes
--running: perform only for running distroboxes
--root/-r: launch podman/docker/lilipod with root privileges. Note that if you need root this is the preferred
way over "sudo distrobox" (note: if using a program other than 'sudo' for root privileges is necessary,
specify it through the DBX_SUDO_PROGRAM env variable, or 'distrobox_sudo_program' config variable)
--verbose/-v: show more verbosity
--version/-V: show version
EOF
}
if [ $# -eq 0 ]; then
show_help
exit
fi
# Parse arguments
while :; do
case $1 in
-h | --help)
# Call a "show_help" function to display a synopsis, then exit.
show_help
exit 0
;;
-v | --verbose)
verbose=1
shift
;;
-V | --version)
printf "distrobox: %s\n" "${version}"
exit 0
;;
-a | --all)
all=1
shift
;;
--running)
running=1
shift
;;
-r | --root)
shift
rootful=1
;;
--) # 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
container_name="${container_name} $1"
shift
else
break
fi
;;
esac
done
set -o errexit
set -o nounset
# set verbosity
if [ "${verbose}" -ne 0 ]; then
set -o xtrace
fi
if [ -z "${container_name}" ] && [ "${all}" -eq 0 ] && [ "${running}" -eq 0 ]; then
printf >&2 "Please specify the name of the container.\n"
exit 1
fi
# We depend on a container manager let's be sure we have it
# First we use podman, else docker, else lilipod
case "${container_manager}" in
autodetect)
if command -v podman > /dev/null; then
container_manager="podman"
elif command -v podman-launcher > /dev/null; then
container_manager="podman-launcher"
elif command -v docker > /dev/null; then
container_manager="docker"
elif command -v lilipod > /dev/null; then
container_manager="lilipod"
fi
;;
podman)
container_manager="podman"
;;
podman-launcher)
container_manager="podman-launcher"
;;
lilipod)
container_manager="lilipod"
;;
docker)
container_manager="docker"
;;
*)
printf >&2 "Invalid input %s.\n" "${container_manager}"
printf >&2 "The available choices are: 'autodetect', 'podman', 'docker', 'lilipod'\n"
;;
esac
# Be sure we have a container manager to work with.
if ! command -v "${container_manager}" > /dev/null; then
# Error: we need at least one between docker, podman or lilipod.
printf >&2 "Missing dependency: we need a container manager.\n"
printf >&2 "Please install one of podman, docker or lilipod.\n"
printf >&2 "You can follow the documentation on:\n"
printf >&2 "\tman distrobox-compatibility\n"
printf >&2 "or:\n"
printf >&2 "\thttps://github.com/89luca89/distrobox/blob/main/docs/compatibility.md\n"
exit 127
fi
# add verbose if -v is specified
if [ "${verbose}" -ne 0 ]; then
container_manager="${container_manager} --log-level debug"
fi
# prepend sudo (or the specified sudo program) if we want our container manager to be rootful
if [ "${rootful}" -ne 0 ]; then
container_manager="${distrobox_sudo_program} ${container_manager}"
distrobox_flags="--root"
fi
# If all, just set container_name to the list of names in distrobox-list
if [ "${all}" -ne 0 ]; then
# prepend sudo (or the specified sudo program) if we want our container manager to be rootful
# shellcheck disable=SC2086,2248
container_name="$("${distrobox_path}"/distrobox-list ${distrobox_flags} --no-color |
tail -n +2 | cut -d'|' -f2 | tr -d ' ')"
# If running, set container_name to the list of names of running instances
if [ "${running}" -ne 0 ]; then
# shellcheck disable=SC2086,2248
container_name="$("${distrobox_path}"/distrobox-list ${distrobox_flags} --no-color |
tail -n +2 | grep -iE '\| running|up' | cut -d'|' -f2 | tr -d ' ')"
fi
fi
# Launch the entrypoint in upgrade mode
for container in ${container_name}; do
printf >&2 "\033[1;31m Upgrading %s...\n\033[0m" "${container}"
# shellcheck disable=SC2086,SC2248
"${distrobox_path}"/distrobox-enter \
${distrobox_flags} ${container} -- sh -c \
"command -v su-exec 2>/dev/null && su-exec root /usr/bin/entrypoint --upgrade || command -v doas 2>/dev/null && doas /usr/bin/entrypoint --upgrade || sudo -S /usr/bin/entrypoint --upgrade"
done

View File

@@ -0,0 +1,706 @@
#!/usr/bin/env bash
# Variables to avoid repeated calls to tput
for n in {0..15}; do
declare C$n=$(tput setaf $n)
done
CR=$(tput sgr0)
CS0=$(tput sgr 0)
# Define traps and trapfunctions early in case any errors before script exits
GLOBAL_VAR_CLEANUP(){
echo "Cleanup up..."
[[ -n "$(command -v TILIX_TMP_CLEANUP)" ]] && TILIX_TMP_CLEANUP
[[ -n "$(command -v ALACRITTY_APPLY_TMP_CLEANUP)" ]] && ALACRITTY_APPLY_TMP_CLEANUP
[[ -n "$(command -v TERMINATOR_APPLY_TMP_CLEANUP)" ]] && TERMINATOR_APPLY_TMP_CLEANUP
[[ -n "$(command -v APPLY_SCRIPT_TMP_CLEANUP)" ]] && APPLY_SCRIPT_TMP_CLEANUP
unset PROFILE_NAME
unset PROFILE_SLUG
unset TILIX_RES
unset TERMINAL
echo "Done"
}
trap 'GLOBAL_VAR_CLEANUP; trap - EXIT' EXIT HUP INT QUIT PIPE TERM
# TO-DO: Investigate dynamically building this array e.g.
# curl -s https://github.com/Gogh-Co/Gogh/tree/master/themes | grep -o "title=.*\.sh\" " | awk -F '=' '{print $2}'
declare -a THEMES=(
'3024-day.sh'
'3024-night.sh'
'aci.sh'
'acme.sh'
'aco.sh'
'adventure-time.sh'
'afterglow.sh'
'alien-blood.sh'
'apprentice.sh'
'arc-dark.sh'
'arc-light.sh'
'argonaut.sh'
'arthur.sh'
'astrodark.sh'
'atelier-cave.sh'
'atelier-dune.sh'
'atelier-estuary.sh'
'atelier-forest.sh'
'atelier-heath.sh'
'atelier-lakeside.sh'
'atelier-plateau.sh'
'atelier-savanna.sh'
'atelier-seaside.sh'
'atelier-sulphurpool.sh'
'atom.sh'
'aura.sh'
'ayaka.sh'
'ayu-dark.sh'
'ayu-light.sh'
'ayu-mirage.sh'
'azu.sh'
'base2tone-cave.sh'
'base2tone-desert.sh'
'base2tone-drawbridge.sh'
'base2tone-earth.sh'
'base2tone-evening.sh'
'base2tone-field.sh'
'base2tone-forest.sh'
'base2tone-garden.sh'
'base2tone-heath.sh'
'base2tone-lake.sh'
'base2tone-lavender.sh'
'base2tone-mall.sh'
'base2tone-meadow.sh'
'base2tone-morning.sh'
'base2tone-motel.sh'
'base2tone-pool.sh'
'base2tone-porch.sh'
'base2tone-sea.sh'
'base2tone-space.sh'
'base2tone-suburb.sh'
'base4tone-classic-a.sh'
'base4tone-classic-b.sh'
'base4tone-classic-c.sh'
'base4tone-classic-d.sh'
'base4tone-classic-e.sh'
'base4tone-classic-f.sh'
'base4tone-classic-i.sh'
'base4tone-classic-l.sh'
'base4tone-classic-o.sh'
'base4tone-classic-p.sh'
'base4tone-classic-q.sh'
'base4tone-classic-r.sh'
'base4tone-classic-s.sh'
'base4tone-classic-t.sh'
'base4tone-classic-u.sh'
'base4tone-classic-w.sh'
'base4tone-modern-c.sh'
'base4tone-modern-n.sh'
'base4tone-modern-w.sh'
'belafonte-day.sh'
'belafonte-night.sh'
'bim.sh'
'birds-of-paradise.sh'
'blazer.sh'
'blue-dolphin.sh'
'blue-moon-light.sh'
'blue-moon.sh'
'bluloco-light.sh'
'bluloco-zsh-light.sh'
'borland.sh'
'breadog.sh'
'breath-darker.sh'
'breath-light.sh'
'breath-silverfox.sh'
'breath.sh'
'breeze.sh'
'broadcast.sh'
'brogrammer.sh'
'butrin.sh'
'c64.sh'
'cai.sh'
'campbell.sh'
'catppuccin-frappe.sh'
'catppuccin-latte.sh'
'catppuccin-macchiato.sh'
'catppuccin-mocha.sh'
'chalk.sh'
'chalkboard.sh'
'chameleon.sh'
'ciapre.sh'
'city-lights.sh'
'clone-of-ubuntu.sh'
'clrs.sh'
'cobalt-2.sh'
'cobalt-neon.sh'
'colorcli.sh'
'crayon-pony-fish.sh'
'dark-pastel.sh'
'darkside.sh'
'dehydration.sh'
'desert.sh'
'dimmed-monokai.sh'
'dissonance.sh'
'doom-one.sh'
'dracula.sh'
'earthsong.sh'
'elemental.sh'
'elementary.sh'
'elic.sh'
'elio.sh'
'espresso-libre.sh'
'espresso.sh'
'everblush.sh'
'everforest-dark-hard.sh'
'everforest-dark-medium.sh'
'everforest-dark-soft.sh'
'everforest-light-hard.sh'
'everforest-light-medium.sh'
'everforest-light-soft.sh'
'fairy-floss-dark.sh'
'fairy-floss.sh'
'fishtank.sh'
'flat-remix.sh'
'flat.sh'
'flatland.sh'
'flexoki-dark.sh'
'flexoki-light.sh'
'foxnightly.sh'
'freya.sh'
'frontend-delight.sh'
'frontend-fun-forrest.sh'
'frontend-galaxy.sh'
'geohot.sh'
'github-dark.sh'
'github-light.sh'
'gogh.sh'
'gooey.sh'
'google-dark.sh'
'google-light.sh'
'gotham.sh'
'grape.sh'
'grass.sh'
'gruvbox-dark.sh'
'gruvbox-material-dark.sh'
'gruvbox-material-light.sh'
'gruvbox.sh'
'guezwhoz.sh'
'hardcore.sh'
'harper.sh'
'hemisu-dark.sh'
'hemisu-light.sh'
'highway.sh'
'hipster-green.sh'
'homebrew-light.sh'
'homebrew-ocean.sh'
'homebrew.sh'
'horizon-bright.sh'
'horizon-dark.sh'
'hurtado.sh'
'hybrid.sh'
'ibm-3270-high-contrast.sh'
'ibm3270.sh'
'ic-green-ppl.sh'
'ic-orange-ppl.sh'
'iceberg.sh'
'idle-toes.sh'
'ir-black.sh'
'jackie-brown.sh'
'japanesque.sh'
'jellybeans.sh'
'jup.sh'
'kanagawa-dragon.sh'
'kanagawa-lotus.sh'
'kanagawa-wave.sh'
'kibble.sh'
'kokuban.sh'
'laserwave.sh'
'later-this-evening.sh'
'lavandula.sh'
'liquid-carbon-transparent.sh'
'liquid-carbon.sh'
'lunaria-dark.sh'
'lunaria-eclipse.sh'
'lunaria-light.sh'
'maia.sh'
'man-page.sh'
'mar.sh'
'material.sh'
'mathias.sh'
'medallion.sh'
'minimalist-dark.sh'
'miramare.sh'
'misterioso.sh'
'modus-operandi-tinted.sh'
'modus-operandi.sh'
'modus-vivendi-tinted.sh'
'modus-vivendi.sh'
'molokai.sh'
'mona-lisa.sh'
'mono-amber.sh'
'mono-cyan.sh'
'mono-green.sh'
'mono-red.sh'
'mono-white.sh'
'mono-yellow.sh'
'monokai-dark.sh'
'monokai-pro-light-sun.sh'
'monokai-pro-light.sh'
'monokai-pro-machine.sh'
'monokai-pro-ocatagon.sh'
'monokai-pro-ristretto.sh'
'monokai-pro-spectrum.sh'
'monokai-pro.sh'
'monokai-soda.sh'
'moonfly.sh'
'morada.sh'
'n0tch2k.sh'
'nanosecond.sh'
'neon-night.sh'
'neopolitan.sh'
'nep.sh'
'neutron.sh'
'night-owl.sh'
'nightfly.sh'
'nightlion-v1.sh'
'nightlion-v2.sh'
'nighty.sh'
'nord-light.sh'
'nord.sh'
'novel.sh'
'obsidian.sh'
'ocean-dark.sh'
'oceanic-next.sh'
'ollie.sh'
'omni.sh'
'one-dark.sh'
'one-half-black.sh'
'one-light.sh'
'oxocarbon-dark.sh'
'palenight.sh'
'pali.sh'
'panda.sh'
'paper.sh'
'papercolor-dark.sh'
'papercolor-light.sh'
'paraiso-dark.sh'
'paul-millr.sh'
'pencil-dark.sh'
'pencil-light.sh'
'peppermint.sh'
'pixiefloss.sh'
'pnevma.sh'
'powershell.sh'
'predawn.sh'
'pro.sh'
'purple-people-eater.sh'
'quiet.sh'
'red-alert.sh'
'red-sands.sh'
'relaxed.sh'
'rippedcasts.sh'
'rose-pine-dawn.sh'
'rose-pine-moon.sh'
'rose-pine.sh'
'royal.sh'
'sat.sh'
'sea-shells.sh'
'seafoam-pastel.sh'
'selenized-black.sh'
'selenized-dark.sh'
'selenized-light.sh'
'selenized-white.sh'
'seoul256-light.sh'
'seoul256.sh'
'seti.sh'
'shaman.sh'
'shel.sh'
'slate.sh'
'smyck.sh'
'snazzy.sh'
'soft-server.sh'
'solarized-darcula.sh'
'solarized-dark-higher-contrast.sh'
'solarized-dark.sh'
'solarized-light.sh'
'sonokai.sh'
'spacedust.sh'
'spacegray-eighties-dull.sh'
'spacegray-eighties.sh'
'spacegray.sh'
'sparky.sh'
'spring.sh'
'square.sh'
'srcery.sh'
'summer-pop.sh'
'sundried.sh'
'sweet-eliverlara.sh'
'sweet-terminal.sh'
'symphonic.sh'
'synthwave-alpha.sh'
'synthwave.sh'
'teerb.sh'
'tempus-autumn.sh'
'tempus-classic.sh'
'tempus-dawn.sh'
'tempus-day.sh'
'tempus-dusk.sh'
'tempus-fugit.sh'
'tempus-future.sh'
'tempus-night.sh'
'tempus-past.sh'
'tempus-rift.sh'
'tempus-spring.sh'
'tempus-summer.sh'
'tempus-tempest.sh'
'tempus-totus.sh'
'tempus-warp.sh'
'tempus-winter.sh'
'tender.sh'
'terminal-basic.sh'
'terminix-dark.sh'
'thayer-bright.sh'
'tin.sh'
'tiwahu-dark.sh'
'tiwahu-light.sh'
'tokyo-night-light.sh'
'tokyo-night-storm.sh'
'tokyo-night.sh'
'tomorrow-night-blue.sh'
'tomorrow-night-bright.sh'
'tomorrow-night-eighties.sh'
'tomorrow-night.sh'
'tomorrow.sh'
'toy-chest.sh'
'treehouse.sh'
'twilight.sh'
'ura.sh'
'urple.sh'
'vacme.sh'
'vag.sh'
'vaombe.sh'
'vaughn.sh'
'vibrant-ink.sh'
'vs-code-dark.sh'
'vs-code-light.sh'
'warm-neon.sh'
'website.sh'
'wez.sh'
'wild-cherry.sh'
'wombat.sh'
'wryan.sh'
'wzoreck.sh'
'zenburn.sh'
)
# Allow developer to change url to forked url for easier testing
BASE_URL=${BASE_URL:-"https://raw.githubusercontent.com/Gogh-Co/Gogh/master"}
PROGRESS_URL="https://raw.githubusercontent.com/phenonymous/shell-progressbar/1.0/progress.sh"
SCRIPT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
capitalize() {
local ARGUMENT=$1
local RES=""
local STR=""
local RES_NO_TRAIL_SPACE=""
for CHAR in $ARGUMENT; do
STR=$(echo "${CHAR:0:1}" | tr "[:lower:]" "[:upper:]")"${CHAR:1} "
RES="${RES}${STR}"
RES_NO_TRAIL_SPACE="$(echo -e "${RES}" | sed -e 's/[[:space:]]*$//')"
done
echo "${RES_NO_TRAIL_SPACE}"
}
# Used to get required python scripts, either from the internet or from local directory
if [[ ! -f "${SCRIPT_PATH}/apply-alacritty.py" ]]; then
ALACRITTY_APPLY_TMP_CLEANUP() {
rm -rf "${GOGH_ALACRITTY_SCRIPT}"
unset GOGH_ALACRITTY_SCRIPT
}
export GOGH_ALACRITTY_SCRIPT="$(mktemp -t gogh.alacritty.XXXXXX)"
if [[ "$(uname)" = "Darwin" ]]; then
# OSX ships with curl and ancient bash
curl -so "${GOGH_ALACRITTY_SCRIPT}" "${BASE_URL}/apply-alacritty.py"
else
# Linux ships with wget
wget -qO "${GOGH_ALACRITTY_SCRIPT}" "${BASE_URL}/apply-alacritty.py"
fi
fi
# Used to get required python scripts, either from the internet or from local directory
if [[ ! -e "${SCRIPT_PATH}/apply-terminator.py" ]]; then
TERMINATOR_APPLY_TMP_CLEANUP() {
rm -rf "${GOGH_TERMINATOR_SCRIPT}"
unset GOGH_TERMINATOR_SCRIPT
}
export GOGH_TERMINATOR_SCRIPT="$(mktemp -t gogh.terminator.XXXXXX)"
if [[ "$(uname)" = "Darwin" ]]; then
# OSX ships with curl and ancient bash
curl -so "${GOGH_TERMINATOR_SCRIPT}" "${BASE_URL}/apply-terminator.py"
else
# Linux ships with wget
wget -qO "${GOGH_TERMINATOR_SCRIPT}" "${BASE_URL}/apply-terminator.py"
fi
fi
# Used to get required shell scripts, either from the internet or from local directory
if [[ ! -e "${SCRIPT_PATH}/apply-colors.sh" ]]; then
APPLY_SCRIPT_TMP_CLEANUP() {
rm -rf "${GOGH_APPLY_SCRIPT}"
unset GOGH_APPLY_SCRIPT
}
export GOGH_APPLY_SCRIPT="$(mktemp -t gogh.apply.XXXXXX)"
if [[ "$(uname)" = "Darwin" ]]; then
# OSX ships with curl and ancient bash
curl -so "${GOGH_APPLY_SCRIPT}" "${BASE_URL}/apply-colors.sh"
else
# Linux ships with wget
wget -qO "${GOGH_APPLY_SCRIPT}" "${BASE_URL}/apply-colors.sh"
fi
fi
set_gogh() {
string=$1
string_r="${string%???}"
string_s=${string_r//\./_}
result=$(capitalize "${string_s}")
url="${BASE_URL}/installs/$1"
export {PROFILE_NAME,PROFILE_SLUG}="$result"
if [[ -e "${SCRIPT_PATH}/installs/$1" ]]; then
bash "${SCRIPT_PATH}/installs/$1"
else
if [[ "$(uname)" = "Darwin" ]]; then
# OSX ships with curl
bash -c "$(curl -sLo- "${url}")"
else
# Linux ships with wget
bash -c "$(wget -qO- "${url}")"
fi
fi
}
remove_file_extension (){
echo "${1%.*}"
}
### Get length of an array
ARRAYLENGTH=${#THEMES[@]}
# |
# | ::::::: Print logo
# |
tput clear
if [[ ${COLUMNS:-$(tput cols)} -ge 80 ]]; then
gogh_str=""
gogh_str+=" \n"
gogh_str+=" █████████ █████ \n"
gogh_str+=" ███ ███ ███ \n"
gogh_str+=" ███ ██████ ███████ ███████ \n"
gogh_str+=" ███ ███ ███ ███ ███ ███ ███ \n"
gogh_str+=" ███ █████ ███ ███ ███ ███ ███ ███ \n"
gogh_str+=" ███ ███ ███ ███ ███ ███ ███ ███ \n"
gogh_str+=" █████████ ██████ ███████ ████ █████ \n"
gogh_str+=" ${C0}█████████${C1}█████████${C2}█████████${C3}█████████${C4}█████${CS0}███${C4}${C5}█████████${C6}█████████${C7}█████████ \n"
gogh_str+=" ${C0}█████████${C1}█████████${C2}█████████${C3}█████████${CS0}███${C4}██${CS0}███${C4}${C5}█████████${C6}█████████${C7}█████████ \n"
gogh_str+=" ${C0}█████████${C1}█████████${C2}█████████${C3}█████████${C4}${CR}██████${C4}██${C5}█████████${C6}█████████${C7}█████████ \n"
gogh_str+=" ${C8}█████████${C9}█████████${C10}█████████${C11}█████████${C12}█████████${C13}█████████${C14}█████████${C15}█████████${CS0} \n"
gogh_str+=" ${C8}█████████${C9}█████████${C10}█████████${C11}█████████${C12}█████████${C13}█████████${C14}█████████${C15}█████████${CS0} \n"
gogh_str+=" ${C8}█████████${C9}█████████${C10}█████████${C11}█████████${C12}█████████${C13}█████████${C14}█████████${C15}█████████${CS0} \n"
gogh_str+=" "
printf '%b\n' "${gogh_str}"
sleep 2.5
else
echo -e "\nGogh\n"
for c in C{0..15}; do
echo -n "${!c}█████${CR}"
[[ $c == C7 ]] && echo # new line
done
echo
fi
# |
# | ::::::: Print Themes
# |
echo -e "\nThemes:\n"
# Cross-platform function to format theme names
format_theme_name() {
local name="$1"
# Remove .sh extension and any other extensions
name="${name%.*}"
# Replace hyphens with spaces
name="${name//-/ }"
# Capitalize first letter of each word using awk (cross-platform)
echo "$name" | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)} 1'
}
# Column display of available themes
# Note: /usr/bin/column uses tabs and does not support ANSI codes yet (merged but not released)
MAXL=$(( $(printf "%s\n" "${THEMES[@]}" | wc -L) - 3 )) # Biggest theme name without the extension
NCOLS=$(( ${COLUMNS:-$(tput cols)} / (10+MAXL) )) # number of columns, 10 is the length of ' ( xxx ) '
NROWS=$(( (ARRAYLENGTH-1)/NCOLS + 1 )) # number of rows
row=0
while ((row < NROWS)); do
col=0
while ((col < NCOLS)); do
NUM=$((col*NROWS+row))
NAME="${THEMES[$NUM]}"
if [[ -n $NAME ]]; then
FORMATTED_NAME=$(format_theme_name "$NAME")
printf " ( ${C4}%3d${CR} ) %-${MAXL}s" $((NUM+1)) "$FORMATTED_NAME"
fi
((col++))
done
echo
((row++))
done
echo -e " (${C4} ALL ${CR}) All themes"
# |
# | ::::::: Select Option
# |
echo -e "\nUsage : Enter Desired Themes Numbers (${C4}OPTIONS${CR}) Separated By A Blank Space"
echo -e " Press ${C4}ENTER${CR} without options to Exit\n"
read -r -p 'Enter OPTION(S) : ' -a OPTION
# Automagically generate options if user opts for all themes
[[ "$OPTION" == ALL ]] && OPTION=($(seq -s " " $ARRAYLENGTH))
# |
# | ::::::: Get terminal
# |
if [[ -z "${TERMINAL:-}" ]]; then
# |
# | Check for the terminal name (depening on os)
# | ===========================================
OS="$(uname)"
if [[ "$OS" = "Darwin" ]]; then
TERMINAL=$TERM_PROGRAM
elif [[ "${OS#CYGWIN}" != "${OS}" ]]; then
TERMINAL="mintty"
elif [[ "$TERM" = "xterm-kitty" ]]; then
TERMINAL="kitty"
elif [[ "${TERM}" = "linux" ]]; then
TERMINAL="linux"
elif [[ "${HOME}" = *com.termux* ]]; then
TERMINAL="termux"
else
# |
# | Depending on how the script was invoked, we need
# | to loop until pid is no longer a subshell
# | ===========================================
pid="$$"
TERMINAL="$(ps -h -o comm -p $pid)"
while [[ "${TERMINAL:(-2)}" == "sh" ]]; do
pid="$(ps -h -o ppid -p $pid)"
TERMINAL="$(ps -h -o comm -p $pid)"
done
fi
fi
# |
# | ::::::: Fancy progressbar for lengthy operations
# |
if [[ ${#OPTION[@]} -gt 5 ]]; then
# Note: We use eval here because we want the functions to be available in this script
if [[ "$(uname)" = "Darwin" ]]; then
eval "$(curl -so- ${PROGRESS_URL})" 2> /dev/null
else
eval "$(wget -qO- ${PROGRESS_URL})" 2> /dev/null
fi
fi
# |
# | Tilix supports fg/bg in color schemes - ask wether user wants to go that route
# | This is to avoid creating multiple profiles just for colors
# | ===========================================
if [[ "$TERMINAL" = "tilix" ]] && [[ ${#OPTION[@]} -gt 0 ]]; then
echo
read -r -p "Tilix detected - use color schemes instead of profiles? [y/N] " -n 1 TILIX_RES
echo
# |
# | When selecting multiple themes and user opts for color schemes, we save all themes
# | in a tmpdir and copy the files once all themes has been processed.. If a user
# | desides to abort before all themes has been processed this section will cleanup the tmpdir
# | =======================================
if [[ ${TILIX_RES::1} =~ ^(y|Y)$ ]]; then
TILIX_TMP_CLEANUP() {
echo
echo "Cleaning up"
rm -rf "$scratchdir"
unset LOOP OPTLENGTH scratchdir
echo "Done..."
exit 0
}
scratchdir=$(mktemp -d -t gogh.tilix.XXXXXXXX)
export scratchdir
fi
fi
# |
# | ::::::: Export one-off variables
# |
[[ -n "${TILIX_RES:-}" ]] && export TILIX_RES
export TERMINAL LOOP OPTLENGTH=${#OPTION[@]}
# |
# | ::::::: Apply Theme
# |
declare color_dot_str
for c in C{0..15}; do
color_dot_str+="${!c}${CR}"
[[ $c == C7 ]] && color_dot_str+=" "
done
# Note:
# Constants with a leading 0 are interpreted as octal numbers
# Hence option 08 and 09 will not work
# Solution is to remove the leading 0 from the parsed options
command -v bar::start > /dev/null && bar::start
for OP in "${OPTION[@]#0}"; do
# See appy_tilixschemes in apply-colors.sh for usage of LOOP
LOOP=$((${LOOP:-0}+1))
command -v bar::status_changed > /dev/null && bar::status_changed $LOOP ${#OPTION[@]}
if [[ OP -le ARRAYLENGTH && OP -gt 0 ]]; then
FILENAME=$(remove_file_extension "${THEMES[((OP-1))]}")
FILENAME_SPACE="${FILENAME//-/ }"
echo -e "\nTheme: $(capitalize "${FILENAME_SPACE}")"
echo "${color_dot_str}"
echo
SET_THEME="${THEMES[((OP-1))]}"
set_gogh "${SET_THEME}"
else
echo -e "${C1} ~ INVALID OPTION! ~${CR}"
exit 1
fi
done
# If you skip || : and the command does not exist the script will exit with code 1
# this will always return exit code 0 if we got this far
command -v bar::stop > /dev/null && bar::stop || :

View File

@@ -0,0 +1,994 @@
#!/usr/bin/env bash
# rofi-pass
# (c) 2015 Rasmus Steinke <rasi@xssn.at>
basecommand="$0"
# set default settings
_rofi () {
rofi -no-auto-select -i "$@"
}
_pwgen () {
pwgen -y "$@"
}
_image_viewer () {
feh -
}
config_dir=${XDG_CONFIG_HOME:-$HOME/.config}
cache_dir=${XDG_CACHE_HOME:-$HOME/.cache}
# We expect to find these fields in pass(1)'s output
URL_field='url'
USERNAME_field='user'
AUTOTYPE_field='autotype'
OTPmethod_field='otp_method'
default_autotype="user :tab pass"
delay=2
wait=0.2
type_delay=12
default_do='menu' # menu, copyPass, typeUser, typePass, copyUser, copyUrl, viewEntry, typeMenu, actionMenu, copyMenu, openUrl
auto_enter='false'
notify='false'
clip=primary
clip_clear=45
default_user="${ROFI_PASS_DEFAULT_USER-$(whoami)}"
default_user2=john_doe
password_length=12
fix_layout=false
# default shortcuts
autotype="Alt+1"
type_user="Alt+2"
type_pass="Alt+3"
open_url="Alt+4"
type_otp="Alt+5"
copy_name="Alt+u"
copy_url="Alt+l"
copy_pass="Alt+p"
show="Alt+o"
copy_menu="Alt+c"
action_menu="Alt+a"
type_menu="Alt+t"
help="Alt+h"
switch="Alt+x"
insert_pass="Alt+n"
qrcode="Alt+q"
previous_root="Shift+Left"
next_root="Shift+Right"
clipboard_backend=${ROFI_PASS_CLIPBOARD_BACKEND:-xclip}
backend=${ROFI_PASS_BACKEND:-xdotool}
case "$clipboard_backend" in
"xclip") ;;
"wl-clipboard") ;;
*)
>&2 echo "Invalid clipboard backend '$clipboard_backend', falling back to xclip"
clipboard_backend=xclip
;;
esac
case "$backend" in
"xdotool") ;;
"ydotool") ;;
"wtype") ;;
*)
>&2 echo "Invalid backend '$backend', falling back to xdotool"
backend=xdotool
;;
esac
# Safe permissions
umask 077
# Backends for clipboard manipulation
_clip_in_primary_wl-clipboard() {
wl-copy -p
}
_clip_in_clipboard_wl-clipboard() {
wl-copy
}
_clip_out_primary_wl-clipboard() {
wl-paste -p
}
_clip_out_clipboard_wl-clipboard() {
wl-paste
}
_clip_in_primary_xclip() {
xclip
}
_clip_in_clipboard_xclip() {
xclip -selection clipboard
}
_clip_out_primary_xclip() {
xclip -o
}
_clip_out_clipboard_xclip() {
xclip --selection clipboard -o
}
# Backends for typing what's in stdin
_do_type_xdotool() {
xdotool type --delay ${type_delay} --clearmodifiers --file -
}
_do_type_ydotool() {
ydotool type --key-delay ${type_delay} --file -
}
_do_type_wtype() {
wtype -d ${type_delay} -
}
# Backends for pressing the key specified by the first argument ($1)
_do_press_key_xdotool() {
xdotool key "$1"
}
_do_press_key_wtype() {
wtype -P "$1" -p "$1"
}
has_qrencode() {
command -v qrencode >/dev/null 2>&1
}
listgpg () {
mapfile -d '' pw_list < <(find -L . -name '*.gpg' -print0)
pw_list=("${pw_list[@]#./}")
printf '%s\n' "${pw_list[@]}" | sort -n
}
# get all password files and output as newline-delimited text
list_passwords() {
cd "${root}" || exit
mapfile -t pw_list < <(listgpg)
printf '%s\n' "${pw_list[@]%.gpg}" | sort -n
}
doClip () {
case "$clip" in
"primary") ${clip_in_primary} ;;
"clipboard") ${clip_in_clipboard} ;;
"both") ${clip_in_primary}; ${clip_out_primary} | ${clip_in_clipboard} ;;
esac
}
checkIfPass () {
printf '%s\n' "${root}: $selected_password" >| "$cache_dir/rofi-pass/last_used"
}
autopass () {
if [[ $backend == "xdotool" ]]; then
x_repeat_enabled=$(xset q | awk '/auto repeat:/ {print $3}')
xset r off
fi
rm -f "$cache_dir/rofi-pass/last_used"
printf '%s\n' "${root}: $selected_password" > "$cache_dir/rofi-pass/last_used"
for word in ${stuff["$AUTOTYPE_field"]}; do
case "$word" in
":tab") ${do_press_key} Tab ;;
":space") ${do_press_key} space ;;
":delay") sleep "${delay}" ;;
":enter") ${do_press_key} Return ;;
":otp") printf '%s' "$(generateOTP)" | ${do_type} ;;
"pass") printf '%s' "${password}" | ${do_type} ;;
"path") printf '%s' "${selected_password}" | rev | cut -d'/' -f1 | rev | ${do_type} ;;
*) printf '%s' "${stuff[${word}]}" | ${do_type} ;;
esac
done
if [[ ${auto_enter} == "true" ]]; then
${do_press_key} Return
fi
if [[ $backend == "xdotool" ]]; then
xset r "$x_repeat_enabled"
unset x_repeat_enabled
fi
clearUp
}
generateQrCode() {
has_qrencode
if [[ $? -eq "1" ]]; then
printf '%s\n' "qrencode not found" | _rofi -dmenu
exit_code=$?
if [[ $exit_code -eq "1" ]]; then
exit
else
"${basecommand}"
fi
fi
checkIfPass
pass "$selected_password" | head -n 1 | qrencode -d 300 -v 8 -l H -o - | _image_viewer
if [[ $? -eq "1" ]]; then
printf '%s\n' "" | _rofi -dmenu -mesg "Image viewer not defined or cannot read from pipe"
exit_value=$?
if [[ $exit_value -eq "1" ]]; then
exit
else
"${basecommand}"
fi
fi
clearUp
}
openURL () {
checkIfPass
$BROWSER "$(PASSWORD_STORE_DIR="${root}" pass "$selected_password" | grep "${URL_field}: " | gawk '{sub(/:/,"")}{print $2}1' | head -1)"; exit;
clearUp
}
typeUser () {
checkIfPass
if [[ $backend == "xdotool" ]]; then
x_repeat_enabled=$(xset q | awk '/auto repeat:/ {print $3}')
xset r off
fi
printf '%s' "${stuff[${USERNAME_field}]}" | ${do_type}
if [[ $backend == "xdotool" ]]; then
xset r "$x_repeat_enabled"
unset x_repeat_enabled
fi
clearUp
}
typePass () {
checkIfPass
if [[ $backend == "xdotool" ]]; then
x_repeat_enabled=$(xset q | awk '/auto repeat:/ {print $3}')
xset r off
fi
printf '%s' "${password}" | ${do_type}
if [[ $notify == "true" ]]; then
if [[ "${stuff[notify]}" == "false" ]]; then
:
else
notify-send "rofi-pass" "finished typing password";
fi
elif [[ $notify == "false" ]]; then
if [[ "${stuff[notify]}" == "true" ]]; then
notify-send "rofi-pass" "finished typing password";
else
:
fi
fi
if [[ $backend == "xdotool" ]]; then
xset r "$x_repeat_enabled"
unset x_repeat_enabled
fi
clearUp
}
typeField () {
checkIfPass
local to_type
if [[ $backend == "xdotool" ]]; then
x_repeat_enabled=$(xset q | awk '/auto repeat:/ {print $3}')
xset r off
fi
case $typefield in
"OTP") to_type="$(generateOTP)" ;;
*) to_type="${stuff[${typefield}]}" ;;
esac
printf '%s' "$to_type" | ${do_type}
if [[ $backend == "xdotool" ]]; then
xset r "$x_repeat_enabled"
unset x_repeat_enabled
fi
unset to_type
clearUp
}
generateOTP () {
checkIfPass
# First, we check if there is a non-conventional OTP command in the pass file
if PASSWORD_STORE_DIR="${root}" pass "$selected_password" | grep -q "${OTPmethod_field}: "; then
# We execute the commands after otp_method: AS-IS
bash -c "$(PASSWORD_STORE_DIR="${root}" pass "$selected_password" | grep "${OTPmethod_field}: " | cut -d' ' -f2-)"
else
# If there is no method defined, fallback to pass-otp
PASSWORD_STORE_DIR="${root}" pass otp "$selected_password"
fi
clearUp
}
copyUser () {
checkIfPass
printf '%s' "${stuff[${USERNAME_field}]}" | doClip
clearUp
}
copyField () {
checkIfPass
printf '%s' "${stuff[${copyfield}]}" | doClip
clearUp
}
copyURL () {
checkIfPass
printf '%s' "${stuff[${URL_field}]}" | doClip
clearUp
}
copyPass () {
checkIfPass
printf '%s' "$password" | doClip
if [[ $notify == "true" ]]; then
notify-send "rofi-pass" "Copied Password\\nClearing in $clip_clear seconds"
fi
if [[ $notify == "true" ]]; then
(sleep $clip_clear; printf '%s' "" | ${clip_in_primary}; printf '%s' "" | ${clip_in_clipboard} | notify-send "rofi-pass" "Clipboard cleared") &
elif [[ $notify == "false" ]]; then
(sleep $clip_clear; printf '%s' "" | ${clip_in_primary}; printf '%s' "" | ${clip_in_clipboard}) &
fi
}
viewEntry () {
checkIfPass
showEntry "${selected_password}"
}
generatePass () {
askmenu_content=(
"Yes"
"No"
)
askGenMenu=$(printf '%s\n' "${askmenu_content[@]}" | _rofi -dmenu -p "Generate new Password for ${selected_password}? > ")
askgen_exit=$?
if [[ $askgen_exit -eq 1 ]]; then
exit
fi
if [[ $askGenMenu == "Yes" ]]; then
true
elif [[ $askGenMenu == "No" ]]; then
actionMenu
fi
checkIfPass
symbols_content=(
"0 Cancel"
"1 Yes"
"2 No"
)
symbols=$(printf '%s\n' "${symbols_content[@]}" | _rofi -dmenu -p "Use Symbols? > ")
symbols_val=$?
if [[ $symbols_val -eq 1 ]]; then
exit
fi
if [[ $symbols == "0 Cancel" ]]; then
mainMenu;
elif [[ $symbols == "1 Yes" ]]; then
symbols="";
elif [[ $symbols == "2 No" ]]; then
symbols="-n";
fi
HELP="Enter Number or hit Enter to use default length"
length=$(printf '%s' "" | _rofi -dmenu -mesg "${HELP}" -p "Password length? (Default: ${password_length}) > ")
length_exit=$?
if [[ $length_exit -eq 1 ]]; then
exit
fi
if [[ $length == "" ]]; then
PASSWORD_STORE_DIR="${root}" pass generate ${symbols} -i "$selected_password" "${password_length}" > /dev/null;
else
PASSWORD_STORE_DIR="${root}" pass generate ${symbols} -i "$selected_password" "${length}" > /dev/null;
fi
}
# main Menu
mainMenu () {
if [[ $1 == "--bmarks" ]]; then
selected_password="$(list_passwords 2>/dev/null \
| _rofi -mesg "Bookmarks Mode. ${switch} to switch" \
-dmenu \
-kb-custom-10 "${switch}" \
-select "$entry" \
-p "rofi-pass > ")"
rofi_exit=$?
if [[ $rofi_exit -eq 1 ]]; then
exit
elif [[ $rofi_exit -eq 19 ]]; then
${basecommand}
elif [[ $rofi_exit -eq 0 ]]; then
openURL
fi
else
unset selected_password
args=( -dmenu
-kb-custom-1 "${autotype}"
-kb-custom-2 "${type_user}"
-kb-custom-3 "${type_pass}"
-kb-custom-4 "${open_url}"
-kb-custom-5 "${copy_name}"
-kb-custom-6 "${copy_pass}"
-kb-custom-7 "${show}"
-kb-custom-8 "${copy_url}"
-kb-custom-9 "${type_menu}"
-kb-custom-10 "${previous_root}"
-kb-custom-11 "${next_root}"
-kb-custom-12 "${type_otp}"
-kb-custom-14 "${action_menu}"
-kb-custom-15 "${copy_menu}"
-kb-custom-16 "${help}"
-kb-custom-17 "${switch}"
-kb-custom-18 "${insert_pass}"
-kb-custom-19 "${qrcode}"
)
args+=( -kb-mode-previous "" # These keyboard shortcut options are needed, because
-kb-mode-next "" # Shift+<Left|Right> are otherwise taken by rofi.
-select "$entry"
-p "> " )
if [[ ${#roots[@]} -gt "1" || $custom_root == "true" ]]; then
args+=(-mesg "PW Store: ${root}")
fi
selected_password="$(list_passwords 2>/dev/null | _rofi "${args[@]}")"
rofi_exit=$?
if [[ $rofi_exit -eq 1 ]]; then
exit
fi
# Actions based on exit code, which do not need the entry.
# The exit code for -kb-custom-X is X+9.
case "${rofi_exit}" in
19) roots_index=$(( (roots_index-1+roots_length) % roots_length)); root=${roots[$roots_index]}; mainMenu; return ;;
20) roots_index=$(( (roots_index+1) % roots_length)); root=${roots[$roots_index]}; mainMenu; return ;;
25) helpMenu; return ;;
26) ${basecommand} --bmarks; return ;;
esac
mapfile -t password_temp < <(PASSWORD_STORE_DIR="${root}" pass show "$selected_password")
password=${password_temp[0]}
if [[ ${password} == "#FILE="* ]]; then
pass_file="${password#*=}"
mapfile -t password_temp2 < <(PASSWORD_STORE_DIR="${root}" pass show "${pass_file}")
password=${password_temp2[0]}
fi
fields=$(printf '%s\n' "${password_temp[@]:1}" | awk '$1 ~ /:$/ || /otpauth:\/\// {$1=$1;print}')
declare -A stuff
stuff["pass"]=${password}
if [[ -n $fields ]]; then
while read -r LINE; do
unset _id _val
case "$LINE" in
"otpauth://"*|"${OTPmethod_field}"*)
_id="OTP"
_val=""
;;
*)
_id="${LINE%%: *}"
_val="${LINE#* }"
;;
esac
if [[ -n "$_id" ]]; then
stuff["${_id}"]=${_val}
fi
done < <(printf '%s\n' "${fields}")
if test "${stuff['autotype']+autotype}"; then
:
else
stuff["autotype"]="${USERNAME_field} :tab pass"
fi
fi
fi
if [[ -z "${stuff["${AUTOTYPE_field}"]}" ]]; then
if [[ -n $default_autotype ]]; then
stuff["${AUTOTYPE_field}"]="${default_autotype}"
fi
fi
if [[ -z "${stuff["${USERNAME_field}"]}" ]]; then
if [[ -n $default_user ]]; then
if [[ "$default_user" == ":filename" ]]; then
stuff["${USERNAME_field}"]="$(basename "$selected_password")"
else
stuff["${USERNAME_field}"]="${default_user}"
fi
fi
fi
pass_content="$(for key in "${!stuff[@]}"; do printf '%s\n' "${key}: ${stuff[$key]}"; done)"
# actions based on keypresses
# The exit code for -kb-custom-X is X+9.
case "${rofi_exit}" in
0) typeMenu ;;
10) sleep $wait; autopass ;;
11) sleep $wait; typeUser ;;
12) sleep $wait; typePass ;;
13) openURL ;;
14) copyUser ;;
15) copyPass ;;
16) viewEntry ;;
17) copyURL ;;
18) default_do="menu" typeMenu ;;
21) sleep $wait; typefield=OTP; typeField ;;
23) actionMenu ;;
24) copyMenu ;;
27) insertPass ;;
28) generateQrCode ;;
esac
clearUp
}
clearUp () {
password=''
selected_password=''
unset stuff
unset password
unset selected_password
unset password_temp
unset stuff
}
helpMenu () {
_rofi -dmenu -mesg "Hint: All hotkeys are configurable in config file" -p "Help > " <<- EOM
${autotype}: Autotype
${type_user}: Type Username
${type_pass}: Type Password
${type_otp}: Type OTP
${qrcode}: Generate and display qrcode
---
${copy_name}: Copy Username
${copy_pass}: Copy Password
${copy_url}: Copy URL
${open_url}: Open URL
${copy_menu}: Copy Custom Field
---
${action_menu}: Edit, Move, Delete, Re-generate Submenu
${show}: Show Password File
${insert_pass}: Insert new Pass Entry
${switch}: Switch Pass/Bookmark Mode
---
${previous_root}: Switch to previous password store (--root)
${next_root}: Switch to next password store (--root)
EOM
help_val=$?
if [[ $help_val -eq 1 ]]; then
exit;
else
unset helptext; mainMenu;
fi
}
typeMenu () {
if [[ -n $default_do ]]; then
if [[ $default_do == "menu" ]]; then
checkIfPass
local -a keys=("${!stuff[@]}")
keys=("${keys[@]/$AUTOTYPE_field}")
typefield=$({ printf '%s' "${AUTOTYPE_field}" ; printf '%s\n' "${keys[@]}" | sort; } | _rofi -dmenu -p "Choose Field to type > ")
typefield_exit=$?
if [[ $typefield_exit -eq 1 ]]; then
exit
fi
case "$typefield" in
'') exit ;;
'pass') sleep $wait; typePass ;;
"${AUTOTYPE_field}") sleep $wait; autopass ;;
*) sleep $wait; typeField
esac
clearUp
elif [[ $default_do == "${AUTOTYPE_field}" ]]; then
sleep $wait; autopass
else
${default_do}
fi
fi
}
copyMenu () {
checkIfPass
copyfield=$(printf '%s\n' "${!stuff[@]}" | sort | _rofi -dmenu -p "Choose Field to copy > ")
val=$?
if [[ $val -eq 1 ]]; then
exit;
fi
if [[ $copyfield == "pass" ]]; then
copyPass;
else
copyField
fi
clearUp
}
actionMenu () {
checkIfPass
action_content=("< Return"
"---"
"1 Move Password File"
"2 Copy Password File"
"3 Delete Password File"
"4 Edit Password File"
"5 Generate New Password"
)
action=$(printf '%s\n' "${action_content[@]}" | _rofi -dmenu -p "Choose Action > ")
if [[ ${action} == "1 Move Password File" ]]; then
manageEntry move;
elif [[ ${action} == "3 Delete Password File" ]]; then
manageEntry delete;
elif [[ ${action} == "2 Copy Password File" ]]; then
manageEntry copy;
elif [[ ${action} == "4 Edit Password File" ]]; then
manageEntry edit;
elif [[ ${action} == "5 Generate New Password" ]]; then
generatePass;
elif [[ ${action} == "< Return" ]]; then
mainMenu;
elif [[ ${action} == "" ]]; then
exit
fi
}
showEntry () {
if [[ -z $pass_content ]]; then
pass_temp=$(PASSWORD_STORE_DIR="${root}" pass show "$selected_password")
password="${pass_temp%%$'\n'*}"
pass_key_value=$(printf '%s\n' "${pass_temp}" | tail -n+2 | grep ': ')
declare -A stuff
while read -r LINE; do
_id="${LINE%%: *}"
_val="${LINE#* }"
stuff["${_id}"]=${_val}
done < <(printf '%s\n' "${pass_key_value}")
stuff["pass"]=${password}
if test "${stuff['autotype']+autotype}"; then
:
else
stuff["autotype"]="${USERNAME_field} :tab pass"
fi
pass_content="$(for key in "${!stuff[@]}"; do printf '%s\n' "${key}: ${stuff[$key]}"; done)"
fi
bla_content=("< Return"
"${pass_content}"
)
bla=$(printf '%s\n' "${bla_content[@]}" | _rofi -dmenu -mesg "Enter: Copy entry to clipboard" -p "> ")
rofi_exit=$?
word=$(printf '%s' "$bla" | gawk -F': ' '{print $1}')
if [[ ${rofi_exit} -eq 1 ]]; then
exit
elif [[ ${rofi_exit} -eq 0 ]]; then
if [[ ${bla} == "< Return" ]]; then
mainMenu
else
if [[ -z $(printf '%s' "${stuff[${word}]}") ]]; then
printf '%s' "$word" | doClip
else
printf '%s' "${stuff[${word}]}" | doClip
fi
if [[ $notify == "true" ]]; then
notify-send "rofi-pass" "Copied Password\\nClearing in $clip_clear seconds"
fi
if [[ $notify == "true" ]]; then
(sleep $clip_clear; printf '%s' "" | ${clip_in_primary}; printf '%s' "" | ${clip_in_clipboard} | notify-send "rofi-pass" "Clipboard cleared") &
elif [[ $notify == "false" ]]; then
(sleep $clip_clear; printf '%s' "" | ${clip_in_primary}; printf '%s' "" | ${clip_in_clipboard}) &
fi
exit
fi
fi
exit
unset stuff
unset password
unset selected_password
unset password_temp
unset stuff
exit
}
manageEntry () {
if [[ "$1" == "edit" ]]; then
EDITOR=$EDITOR PASSWORD_STORE_DIR="${root}" pass edit "${selected_password}"
mainMenu
elif [[ $1 == "move" ]]; then
cd "${root}" || exit
group_array=(*/)
group=$(printf '%s\n' "${group_array[@]%/}" | _rofi -dmenu -p "Choose Group > ")
if [[ $group == "" ]]; then
exit
fi
PASSWORD_STORE_DIR="${root}" pass mv "$selected_password" "${group}"
mainMenu
elif [[ $1 == "copy" ]]; then
cd "${root}" || exit
group_array=(*/)
group=$(printf '%s\n' "${group_array[@]%/}" | _rofi -dmenu -p "Choose Group > ")
if [[ $group == "" ]]; then
exit
else
new_name="$(listgpg | _rofi -dmenu -format 'f' -mesg "Copying to same Group. Please enter a name for the new entry" -p "> ")"
fi
PASSWORD_STORE_DIR="${root}" pass cp "$selected_password" "${group}/${new_name}"
mainMenu
elif [[ "$1" == "delete" ]]; then
HELP="Selected entry: ${selected_password}"
ask_content=("Yes"
"No"
)
ask=$(printf '%s\n' "${ask_content[@]}" | _rofi -mesg "${HELP}" -dmenu -p "Are You Sure? > ")
if [[ "$ask" == "Yes" ]]; then
PASSWORD_STORE_DIR="${root}" pass rm --force "${selected_password}"
elif [[ "$ask" == "No" ]]; then
mainMenu
elif [[ -z "$ask" ]]; then
exit
fi
else
mainMenu
fi
}
edit_pass() {
if [[ $edit_new_pass == "true" ]]; then
PASSWORD_STORE_DIR="${root}" pass edit "${1}"
fi
}
insertPass () {
url=$(${clip_out_clipboard})
if [[ "${url:0:4}" == "http" ]]; then
domain_name="$(printf '%s\n' "${url}" | awk -F / '{l=split($3,a,"."); print (a[l-1]=="com"?a[l-2] OFS:X) a[l-1] OFS a[l]}' OFS=".")"
help_content="Domain: ${domain_name}
Type name, make sure it is unique"
else
help_content="Hint: Copy URL to clipboard before calling this menu.
Type name, make sure it is unique"
fi
cd "${root}" || exit
group_array=(*/)
grouplist=$(printf '%s\n' "${group_array[@]%/}")
name="$(listgpg | _rofi -dmenu -format 'f' -filter "${domain_name}" -mesg "${help_content}" -p "> ")"
val=$?
if [[ $val -eq 1 ]]; then
exit
fi
user_content=("${default_user2}"
"${USER}"
"${default_user}"
)
user=$(printf '%s\n' "${user_content[@]}" | _rofi -dmenu -mesg "Chose Username or type" -p "> ")
val=$?
if [[ $val -eq 1 ]]; then
exit
fi
group_content=("No Group"
"---"
"${grouplist}"
)
group=$(printf '%s\n' "${group_content[@]}" | _rofi -dmenu -p "Choose Group > ")
val=$?
if [[ $val -eq 1 ]]; then
exit
fi
pw=$(printf '%s' "Generate" | _rofi -dmenu -password -p "Password > " -mesg "Type Password or hit Enter to generate one")
if [[ $pw == "Generate" ]]; then
pw=$(_pwgen "${password_length}")
fi
clear
if [[ "$group" == "No Group" ]]; then
if [[ $url == http* ]]; then
pass_content=("${pw}"
"---"
"${USERNAME_field}: ${user}"
"${URL_field}: ${url}"
)
printf '%s\n' "${pass_content[@]}" | PASSWORD_STORE_DIR="${root}" pass insert -m "${name}" > /dev/null && edit_pass "${name}"
else
pass_content=("${pw}"
"---"
"${USERNAME_field}: ${user}"
)
printf '%s\n' "${pass_content[@]}" | PASSWORD_STORE_DIR="${root}" pass insert -m "${name}" > /dev/null && edit_pass "${name}"
fi
else
if [[ $url == http* ]]; then
pass_content=("${pw}"
"---"
"${USERNAME_field}: ${user}"
"${URL_field}: ${url}"
)
printf '%s\n' "${pass_content[@]}" | PASSWORD_STORE_DIR="${root}" pass insert -m "${group}/${name}" > /dev/null && edit_pass "${group}/${name}"
else
pass_content=("${pw}"
"---"
"${USERNAME_field}: ${user}"
)
printf '%s\n' "${pass_content[@]}" | PASSWORD_STORE_DIR="${root}" pass insert -m "${group}/${name}" > /dev/null && edit_pass "${group}/${name}"
fi
fi
}
help_msg () {
cat <<'EOF'
Usage:
rofi-pass [command]
Commands:
--insert insert new entry to password store
--root set custom root directories (colon separated)
--last-used highlight last used item
--show-last show details of last used Entry
--bmarks start in bookmarks mode
rofi-pass version 1.5.3
EOF
}
get_config_file () {
configs=("$ROFI_PASS_CONFIG"
"$config_dir/rofi-pass/config"
"/etc/rofi-pass.conf")
# return the first config file with a valid path
for config in "${configs[@]}"; do
# '-n' is needed in case ROFI_PASS_CONFIG is not set
if [[ -n "${config}" && -f "${config}" ]]; then
printf "%s" "$config"
return
fi
done
}
main () {
# load config file
config_file="$(get_config_file)"
[[ -n "$config_file" ]] && source "$config_file"
# create tmp dir
if [[ ! -d "$cache_dir/rofi-pass" ]]; then
mkdir -p "$cache_dir/rofi-pass"
fi
# set backends
clip_in_primary=_clip_in_primary_${clipboard_backend}
clip_in_clipboard=_clip_in_clipboard_${clipboard_backend}
clip_out_primary=_clip_out_primary_${clipboard_backend}
clip_out_clipboard=_clip_out_clipboard_${clipboard_backend}
do_type=_do_type_${backend}
do_press_key=_do_press_key_${backend}
# backwards compat
if [[ -n "$xdotool_delay" ]]; then
>&2 echo "Setting 'xdotool_delay' is deprecated. Please update your configuration to use 'type_delay' instead"
type_delay=${xdotool_delay}
fi
# Only valid for the xdotool backend
if [[ $backend == "xdotool" ]]; then
# fix keyboard layout if enabled in config
if [[ $fix_layout == "true" ]]; then
layout_cmd
fi
fi
# check for BROWSER variable, use xdg-open as fallback
if [[ -z $BROWSER ]]; then
export BROWSER=xdg-open
fi
# check if alternative root directory was given on commandline
if [[ -r "$cache_dir/rofi-pass/last_used" ]] && [[ $1 == "--last-used" || $1 == "--show-last" ]]; then
roots=("$(awk -F ': ' '{ print $1 }' "$cache_dir/rofi-pass/last_used")")
elif [[ -n "$2" && "$1" == "--root" ]]; then
custom_root=true; IFS=: read -r -a roots <<< "$2"
elif [[ -n $root ]]; then
custom_root=true; IFS=: read -r -a roots <<< "${root}"
elif [[ -n ${PASSWORD_STORE_DIR} ]]; then
roots=("${PASSWORD_STORE_DIR}")
else
roots=("$HOME/.password-store")
fi
roots_index=0
roots_length=${#roots[@]}
export root=${roots[$roots_index]}
export PASSWORD_STORE_DIR="${root}"
case $1 in
--insert)
insertPass
;;
--root)
mainMenu
;;
--help)
help_msg
;;
--last-used)
if [[ -r "$cache_dir/rofi-pass/last_used" ]]; then
entry="$(awk -F ': ' '{ print $2 }' "$cache_dir/rofi-pass/last_used")"
fi
mainMenu
;;
--show-last)
if [[ -r "$cache_dir/rofi-pass/last_used" ]]; then
selected_password="$(awk -F ': ' '{ print $2 }' "$cache_dir/rofi-pass/last_used")" viewEntry
else
mainMenu
fi
;;
--bmarks)
mainMenu --bmarks;
;;
*)
mainMenu
;;
esac
}
main "$@"

View File

@@ -0,0 +1,49 @@
#!/usr/bin/env bash
usage() {
echo "Usage: $(basename "$0") file_name start end"
}
extract_images() {
local video_file=$1
local start=$2
local end=$3
local start_in_seconds
local end_in_seconds
local date_diff
start_in_seconds=$(date --date "${start}" +%s)
end_in_seconds=$(date --date "${end}" +%s)
date_diff=$((end_in_seconds - start_in_seconds))
ffmpeg -ss "${start}" -t ${date_diff} -i "${video_file}" -qscale:v 2 output_%03d.jpg
}
if [[ $# -ne 3 ]]; then
usage
exit 0
fi
if ! command -v ffmpeg >/dev/null 2>&1; then
echo "ffmpeg: command not found"
exit 1
fi
FILENAME=$1
START=$2
END=$3
if [ ! -f "${FILENAME}" ]; then
echo "${FILENAME}: not a file"
exit 1
fi
if ! { [[ ${START} =~ ^([0-9]{2}:)?[0-9]{2}:[0-9]{2}$ ]] && date -d "${START}" >/dev/null 2>&1; }; then
echo "${START}: incorrect time"
exit 1
fi
if ! { [[ ${END} =~ ^([0-9]{2}:)?[0-9]{2}:[0-9]{2}$ ]] && date -d "${END}" >/dev/null 2>&1; }; then
echo "${END}: incorrect time"
exit 1
fi
extract_images "${FILENAME}" "${START}" "${END}"