From 37f08cfd9d3f768e93185a3379ba0af26524a81e Mon Sep 17 00:00:00 2001 From: Steven Briscoe Date: Wed, 12 Oct 2022 08:40:02 +1100 Subject: [PATCH] Remote Tor access toggle Co-authored-by: Luke Childs Co-authored-by: Steven Briscoe --- docker-compose.tor.yml | 14 ++ docker-compose.yml | 11 +- events/triggers/remote-tor-access | 61 ++++++++ scripts/app | 137 +++++++++++++----- scripts/configure | 32 +--- scripts/debug | 2 +- scripts/start | 40 +++-- scripts/stop | 21 ++- scripts/support/docker-compose.tor.yml | 10 +- scripts/support/tor-entrypoint.sh | 17 +++ scripts/support/torrc.template | 3 - scripts/update/.updateinclude | 3 +- scripts/update/01-run.sh | 3 + scripts/update/steps/add-remote-tor-access.sh | 22 +++ templates/torrc-proxy-sample | 12 +- templates/torrc-server-sample | 13 ++ 16 files changed, 297 insertions(+), 104 deletions(-) create mode 100644 docker-compose.tor.yml create mode 100755 events/triggers/remote-tor-access create mode 100755 scripts/support/tor-entrypoint.sh delete mode 100644 scripts/support/torrc.template create mode 100755 scripts/update/steps/add-remote-tor-access.sh create mode 100644 templates/torrc-server-sample diff --git a/docker-compose.tor.yml b/docker-compose.tor.yml new file mode 100644 index 000000000..a9b11162e --- /dev/null +++ b/docker-compose.tor.yml @@ -0,0 +1,14 @@ +version: '3.7' + +services: + tor_server: + container_name: tor_server + image: getumbrel/tor:0.4.7.8@sha256:2ace83f22501f58857fa9b403009f595137fa2e7986c4fda79d82a8119072b6a + user: "1000:1000" + # build: ./deps/tor + restart: on-failure + volumes: + - ${PWD}/tor/torrc-server:/etc/tor/torrc:ro + - ${PWD}/tor/data:/data + environment: + HOME: "/tmp" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 804f48277..d07428a28 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,9 +2,9 @@ version: '3.7' services: tor_proxy: - container_name: tor - image: lncm/tor:0.4.7.7@sha256:3c4ae833d2fefbea7d960f833a1e89fc9b2069a6e5f360109b5ddc9334ac0227 - user: toruser + container_name: tor_proxy + image: getumbrel/tor:0.4.7.8@sha256:2ace83f22501f58857fa9b403009f595137fa2e7986c4fda79d82a8119072b6a + user: "1000:1000" restart: on-failure volumes: - ${PWD}/tor/torrc-proxy:/etc/tor/torrc:ro @@ -40,7 +40,6 @@ services: manager: container_name: manager image: getumbrel/manager:v0.5.0@sha256:c780ffb2619ba32e392ed9343d0336867ad3b2e9f3f08b8b0c7f9083e2c44a26 - depends_on: [ tor_proxy ] restart: on-failure stop_grace_period: 5m30s volumes: @@ -63,8 +62,6 @@ services: JWT_PRIVATE_KEY_FILE: "/jwt-private-key/jwt.key" JWT_EXPIRATION: "3600" DOCKER_COMPOSE_DIRECTORY: $PWD - DEVICE_HOSTS: ${DEVICE_HOSTS:-"http://umbrel.local"} - DEVICE_HOSTNAME: ${DEVICE_HOSTNAME:-""} UMBREL_SEED_FILE: "/db/umbrel-seed/seed" UMBREL_DASHBOARD_HIDDEN_SERVICE_FILE: "/var/lib/tor/web/hostname" UMBREL_AUTH_SECRET: $UMBREL_AUTH_SECRET @@ -87,8 +84,6 @@ services: UPDATE_LOCK_FILE: "/statuses/update-in-progress" BACKUP_STATUS_FILE: "/statuses/backup-status.json" DEBUG_STATUS_FILE: "/statuses/debug-status.json" - TOR_PROXY_IP: "${TOR_PROXY_IP}" - TOR_PROXY_PORT: "${TOR_PROXY_PORT}" TOR_HIDDEN_SERVICE_DIR: "/var/lib/tor" IS_UMBREL_OS: ${IS_UMBREL_OS:-"false"} UMBREL_APP_REPO_URL: "https://github.com/getumbrel/umbrel-apps.git" diff --git a/events/triggers/remote-tor-access b/events/triggers/remote-tor-access new file mode 100755 index 000000000..edc57cb78 --- /dev/null +++ b/events/triggers/remote-tor-access @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)" +USER_FILE="${UMBREL_ROOT}/db/user.json" +STATUS_FILE="${UMBREL_ROOT}/statuses/remote-tor-access-status.json" +SIGNAL_FILE="${UMBREL_ROOT}/events/signals/remote-tor-access" + +updateStatus() { + local -r state="${1}" + local -r progress="${2}" + + cat < "${STATUS_FILE}" +{"state": "${state}", "progress": ${progress}} +EOF +} + +if [[ ! -f "${SIGNAL_FILE}" ]]; then + exit +fi + +enabled=$(cat "${SIGNAL_FILE}") +rm -f "${SIGNAL_FILE}" + +if [[ -f "${STATUS_FILE}" ]]; then + state=$(cat "${STATUS_FILE}" 2> /dev/null | jq -r 'if has("state") then .state else "" end' || true) + + if [[ "${state}" == "running" ]]; then + >&2 echo "Error: Already running!" + exit 1 + fi +fi + +updateStatus "running" "20" + +echo "Stopping Umbrel..." +"${UMBREL_ROOT}/scripts/stop" || true + +updateStatus "running" "50" + +echo +echo "Saving 'remoteTorAccess' setting..." + +while ! (set -o noclobber; echo "$$" > "${USER_FILE}.lock") 2> /dev/null; do + echo "Waiting for JSON lock to be released for ${app} update..." + sleep 1 +done +# This will cause the lock-file to be deleted in case of a +# premature exit. +trap "rm -f "${USER_FILE}.lock"; exit $?" INT TERM EXIT + +jq ".remoteTorAccess = ${enabled}" "${USER_FILE}" > /tmp/user.json +mv /tmp/user.json "${USER_FILE}" + +rm -f "${USER_FILE}.lock" + +echo +echo "Starting Umbrel..." +"${UMBREL_ROOT}/scripts/start" + +updateStatus "complete" "100" \ No newline at end of file diff --git a/scripts/app b/scripts/app index 69a411809..992df2814 100755 --- a/scripts/app +++ b/scripts/app @@ -7,6 +7,12 @@ UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)" USER_FILE="${UMBREL_ROOT}/db/user.json" ACTIVE_APP_REPO_PATH=$($UMBREL_ROOT/scripts/repo path) +APP_PROXY_SERVICE_NAME="app_proxy" +REMOTE_TOR_ACCESS="false" +if [[ -f "${USER_FILE}" ]]; then + REMOTE_TOR_ACCESS=$(cat "${USER_FILE}" | jq 'has("remoteTorAccess") and .remoteTorAccess') +fi + show_help() { cat << EOF CLI (v${VERSION}) for managing Umbrel apps @@ -17,8 +23,8 @@ Commands: install Pulls down images for an app and starts it uninstall Removes images and destroys all data for an app reinstall Calls 'uninstall', followed by 'install' for an app - stop Stops an installed app start Starts an installed app + stop Stops an installed app restart Restarts an installed app compose Passes all arguments to docker-compose ls-installed Lists installed apps @@ -59,15 +65,6 @@ derive_entropy () { printf "%s" "${identifier}" | openssl dgst -sha256 -hmac "${umbrel_seed}" | sed 's/^.* //' } -# If the app does not provide a torrc or torrc.template file -# Then we'll provide a default Tor torrc.template file -copy_default_torrc_template() { - if [[ ! -f "${app_data_dir}/torrc.template" ]] && [[ ! -f "${app_data_dir}/torrc" ]]; then - local -r torrc_template_file="${UMBREL_ROOT}/scripts/support/torrc.template" - cp --archive "${torrc_template_file}" "${app_data_dir}" - fi -} - # Setup env. for this context for a given app source_app() { local -r app="${1}" @@ -119,10 +116,27 @@ source_app() { export APP_DATA_DIR="${app_data_dir}" export APP_DOMAIN="${app_domain}" - export APP_HIDDEN_SERVICE="$(cat "${app_hidden_service_file}" 2>/dev/null || echo "notyetset.onion")" + export APP_HIDDEN_SERVICE="not-enabled.onion" + if [[ "${REMOTE_TOR_ACCESS}" == "true" ]]; then + export APP_HIDDEN_SERVICE="$(cat "${app_hidden_service_file}" 2>/dev/null || echo "notyetset.onion")" + fi export APP_SEED=$(derive_entropy "${app_entropy_identifier}") export APP_PASSWORD=$(derive_entropy "${app_entropy_identifier}-APP_PASSWORD") + # Tor specific exports + export TOR_DATA_DIR="${UMBREL_ROOT}/tor/data" + export TOR_ENTRYPOINT_SCRIPT="${UMBREL_ROOT}/scripts/support/tor-entrypoint.sh" + export TOR_HS_APP_DIR="/data/app-${app}" + export TOR_HS_PORTS="80:${APP_PROXY_HOSTNAME}:${APP_PROXY_PORT}" + + tor_extra_hs_varname=$(echo "APP_${APP_ID^^}_TOR_HS_EXTRA_PORTS" | tr '-' '_') + tor_hs_extra_ports="${!tor_extra_hs_varname:-}" + + if [[ ! -z "${tor_hs_extra_ports}" ]]; then + export TOR_HS_PORTS="${TOR_HS_PORTS} ${tor_hs_extra_ports}" + fi + + # Other export UMBREL_ROOT } @@ -149,7 +163,7 @@ else app="$2" app_repo_dir="${ACTIVE_APP_REPO_PATH}/${app}" app_data_dir="${UMBREL_ROOT}/app-data/${app}" - app_torrc_file="${app_data_dir}/torrc" + app_hidden_service_file="${UMBREL_ROOT}/tor/data/app-${app}/hostname" if [[ "${app}" == "installed" ]]; then @@ -174,6 +188,20 @@ else args="${@:3}" fi +execute_hook() { + local -r app="${1}" + local -r name="${2}" + + local -r app_hooks_dir="${UMBREL_ROOT}/app-data/${app}/hooks" + local -r hook="${app_hooks_dir}/${name}" + + if [[ -x "${hook}" ]]; then + echo "Executing hook: ${hook}" + # Swallow non-zero exit code + "${hook}" || true + fi +} + compose() { local -r app="${1}" shift @@ -185,11 +213,9 @@ compose() { local -r app_proxy_compose_file="${UMBREL_ROOT}/scripts/support/docker-compose.app_proxy.yml" local -r tor_compose_file="${UMBREL_ROOT}/scripts/support/docker-compose.tor.yml" local -r common_compose_file="${UMBREL_ROOT}/scripts/support/docker-compose.common.yml" - - local -r umbrel_env_file="${UMBREL_ROOT}/.env" local -r app_compose_file="${app_data_dir}/docker-compose.yml" - export TOR_DATA_DIR="${UMBREL_ROOT}/tor/data" + local -r umbrel_env_file="${UMBREL_ROOT}/.env" # We need to use the proxy compose file first # To allow vars. in the app's compose file to override variables @@ -197,16 +223,15 @@ compose() { # Detect if the 'app_proxy' service has been defined # In the app's docker-compose file - APP_PROXY_SERVICE_NAME="app_proxy" - HAS_APP_PROXY_SERVICE=$(cat "${app_compose_file}" | yq ".services | has(\"${APP_PROXY_SERVICE_NAME}\")") + has_app_proxy_service=$(cat "${app_compose_file}" | yq ".services | has(\"${APP_PROXY_SERVICE_NAME}\")") - if [[ "${HAS_APP_PROXY_SERVICE}" == "true" ]]; then + if [[ "${has_app_proxy_service}" == "true" ]]; then compose_files+=( "--file" "${app_proxy_compose_file}" ) fi - - # If an app has a torrc file + + # If remote Tor access is enabled # Then include a compose file for Tor - if [[ -f "${app_torrc_file}" ]]; then + if [[ "${REMOTE_TOR_ACCESS}" == "true" ]]; then compose_files+=( "--file" "${tor_compose_file}" ) fi @@ -268,7 +293,7 @@ copy_app_files() { APP_FILES="${app_repo_dir}/${filename}" for app_file in $APP_FILES; do - if [[ -f "${app_file}" ]]; then + if [[ -f "${app_file}" ]] || [[ -d "${app_file}" ]]; then cp --archive "${app_file}" "${app_data_dir}" fi done @@ -277,20 +302,29 @@ copy_app_files() { wait_for_tor_hs() { local -r app="${1}" + + # Check if the app's hidden service hostname + # Has been already generated and exit early + if [[ -f "${app_hidden_service_file}" ]]; then + return + fi - # Check only on start if a 'torrc` - # config. file does NOT exist - if [[ ! -f "${app_torrc_file}" ]]; then + # Check that the app has the App Proxy service defined + local -r app_compose_file="${app_data_dir}/docker-compose.yml" + has_app_proxy_service=$(cat "${app_compose_file}" | yq ".services | has(\"${APP_PROXY_SERVICE_NAME}\")") + + if [[ "${has_app_proxy_service}" == "false" ]]; then echo - >&2 echo "Warning: \"${app}\" is missing a 'torrc' config. file" - >&2 echo " \"${app}\" will be inaccessible over Tor" + >&2 echo "Warning: \"${app}\" has no '${APP_PROXY_SERVICE_NAME}' defined" + >&2 echo " \"${app}\" needs this to generate Tor HS" echo + return fi # If a tor service will start # and there is no existing tor hs hostname # Let's allow 10 seconds to generate it and then start the app - if [[ -f "${app_torrc_file}" ]] && [[ ! -f "${app_hidden_service_file}" ]]; then + if [[ "${REMOTE_TOR_ACCESS}" == "true" ]]; then echo "Generating hidden services for ${app}..." # We must first start the App Proxy # So that it's hostname is resolvable by Tor @@ -327,8 +361,12 @@ start_app() { # Wait for Tor's HS hostname to exist wait_for_tor_hs "${app}" + execute_hook "${app}" "pre-start" + # Start all the app's containers compose "${app}" up --detach + + execute_hook "${app}" "post-start" } # Check that the app is installed @@ -358,8 +396,7 @@ if [[ "$command" = "install" ]]; then # Copy all app files rsync --archive --verbose --exclude ".gitkeep" "${app_repo_dir}/." "${app_data_dir}" - # Copy default torrc.template file (if needed) - copy_default_torrc_template + execute_hook "${app}" "pre-install" # Source env. source_app "${app}" @@ -378,6 +415,8 @@ if [[ "$command" = "install" ]]; then echo "Saving app ${app} in DB..." update_installed_apps add "${app}" + execute_hook "${app}" "post-install" + echo "Successfully installed app ${app}" exit fi @@ -387,6 +426,22 @@ if [[ "$command" = "uninstall" ]]; then must_be_installed_guard + execute_hook "${app}" "pre-uninstall" + + # If a post uninstal hook exists + # Then make a copy before it's deleted below + app_hooks_dir="${UMBREL_ROOT}/app-data/${app}/hooks" + post_uninstall_app_hook="${app_hooks_dir}/post-uninstall" + if [[ -x "${post_uninstall_app_hook}" ]]; then + temp_post_uninstall_app_hook="/tmp/${app}-post-uninstall" + + cp --archive "${post_uninstall_app_hook}" "${temp_post_uninstall_app_hook}" + + post_uninstall_app_hook="${temp_post_uninstall_app_hook}" + else + post_uninstall_app_hook="" + fi + echo "Removing images for app ${app}..." compose "${app}" down --rmi all --remove-orphans @@ -398,6 +453,12 @@ if [[ "$command" = "uninstall" ]]; then echo "Removing app ${app} from DB..." update_installed_apps remove "${app}" + if [[ ! -z "${post_uninstall_app_hook}" ]]; then + "${post_uninstall_app_hook}" || true + + rm -rf "${post_uninstall_app_hook}" + fi + echo "Successfully uninstalled app ${app}" exit fi @@ -405,9 +466,13 @@ fi # Stops an installed app if [[ "$command" = "stop" ]]; then + execute_hook "${app}" "pre-stop" + echo "Stopping app ${app}..." compose "${app}" rm --force --stop + execute_hook "${app}" "post-stop" + exit fi @@ -458,9 +523,11 @@ if [[ "$command" = "update" ]]; then if [[ "$*" != *"--skip-stop"* ]]; then "${0}" "stop" "${app}" fi + + execute_hook "${app}" "pre-update" # App updates will only copy files from this whitelist: - UPDATE_FILES_WHITELIST_PRE="docker-compose.yml *.template exports.sh torrc" + UPDATE_FILES_WHITELIST_PRE="docker-compose.yml *.template exports.sh torrc hooks" # We copy umbrel-app.yml after the app has started # That way the frontend knows the update has finished @@ -472,9 +539,6 @@ if [[ "$command" = "update" ]]; then # Copy remaining files in the event of error or success trap "copy_app_files "${UPDATE_FILES_WHITELIST_POST}"; exit $?" INT TERM EXIT - # Copy default torrc.template file (if needed) - copy_default_torrc_template - # Source env. after new exports.sh is copied (done above via 'copy_app_files') source_app "${app}" @@ -490,13 +554,14 @@ if [[ "$command" = "update" ]]; then # Remove any old images we don't need anymore docker rmi $app_old_images || true + + execute_hook "${app}" "post-update" + exit fi # Passes all arguments to docker-compose if [[ "$command" = "compose" ]]; then - - must_be_installed_guard compose "${app}" ${args} diff --git a/scripts/configure b/scripts/configure index 9288c7470..af562bcd9 100755 --- a/scripts/configure +++ b/scripts/configure @@ -67,17 +67,20 @@ echo # Store paths to intermediary config files NGINX_CONF_FILE="./templates/nginx.conf" TOR_PROXY_CONF_FILE="./templates/torrc-proxy" +TOR_SERVER_CONF_FILE="./templates/torrc-server" ENV_FILE="./templates/.env" # Remove intermediary files if they exist from any # previous unclean configuration run [[ -f "$NGINX_CONF_FILE" ]] && rm -f "$NGINX_CONF_FILE" [[ -f "$TOR_PROXY_CONF_FILE" ]] && rm -f "$TOR_PROXY_CONF_FILE" +[[ -f "$TOR_SERVER_CONF_FILE" ]] && rm -f "$TOR_SERVER_CONF_FILE" [[ -f "$ENV_FILE" ]] && rm -f "$ENV_FILE" # Copy template configs to intermediary configs [[ -f "./templates/nginx-sample.conf" ]] && cp "./templates/nginx-sample.conf" "$NGINX_CONF_FILE" [[ -f "./templates/torrc-proxy-sample" ]] && cp "./templates/torrc-proxy-sample" "$TOR_PROXY_CONF_FILE" +[[ -f "./templates/torrc-server-sample" ]] && cp "./templates/torrc-server-sample" "$TOR_SERVER_CONF_FILE" [[ -f "./templates/.env-sample" ]] && cp "./templates/.env-sample" "$ENV_FILE" @@ -134,10 +137,9 @@ if [[ -z ${TOR_PASSWORD+x} ]] || [[ -z ${TOR_HASHED_PASSWORD+x} ]]; then echo "Generating Tor password" echo TOR_PASSWORD=$("./scripts/rpcauth.py" "itdoesntmatter" | tail -1) - TOR_HASHED_PASSWORD=$(docker run --rm lncm/tor:0.4.7.7@sha256:3c4ae833d2fefbea7d960f833a1e89fc9b2069a6e5f360109b5ddc9334ac0227 --quiet --hash-password "$TOR_PASSWORD") + TOR_HASHED_PASSWORD=$(docker run --rm getumbrel/tor:0.4.7.8@sha256:2ace83f22501f58857fa9b403009f595137fa2e7986c4fda79d82a8119072b6a --quiet --hash-password "$TOR_PASSWORD") fi - ########################################################## ### Update config files with configuration variables ##### ########################################################## @@ -166,7 +168,7 @@ DOCKER_BINARY=$(readlink -f "$(which docker)") sed -i "s#DOCKER_BINARY=#DOCKER_BINARY=$DOCKER_BINARY#g;" "$ENV_FILE" # TODO: Update all the above code to use this simpler logic -for template in "${NGINX_CONF_FILE}" "${TOR_PROXY_CONF_FILE}" "${ENV_FILE}"; do +for template in "${NGINX_CONF_FILE}" "${TOR_PROXY_CONF_FILE}" "${TOR_SERVER_CONF_FILE}" "${ENV_FILE}"; do # Umbrel sed -i "s//${NETWORK_IP}/g" "${template}" sed -i "s//${GATEWAY_IP}/g" "${template}" @@ -201,31 +203,9 @@ done mv -f "$NGINX_CONF_FILE" "./nginx/nginx.conf" mv -f "$TOR_PROXY_CONF_FILE" "./tor/torrc-proxy" +mv -f "$TOR_SERVER_CONF_FILE" "./tor/torrc-server" mv -f "$ENV_FILE" "./.env" -########################################################## -######### Generate hidden services on first run ########## -########################################################## - -# Prevents CORS issue if dashboard's hidden service -# doesn't exist on the first run -if [[ ! -f "${STATUS_DIR}/configured" ]]; then - echo "Generating hidden services..." - echo - docker-compose up --detach tor_proxy - wait_for_tor=10 - while [[ ! -f "${UMBREL_ROOT}/tor/data/web/hostname" ]]; do - if [[ "${wait_for_tor}" == 0 ]]; then - echo "Dashboard's hidden service file wasn't created..." - echo - break - fi - sleep 1 - ((wait_for_tor--)) - done - docker-compose down -fi - ########################################################## ################ Configuration complete ################## ########################################################## diff --git a/scripts/debug b/scripts/debug index 23d49b070..552a2a710 100755 --- a/scripts/debug +++ b/scripts/debug @@ -131,7 +131,7 @@ echo docker-compose logs --tail=30 manager echo -echo "Tor logs" +echo "Tor Proxy logs" echo "--------" echo docker-compose logs --tail=10 tor_proxy diff --git a/scripts/start b/scripts/start index 2edfb5764..f5d021ad3 100755 --- a/scripts/start +++ b/scripts/start @@ -30,6 +30,12 @@ check_dependencies rsync jq curl UMBREL_ROOT="$(dirname $(readlink -f "${BASH_SOURCE[0]}"))/.." UMBREL_LOGS="${UMBREL_ROOT}/logs" +USER_FILE="${UMBREL_ROOT}/db/user.json" + +REMOTE_TOR_ACCESS="false" +if [[ -f "${USER_FILE}" ]]; then + REMOTE_TOR_ACCESS=$(cat "${USER_FILE}" | jq 'has("remoteTorAccess") and .remoteTorAccess') +fi set_status="${UMBREL_ROOT}/scripts/umbrel-os/status-server/set-status" @@ -75,17 +81,6 @@ else export IS_UMBREL_OS="true" fi -# Whitelist device IP, hostname and hidden service for CORS -DEVICE_IP="$(ip -o route get to 8.8.8.8 | sed -n 's/.*src \([0-9.]\+\).*/\1/p')" -DEVICE_HOSTNAME="$(hostname)" -DEVICE_HOSTS="http://${DEVICE_IP},http://${DEVICE_HOSTNAME}.local,https://${DEVICE_HOSTNAME}.local,http://${DEVICE_HOSTNAME},https://${DEVICE_HOSTNAME}" -if [[ -f "${UMBREL_ROOT}/tor/data/web/hostname" ]]; then - hidden_service_url=$(cat "${UMBREL_ROOT}/tor/data/web/hostname") - DEVICE_HOSTS="${DEVICE_HOSTS},http://${hidden_service_url}" -fi -export DEVICE_HOSTS=$DEVICE_HOSTS -export DEVICE_HOSTNAME="${DEVICE_HOSTNAME}.local" - # Increase default Docker and Compose timeouts to 240s # as bitcoin can take a long while to respond export DOCKER_CLIENT_TIMEOUT=240 @@ -126,10 +121,23 @@ echo "Starting decoy backup trigger..." echo ./scripts/backup/decoy-trigger &>> "${UMBREL_LOGS}/backup-decoy-trigger.log" & +compose_files=() + +if [[ "${REMOTE_TOR_ACCESS}" == "true" ]]; then + compose_files+=( "--file" "docker-compose.tor.yml" ) +fi + +compose_files+=( "--file" "docker-compose.yml" ) + +UMBREL_DEV_OVERRIDE="${UMBREL_ROOT}/docker-compose.override.yml" +if [[ -f "${UMBREL_DEV_OVERRIDE}" ]]; then + compose_files+=( "--file" "${UMBREL_DEV_OVERRIDE}" ) +fi + echo echo "Starting Docker services..." echo -docker-compose up --detach --build --remove-orphans || { +docker-compose "${compose_files[@]}" up --detach --build --remove-orphans || { echo "Failed to start containers" $set_status umbrel errored docker-failed exit 1 @@ -165,11 +173,15 @@ if [[ -f "${RESOLV_CONF_BACKUP_FILE}" ]]; then rm --force "${RESOLV_CONF_BACKUP_FILE}" || true fi +DEVICE_HOSTNAME="$(hostname).local" +DEVICE_IP="$(ip -o route get to 8.8.8.8 | sed -n 's/.*src \([0-9.]\+\).*/\1/p')" +TOR_HS_WEB_HOSTNAME_FILE="${UMBREL_ROOT}/tor/data/web/hostname" + echo "Umbrel is now accessible at" echo " http://${DEVICE_HOSTNAME}" echo " http://${DEVICE_IP}" -if [[ ! -z "${hidden_service_url:-}" ]]; then - echo " http://${hidden_service_url}" +if [[ "${REMOTE_TOR_ACCESS}" == "true" ]] && [[ -f "${TOR_HS_WEB_HOSTNAME_FILE}" ]]; then + echo " http://$(cat "${TOR_HS_WEB_HOSTNAME_FILE}")" fi $set_status umbrel completed diff --git a/scripts/stop b/scripts/stop index d12e0ca9d..e37fbf922 100755 --- a/scripts/stop +++ b/scripts/stop @@ -11,6 +11,12 @@ if [[ $UID != 0 ]]; then fi UMBREL_ROOT="$(dirname $(readlink -f "${BASH_SOURCE[0]}"))/.." +USER_FILE="${UMBREL_ROOT}/db/user.json" + +REMOTE_TOR_ACCESS="false" +if [[ -f "${USER_FILE}" ]]; then + REMOTE_TOR_ACCESS=$(cat "${USER_FILE}" | jq 'has("remoteTorAccess") and .remoteTorAccess') +fi if [[ ! -d "$UMBREL_ROOT" ]]; then echo "Root dir does not exist '$UMBREL_ROOT'" @@ -29,9 +35,22 @@ echo "${UMBREL_ROOT}/scripts/app" stop installed echo +compose_files=() + +if [[ "${REMOTE_TOR_ACCESS}" == "true" ]]; then + compose_files+=( "--file" "docker-compose.tor.yml" ) +fi + +compose_files+=( "--file" "docker-compose.yml" ) + +UMBREL_DEV_OVERRIDE="${UMBREL_ROOT}/docker-compose.override.yml" +if [[ -f "${UMBREL_DEV_OVERRIDE}" ]]; then + compose_files+=( "--file" "${UMBREL_DEV_OVERRIDE}" ) +fi + echo "Stopping Docker services..." echo -docker-compose down +docker-compose "${compose_files[@]}" down # TODO: Transition to pidfiles so we can reliably kill these processes echo "Killing background services" diff --git a/scripts/support/docker-compose.tor.yml b/scripts/support/docker-compose.tor.yml index eb57cde04..bb3211005 100644 --- a/scripts/support/docker-compose.tor.yml +++ b/scripts/support/docker-compose.tor.yml @@ -2,10 +2,14 @@ version: "3.7" services: tor_server: - image: lncm/tor:0.4.7.7@sha256:3c4ae833d2fefbea7d960f833a1e89fc9b2069a6e5f360109b5ddc9334ac0227 + image: getumbrel/tor:0.4.7.8@sha256:2ace83f22501f58857fa9b403009f595137fa2e7986c4fda79d82a8119072b6a + user: "1000:1000" restart: on-failure volumes: - - ${APP_DATA_DIR}/torrc:/etc/tor/torrc:ro + - ${TOR_ENTRYPOINT_SCRIPT}:/umbrel/entrypoint.sh - ${TOR_DATA_DIR}:/data environment: - HOME: "/tmp" \ No newline at end of file + HOME: "/tmp" + HS_DIR: "${TOR_HS_APP_DIR}" + HS_PORTS: "${TOR_HS_PORTS}" + entrypoint: "/umbrel/entrypoint.sh" \ No newline at end of file diff --git a/scripts/support/tor-entrypoint.sh b/scripts/support/tor-entrypoint.sh new file mode 100755 index 000000000..7cdb7a2b2 --- /dev/null +++ b/scripts/support/tor-entrypoint.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +TORRC_PATH="/tmp/torrc" + +echo "HiddenServiceDir ${HS_DIR}" > "${TORRC_PATH}" + +# Loop through all ports we want to expose +# On this hidden service +for service in $HS_PORTS +do + virtual_port=$(echo $service | cut -d : -f 1) + source_host=$(echo $service | cut -d : -f 2) + source_port=$(echo $service | cut -d : -f 3) + echo "HiddenServicePort ${virtual_port} ${source_host}:${source_port}" >> "${TORRC_PATH}" +done + +tor -f "${TORRC_PATH}" \ No newline at end of file diff --git a/scripts/support/torrc.template b/scripts/support/torrc.template deleted file mode 100644 index fc037dccf..000000000 --- a/scripts/support/torrc.template +++ /dev/null @@ -1,3 +0,0 @@ -# $APP_ID Hidden Service -HiddenServiceDir /data/app-$APP_ID -HiddenServicePort 80 $APP_PROXY_HOSTNAME:$APP_PROXY_PORT \ No newline at end of file diff --git a/scripts/update/.updateinclude b/scripts/update/.updateinclude index 1c447c154..0fb15e193 100644 --- a/scripts/update/.updateinclude +++ b/scripts/update/.updateinclude @@ -1,2 +1,3 @@ /.env -/tor/torrc-proxy \ No newline at end of file +/tor/torrc-proxy +/tor/torrc-server diff --git a/scripts/update/01-run.sh b/scripts/update/01-run.sh index 67c5c0f35..0375020e8 100755 --- a/scripts/update/01-run.sh +++ b/scripts/update/01-run.sh @@ -140,6 +140,9 @@ rsync --archive \ "$UMBREL_ROOT"/.umbrel-"$RELEASE"/ \ "$UMBREL_ROOT"/ +# Add user setting to enable/disable remote tor access +"${UMBREL_ROOT}/scripts/update/steps/add-remote-tor-access.sh" "$RELEASE" "$UMBREL_ROOT" + # Fix permissions echo "Fixing permissions" find "$UMBREL_ROOT" -path "$UMBREL_ROOT/app-data" -prune -o -exec chown 1000:1000 {} + diff --git a/scripts/update/steps/add-remote-tor-access.sh b/scripts/update/steps/add-remote-tor-access.sh new file mode 100755 index 000000000..cf04e0229 --- /dev/null +++ b/scripts/update/steps/add-remote-tor-access.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +RELEASE=$1 +UMBREL_ROOT=$2 + +USER_FILE="${UMBREL_ROOT}/db/user.json" + +HAS_REMOTE_TOR_ACCESS=$(cat "${USER_FILE}" | jq 'has("remoteTorAccess")') + +if [[ "${HAS_REMOTE_TOR_ACCESS}" == "true" ]]; then + echo "'remoteTorAccess' user setting already setup..." + + exit +fi + +echo "Adding 'remoteTorAccess' user setting..." + +# Default to true for existing Umbrel users +# The default for new installs will be false +jq ".remoteTorAccess = true" "${USER_FILE}" > /tmp/user.json +mv /tmp/user.json "${USER_FILE}" \ No newline at end of file diff --git a/templates/torrc-proxy-sample b/templates/torrc-proxy-sample index 9d188c505..16ee69a1f 100644 --- a/templates/torrc-proxy-sample +++ b/templates/torrc-proxy-sample @@ -6,14 +6,4 @@ SocksPort : ControlPort :29051 -HashedControlPassword - -# Umbrel - -# Dashboard Hidden Service -HiddenServiceDir /data/web -HiddenServicePort 80 :80 - -# Auth Hidden Service -HiddenServiceDir /data/auth -HiddenServicePort 80 : \ No newline at end of file +HashedControlPassword \ No newline at end of file diff --git a/templates/torrc-server-sample b/templates/torrc-server-sample new file mode 100644 index 000000000..e98da5dff --- /dev/null +++ b/templates/torrc-server-sample @@ -0,0 +1,13 @@ +# Warning: it's not recommended to modify these files directly. Any +# modifications you make can break the functionality of your umbrel. These files +# are automatically reset with every Umbrel update. + +# Umbrel + +# Dashboard Hidden Service +HiddenServiceDir /data/web +HiddenServicePort 80 :80 + +# Auth Hidden Service +HiddenServiceDir /data/auth +HiddenServicePort 80 : \ No newline at end of file