#!/usr/bin/env bash set -euo pipefail UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)" USER_FILE="${UMBREL_ROOT}/db/user.json" show_help() { cat << EOF app 0.0.1 CLI for managing Umbrel apps Usage: app [] Commands: install Pulls down images for an app and starts it uninstall Removes images and destroys all data for an app stop Stops an installed app start Starts an installed app compose Passes all arguments to docker-compose ls-installed Lists installed apps EOF } check_dependencies () { for cmd in "$@"; do if ! command -v $cmd >/dev/null 2>&1; then echo "This script requires \"${cmd}\" to be installed" exit 1 fi done } list_installed_apps() { cat "${USER_FILE}" 2> /dev/null | jq -r 'if has("installedApps") then .installedApps else [] end | join("\n")' || true } # Deterministically derives 128 bits of cryptographically secure entropy derive_entropy () { identifier="${1}" umbrel_seed=$(cat "${UMBREL_ROOT}/db/umbrel-seed/seed") || true if [[ -z "$umbrel_seed" ]] || [[ -z "$identifier" ]]; then >&2 echo "Missing derivation parameter, this is unsafe, exiting." exit 1 fi # We need `sed 's/^.* //'` to trim the "(stdin)= " prefix from some versions of openssl printf "%s" "${identifier}" | openssl dgst -sha256 -hmac "${umbrel_seed}" | sed 's/^.* //' } # Check dependencies check_dependencies docker-compose jq openssl if [ -z ${1+x} ]; then command="" else command="$1" fi # Lists installed apps if [[ "$command" = "ls-installed" ]]; then list_installed_apps exit fi if [ -z ${2+x} ]; then show_help exit 1 else app="$2" app_dir="${UMBREL_ROOT}/apps/${app}" app_data_dir="${UMBREL_ROOT}/app-data/${app}" if [[ "${app}" == "installed" ]]; then list_installed_apps | while read app; do if [[ "${app}" != "" ]]; then "${0}" "${1}" "${app}" "${@:3}" || true fi done exit fi if [[ -z "${app}" ]] || [[ ! -d "${app_dir}" ]]; then echo "Error: \"${app}\" is not a valid app" exit 1 fi fi if [ -z ${3+x} ]; then args="" else args="${@:3}" fi compose() { local app="${1}" shift local env_file="${UMBREL_ROOT}/.env" local app_base_compose_file="${UMBREL_ROOT}/apps/docker-compose.common.yml" local app_compose_file="${app_dir}/docker-compose.yml" local app_hidden_servive_file="${UMBREL_ROOT}/tor/data/app-${app}/hostname" local app_entropy_identifier="app-${app}-seed" export BITCOIN_DATA_DIR="${UMBREL_ROOT}/bitcoin" export LND_DATA_DIR="${UMBREL_ROOT}/lnd" export APP_DATA_DIR="${app_data_dir}" export APP_HIDDEN_SERVICE="$(cat "${app_hidden_servive_file}" 2>/dev/null || echo "notyetset.onion")" export APP_SEED=$(derive_entropy "${app_entropy_identifier}") docker-compose \ --env-file "${env_file}" \ --project-name "${app}" \ --file "${app_base_compose_file}" \ --file "${app_compose_file}" \ "${@}" } update_installed_apps() { local action="${1}" local app="${2}" [[ "${action}" == "add" ]] && operator="+" || operator="-" updated_json=$(cat "${USER_FILE}" | jq ".installedApps |= (. ${operator} [\"${app}\"] | unique)") echo "${updated_json}" > "${USER_FILE}" } # Pulls down images for an app and starts it if [[ "$command" = "install" ]]; then echo "Setting up data dir for app ${app}..." mkdir -p "${app_data_dir}" rsync --archive --verbose --exclude ".gitkeep" "${app_dir}/." "${app_data_dir}" echo "Pulling images for app ${app}..." compose "${app}" pull echo "Starting app ${app}..." compose "${app}" up --detach echo "Saving app ${app} in DB..." update_installed_apps add "${app}" echo "Successfully installed app ${app}" exit fi # Removes images and destroys all data for an app if [[ "$command" = "uninstall" ]]; then echo "Removing images for app ${app}..." compose "${app}" down --rmi all --remove-orphans echo "Deleting app data for app ${app}..." if [[ -d "${app_data_dir}" ]]; then rm -rf "${app_data_dir}" fi echo "Removing app ${app} from DB..." update_installed_apps remove "${app}" echo "Successfully uninstalled app ${app}" exit fi # Stops an installed app if [[ "$command" = "stop" ]]; then echo "Stopping app ${app}..." compose "${app}" rm --force --stop exit fi # Starts an installed app if [[ "$command" = "start" ]]; then if ! list_installed_apps | grep --quiet "^${app}$"; then echo "Error: app \"${app}\" is not installed yet" exit 1 fi echo "Starting app ${app}..." compose "${app}" up --detach exit fi # Passes all arguments to docker-compose if [[ "$command" = "compose" ]]; then compose "${app}" ${args} exit fi # If we get here it means no valid command was supplied # Show help and exit show_help exit 1