Remove unused scripts and comment existing scripts

This commit is contained in:
Luke Childs
2024-10-04 21:17:19 +07:00
committed by GitHub
parent e925faa070
commit 36e904cc7f
56 changed files with 11 additions and 4375 deletions
+1
View File
@@ -1,4 +1,5 @@
{
"NOTE": "We must keep this file here forever to allow old umbrelOS 0.5.x Umbrel Homes to find the 1.0.0 update and bootstrap themselves into the new mender based update system.",
"version": "1.0.0",
"name": "umbrelOS 1.0",
"requires": ">=0.5.3",
-594
View File
@@ -1,594 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
VERSION="0.0.3"
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)"
USER_FILE="${UMBREL_ROOT}/db/user.json"
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
Usage: app <command> <app> [<arguments>]
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
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
EOF
}
check_dependencies () {
for cmd in "$@"; do
if ! command -v $cmd >/dev/null 2>&1; then
>&2 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 () {
# Make sure we use the seed from the real Umbrel installation if this is
# an OTA update.
SEED_FILE="${UMBREL_ROOT}/db/umbrel-seed/seed"
if [[ ! -f "${SEED_FILE}" ]] && [[ -f "${UMBREL_ROOT}/../.umbrel" ]]; then
SEED_FILE="${UMBREL_ROOT}/../db/umbrel-seed/seed"
fi
identifier="${1}"
umbrel_seed=$(cat "${SEED_FILE}") || 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/^.* //'
}
# Setup env. for this context for a given app
source_app() {
local -r app="${1}"
local -r app_domain="$(hostname -s 2>/dev/null || echo "umbrel").local"
local -r app_entropy_identifier="app-${app}-seed"
# Load in existing Umbrel .env
# So that apps in their exports.sh can access
# e.g. $TOR_PROXY_IP, $TOR_PROXY_PORT
[[ -f "${UMBREL_ROOT}/.env" ]] && . "${UMBREL_ROOT}/.env"
export NETWORK_IP="${NETWORK_IP}"
# Set other useful vars. used in exports
export DEVICE_HOSTNAME="$(cat /proc/sys/kernel/hostname 2>/dev/null || echo "umbrel")"
export DEVICE_DOMAIN_NAME="${DEVICE_HOSTNAME}.local"
# Set env using all installed apps exports.sh
# Do this first so that no app exports can
# Override any app specific exports defined below
EXPORTS_TOR_DATA_DIR="${UMBREL_ROOT}/tor/data"
APPS_TO_SOURCE="$(list_installed_apps)"
# $app might not be in the 'installed apps list' yet
# i.e. If it is currently being installed
# So we'll add it to the list of apps that will be 'sourced'
if ! echo "${APPS_TO_SOURCE}" | grep --quiet "^${app}$"; then
APPS_TO_SOURCE="${APPS_TO_SOURCE}"$'\n'"${app}"
fi
for EXPORTS_APP_ID in $APPS_TO_SOURCE; do
EXPORTS_APP_DIR="${UMBREL_ROOT}/app-data/${EXPORTS_APP_ID}"
EXPORTS_APP_FILE="${EXPORTS_APP_DIR}/exports.sh"
EXPORTS_APP_DATA_DIR="${EXPORTS_APP_DIR}/data"
[[ -f "${EXPORTS_APP_FILE}" ]] && . "${EXPORTS_APP_FILE}" || true
done
# App specific exports
export APP_ID="${app}"
export APP_MANIFEST_FILE="${app_data_dir}/umbrel-app.yml"
export APP_VERSION=$(cat "${APP_MANIFEST_FILE}" | yq '.version')
# This provides the app proxy with context of the app
export APP_PROXY_HOSTNAME="app_proxy_${app}"
export APP_PROXY_PORT=$(cat "${APP_MANIFEST_FILE}" | yq '.port')
export APP_DATA_DIR="${app_data_dir}"
export APP_DOMAIN="${app_domain}"
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
}
# Check dependencies
check_dependencies docker-compose jq yq openssl envsubst
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"
repo=$(cat "${USER_FILE}" 2> /dev/null | jq -r ".appOrigin.\"${app}\"" || true)
repo_path=$("${UMBREL_ROOT}/scripts/repo" "path" "${repo}")
app_repo_dir="${repo_path}/${app}"
app_data_dir="${UMBREL_ROOT}/app-data/${app}"
app_hidden_service_file="${UMBREL_ROOT}/tor/data/app-${app}/hostname"
if [[ "${app}" == "installed" ]]; then
for app in $(list_installed_apps); do
if [[ "${app}" != "" ]]; then
"${0}" "${1}" "${app}" "${@:3}" &
fi
done
wait
exit
fi
if [[ -z "${app}" ]]; then
>&2 echo "Error: \"${app}\" is not a valid app"
exit 1
fi
fi
if [ -z ${3+x} ]; then
args=""
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
# Source env.
source_app "${app}"
# Define support compose files
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 app_compose_file="${app_data_dir}/docker-compose.yml"
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
compose_files=()
# Detect if the 'app_proxy' service has been defined
# In the app's docker-compose file
has_app_proxy_service=$(cat "${app_compose_file}" | yq ".services | has(\"${APP_PROXY_SERVICE_NAME}\")")
if [[ "${has_app_proxy_service}" == "true" ]]; then
compose_files+=( "--file" "${app_proxy_compose_file}" )
fi
# If remote Tor access is enabled
# Then include a compose file for Tor
if [[ "${REMOTE_TOR_ACCESS}" == "true" ]]; then
compose_files+=( "--file" "${tor_compose_file}" )
fi
# Add app's compose file last so that it can override
# Any of the other compose files
compose_files+=( "--file" "${common_compose_file}" )
compose_files+=( "--file" "${app_compose_file}" )
# Merge compose files and args. passed into 'compose'
compose_args=("${compose_files[@]}" "${@}")
docker-compose \
--env-file "${umbrel_env_file}" \
--project-name "${app}" \
"${compose_args[@]}"
}
update_installed_apps() {
local -r action="${1}"
local -r app="${2}"
local -r repo="${3:-null}"
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
[[ "${action}" == "add" ]] && operator="+" || operator="-"
updated_json=$(cat "${USER_FILE}" | jq ".installedApps |= (. ${operator} [\"${app}\"] | unique)")
echo "${updated_json}" > "${USER_FILE}"
if [[ "${action}" == "add" ]]; then
updated_json=$(cat "${USER_FILE}" | jq ".appOrigin |= (. ${operator} {\"${app}\":\"${repo}\"})")
else
updated_json=$(cat "${USER_FILE}" | jq "del(.appOrigin.\"${app}\")")
fi
echo "${updated_json}" > "${USER_FILE}"
rm -f "${USER_FILE}.lock"
}
template_app() {
local -r app="${1}"
# Loop over all templates within app and populate them
APP_TEMPLATE_FILES="${app_data_dir}/*.template"
shopt -s nullglob
for APP_TEMPLATE_INPUT_FILE in $APP_TEMPLATE_FILES; do
# Output filename is the same as input with .template stripped off
APP_TEMPLATE_OUTPUT_FILE="${APP_TEMPLATE_INPUT_FILE%.*}"
# First we'll copy the file so we ensure the output
# has the same fs permissions as the input
cp --archive "${APP_TEMPLATE_INPUT_FILE}" "${APP_TEMPLATE_OUTPUT_FILE}"
cat "${APP_TEMPLATE_INPUT_FILE}" | envsubst > "${APP_TEMPLATE_OUTPUT_FILE}"
done
}
copy_app_files() {
local -r files_to_copy="${1}"
for filename in $files_to_copy; do
APP_FILES="${app_repo_dir}/${filename}"
for app_file in $APP_FILES; do
if [[ -f "${app_file}" ]] || [[ -d "${app_file}" ]]; then
cp --archive "${app_file}" "${app_data_dir}"
fi
done
done
}
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 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}\" 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 [[ "${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
# More details here: https://github.com/torproject/tor/blob/01bda6c23f58947ad1e20ea6367a5c260f53dfab/src/feature/hs/hs_common.c#L743
# And here: https://github.com/torproject/tor/blob/22552ad88e1e95ef9d2c6655c7602b7b25836075/src/lib/net/resolve.c#L297
# Otherwise Tor will throw this error:
# Unparseable address in hidden service port configuration.
compose "${app}" up --detach app_proxy
compose "${app}" up --detach tor_server
for attempt in $(seq 1 100); do
if [[ -f "${app_hidden_service_file}" ]]; then
echo "Hidden service file created successfully!"
break
fi
sleep 0.1
done
if [[ ! -f "${app_hidden_service_file}" ]]; then
echo "Hidden service file wasn't created"
fi
fi
}
start_app() {
local -r app="${1}"
# Source env.
source_app "${app}"
# Now apply templates
template_app "${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
must_be_installed_guard() {
if ! list_installed_apps | grep --quiet "^${app}$"; then
>&2 echo "Error: app \"${app}\" is not installed yet"
exit 1
fi
}
# Pulls down images for an app and starts it
if [[ "$command" = "install" ]]; then
repo=$("${UMBREL_ROOT}/scripts/repo" "locate" "${app}")
if [[ -z "${repo}" ]]; then
>&2 echo "Error: \"${app}\" not found in any local app repo"
exit 1
fi
app_repo_dir=$("${UMBREL_ROOT}/scripts/repo" "path" "${repo}")
app_repo_dir="${app_repo_dir}/${app}"
echo "Installing '${app}' from: ${repo}"
echo "Setting up data dir for app ${app}..."
mkdir -p "${app_data_dir}"
# Copy all app files
rsync --archive --verbose --exclude ".gitkeep" "${app_repo_dir}/." "${app_data_dir}"
execute_hook "${app}" "pre-install"
# Source env.
source_app "${app}"
# Now apply templates
template_app "${app}"
echo "Pulling images for app ${app}..."
compose "${app}" pull
if [[ "$*" != *"--skip-start"* ]]; then
echo "Starting app ${app}..."
start_app "${app}"
fi
echo "Saving app ${app} in DB..."
update_installed_apps add "${app}" "${repo}"
execute_hook "${app}" "post-install"
echo "Successfully installed app ${app}"
exit
fi
# Removes images and destroys all data for an app
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
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}"
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
# Stops an installed app
if [[ "$command" = "stop" ]]; then
must_be_installed_guard
execute_hook "${app}" "pre-stop"
echo "Stopping app ${app}..."
compose "${app}" rm --force --stop
execute_hook "${app}" "post-stop"
exit
fi
if [[ "$command" = "reinstall" ]]; then
"${0}" "uninstall" "${app}"
echo
"${0}" "install" "${app}"
exit
fi
# Starts an installed app
if [[ "$command" = "start" ]]; then
must_be_installed_guard
echo "Starting app ${app}..."
start_app "${app}"
exit
fi
# Restarts an installed app
if [[ "$command" = "restart" ]]; then
"${0}" "stop" "${app}"
"${0}" "start" "${app}"
exit
fi
# Update an installed app
if [[ "$command" = "update" ]]; then
must_be_installed_guard
# Check that the app folder still exists
# Within the associated local app repo
if [[ ! -d "${app_repo_dir}" ]]; then
>&2 echo "Error: Local app repo no longer exists for ${app}"
exit 1
fi
echo "Updating '${app}' from: ${repo}"
# Save current images to clean up later
app_compose_file="${app_data_dir}/docker-compose.yml"
app_old_images=$(yq e '.services | map(select(.image != null)) | .[].image' "${app_compose_file}")
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 hooks"
# We copy umbrel-app.yml after the app has started
# That way the frontend knows the update has finished
# And the app is running again
UPDATE_FILES_WHITELIST_POST="umbrel-app.yml"
copy_app_files "${UPDATE_FILES_WHITELIST_PRE}"
# Ensure remaining files are copied in case of unexpected exit
trap "copy_app_files "${UPDATE_FILES_WHITELIST_POST}"; exit $?" INT TERM EXIT
# Source env. after new exports.sh is copied (done above via 'copy_app_files')
source_app "${app}"
# Now apply templates
template_app "${app}"
echo "Pulling images for app ${app}..."
compose "${app}" pull
# Copy remaining files to mark update as complete
copy_app_files "${UPDATE_FILES_WHITELIST_POST}"
if [[ "$*" != *"--skip-start"* ]]; then
"${0}" "start" "${app}"
# Remove any old images we don't need anymore
docker rmi $app_old_images || true
fi
execute_hook "${app}" "post-update"
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
-14
View File
@@ -1,14 +0,0 @@
# Automatic Encrypted Backups
The backups are encrypted client side before being uploaded over Tor and are padded with random data. Backups are made immediately as soon as the channel state changes. However, Umbrel also makes decoy backups at random intervals to prevent timing-analysis attacks.
These features combined ensure that the backup server doesn't learn any sensitive information about the user's Umbrel.
- The IP address of user is hidden due to Tor.
- User's channel data are encrypted client side with a key only known to the Umbrel device.
- Random interval decoy backups ensure the server can't correlate backup activity with channel state changes on the Lightning network and correlate a backup ID with a channel pubkey.
- Random padding obscures if the backup size has increased/decreased or remains unchanged due to it being a decoy.
Due to the key/id being deterministically derived from the Umbrel seed, all that's needed to fully recover an Umbrel is the mnemonic seed phrase. Upon recovery the device can automatically regenerate the same backup id/encryption key, request the latest backup from the backup server, decrypt it, and restore the user's settings and Lightning network channel data.
There is currently no way to disable backups or recover from them in the dashboard yet. Both of these features will be introduced in the coming updates.
-138
View File
@@ -1,138 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
BACKUP_ROOT="${UMBREL_ROOT}/.backup/$RANDOM"
BACKUP_FOLDER_NAME="backup"
BACKUP_FOLDER_PATH="${BACKUP_ROOT}/${BACKUP_FOLDER_NAME}"
BACKUP_FILE="${BACKUP_ROOT}/backup.tar.gz.pgp"
BACKUP_STATUS_FILE="${UMBREL_ROOT}/statuses/backup-status.json"
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
}
check_dependencies openssl tar gpg shuf curl
# Deterministically derives 128 bits of cryptographically secure entropy
derive_entropy () {
identifier="${1}"
# Attempt to get new app seed
{
seed_length=$(jq -r '.seed | length' "${UMBREL_ROOT}/app-data/lightning/data/lightning/state.json")
if [[ "${seed_length}" = "24" ]]; then
umbrel_seed=$(jq -r '.seed | join(" ")' "${UMBREL_ROOT}/app-data/lightning/data/lightning/state.json")
else
umbrel_seed=""
fi
} 2>/dev/null || true
# Else fall back to umbrel seed
if [[ -z "$umbrel_seed" ]]; then
umbrel_seed=$(cat "${UMBREL_ROOT}/db/umbrel-seed/seed" 2>/dev/null) || true
fi
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/^.* //'
}
[[ -f "${UMBREL_ROOT}/.env" ]] && source "${UMBREL_ROOT}/.env"
BITCOIN_NETWORK=${BITCOIN_NETWORK:-mainnet}
echo "Deriving keys..."
backup_id=$(derive_entropy "umbrel_backup_id")
encryption_key=$(derive_entropy "umbrel_backup_encryption_key")
echo "Creating backup..."
mkdir -p "${BACKUP_FOLDER_PATH}"
CHANNEL_BACKUP_FILE="${UMBREL_ROOT}/app-data/lightning/data/lnd/data/chain/bitcoin/${BITCOIN_NETWORK}/channel.backup"
if [[ -f "${CHANNEL_BACKUP_FILE}" ]]; then
cp --archive "${CHANNEL_BACKUP_FILE}" "${BACKUP_FOLDER_PATH}/channel.backup"
fi
# We want to back up user settings too, however we currently store the encrypted
# mnemonic in this file which is not safe to backup remotely.
# Uncomment this in the future once we've ensured there's no critical data in
# this file.
# cp --archive "${UMBREL_ROOT}/db/user.json" "${BACKUP_FOLDER_PATH}/user.json"
echo "Adding random padding..."
# Up to 10KB of random binary data
# This prevents the server from being able to tell if the backup has increased
# decreased or stayed the sme size. Combined with random interval decoy backups
# this makes a (already very difficult) timing analysis attack to correlate backup
# activity with channel state changes practically impossible.
padding="$(shuf -i 0-10240 -n 1)"
dd if=/dev/urandom bs="${padding}" count=1 > "${BACKUP_FOLDER_PATH}/.padding"
echo "Creating encrypted tarball..."
tar \
--create \
--gzip \
--verbose \
--directory "${BACKUP_FOLDER_PATH}/.." \
"${BACKUP_FOLDER_NAME}" \
| gpg \
--batch \
--symmetric \
--cipher-algo AES256 \
--passphrase "${encryption_key}" \
--output "${BACKUP_FILE}"
# To decrypt:
# cat "${BACKUP_FILE}" | gpg \
# --batch \
# --decrypt \
# --passphrase "${encryption_key}" \
# | tar \
# --extract \
# --verbose \
# --gzip
BACKUP_API_URL="https://pvf3ozmmfl.execute-api.us-east-1.amazonaws.com/prod/v1/upload"
if [[ $BITCOIN_NETWORK == "testnet" ]]; then
BACKUP_API_URL="https://as0ot0lg7h.execute-api.us-east-1.amazonaws.com/dev/v1/upload"
fi
if [[ $BITCOIN_NETWORK == "regtest" ]]; then
BACKUP_API_URL="https://5fxwqbum7g.execute-api.us-east-1.amazonaws.com/dev/v1/upload"
fi
echo "Uploading backup..."
if curl --socks5 "localhost:${TOR_PROXY_PORT}" -F "file=@/${BACKUP_FILE}" "${BACKUP_API_URL}/${backup_id}"; then
status="success"
else
status="failed"
fi
echo
rm -rf "${BACKUP_ROOT}"
# Update status file
cat <<EOF > ${BACKUP_STATUS_FILE}
{"status": "${status}", "timestamp": $(date +%s000)}
EOF
echo "============================="
echo "====== Backup ${status} ======="
echo "============================="
exit 0
-42
View File
@@ -1,42 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
MAX_BACKUP_INTERVAL_IN_HOURS="12"
check_if_not_already_running() {
if ps ax | grep $0 | grep -v $$ | grep bash | grep -v grep
then
echo "decoy trigger is already running"
exit 1
fi
}
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
}
check_if_not_already_running
check_dependencies shuf
main () {
while true; do
minutes_in_seconds="60"
hours_in_seconds="$((60 * ${minutes_in_seconds}))"
max_interval="$((${MAX_BACKUP_INTERVAL_IN_HOURS} * ${hours_in_seconds}))"
delay="$(shuf -i 0-${max_interval} -n 1)"
echo "Sleeping for ${delay} seconds..."
sleep $delay
echo "Triggering decoy backup..."
touch "${UMBREL_ROOT}/events/signals/backup"
done
}
main
-69
View File
@@ -1,69 +0,0 @@
#!/usr/bin/env bash
check_root () {
if [[ $UID != 0 ]]; then
echo "Error: This script must be run as root."
exit 1
fi
}
check_if_not_already_running() {
if ps ax | grep $0 | grep -v $$ | grep bash | grep -v grep
then
echo "backup monitor is already running"
exit 1
fi
}
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
}
check_root
check_if_not_already_running
check_dependencies fswatch readlink dirname
UMBREL_ROOT="$(dirname $(readlink -f "${BASH_SOURCE[0]}"))/../.."
monitor_file () {
local file_path="${1}"
echo "Monitoring $file_path"
echo
if [[ ! -e "${file_path}" ]]; then
echo "$file_path doesn't exist, waiting for it to be created..."
echo
until [[ -e "${file_path}" ]]; do
sleep 1
done
echo "$file_path created! Triggering backup..."
touch "${UMBREL_ROOT}/events/signals/backup"
fi
fswatch -0 --event Updated $file_path | xargs -0 -n 1 -I {} touch "${UMBREL_ROOT}/events/signals/backup"
}
if [[ ! -d "${UMBREL_ROOT}" ]]; then
echo "Root dir does not exist '$UMBREL_ROOT'"
exit 1
fi
[[ -f "${UMBREL_ROOT}/.env" ]] && source "${UMBREL_ROOT}/.env"
BITCOIN_NETWORK=${BITCOIN_NETWORK:-mainnet}
# Monitor LND channel.backup
monitor_file "${UMBREL_ROOT}/app-data/lightning/data/lnd/data/chain/bitcoin/${BITCOIN_NETWORK}/channel.backup" &
# Monitor db/user.json
# We want to back up user settings too, however we currently store the encrypted
# mnemonic in this file which is not safe to backup remotely.
# Uncomment this in the future once we've ensured there's no critical data in
# this file.
# monitor_file "${UMBREL_ROOT}/db/user.json" &
-227
View File
@@ -1,227 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
##########################################################
################## Check dependencies ####################
##########################################################
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
}
if [ ! "$(uname -s)" == "Linux" ]; then
echo "Sorry, Umbrel only supports Linux-based systems at this point."
echo
echo "You may work around this by modifying the configuration script yourself, but it's highly experimental."
echo "If you get it working, we hope you consider making a PR. :)"
exit 1
fi
check_dependencies docker docker-compose dirname readlink
# Switch to Umbrel's root directory
UMBREL_ROOT="$(readlink -f "$(dirname "${BASH_SOURCE[0]}")"/..)"
if [[ ! -d "$UMBREL_ROOT" ]]; then
echo "Root dir does not exist '$UMBREL_ROOT'"
exit 1
fi
cd "$UMBREL_ROOT"
# Make sure we use the status dir from the real Umbrel installation if this is
# an OTA update.
STATUS_DIR="${UMBREL_ROOT}/statuses"
if [[ -f "${UMBREL_ROOT}/../.umbrel" ]]; then
STATUS_DIR="${UMBREL_ROOT}/../statuses"
fi
# Configure for mainnet or testnet or regtest depending
# upon the user-supplied value of $NETWORK
BITCOIN_NETWORK="${NETWORK:-mainnet}"
if [ "$BITCOIN_NETWORK" != "mainnet" ] && [ "$BITCOIN_NETWORK" != "testnet" ] && [ "$BITCOIN_NETWORK" != "regtest" ]; then
echo "Error: Umbrel can only be configured for mainnet (default), testnet or regtest"
exit 1
fi
echo
echo "======================================"
if [[ -f "${STATUS_DIR}/configured" ]]; then
echo "=========== RECONFIGURING ============"
else
echo "============ CONFIGURING ============="
fi
echo "=============== UMBREL ==============="
echo "======================================"
echo
##########################################################
############### Setup configuration files ###############
##########################################################
# 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"
##########################################################
############ Generate configuration variables ############
##########################################################
# Load existing credentials if we have some
[[ -f "./.env" ]] && source "./.env"
[[ ! -z ${PREV_ENV_FILE+x} ]] && [[ -f "${PREV_ENV_FILE}" ]] && source "${PREV_ENV_FILE}"
# Define Umbrel Networking
NETWORK_IP="10.21.0.0"
GATEWAY_IP="10.21.0.1"
NGINX_IP="10.21.21.2"
NGINX_PORT="${NGINX_PORT:-80}"
DASHBOARD_IP="10.21.21.3"
MANAGER_IP="10.21.21.4"
AUTH_IP="10.21.21.6"
AUTH_PORT="2000"
TOR_PROXY_IP="10.21.21.11"
TOR_PROXY_PORT="9050"
# App specific state
BITCOIN_IP="10.21.21.8"
BITCOIN_RPC_PORT="8332"
BITCOIN_P2P_PORT="8333"
BITCOIN_ZMQ_RAWBLOCK_PORT="28332"
BITCOIN_ZMQ_RAWTX_PORT="28333"
BITCOIN_ZMQ_HASHBLOCK_PORT="28334"
BITCOIN_ZMQ_SEQUENCE_PORT="28335"
LND_IP="10.21.21.9"
LND_GRPC_PORT="10009"
LND_REST_PORT="8080"
ELECTRUM_IP="10.21.21.10"
ELECTRUM_PORT="50001"
if [[ -z ${UMBREL_AUTH_SECRET+x} ]]; then
UMBREL_AUTH_SECRET="$(openssl rand -hex 32)"
fi
# Generate RPC credentials
if [[ -z ${BITCOIN_RPC_USER+x} ]] || [[ -z ${BITCOIN_RPC_PASS+x} ]] || [[ -z ${BITCOIN_RPC_AUTH+x} ]]; then
echo "Generating auth credentials"
echo
BITCOIN_RPC_USER="umbrel"
BITCOIN_RPC_DETAILS=$("./scripts/rpcauth.py" "$BITCOIN_RPC_USER")
BITCOIN_RPC_PASS=$(echo "$BITCOIN_RPC_DETAILS" | tail -1)
BITCOIN_RPC_AUTH=$(echo "$BITCOIN_RPC_DETAILS" | head -2 | tail -1 | sed -e "s/^rpcauth=//")
fi
# Pull Tor image and generate Tor password
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 getumbrel/tor:0.4.7.8@sha256:2ace83f22501f58857fa9b403009f595137fa2e7986c4fda79d82a8119072b6a --quiet --hash-password "$TOR_PASSWORD")
fi
##########################################################
### Update config files with configuration variables #####
##########################################################
# Update RPC, P2P and ZMQ Ports
sed -i "s/BITCOIN_RPC_PORT=<port>/BITCOIN_RPC_PORT=$BITCOIN_RPC_PORT/g;" "$ENV_FILE"
sed -i "s/BITCOIN_P2P_PORT=<port>/BITCOIN_P2P_PORT=$BITCOIN_P2P_PORT/g;" "$ENV_FILE"
# Add rpcauth
sed -i "s/BITCOIN_RPC_AUTH=<rpcauth>/BITCOIN_RPC_AUTH='$BITCOIN_RPC_AUTH'/g;" "$ENV_FILE"
# Add RPC credentials to env file
sed -i "s/BITCOIN_RPC_USER=<username>/BITCOIN_RPC_USER=$BITCOIN_RPC_USER/g;" "$ENV_FILE"
sed -i "s/BITCOIN_RPC_PASS=<password>/BITCOIN_RPC_PASS=$BITCOIN_RPC_PASS/g;" "$ENV_FILE"
# Add chain to env file
sed -i "s/BITCOIN_NETWORK=<network>/BITCOIN_NETWORK=$BITCOIN_NETWORK/g;" "$ENV_FILE"
# Add Tor password
sed -i "s/HashedControlPassword <password>/HashedControlPassword $TOR_HASHED_PASSWORD/g;" "$TOR_PROXY_CONF_FILE"
sed -i "s/TOR_PASSWORD=<password>/TOR_PASSWORD=$TOR_PASSWORD/g;" "$ENV_FILE"
sed -i "s/TOR_HASHED_PASSWORD=<password>/TOR_HASHED_PASSWORD=$TOR_HASHED_PASSWORD/g;" "$ENV_FILE"
# Store docker binary path
DOCKER_BINARY=$(readlink -f "$(which docker)")
sed -i "s#DOCKER_BINARY=<path>#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}" "${TOR_SERVER_CONF_FILE}" "${ENV_FILE}"; do
# Umbrel
sed -i "s/<network-ip>/${NETWORK_IP}/g" "${template}"
sed -i "s/<gateway-ip>/${GATEWAY_IP}/g" "${template}"
sed -i "s/<nginx-ip>/${NGINX_IP}/g" "${template}"
sed -i "s/<nginx-port>/${NGINX_PORT}/g" "${template}"
sed -i "s/<dashboard-ip>/${DASHBOARD_IP}/g" "${template}"
sed -i "s/<manager-ip>/${MANAGER_IP}/g" "${template}"
sed -i "s/<auth-ip>/${AUTH_IP}/g" "${template}"
sed -i "s/<auth-port>/${AUTH_PORT}/g" "${template}"
sed -i "s/<umbrel-auth-secret>/${UMBREL_AUTH_SECRET}/g" "${template}"
# App specific state
sed -i "s/<bitcoin-ip>/${BITCOIN_IP}/g" "${template}"
sed -i "s/<bitcoin-p2p-port>/${BITCOIN_P2P_PORT}/g" "${template}"
sed -i "s/<bitcoin-rpc-port>/$BITCOIN_RPC_PORT/g" "${template}"
sed -i "s/<lnd-ip>/${LND_IP}/g" "${template}"
sed -i "s/<lnd-grpc-port>/${LND_GRPC_PORT}/g" "${template}"
sed -i "s/<lnd-rest-port>/${LND_REST_PORT}/g" "${template}"
sed -i "s/<electrum-ip>/${ELECTRUM_IP}/g" "${template}"
sed -i "s/<electrum-port>/${ELECTRUM_PORT}/g" "${template}"
sed -i "s/<tor-proxy-ip>/${TOR_PROXY_IP}/g" "${template}"
sed -i "s/<tor-proxy-port>/${TOR_PROXY_PORT}/g" "${template}"
sed -i "s/<zmq-rawblock-port>/${BITCOIN_ZMQ_RAWBLOCK_PORT}/g;" "${template}"
sed -i "s/<zmq-rawtx-port>/${BITCOIN_ZMQ_RAWTX_PORT}/g;" "${template}"
sed -i "s/<zmq-hashblock-port>/${BITCOIN_ZMQ_HASHBLOCK_PORT}/g;" "${template}"
sed -i "s/<zmq-sequence-port>/${BITCOIN_ZMQ_SEQUENCE_PORT}/g;" "${template}"
done
##########################################################
############## Override main config files ################
##########################################################
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"
##########################################################
################ Configuration complete ##################
##########################################################
echo "Configuring permissions..."
echo
UMBREL_DEV_FILE="${UMBREL_ROOT}/.umbrel-dev"
if [[ ! -f "${UMBREL_DEV_FILE}" ]]; then
find "$UMBREL_ROOT" -path "$UMBREL_ROOT/app-data" -prune -o -exec chown 1000:1000 {} + || true
fi
# Create configured status
touch "${STATUS_DIR}/configured"
echo "Configuration successful"
echo "You can now start Umbrel by running:"
echo " sudo ./scripts/start"
echo
+3
View File
@@ -1,5 +1,8 @@
#!/usr/bin/env bash
# This script is exposed via: curl -sL https://export.umbrel.sh | sudo bash
# Which resolves to this file at the 1.1.1 tag in this repo
set -euo pipefail
# This script will:
-190
View File
@@ -1,190 +0,0 @@
#!/bin/bash
# This script is based on raspinfo: https://github.com/raspberrypi/utils/blob/master/raspinfo/raspinfo
# raspinfo is Copyright: 2018-2019, Raspberry Pi (Trading) Ltd.
# This fork is Copyright 2020-2021, Umbrel
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the University nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HOLDERS OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Try to load Umbrel OS version info to detect Umbrel OS
[[ -f "/etc/default/umbrel" ]] && source "/etc/default/umbrel"
echo "====================="
echo "= Umbrel debug info ="
echo "====================="
UMBREL_ROOT="$(dirname $(readlink -f "${BASH_SOURCE[0]}"))/.."
cd "${UMBREL_ROOT}"
echo
echo "Umbrel version"
echo "--------------"
cat info.json | jq .version | sed 's/"//g'
if [[ ! -z "${UMBREL_OS:-}" ]]; then
echo
echo "Flashed OS version"
echo "-----------------"
echo $UMBREL_OS
echo
echo "Raspberry Pi Model"
echo "------------------"
cat /proc/cpuinfo | tail -3
echo
echo "Firmware"
echo "--------"
vcgencmd version
echo
echo "Temperature"
echo "-----------"
vcgencmd measure_temp
echo
echo "Throttling"
echo "----------"
vcgencmd get_throttled
fi
echo
echo "Memory usage"
echo "------------"
free --human --mega
echo
"${UMBREL_ROOT}/scripts/memory-usage"
echo
echo "Memory monitor logs"
echo "-------------------"
tail -n 10 "${UMBREL_ROOT}/logs/memory-monitor.log"
echo
echo "Filesystem information"
echo "----------------------"
df --human-readable / "${UMBREL_ROOT}"
if [[ ! -z "${UMBREL_OS:-}" ]]; then
echo
echo "Startup service logs"
echo "--------------------"
journalctl --unit=umbrel-startup.service | tail -n 30 | sed '/onion/d'
echo
echo "External storage service logs"
echo "-----------------------------"
journalctl --unit=umbrel-external-storage.service | tail -n 30
echo
echo "External storage SD card update service logs"
echo "--------------------------------------------"
journalctl --unit=umbrel-external-storage-sdcard-update.service | tail -n 30
fi
echo
echo "Karen logs"
echo "----------"
echo
tail -n 50 logs/karen.log | sed '/onion/d'
echo
echo "Docker containers"
echo "-----------------"
docker ps --format="table {{.Names}}\t{{.Status}}"
echo
echo "Umbrel logs"
echo "-----------"
echo
docker-compose logs --tail=30 manager
echo
echo "Tor Proxy logs"
echo "--------"
echo
docker-compose logs --tail=10 tor_proxy
installed_apps=$(./scripts/app ls-installed)
if [[ ! -z "${installed_apps:-}" ]]; then
echo
echo "App logs"
echo "--------"
for app in $installed_apps; do
echo
echo "${app}"
echo
# 10 lines from each container
./scripts/app compose $app logs --tail 10 | grep -v 'tor_server_1 '
done
fi
if [[ ! -z "${UMBREL_OS:-}" ]]; then
list_block_devices () {
# Documented in the mount script for Umbrel OS
sync
(ls -d /sys/block/sd* 2>/dev/null || true) | sed 's!.*/!!'
}
no_of_block_devices=$(list_block_devices | wc -l)
fi
echo "================"
echo "==== Result ===="
echo "================"
if [[ ! -z "${UMBREL_OS:-}" ]]; then
if [[ $(vcgencmd get_throttled) != "throttled=0x0" ]]; then
if [[ $(vcgencmd get_throttled) = "throttled=0x5"* ]]; then
echo "There is probably issue with your power supply. We recommend using the official power supply to avoid this issue"
else
echo "The temperature of your Raspberry Pi is too high. We recommend either using a case that lowers the temperature of your Pi or a case with a builtin fan."
fi
exit 0
fi
if [[ $no_of_block_devices -lt 1 ]]; then
echo "You have either no external drive connected to your Raspberry Pi or the drive is not being detected."
echo "Please shutdown your Raspberry Pi ('sudo shutdown'), then plug the SSD into the other USB3 port."
echo "After you've finished that, boot your Raspberry Pi back up. If it still still doesn't work,"
echo "you can contact us on Telegram (t.me/getumbrel) and share the output of this script."
exit 0
fi
if [[ $no_of_block_devices -gt 1 ]]; then
echo "================"
echo "==== Result ===="
echo "================"
echo "You have multiple external drives connected to your Raspberry Pi. Please remove the drive(s) you don't want to use for Umbrel."
exit 0
fi
fi
echo "==== END ====="
+3
View File
@@ -1,6 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
# This script is exposed via: curl -L https://umbrel.sh | bash
# Which resolves to this file in the master branch of this repo
cat << "EOF"
,;###GGGGGGGGGGl#Sp
-31
View File
@@ -1,31 +0,0 @@
#!/usr/bin/env bash
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)"
PERCENT_MEMORY_THRESHOLD=90
check_if_not_already_running() {
if ps ax | grep $0 | grep -v $$ | grep bash | grep -v grep
then
echo "Memory monitor is already running"
exit 1
fi
}
log () {
echo "$(date "+%Y-%m-%d %H:%M:%S") ${@}"
}
check_if_not_already_running
log "Memory monitor running!"
while true; do
percent_memory_used=$(free | awk 'NR==2{print int($3*100/$2) }')
if (( $percent_memory_used > $PERCENT_MEMORY_THRESHOLD )); then
log "Warning memory usage at ${percent_memory_used}%"
if [[ "$(jq -r '.installedApps | length > 0' ${UMBREL_ROOT}/db/user.json)" == "true" ]]; then
touch "${UMBREL_ROOT}/statuses/memory-warning"
fi
fi
sleep 60
done
-54
View File
@@ -1,54 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)"
get_container_memory_use () {
local container="${1}"
local container_memory=0
local container_pids=$(docker top "${container}" | tail -n +2 | awk '{print $2}')
for pid in $container_pids; do
local pid_memory=$(ps u "${pid}" | awk '{print $4}' | grep -v 'MEM')
if [[ ! -z "${pid_memory}" ]]; then
container_memory=$(awk "BEGIN {print ${container_memory}+${pid_memory}}")
fi
done
echo "${container_memory}"
}
get_app_memory_use () {
local app="${1}"
local app_memory=0
local app_containers=$("${UMBREL_ROOT}/scripts/app" compose "${app}" ps | awk '{print $1}' | grep -v 'Name\|-----')
for container in $app_containers; do
local container_memory=$(get_container_memory_use "${container}")
app_memory=$(awk "BEGIN {print ${app_memory}+${container_memory}}")
done
echo "${app_memory}"
}
main() {
local total_system_memory_usage=$(free | awk 'NR==2{print sprintf("%.1f", $3*100/$2) }')
echo "total: ${total_system_memory_usage}%"
local total_container_memory=0
# Installed app memory use
for app in $("${UMBREL_ROOT}/scripts/app" ls-installed); do
local app_memory=$(get_app_memory_use "${app}")
total_container_memory=$(awk "BEGIN {print ${total_container_memory}+${app_memory}}")
echo "${app}: ${app_memory}%"
done
# Misc system memory use
local system_memory=$(awk "function pos(x){return(x<0?0:x)} BEGIN {print pos(${total_system_memory_usage}-${total_container_memory})}")
echo "system: ${system_memory}%"
}
main 2> /dev/null | sort --key 2 --numeric-sort --reverse
-396
View File
@@ -1,396 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
VERSION="0.0.2"
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)"
USER_FILE="${UMBREL_ROOT}/db/user.json"
DEFAULT_UMBREL_APP_REPO_URL="https://github.com/getumbrel/umbrel-apps.git"
show_help() {
cat << EOF
CLI (v${VERSION}) for managing local Umbrel App Repos
Usage: repo <command> <arguments...>
Commands:
add Add app repo
remove Remove app repo
update Update local app repos
branch Set the branch for a given local app repo
checkout Checkout an app repo (branch is optional) e.g. https://github.com/coolapp/umbrel-apps.git#staging
path Get the absolute path for a given local app repo
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
}
# Check dependencies
check_dependencies git jq timeout
if [ -z ${1+x} ]; then
command=""
else
command="$1"
fi
USER_JSON=$(cat "${USER_FILE}" 2> /dev/null || echo "null")
function repo_slug() {
echo "${1}" | sed 's/[^a-zA-Z0-9]/-/g'
}
function repo_path() {
path=$(repo_slug "${1}")
echo "${UMBREL_ROOT}/repos/${path}"
}
function list_repos() {
cat "${USER_FILE}" 2> /dev/null | jq -r 'if has("repos") then .repos else [] end | join("\n")' || {
echo "${DEFAULT_UMBREL_APP_REPO_URL}"
}
}
function parse_repo_url() {
local -r descriptor="${1}"
# Return the string before the # character
repo_url="${descriptor%%#*}"
# Check whether this looks like a url searching for '://' or '@'
if [[ "${repo_url}" == *://* ]] || [[ "${repo_url}" == *@* ]]; then
echo "${repo_url}"
exit
fi
# And if not, default to github.com via HTTPS
echo "https://github.com/${repo_url}"
}
function list_repo_ids() {
local -r ignore_repo_url="${1}"
# Check if a repo already exists with this id
all_repo_urls=$(list_repos)
# Through existing repos
for repo_url in $all_repo_urls; do
# Ignore a given repo url
if [[ "${repo_url}" != "${ignore_repo_url}" ]]; then
local_repo_path=$(repo_path "${repo_url}")
repo_manifest_file="${local_repo_path}/umbrel-app-store.yml"
# Check a manifest file exists
if [[ -f "${repo_manifest_file}" ]]; then
local_repo_id=$(cat "${repo_manifest_file}" | yq '.id')
echo "${local_repo_id}"
fi
fi
done
}
# Add a new app repo
if [[ "$command" = "add" ]]; then
descriptor="${2-}"
if [[ -z "${descriptor}" ]]; then
>&2 echo "A valid remote repo url must be set. e.g. ${DEFAULT_UMBREL_APP_REPO_URL}"
exit 1
fi
repo_url=$(parse_repo_url "${descriptor}")
has_repo=$(cat "${USER_FILE}" | jq ".repos | any(. == \"${repo_url}\")")
if [[ "${has_repo}" == "true" ]]; then
>&2 echo "Repo: ${repo_url} already exists."
exit
fi
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
updated_json=$(cat "${USER_FILE}" | jq ".repos |= (. + [\"${repo_url}\"] | unique)")
echo "${updated_json}" > "${USER_FILE}"
rm -f "${USER_FILE}.lock"
echo "Added: ${repo_url}"
exit
fi
# Remove an app repo
if [[ "$command" = "remove" ]]; then
repo_url="${2-}"
if [[ -z "${repo_url}" ]]; then
>&2 echo "A valid remote repo url must be set. e.g. ${DEFAULT_UMBREL_APP_REPO_URL}"
exit 1
fi
has_repo=$(cat "${USER_FILE}" | jq ".repos | any(. == \"${repo_url}\")")
if [[ "${has_repo}" == "false" ]]; then
>&2 echo "Repo: ${repo_url} does not exist locally."
exit
fi
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
updated_json=$(cat "${USER_FILE}" | jq ".repos |= (. - [\"${repo_url}\"] | unique)")
echo "${updated_json}" > "${USER_FILE}"
rm -f "${USER_FILE}.lock"
local_repo_path=$(repo_path "${repo_url}")
rm -rf "${local_repo_path}"
echo "Removed: ${repo_url}"
exit
fi
# Update local copies of repos from remote
if [[ "$command" = "update" ]]; then
# Check if an argument exists to
# only update a specific repo
repo_url="${2-}"
if [[ ! -z "${repo_url}" ]]; then
all_repo_urls="${repo_url}"
else
all_repo_urls=$(list_repos)
fi
for repo_url in $all_repo_urls; do
echo "Processing: ${repo_url}"
local_repo_path=$(repo_path "${repo_url}")
# Check the local app repo is marked as safe by Git
if ! git config --global --get safe.directory | grep --quiet "^${local_repo_path}$"; then
git config --global --add safe.directory "${local_repo_path}"
fi
# Check integrity of local app repo
if [[ -d "${local_repo_path}" ]]; then
set +e
out=$(git -C "${local_repo_path}" status 2>&1)
# 1 = network error (and other errors)
# 128 = git repo bad/corrupt/incomplete
exit_code=$?
set -e
if [[ "${exit_code}" == "128" ]]; then
echo "${out}"
echo
echo "Bad local app repo detected. Removing..."
rm -rf "${local_repo_path}"
fi
fi
# Ignore exit code just in case 1 repo is bad
# Which would then prevent other repos getting updated
{
if [[ -d "${local_repo_path}" ]]; then
# Git can leave behind write locks if something fails
# These locks should only exist for milliseconds
# We'll delete any lock older than 10 seconds
# Otherwise git will not proceed
epoch_ts=$(date +%s)
for file in "${local_repo_path}/.git"/*.lock; do
[[ ! -f "${file}" ]] && continue
age_of_lock=$(stat -c '%X' "${file}")
age_seconds=$((epoch_ts-age_of_lock))
if [[ "${age_seconds}" -gt "10" ]]; then
echo "Deleting old lock: ${file}"
rm -rf "${file}"
fi
done
repo_branch=$(git -C "${local_repo_path}" rev-parse --abbrev-ref HEAD)
echo "Updating repo: ${repo_url} (${repo_branch})"
timeout --foreground 30 git -C "${local_repo_path}" pull
else
echo "Cloning repo: ${repo_url}"
timeout --foreground 30 git clone "${repo_url}" "${local_repo_path}"
# Check we have a valid app repo
if [[ "${repo_url}" != "${DEFAULT_UMBREL_APP_REPO_URL}" ]]; then
repo_manifest_file="${local_repo_path}/umbrel-app-store.yml"
if [[ -f "${repo_manifest_file}" ]]; then
local_repo_id=$(cat "${repo_manifest_file}" | yq '.id')
ids=$(list_repo_ids "${repo_url}")
if echo "${ids}" | grep --quiet "^${local_repo_id}$"; then
echo
echo "Error: Repo (${local_repo_id}) already exists"
echo
"${0}" "remove" "${repo_url}"
fi
else
echo
echo "Error: Repo (${repo_url}) missing umbrel-app-store.yml"
echo
"${0}" "remove" "${repo_url}"
fi
fi
fi
} || true
# Make sure app repo doesn't end up being owned by root or some apps will fail
# with fs permission errors
if [[ -d "${local_repo_path}" ]]; then
chown -R "1000:1000" "${local_repo_path}"
fi
echo
done
exit
fi
if [[ "$command" = "branch" ]]; then
repo="${2-}"
branch="${3-}"
if [[ -z "${repo}" ]]; then
>&2 echo "A valid repo must be given. e.g. ${DEFAULT_UMBREL_APP_REPO_URL}"
exit 1
fi
if [[ -z "${branch}" ]]; then
>&2 echo "A valid branch name must be given. e.g. staging"
exit 1
fi
local_repo_path=$(repo_path "${repo}")
if [[ ! -d "${local_repo_path}" ]]; then
>&2 echo "App repo does not yet exist locally"
>&2 echo "Please use the 'update' subcommand"
exit 1
fi
# Check whether it's only fetching for master
git_config_file="${local_repo_path}/.git/config"
if grep -q "refs/heads/master:refs/remotes/origin/master" "${git_config_file}"; then
sed -i "s/refs\/heads\/master:refs\/remotes\/origin\/master/refs\/heads\/*:refs\/remotes\/origin\/*/g;" "${git_config_file}"
fi
# Fetch remote commits, files, and refs
git -C "${local_repo_path}" fetch
# Checkout branch
git -C "${local_repo_path}" checkout "${branch}"
# Update local repo
"${0}" "update" "${repo}"
exit
fi
if [[ "$command" = "checkout" ]]; then
descriptor="${2-}"
if [[ -z "${descriptor}" ]]; then
>&2 echo "Error: A valid remote git repo descriptor must be given. e.g. coolapp/umbrel-apps"
exit 1
fi
# These are all valid repo descriptors
# - http://github.com/coolapp/umbrel-apps
# - coolapp/umbrel-apps (will use github.com via https)
# - coolapp/umbrel-apps#staging
# Note: using the .git extension is optional
repo=$(parse_repo_url "${descriptor}")
branch="master"
if [[ "${descriptor}" == *"#"* ]]; then
branch="${descriptor#*#}"
fi
default_repo_path=$(repo_path "${DEFAULT_UMBREL_APP_REPO_URL}")
if [[ ! -d "${default_repo_path}" ]]; then
>&2 echo "Error: The default Umbrel app repo must be setup first."
exit 1
fi
if [[ "${repo}" == "${DEFAULT_UMBREL_APP_REPO_URL}" ]]; then
origin_name="origin"
else
origin_name=$(printf "%s" "${repo}" | openssl dgst -sha256 | sed 's/^.* //')
fi
if ! git -C "${default_repo_path}" remote | grep "${origin_name}" > /dev/null; then
git -C "${default_repo_path}" remote add "${origin_name}" "${repo}"
fi
git -C "${default_repo_path}" fetch "${origin_name}" "${branch}"
git -C "${default_repo_path}" checkout "${origin_name}/${branch}"
# Make sure app repo doesn't end up being owned by root or some apps will fail
# with fs permission errors
if [[ -d "${default_repo_path}" ]]; then
chown -R "1000:1000" "${default_repo_path}"
fi
exit
fi
if [[ "$command" = "path" ]]; then
repo_url="${2-}"
repo_path "${repo_url}"
exit
fi
if [[ "$command" = "locate" ]]; then
app="${2-}"
all_repo_urls=$(list_repos)
for repo_url in $all_repo_urls; do
local_repo_path=$(repo_path "${repo_url}")
if [[ -d "${local_repo_path}/${app}" ]]; then
echo "${repo_url}"
exit
fi
done
exit
fi
# If we get here it means no valid command was supplied
# Show help and exit
show_help
exit 1
-46
View File
@@ -1,46 +0,0 @@
#!/usr/bin/env python3
# Copyright (c) 2015-2018 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
from argparse import ArgumentParser
from base64 import urlsafe_b64encode
from binascii import hexlify
from getpass import getpass
from os import urandom
import hmac
def generate_salt(size):
"""Create size byte hex salt"""
return hexlify(urandom(size)).decode()
def generate_password():
"""Create 32 byte b64 password"""
return urlsafe_b64encode(urandom(32)).decode('utf-8')
def password_to_hmac(salt, password):
m = hmac.new(bytearray(salt, 'utf-8'), bytearray(password, 'utf-8'), 'SHA256')
return m.hexdigest()
def main():
parser = ArgumentParser(description='Create login credentials for a JSON-RPC user')
parser.add_argument('username', help='the username for authentication')
parser.add_argument('password', help='leave empty to generate a random password or specify "-" to prompt for password', nargs='?')
args = parser.parse_args()
if not args.password:
args.password = generate_password()
elif args.password == '-':
args.password = getpass()
# Create 16 byte hex salt
salt = generate_salt(16)
password_hmac = password_to_hmac(salt, args.password)
print('String to be appended to bitcoin.conf:')
print('rpcauth={0}:{1}${2}'.format(args.username, salt, password_hmac))
print('Your password:\n{0}'.format(args.password))
if __name__ == '__main__':
main()
-197
View File
@@ -1,197 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Start Umbrel
if [[ $UID != 0 ]]; then
echo "Umbrel must be started as root"
echo "Please re-run this script as"
echo " sudo ./scripts/start"
exit 1
fi
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
}
# Check system's dependencies
check_dependencies readlink dirname ip docker docker-compose
# Check karen's dependencies
check_dependencies fswatch
# Check OTA update scripts' dependencies
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"
$set_status umbrel started
if [[ ! -d "$UMBREL_ROOT" ]]; then
echo "Root dir does not exist '$UMBREL_ROOT'"
$set_status umbrel errored umbrel-root-missing
exit 1
fi
# Configure Umbrel if it isn't already configured
if [[ ! -f "${UMBREL_ROOT}/statuses/configured" ]]; then
NGINX_PORT=${NGINX_PORT:-80} NETWORK="${NETWORK:-mainnet}" "${UMBREL_ROOT}/scripts/configure"
fi
UMBREL_UPDATE_REBOOT_PERFORMED="${UMBREL_ROOT}/statuses/umbrel-update-reboot-performed"
if [[ -f "${UMBREL_UPDATE_REBOOT_PERFORMED}" ]]; then
# Cleanup status file
rm "${UMBREL_UPDATE_REBOOT_PERFORMED}"
# Mark update status as successful
cat <<EOF > "${UMBREL_ROOT}/statuses/update-status.json"
{"state": "success", "progress": 100, "description": "", "updateTo": ""}
EOF
fi
echo
echo "======================================"
echo "============= STARTING ==============="
echo "============== UMBREL ================"
echo "======================================"
echo
echo "Setting environment variables..."
echo
# Check Umbrel OS
[[ -f "/etc/default/umbrel" ]] && source "/etc/default/umbrel"
if [[ -z "${UMBREL_OS:-}" ]]; then
export IS_UMBREL_OS="false"
else
export IS_UMBREL_OS="true"
fi
# Increase default Docker and Compose timeouts to 240s
# as bitcoin can take a long while to respond
export DOCKER_CLIENT_TIMEOUT=240
export COMPOSE_HTTP_TIMEOUT=240
cd "$UMBREL_ROOT"
echo "Starting karen..."
echo
./karen &>> "${UMBREL_LOGS}/karen.log" &
echo "Starting status monitors..."
pkill -f ./scripts/status-monitor || true
./scripts/status-monitor memory 60 &>> "${UMBREL_LOGS}/status-monitor.log" &
./scripts/status-monitor storage 60 &>> "${UMBREL_LOGS}/status-monitor.log" &
./scripts/status-monitor temperature 15 &>> "${UMBREL_LOGS}/status-monitor.log" &
./scripts/status-monitor uptime 15 &>> "${UMBREL_LOGS}/status-monitor.log" &
# Automatically update app repo every 5 minutes
# If the app repo has not yet been cloned (ie it's a fresh Umbrel install)
# Then this will do the intial pull/initialisation
./scripts/status-monitor repo 300 &>> "${UMBREL_LOGS}/status-monitor.log" &
if [[ "${IS_UMBREL_OS}" == "true" ]]; then
# Set a default value for the SD card health (healthy)
echo "false" > "${UMBREL_ROOT}/statuses/sd-card-health-status.json"
# Check the SD card health every 7 days
# ./scripts/status-monitor sd-card-health 604800 &>> "${UMBREL_LOGS}/status-monitor.log" &
fi
echo "Starting memory monitor..."
echo
./scripts/memory-monitor &>> "${UMBREL_LOGS}/memory-monitor.log" &
echo "Starting backup monitor..."
echo
./scripts/backup/monitor &>> "${UMBREL_LOGS}/backup-monitor.log" &
echo "Starting decoy backup trigger..."
echo
./scripts/backup/decoy-trigger &>> "${UMBREL_LOGS}/backup-decoy-trigger.log" &
if [[ "${@}" != *"--no-start-server"* ]]; then
echo "Starting umbreld..."
echo
pkill --full 'caxa.*umbreld' || true
./bin/umbreld --port 81 --data-directory "${UMBREL_ROOT}" &>> "${UMBREL_LOGS}/umbreld.log" &
fi
compose_files=()
if [[ "${REMOTE_TOR_ACCESS}" == "true" ]]; then
compose_files+=( "--file" "docker-compose.tor.yml" )
fi
compose_files+=( "--file" "docker-compose.yml" )
UMBREL_DEV_FILE="${UMBREL_ROOT}/.umbrel-dev"
if [[ -f "${UMBREL_DEV_FILE}" ]]; then
compose_files+=( "--file" "${UMBREL_ROOT}/docker-compose.dev.yml" )
fi
echo
echo "Starting Docker services..."
echo
docker-compose "${compose_files[@]}" up --detach --build --remove-orphans || {
echo "Failed to start containers"
$set_status umbrel errored docker-failed
exit 1
}
echo
echo "Removing status server iptables entry..."
"${UMBREL_ROOT}/scripts/umbrel-os/status-server/setup-iptables" --delete
echo
echo "Starting installed apps..."
echo
# Unlock the user file on each start of Umbrel to avoid issues
# Normally, the user file shouldn't ever be locked, if it is, something went wrong, but it could still be working
if [[ -f "${UMBREL_ROOT}/db/user.json.lock" ]]; then
echo "WARNING: The user file was locked, Umbrel probably wasn't shut down properly"
rm "${UMBREL_ROOT}/db/user.json.lock"
fi
"${UMBREL_ROOT}/scripts/app" start installed
echo
# If a backup of resolv.conf exists
# (that got created during the Umbrel update process)
# then we'll now restore this after Umbrel
# and the apps have started
# That way if e.g. a Docker image is still missing,
# we would use public DNS servers
RESOLV_CONF_FILE="/etc/resolv.conf"
RESOLV_CONF_BACKUP_FILE="/tmp/resolv.conf"
if [[ -f "${RESOLV_CONF_BACKUP_FILE}" ]]; then
cat "${RESOLV_CONF_BACKUP_FILE}" > "${RESOLV_CONF_FILE}" || true
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 [[ "${REMOTE_TOR_ACCESS}" == "true" ]] && [[ -f "${TOR_HS_WEB_HOSTNAME_FILE}" ]]; then
echo " http://$(cat "${TOR_HS_WEB_HOSTNAME_FILE}")"
fi
$set_status umbrel completed
-20
View File
@@ -1,20 +0,0 @@
#!/usr/bin/env bash
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)"
resource="${1}"
interval="${2}"
log () {
echo "$(date "+%Y-%m-%d %H:%M:%S") ${@}"
}
log "Status monitor for \"${resource}\" running every ${interval} seconds!"
status_script="${UMBREL_ROOT}/scripts/status/${resource}"
json_path="${UMBREL_ROOT}/statuses/${resource}-status.json"
while true; do
result=$("${status_script}")
echo "$result" > "${json_path}"
sleep "${interval}"
done
-64
View File
@@ -1,64 +0,0 @@
#!/usr/bin/env bash
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
memory_total_bytes() {
free --bytes | awk 'NR==2 {print $2}'
}
memory_used_bytes() {
free --bytes | awk 'NR==2 {print $3}'
}
get_container_memory_use() {
local container="${1}"
local container_memory=0
local container_pids=$(docker top "${container}" | tail -n +2 | awk '{print $2}')
for pid in $container_pids; do
local pid_memory=$(ps u "${pid}" | awk '{print $4}' | grep -v 'MEM')
if [[ ! -z "${pid_memory}" ]]; then
container_memory=$(awk "BEGIN {print ${container_memory}+${pid_memory}}")
fi
done
echo "${container_memory}"
}
get_app_memory_use() {
local app="${1}"
local app_memory=0
local app_containers=$("${UMBREL_ROOT}/scripts/app" compose "${app}" ps | awk '{print $1}' | grep -v 'Name\|-----')
for container in $app_containers; do
local container_memory=$(get_container_memory_use "${container}")
app_memory=$(awk "BEGIN {print ${app_memory}+${container_memory}}")
done
# Above values return % of total memory used so we need to convert to bytes
total=$(memory_total_bytes)
awk "BEGIN {printf(\"%.f\", ${total}*${app_memory}/100)}"
}
json="{}"
total=$(memory_total_bytes)
json=$(echo $json | jq --arg total "${total}" '. + {total: $total|tonumber}')
used=$(memory_used_bytes)
json=$(echo $json | jq --arg used "${used}" '. + {used: $used|tonumber}')
cumulative_app_memory="0"
for app in $( "${UMBREL_ROOT}/scripts/app" ls-installed)
do
app_memory=$(get_app_memory_use "${app}")
cumulative_app_memory=$(($cumulative_app_memory+$app_memory))
json=$(echo $json | jq --arg app "${app}" --arg app_memory "${app_memory}" '.breakdown |= .+ [{id: $app, used: $app_memory|tonumber}]')
done
umbrel=$(($used-$cumulative_app_memory))
json=$(echo $json | jq --arg umbrel "${umbrel}" '.breakdown |= .+ [{id: "umbrel", used: $umbrel|tonumber}]')
echo $json | jq '.breakdown |= sort_by(-.used)'
-5
View File
@@ -1,5 +0,0 @@
#!/usr/bin/env bash
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
$UMBREL_ROOT/scripts/repo update
-33
View File
@@ -1,33 +0,0 @@
#!/usr/bin/env bash
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
# The Raspberry Pi Foundation recommends using Class A1 SD cards
# Link here: https://www.raspberrypi.com/news/sd-card-speed-test/
# According to the standard, defined here: https://www.sdcard.org/developers/sd-standard-overview/application-performance-class/
# A Class A1 SD card, we should be able to achieve the following performance metrics:
# Seq. write BW 10000 KB/sec
# Rand. write 500 IOPS
# Rand. read 1500 IOPS
# Run twice, the first run is a warmup, the second is the test
for i in 1 2
do
RES=$(fio --output-format=terse --max-jobs=4 "${UMBREL_ROOT}/scripts/umbrel-os/sd-card-health/sd_bench.fio" | cut -f 3,7,8,48,49 -d";" -)
rm -f /var/tmp/sd.test.file
done
swri=$(echo "$RES" | head -n 2 | tail -n 1 | cut -d ";" -f 4)
rwri=$(echo "$RES" | head -n 3 | tail -n 1 | cut -d ";" -f 5)
rrea=$(echo "$RES" | head -n 4 | tail -n 1 | cut -d ";" -f 3)
# echo "${swri} ${rwri} ${rrea}"
# Check if any metric fails the threshold:
if [[ "$swri" -lt 10000 ]] || [[ "$rwri" -lt 500 ]] || [[ "$rrea" -lt 1500 ]]; then
echo "true"
else
echo "false"
fi
-39
View File
@@ -1,39 +0,0 @@
#!/usr/bin/env bash
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
filesystem_total_storage_bytes() {
local directory="${1}"
df --block-size=1 --output=size "${directory}" | tail -n 1
}
filesystem_used_storage_bytes() {
local directory="${1}"
df --block-size=1 --output=used "${directory}" | tail -n 1
}
directory_size_bytes() {
local directory="${1}"
du --block-size=1 --max-depth=0 "${directory}" | awk '{print $1}'
}
json="{}"
total=$(filesystem_total_storage_bytes "${UMBREL_ROOT}")
json=$(echo $json | jq --arg total "${total}" '. + {total: $total|tonumber}')
used=$(filesystem_used_storage_bytes "${UMBREL_ROOT}")
json=$(echo $json | jq --arg used "${used}" '. + {used: $used|tonumber}')
cumulative_app_size="0"
for app in $( "${UMBREL_ROOT}/scripts/app" ls-installed)
do
app_size=$(directory_size_bytes "${UMBREL_ROOT}/app-data/${app}")
cumulative_app_size=$(($cumulative_app_size+$app_size))
json=$(echo $json | jq --arg app "${app}" --arg app_size "${app_size}" '.breakdown |= .+ [{id: $app, used: $app_size|tonumber}]')
done
umbrel=$(($used-$cumulative_app_size))
json=$(echo $json | jq --arg umbrel "${umbrel}" '.breakdown |= .+ [{id: "umbrel", used: $umbrel|tonumber}]')
echo $json | jq '.breakdown |= sort_by(-.used)'
-19
View File
@@ -1,19 +0,0 @@
#!/usr/bin/env bash
# This may not always be the CPU sensor on other hardware.
# To be sure we need to iterate through /sys/class/thermal/thermal_zone*/type
# https://www.kernel.org/doc/Documentation/thermal/sysfs-api.txt
temperature_file="/sys/class/thermal/thermal_zone0/temp"
# Check temperature file exists
if [[ ! -f "${temperature_file}" ]]
then
echo "null"
exit
fi
# Read temperature file
temperature=$(cat "${temperature_file}")
# Convert millidegrees celsius to degrees celsius
echo "${temperature}" | awk '{print int($1/1000)}'
-5
View File
@@ -1,5 +0,0 @@
#!/usr/bin/env bash
json="{}"
cat /proc/uptime | awk '{print int($1)}'
-66
View File
@@ -1,66 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Stop Umbrel
if [[ $UID != 0 ]]; then
echo "Umbrel must be stopped as root"
echo "Please re-run this script as"
echo " sudo ./scripts/stop"
exit 1
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'"
exit 1
fi
cd "$UMBREL_ROOT"
# Increase default Docker and Compose timeouts to 240s
# As bitcoin can take a long while to respond
export DOCKER_CLIENT_TIMEOUT=240
export COMPOSE_HTTP_TIMEOUT=240
# TODO: Transition to pidfiles so we can reliably kill these processes
# Run this before Docker, as Docker can fail to stop cleanly
echo "Killing background services"
ps -Af \
| grep '\./karen\|\./scripts/status-monitor\|\./scripts/memory-monitor\|\./scripts/backup/\|umbrel/scripts\|fswatch' \
| grep -v '\-run.sh\|update\|stop\|umbreld' \
| awk '{print $2}' \
| xargs sudo kill 2> /dev/null || true
if [[ "${@}" != *"--no-stop-server"* ]]; then
pkill --full 'caxa.*umbreld' || true
fi
echo "Stopping installed apps..."
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_FILE="${UMBREL_ROOT}/.umbrel-dev"
if [[ -f "${UMBREL_DEV_FILE}" ]]; then
compose_files+=( "--file" "${UMBREL_ROOT}/docker-compose.dev.yml" )
fi
echo "Stopping Docker services..."
echo
docker-compose "${compose_files[@]}" down
@@ -1,27 +0,0 @@
version: "3.7"
services:
app_proxy:
image: getumbrel/app-proxy:v0.5.2@sha256:a2e3e0ddfcf84838bf0ba66f4b839ec958832d51f0ac9ace47962459c838b2d6
#build: ../../containers/app-proxy
user: "1000:1000"
restart: on-failure
hostname: $APP_PROXY_HOSTNAME
ports:
- "${APP_PROXY_PORT}:${APP_PROXY_PORT}"
volumes:
- "${APP_MANIFEST_FILE}:/extra/umbrel-app.yml:ro"
- "${TOR_DATA_DIR}:/var/lib/tor:ro"
- "${APP_DATA_DIR}:/data:ro"
environment:
LOG_LEVEL: info
PROXY_PORT: $APP_PROXY_PORT
PROXY_AUTH_ADD: "true"
PROXY_AUTH_WHITELIST:
PROXY_AUTH_BLACKLIST:
APP_HOST:
APP_PORT:
AUTH_SERVICE_PORT: $AUTH_PORT
UMBREL_AUTH_SECRET: $UMBREL_AUTH_SECRET
MANAGER_IP: $MANAGER_IP
MANAGER_PORT: 3006
@@ -1,6 +0,0 @@
version: "3.7"
networks:
default:
external:
name: umbrel_main_network
-15
View File
@@ -1,15 +0,0 @@
version: "3.7"
services:
tor_server:
image: getumbrel/tor:0.4.7.8@sha256:2ace83f22501f58857fa9b403009f595137fa2e7986c4fda79d82a8119072b6a
user: "1000:1000"
restart: on-failure
volumes:
- ${TOR_ENTRYPOINT_SCRIPT}:/umbrel/entrypoint.sh
- ${TOR_DATA_DIR}:/data
environment:
HOME: "/tmp"
HS_DIR: "${TOR_HS_APP_DIR}"
HS_PORTS: "${TOR_HS_PORTS}"
entrypoint: "/umbrel/entrypoint.sh"
-17
View File
@@ -1,17 +0,0 @@
#!/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}"
-35
View File
@@ -1,35 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
PASSWORD_FILE="${UMBREL_ROOT}/statuses/password"
check_root () {
if [[ $UID != 0 ]]; then
echo "This script must be run as root"
exit 1
fi
}
check_umbrel_os () {
[[ -f "/etc/default/umbrel" ]] && source "/etc/default/umbrel"
if [[ -z "${UMBREL_OS:-}" ]]; then
echo "This script must only be run on Umbrel OS"
exit 1
fi
}
password="$(cat ${PASSWORD_FILE})"
echo "false" > "${PASSWORD_FILE}"
check_root
check_umbrel_os
if [[ "${password}" == "" ]] || [[ "${password}" == "false" ]] || [[ "${password}" == "true" ]]; then
echo "Password not set in status file: \"${PASSWORD_FILE}\""
exit 1
fi
echo -e "${password}\n${password}" | passwd umbrel
echo "true" > "${PASSWORD_FILE}"
@@ -1,51 +0,0 @@
#!/usr/bin/env bash
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../../..)"
block_device="${1}"
mount_point="${2}"
set_status="/status-server/set-status"
setup_iptables="/status-server/setup-iptables"
check_if_not_already_running() {
if ps ax | grep $0 | grep -v $$ | grep bash | grep -v grep
then
echo "storage monitor is already running"
exit 1
fi
}
main () {
check_if_not_already_running
while true; do
if ! [[ -e "/dev/${block_device}" ]]; then
echo "Mounted block device no longer exists!"
break
fi
if ! grep --quiet " ${mount_point} " /proc/mounts; then
echo "External storage is no longer mounted!"
break
fi
if grep " ${mount_point} " /proc/mounts | grep --quiet '[ ,]ro[ ,]'; then
echo "External storage is mounted as read only!"
break
fi
if ! df -h "${UMBREL_ROOT}" | grep --quiet "/dev/${block_device}"; then
echo "Umbrel root is no longer bind mounted to external storage!"
break
fi
sleep 1
done
echo "Stopping Umbrel due to failed storage device check..."
docker kill $(docker ps -aq)
$set_status mount errored monitor-check
$setup_iptables --force
}
main
@@ -1,69 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../../..)"
SD_MOUNT_POINT="/sd-root"
SD_UMBREL_ROOT="${SD_MOUNT_POINT}${UMBREL_ROOT}"
set_status="/status-server/set-status"
check_root () {
if [[ $UID != 0 ]]; then
echo "This script must be run as root"
exit 1
fi
}
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
}
check_semver_range () {
local range="${1}"
local version="${2}"
"${UMBREL_ROOT}/scripts/umbrel-os/semver" -r "${range}" "${version}" | grep --quiet "^${version}$"
}
main () {
check_root
check_dependencies jq
$set_status sdcard-update started
echo "Checking if SD card Umbrel is newer than external storage..."
local external_version=$(cat "${UMBREL_ROOT}/info.json" | jq -r .version | cut -d "-" -f "1")
local sd_version=$(cat "${SD_UMBREL_ROOT}/info.json" | jq -r .version | cut -d "-" -f "1")
if ! check_semver_range ">${external_version}" "${sd_version}"; then
echo "No, SD version is not newer, exiting."
$set_status sdcard-update completed
exit 0
fi
echo "Yes, SD version is newer."
# This will fail if we ever have multiple ranges with pre-release tags.
# e.g `1.2.3-beta <2.0.0` will become `1.2.3` due to striping the `-`
local update_requirement=$(cat "${SD_UMBREL_ROOT}/info.json" | jq -r .requires | cut -d "-" -f "1")
echo "Checking if the external storage version \"${external_version}\" satisfies update requirement \"${update_requirement}\"..."
if ! check_semver_range "${update_requirement}" "${external_version}"; then
echo "No, we can't do an automatic update, exiting."
$set_status sdcard-update errored semver-mismatch
exit 0
fi
# As the system just booted, there cannot be an update in progress...
# We'll delete the update lock (update-in-progress)
# to avoid the update (from the SD card) aborting
rm --force "${UMBREL_ROOT}/statuses/update-in-progress" || true
echo "Yes, it does, attempting an automatic update..."
"${UMBREL_ROOT}/scripts/update/update" --path "${SD_UMBREL_ROOT}"
$set_status sdcard-update completed
}
main
-98
View File
@@ -1,98 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
DOCKER_ROOT="/var/lib/docker"
if [[ "$EUID" -ne 0 ]]; then
echo "Please run as root"
exit
fi
show_help() {
cat << EOF
fdocker 0.0.1
CLI for managing Docker
Usage: docker <command> <arguments...>
Commands:
clean Cleanup containers using 'containers' as argument
EOF
}
if [ -z ${2+x} ]; then
command=""
else
command="${1}"
fi
function delete_mount() {
id_path="${1}"
overlay_path="${DOCKER_ROOT}/overlay2"
if [[ -f "${id_path}" ]]; then
overlay_id=$(cat "${id_path}")
overlay_path="${overlay_path}/${overlay_id}"
if [[ -d "${overlay_path}" ]]; then
echo "Deleting: ${overlay_path}"
rm -rf "${overlay_path}"
fi
rm -rf "${id_path}"
fi
}
function cleanup_containers() {
echo "Cleaning up containers..."
containers_path="${DOCKER_ROOT}/containers"
mounts_path="${DOCKER_ROOT}/image/overlay2/layerdb/mounts"
# Loop through all the containers
containers="${containers_path}/*"
for dir in $containers; do
container_id=$(basename "${dir}")
if [[ ! -d "${dir}" ]]; then
continue
fi
echo "Removing container: ${container_id}"
# Determine the overlay IDs from the mounts
delete_mount "${mounts_path}/${container_id}/init-id"
delete_mount "${mounts_path}/${container_id}/mount-id"
# Delete the container folder
rm -rf "${dir}"
echo
done
echo "Done"
}
if [[ "${command}" == "clean" ]]; then
resource="${2}"
if [[ "${resource}" == "containers" ]]; then
cleanup_containers
else
echo "Cannot cleanup: ${resource}"
exit 1
fi
exit
fi
# If we get here it means no valid command was supplied
# Show help and exit
show_help
exit 1
@@ -1,39 +0,0 @@
# Full credits: https://github.com/raspberrypi-ui/agnostics
# Use FIO to emulate the Apps Class A1 performance test.
# This isn't an exact benchmark as the card isn't in the state required by the
# specification, but is good enough as a sniff test.
#
[global]
ioengine=libaio
iodepth=4
size=64m
direct=1
end_fsync=1
directory=/var/tmp
filename=sd.test.file
[prepare-file]
rw=write
bs=512k
stonewall
[seq-write]
rw=write
bs=512k
stonewall
[rand-4k-write]
rw=randwrite
bs=4k
runtime=10
stonewall
[rand-4k-read]
rw=randread
bs=4k
runtime=10
stonewall
# execute with command $ fio --output-format=terse sd_bench.fio | cut -f 3,7,8,48,49 -d";" -
# testname, read bandwidth, read iops, write bandwidth, write iops
-521
View File
@@ -1,521 +0,0 @@
#!/usr/bin/env bash
# https://github.com/qzb/sh-semver
# Commit: 2ac2437
_num_part='([0-9]|[1-9][0-9]*)'
_lab_part='([0-9]|[1-9][0-9]*|[0-9]*[a-zA-Z-][a-zA-Z0-9-]*)'
_met_part='([0-9A-Za-z-]+)'
RE_NUM="$_num_part(\.$_num_part)*"
RE_LAB="$_lab_part(\.$_lab_part)*"
RE_MET="$_met_part(\.$_met_part)*"
RE_VER="[ \t]*$RE_NUM(-$RE_LAB)?(\+$RE_MET)?"
BRE_DIGIT='[0-9]\{1,\}'
BRE_ALNUM='[0-9a-zA-Z-]\{1,\}'
BRE_IDENT="$BRE_ALNUM\(\.$BRE_ALNUM\)*"
BRE_MAJOR="$BRE_DIGIT"
BRE_MINOR="\(\.$BRE_DIGIT\)\{0,1\}"
BRE_PATCH="\(\.$BRE_DIGIT\)\{0,1\}"
BRE_PRERE="\(-$BRE_IDENT\)\{0,1\}"
BRE_BUILD="\(+$BRE_IDENT\)\{0,1\}"
BRE_VERSION="${BRE_MAJOR}${BRE_MINOR}${BRE_PATCH}${BRE_PRERE}${BRE_BUILD}"
filter()
{
local text="$1"
local regex="$2"
shift 2
echo "$text" | grep -E "$@" "$regex"
}
# Gets number part from normalized version
get_number()
{
echo "${1%%-*}"
}
# Gets prerelase part from normalized version
get_prerelease()
{
local pre_and_meta=${1%+*}
local pre=${pre_and_meta#*-}
if [ "$pre" = "$1" ]; then
echo
else
echo "$pre"
fi
}
# Gets major number from normalized version
get_major()
{
echo "${1%%.*}"
}
# Gets minor number from normalized version
get_minor()
{
local minor_major_bug=${1%%-*}
local minor_major=${minor_major_bug%.*}
local minor=${minor_major#*.}
if [ "$minor" = "$minor_major" ]; then
echo
else
echo "$minor"
fi
}
get_bugfix()
{
local minor_major_bug=${1%%-*}
local bugfix=${minor_major_bug##*.*.}
if [ "$bugfix" = "$minor_major_bug" ]; then
echo
else
echo "$bugfix"
fi
}
strip_metadata()
{
echo "${1%+*}"
}
semver_eq()
{
local ver1 ver2 part1 part2
ver1=$(get_number "$1")
ver2=$(get_number "$2")
local count=1
while true; do
part1=$(echo "$ver1"'.' | cut -d '.' -f $count)
part2=$(echo "$ver2"'.' | cut -d '.' -f $count)
if [ -z "$part1" ] || [ -z "$part2" ]; then
break
fi
if [ "$part1" != "$part2" ]; then
return 1
fi
local count=$(( count + 1 ))
done
if [ "$(get_prerelease "$1")" = "$(get_prerelease "$2")" ]; then
return 0
else
return 1
fi
}
semver_lt()
{
local number_a number_b prerelease_a prerelease_b
number_a=$(get_number "$1")
number_b=$(get_number "$2")
prerelease_a=$(get_prerelease "$1")
prerelease_b=$(get_prerelease "$2")
local head_a=''
local head_b=''
local rest_a=$number_a.
local rest_b=$number_b.
while [ -n "$rest_a" ] || [ -n "$rest_b" ]; do
head_a=${rest_a%%.*}
head_b=${rest_b%%.*}
rest_a=${rest_a#*.}
rest_b=${rest_b#*.}
if [ -z "$head_a" ] || [ -z "$head_b" ]; then
return 1
fi
if [ "$head_a" -eq "$head_b" ]; then
continue
fi
if [ "$head_a" -lt "$head_b" ]; then
return 0
else
return 1
fi
done
if [ -n "$prerelease_a" ] && [ -z "$prerelease_b" ]; then
return 0
elif [ -z "$prerelease_a" ] && [ -n "$prerelease_b" ]; then
return 1
fi
local head_a=''
local head_b=''
local rest_a=$prerelease_a.
local rest_b=$prerelease_b.
while [ -n "$rest_a" ] || [ -n "$rest_b" ]; do
head_a=${rest_a%%.*}
head_b=${rest_b%%.*}
rest_a=${rest_a#*.}
rest_b=${rest_b#*.}
if [ -z "$head_a" ] && [ -n "$head_b" ]; then
return 0
elif [ -n "$head_a" ] && [ -z "$head_b" ]; then
return 1
fi
if [ "$head_a" = "$head_b" ]; then
continue
fi
# If both are numbers then compare numerically
if [ "$head_a" = "${head_a%[!0-9]*}" ] && [ "$head_b" = "${head_b%[!0-9]*}" ]; then
[ "$head_a" -lt "$head_b" ] && return 0 || return 1
# If only a is a number then return true (number has lower precedence than strings)
elif [ "$head_a" = "${head_a%[!0-9]*}" ]; then
return 0
# If only b is a number then return false
elif [ "$head_b" = "${head_b%[!0-9]*}" ]; then
return 1
# Finally if of identifiers is a number compare them lexically
else
test "$head_a" \< "$head_b" && return 0 || return 1
fi
done
return 1
}
semver_gt()
{
if semver_lt "$1" "$2" || semver_eq "$1" "$2"; then
return 1
else
return 0
fi
}
semver_le()
{
semver_gt "$1" "$2" && return 1 || return 0
}
semver_ge()
{
semver_lt "$1" "$2" && return 1 || return 0
}
semver_sort()
{
if [ $# -le 1 ]; then
echo "$1"
return
fi
local pivot=$1
local args_a=()
local args_b=()
shift 1
for ver in "$@"; do
if semver_le "$ver" "$pivot"; then
args_a=( "${args_a[@]}" "$ver" )
else
args_b=( "$ver" "${args_b[@]}" )
fi
done
args_a=( $(semver_sort "${args_a[@]}") )
args_b=( $(semver_sort "${args_b[@]}") )
echo "${args_a[@]}" "$pivot" "${args_b[@]}"
}
regex_match()
{
local string="$1 "
local regexp="$2"
local match
match="$(eval "echo '$string' | grep -E -o '^[ \t]*($regexp)[ \t]+'")";
for i in $(seq 0 9); do
unset "MATCHED_VER_$i"
unset "MATCHED_NUM_$i"
done
unset REST
if [ -z "$match" ]; then
return 1
fi
local match_len=${#match}
REST="${string:$match_len}"
local part
local i=1
for part in $string; do
local ver num
ver="$(eval "echo '$part' | grep -E -o '$RE_VER' | head -n 1 | sed 's/ \t//g'")";
num=$(get_number "$ver")
if [ -n "$ver" ]; then
eval "MATCHED_VER_$i='$ver'"
eval "MATCHED_NUM_$i='$num'"
i=$(( i + 1 ))
fi
done
return 0
}
# Normalizes rules string
#
# * replaces chains of whitespaces with single spaces
# * replaces whitespaces around hyphen operator with "_"
# * removes wildcards from version numbers (1.2.* -> 1.2)
# * replaces "x" with "*"
# * removes whitespace between operators and version numbers
# * removes leading "v" from version numbers
# * removes leading and trailing spaces
normalize_rules()
{
echo " $1" \
| sed 's/\\t/ /g' \
| sed 's/ / /g' \
| sed 's/ \{2,\}/ /g' \
| sed 's/ - /_-_/g' \
| sed 's/\([~^<>=]\) /\1/g' \
| sed 's/\([ _~^<>=]\)v/\1/g' \
| sed 's/\.[xX*]//g' \
| sed 's/[xX]/*/g' \
| sed 's/^ //g' \
| sed 's/ $//g'
}
# Reads rule from provided string
resolve_rule()
{
local rule operator operands
rule="$1"
operator="$( echo "$rule" | sed "s/$BRE_VERSION/#/g" )"
operands=( $( echo "$rule" | grep -o "$BRE_VERSION") )
case "$operator" in
'*') echo "all" ;;
'#') echo "eq ${operands[0]}" ;;
'=#') echo "eq ${operands[0]}" ;;
'<#') echo "lt ${operands[0]}" ;;
'>#') echo "gt ${operands[0]}" ;;
'<=#') echo "le ${operands[0]}" ;;
'>=#') echo "ge ${operands[0]}" ;;
'#_-_#') echo "ge ${operands[0]}"
echo "le ${operands[1]}" ;;
'~#') echo "tilde ${operands[0]}" ;;
'^#') echo "caret ${operands[0]}" ;;
*) return 1
esac
}
resolve_rules()
{
local rules
rules="$(normalize_rules "$1")"
IFS=' ' read -ra rules <<< "${rules:-all}"
for rule in "${rules[@]}"; do
resolve_rule "$rule"
done
}
rule_eq()
{
local rule_ver="$1"
local tested_ver="$2"
semver_eq "$tested_ver" "$rule_ver" && return 0 || return 1;
}
rule_le()
{
local rule_ver="$1"
local tested_ver="$2"
semver_le "$tested_ver" "$rule_ver" && return 0 || return 1;
}
rule_lt()
{
local rule_ver="$1"
local tested_ver="$2"
semver_lt "$tested_ver" "$rule_ver" && return 0 || return 1;
}
rule_ge()
{
local rule_ver="$1"
local tested_ver="$2"
semver_ge "$tested_ver" "$rule_ver" && return 0 || return 1;
}
rule_gt()
{
local rule_ver="$1"
local tested_ver="$2"
semver_gt "$tested_ver" "$rule_ver" && return 0 || return 1;
}
rule_tilde()
{
local rule_ver="$1"
local tested_ver="$2"
if rule_ge "$rule_ver" "$tested_ver"; then
local rule_major rule_minor
rule_major=$(get_major "$rule_ver")
rule_minor=$(get_minor "$rule_ver")
if [ -n "$rule_minor" ] && rule_eq "$rule_major.$rule_minor" "$(get_number "$tested_ver")"; then
return 0
fi
if [ -z "$rule_minor" ] && rule_eq "$rule_major" "$(get_number "$tested_ver")"; then
return 0
fi
fi
return 1
}
rule_caret()
{
local rule_ver="$1"
local tested_ver="$2"
if rule_ge "$rule_ver" "$tested_ver"; then
local rule_major
rule_major="$(get_major "$rule_ver")"
if [ "$rule_major" != "0" ] && rule_eq "$rule_major" "$(get_number "$tested_ver")"; then
return 0
fi
if [ "$rule_major" = "0" ] && rule_eq "$rule_ver" "$(get_number "$tested_ver")"; then
return 0
fi
fi
return 1
}
rule_all()
{
return 0
}
apply_rules()
{
local rules_string="$1"
shift
local versions=( "$@" )
# Loop over sets of rules (sets of rules are separated with ||)
for ver in "${versions[@]}"; do
rules_tail="$rules_string";
while [ -n "$rules_tail" ]; do
head="${rules_tail%%||*}"
if [ "$head" = "$rules_tail" ]; then
rules_string=""
else
rules_tail="${rules_tail#*||}"
fi
#if [ -z "$head" ] || [ -n "$(echo "$head" | grep -E -x '[ \t]*')" ]; then
#group=$(( $group + 1 ))
#continue
#fi
rules="$(resolve_rules "$head")"
# If specified rule cannot be recognised - end with error
if [ $? -eq 1 ]; then
exit 1
fi
if ! echo "$ver" | grep -q -E -x "[v=]?[ \t]*$RE_VER"; then
continue
fi
ver=$(echo "$ver" | grep -E -x "$RE_VER")
success=true
allow_prerel=false
if $FORCE_ALLOW_PREREL; then
allow_prerel=true
fi
while read -r rule; do
comparator="${rule%% *}"
operand="${rule#* }"
if [ -n "$(get_prerelease "$operand")" ] && semver_eq "$(get_number "$operand")" "$(get_number "$ver")" || [ "$rule" = "all" ]; then
allow_prerel=true
fi
"rule_$comparator" "$operand" "$ver"
if [ $? -eq 1 ]; then
success=false
break
fi
done <<< "$rules"
if $success; then
if [ -z "$(get_prerelease "$ver")" ] || $allow_prerel; then
echo "$ver"
break;
fi
fi
done
group=$(( group + 1 ))
done
}
FORCE_ALLOW_PREREL=false
USAGE="Usage: $0 [-r <rule>] [<version>... ]
Omitting <version>s reads them from STDIN.
Omitting -r <rule> simply sorts the versions according to semver ordering."
while getopts ar:h o; do
case "$o" in
a) FORCE_ALLOW_PREREL=true ;;
r) RULES_STRING="$OPTARG||";;
h) echo "$USAGE" && exit ;;
?) echo "$USAGE" && exit 1;;
esac
done
shift $(( OPTIND-1 ))
VERSIONS=( ${@:-$(cat -)} )
# Sort versions
VERSIONS=( $(semver_sort "${VERSIONS[@]}") )
if [ -z "$RULES_STRING" ]; then
printf '%s\n' "${VERSIONS[@]}"
else
apply_rules "$RULES_STRING" "${VERSIONS[@]}"
fi
-62
View File
@@ -1,62 +0,0 @@
#!/usr/bin/env bash
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
STATUS_FILE_PATH="/umbrel-status"
set_status="/status-server/set-status"
check_root () {
if [[ $UID != 0 ]]; then
echo "This script must be run as root"
exit 1
fi
}
check_umbrel_os () {
[[ -f "/etc/default/umbrel" ]] && source "/etc/default/umbrel"
if [[ -z "${UMBREL_OS:-}" ]]; then
echo "This script must only be run on Umbrel OS"
exit 1
fi
}
has_service_failed () {
systemctl is-failed --quiet '*'
}
has_umbrel_started () {
cat "${STATUS_FILE_PATH}" | grep --silent '^umbrel:completed$'
}
has_error_been_caught () {
cat "${STATUS_FILE_PATH}" | grep --silent ':errored:'
}
check_root
check_umbrel_os
$set_status service-monitor started
echo "Starting service monitor..."
while true
do
if has_umbrel_started
then
echo "Umbrel successfully started, exiting service monitor!"
exit 0
fi
if has_service_failed
then
echo "Service failed!"
if has_error_been_caught
then
echo "Error already caught, doing nothing."
else
echo "Error not caught, writing an error."
$set_status service-monitor errored service-failed
fi
echo "Exiting service monitor"
exit 0
fi
sleep 1
done
@@ -1,20 +0,0 @@
# Umbrel Connection Details Logger (TTY)
# Installed at /etc/systemd/system/umbrel-connection-details.service
[Unit]
Description=Connection Details
Requires=umbrel-startup.service
After=umbrel-startup.service
[Service]
Type=oneshot
Restart=no
ExecStart=/home/umbrel/umbrel/scripts/umbrel-os/umbrel-details
User=root
Group=root
StandardOutput=tty
TTYPath=/dev/tty1
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
@@ -1,19 +0,0 @@
# Umbrel Docker Cleanup
# Installed at /etc/systemd/system/umbrel-docker-cleanup.service
[Unit]
Description=Umbrel Docker Cleanup
After=umbrel-external-storage.service
Before=docker.service
[Service]
Type=oneshot
ExecStart=/home/umbrel/umbrel/scripts/umbrel-os/fdocker clean containers
User=root
Group=root
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=umbrel docker cleanup
[Install]
WantedBy=multi-user.target
@@ -1,26 +0,0 @@
# Umbrel External Storage SDcard Updater
# Installed at /etc/systemd/system/umbrel-external-storage-sdcard-update.service
[Unit]
Description=External Storage SDcard Updater
Requires=umbrel-external-storage.service
After=umbrel-external-storage.service
Wants=network-online.target
After=network-online.target
Wants=docker.service
After=docker.service
[Service]
Type=oneshot
Restart=no
ExecStart=/home/umbrel/umbrel/scripts/umbrel-os/external-storage/update-from-sdcard
TimeoutStartSec=45min
User=root
Group=root
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=external storage updater
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
@@ -1,21 +0,0 @@
# Umbrel Service Monitor
# Installed at /etc/systemd/system/umbrel-service-monitor.service
[Unit]
Description=Service Monitor
After=umbrel-status-server.service
[Service]
Type=oneshot
Restart=no
ExecStart=/home/umbrel/umbrel/scripts/umbrel-os/service-monitor
TimeoutStartSec=45min
User=root
Group=root
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=service monitor
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
@@ -1,35 +0,0 @@
# Umbrel Startup Service
# Installed at /etc/systemd/system/umbrel-startup.service
[Unit]
Description=Umbrel Startup Service
Requires=umbrel-external-storage.service
After=umbrel-external-storage.service
Requires=umbrel-external-storage-sdcard-update.service
After=umbrel-external-storage-sdcard-update.service
Wants=network-online.target
After=network-online.target
Wants=docker.service
After=docker.service
# This prevents us hitting restart rate limits and ensures we keep restarting
# indefinitely.
StartLimitInterval=0
[Service]
Type=forking
TimeoutStartSec=infinity
TimeoutStopSec=16min
ExecStart=/home/umbrel/umbrel/scripts/start
ExecStop=/home/umbrel/umbrel/scripts/stop
User=root
Group=root
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=umbrel startup
RemainAfterExit=yes
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
@@ -1,25 +0,0 @@
# Umbrel Status Server iptables Update
# Installed at /etc/systemd/system/umbrel-status-server-iptables-update.service
# This is needed because when Docker starts it appends its own iptables rules
# after ours. This means traffic on port 80 will never arrive at a Docker container
# because we always redirect it. We can remove the rule and then re-apply it so
# it gets appended after the Docker rule so port 80 will only continue to be
# routed to the status server until a Docker container listens on port 80.
[Unit]
Description=Status Server iptables Update
Wants=docker.service
After=docker.service
[Service]
Type=oneshot
ExecStart=/status-server/setup-iptables
User=root
Group=root
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=status server iptables
[Install]
WantedBy=multi-user.target
@@ -1,21 +0,0 @@
# Umbrel Status Server
# Installed at /etc/systemd/system/umbrel-status-server.service
[Unit]
Description=Status Server
Before=umbrel-external-storage-sdcard-update.service
Before=umbrel-external-storage.service
Before=umbrel-startup.service
[Service]
Type=exec
ExecStartPre=/home/umbrel/umbrel/scripts/umbrel-os/status-server/setup
ExecStart=/status-server/status-server
User=root
Group=root
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=status server
[Install]
WantedBy=multi-user.target
@@ -1,31 +0,0 @@
#!/usr/bin/env bash
STATUS_FILE_PATH="/umbrel-status"
service_id="${1}"
status="${2}"
error_code="${3}"
source /etc/default/umbrel 2> /dev/null
if [[ -z "${UMBREL_OS:-}" ]]; then
echo "Skipping status update when not on Umbrel OS"
exit
fi
if [[ "${service_id}" == "" ]]; then
echo "Error: Missing ID"
exit 1
fi
if [[ "${status}" == "" ]]; then
echo "Error: Missing Status"
exit 1
fi
entry="${service_id}:${status}"
if [[ "${error_code}" != "" ]]; then
entry="${entry}:${error_code}"
fi
echo "${entry}" >> "${STATUS_FILE_PATH}"
-24
View File
@@ -1,24 +0,0 @@
#!/usr/bin/env bash
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../../..)"
BIND_MOUNT_PATH="/status-server"
STATUS_FILE_PATH="/umbrel-status"
# Bind mount status server to new location so we always run on the SD card
echo "Bind mounting status server to ${BIND_MOUNT_PATH}..."
[[ ! -d "${BIND_MOUNT_PATH}" ]] && mkdir -p "${BIND_MOUNT_PATH}"
mount --bind "${UMBREL_ROOT}/scripts/umbrel-os/status-server/" "${BIND_MOUNT_PATH}"
sync
sleep 1
# Clear status file
echo "clearing status file..."
echo "" > "${STATUS_FILE_PATH}"
# Append iptables rule to forward port 80 to port 8000
# The status server runs on port 8000 but this rule will route all port 80
# HTTP traffic to it.
# When the Umbrel service has started Docker will overwrite this rule and
# instead forward port 80 to the Umbrel HTTP server container.
echo "Setting iptables rules..."
"${BIND_MOUNT_PATH}/setup-iptables"
@@ -1,55 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../../..)"
source /etc/default/umbrel 2> /dev/null || true
if [[ -z "${UMBREL_OS:-}" ]]; then
echo "Exiting iptables setup when not on Umbrel OS"
exit
fi
delete="false"
if [[ "${@}" == *"--delete"* ]]; then
delete="true"
fi
force="false"
if [[ "${@}" == *"--force"* ]]; then
force="true"
fi
check_root () {
if [[ $UID != 0 ]]; then
echo "This script must be run as root"
exit 1
fi
}
main () {
check_root
# Remove and then re-append iptables rule
rule=(PREROUTING \
--table nat \
--proto tcp \
--dport 80 \
--jump REDIRECT \
--to-port 8000)
if iptables --delete ${rule[@]} 2> /dev/null; then
echo "Removed existing iptables entry."
else
echo "No existing iptables entry found."
fi
if [[ "${delete}" == "false" ]]; then
position="--append"
if [[ "${force}" == "true" ]]; then
position="--insert"
fi
iptables "${position}" ${rule[@]}
echo "Appended new iptables entry."
fi
}
main
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

@@ -1,130 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Umbrel</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="robots" content="noindex, nofollow" />
<meta name="referrer" content="no-referrer" />
<link rel="icon" href="/favicon.png">
<link rel="stylesheet" href="styles.css">
<meta name="description" content="Welcome back">
</head>
<body>
<noscript>
<strong>We're sorry but Umbrel
doesn't work properly without JavaScript enabled. Please enable it to
continue.</strong>
</noscript>
<div class="container">
<div class="view starting">
<svg class="logo" width="180" height="200" viewBox="0 0 180 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M90.166 200C93.4473 200 97.373 199.395 101.943 198.184C117.373 192.402 125.088 181.27 125.088 164.785V117.617C125.088 116.719 125.029 115.937 124.912 115.273C124.912 115.156 124.902 115.078 124.883 115.039C124.863 115 124.814 114.922 124.736 114.805C123.135 110.781 120.596 108.77 117.119 108.77H116.65L115.654 108.828H115.127C115.01 108.828 114.873 108.887 114.717 109.004C110.693 110.566 108.682 113.867 108.682 118.906V167.246C108.682 169.863 108.018 172.422 106.689 174.922C102.939 180.82 97.373 183.77 89.9902 183.77C84.4043 183.77 79.6777 182.031 75.8105 178.555C72.6465 175.977 71.0254 171.23 70.9473 164.316L70.8301 116.797C70.8301 116.367 70.791 115.918 70.7129 115.449C70.7129 115.293 70.7031 115.186 70.6836 115.127C70.6641 115.068 70.5957 114.961 70.4785 114.805C68.8379 110.703 66.2402 108.652 62.6855 108.652C61.5527 108.77 60.791 108.906 60.4004 109.062C56.4941 110.625 54.541 113.262 54.541 116.973L54.7168 166.25C54.7559 173.594 56.7871 180.215 60.8105 186.113C68.3105 195.371 78.0176 200 89.9316 200H90.166Z" fill="#5351FB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.0267 78.2397C25.0747 78.2397 19.5903 80.8572 16.1027 84.9716C13.2457 88.3419 8.19753 88.7581 4.82721 85.9011C1.45689 83.0442 1.04072 77.996 3.89766 74.6257C11.2138 65.9949 21.7471 62.2397 34.0267 62.2397C44.3462 62.2397 53.8044 64.8604 62.1984 70.1408C70.8948 64.9859 80.0577 62.2397 89.6031 62.2397C98.9359 62.2397 107.747 64.8667 115.938 69.8647C123.248 64.9426 132.023 62.6809 141.8 62.6809C154.063 62.6809 164.865 66.2318 173.642 73.7085C177.006 76.5734 177.41 81.6226 174.545 84.9861C171.68 88.3496 166.631 88.7538 163.267 85.8888C157.773 81.2084 150.804 78.6809 141.8 78.6809C133.295 78.6809 127.181 80.9311 122.714 84.787C119.22 87.8032 114.116 88.036 110.361 85.3504L110.22 85.2494C103.457 80.448 96.6344 78.2397 89.6031 78.2397C82.445 78.2397 75.1767 80.531 67.6755 85.6062C64.1535 87.9891 59.5099 87.8844 56.099 85.3452L55.8512 85.1607C49.5737 80.5653 42.3951 78.2397 34.0267 78.2397Z" fill="#5351FB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M133.969 32.9107C122.302 22.7916 107.261 17.6512 88.1548 18.0182C68.9596 18.3871 54.0522 23.9035 42.7387 34.0244C31.3296 44.2309 22.8201 59.7981 17.7954 81.5961C16.803 85.9015 12.5082 88.5872 8.20286 87.5947C3.89749 86.6023 1.21181 82.3076 2.20425 78.0022C7.71919 54.0773 17.4796 35.153 32.071 22.0997C46.7579 8.96082 65.5814 2.44902 87.8474 2.0212C110.203 1.59166 129.301 7.68281 144.452 20.8236C159.471 33.8502 169.858 53.1503 176.202 77.8057C177.303 82.0846 174.727 86.4459 170.448 87.5468C166.169 88.6478 161.808 86.0715 160.707 81.7926C154.876 59.1318 145.767 43.1438 133.969 32.9107Z" fill="#5351FB"/>
</svg>
<p><span class="spinner rotate"></span>Starting Umbrel...</p>
</div>
<div class="view restarting">
<svg class="logo" width="180" height="200" viewBox="0 0 180 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M90.166 200C93.4473 200 97.373 199.395 101.943 198.184C117.373 192.402 125.088 181.27 125.088 164.785V117.617C125.088 116.719 125.029 115.937 124.912 115.273C124.912 115.156 124.902 115.078 124.883 115.039C124.863 115 124.814 114.922 124.736 114.805C123.135 110.781 120.596 108.77 117.119 108.77H116.65L115.654 108.828H115.127C115.01 108.828 114.873 108.887 114.717 109.004C110.693 110.566 108.682 113.867 108.682 118.906V167.246C108.682 169.863 108.018 172.422 106.689 174.922C102.939 180.82 97.373 183.77 89.9902 183.77C84.4043 183.77 79.6777 182.031 75.8105 178.555C72.6465 175.977 71.0254 171.23 70.9473 164.316L70.8301 116.797C70.8301 116.367 70.791 115.918 70.7129 115.449C70.7129 115.293 70.7031 115.186 70.6836 115.127C70.6641 115.068 70.5957 114.961 70.4785 114.805C68.8379 110.703 66.2402 108.652 62.6855 108.652C61.5527 108.77 60.791 108.906 60.4004 109.062C56.4941 110.625 54.541 113.262 54.541 116.973L54.7168 166.25C54.7559 173.594 56.7871 180.215 60.8105 186.113C68.3105 195.371 78.0176 200 89.9316 200H90.166Z" fill="#5351FB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.0267 78.2397C25.0747 78.2397 19.5903 80.8572 16.1027 84.9716C13.2457 88.3419 8.19753 88.7581 4.82721 85.9011C1.45689 83.0442 1.04072 77.996 3.89766 74.6257C11.2138 65.9949 21.7471 62.2397 34.0267 62.2397C44.3462 62.2397 53.8044 64.8604 62.1984 70.1408C70.8948 64.9859 80.0577 62.2397 89.6031 62.2397C98.9359 62.2397 107.747 64.8667 115.938 69.8647C123.248 64.9426 132.023 62.6809 141.8 62.6809C154.063 62.6809 164.865 66.2318 173.642 73.7085C177.006 76.5734 177.41 81.6226 174.545 84.9861C171.68 88.3496 166.631 88.7538 163.267 85.8888C157.773 81.2084 150.804 78.6809 141.8 78.6809C133.295 78.6809 127.181 80.9311 122.714 84.787C119.22 87.8032 114.116 88.036 110.361 85.3504L110.22 85.2494C103.457 80.448 96.6344 78.2397 89.6031 78.2397C82.445 78.2397 75.1767 80.531 67.6755 85.6062C64.1535 87.9891 59.5099 87.8844 56.099 85.3452L55.8512 85.1607C49.5737 80.5653 42.3951 78.2397 34.0267 78.2397Z" fill="#5351FB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M133.969 32.9107C122.302 22.7916 107.261 17.6512 88.1548 18.0182C68.9596 18.3871 54.0522 23.9035 42.7387 34.0244C31.3296 44.2309 22.8201 59.7981 17.7954 81.5961C16.803 85.9015 12.5082 88.5872 8.20286 87.5947C3.89749 86.6023 1.21181 82.3076 2.20425 78.0022C7.71919 54.0773 17.4796 35.153 32.071 22.0997C46.7579 8.96082 65.5814 2.44902 87.8474 2.0212C110.203 1.59166 129.301 7.68281 144.452 20.8236C159.471 33.8502 169.858 53.1503 176.202 77.8057C177.303 82.0846 174.727 86.4459 170.448 87.5468C166.169 88.6478 161.808 86.0715 160.707 81.7926C154.876 59.1318 145.767 43.1438 133.969 32.9107Z" fill="#5351FB"/>
</svg>
<p><span class="spinner rotate"></span>Restarting...</p>
<p>Please do not disconnect the power supply while the restart is in progress.</p>
</div>
<div class="view shutting-down">
<svg class="logo" width="180" height="200" viewBox="0 0 180 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M90.166 200C93.4473 200 97.373 199.395 101.943 198.184C117.373 192.402 125.088 181.27 125.088 164.785V117.617C125.088 116.719 125.029 115.937 124.912 115.273C124.912 115.156 124.902 115.078 124.883 115.039C124.863 115 124.814 114.922 124.736 114.805C123.135 110.781 120.596 108.77 117.119 108.77H116.65L115.654 108.828H115.127C115.01 108.828 114.873 108.887 114.717 109.004C110.693 110.566 108.682 113.867 108.682 118.906V167.246C108.682 169.863 108.018 172.422 106.689 174.922C102.939 180.82 97.373 183.77 89.9902 183.77C84.4043 183.77 79.6777 182.031 75.8105 178.555C72.6465 175.977 71.0254 171.23 70.9473 164.316L70.8301 116.797C70.8301 116.367 70.791 115.918 70.7129 115.449C70.7129 115.293 70.7031 115.186 70.6836 115.127C70.6641 115.068 70.5957 114.961 70.4785 114.805C68.8379 110.703 66.2402 108.652 62.6855 108.652C61.5527 108.77 60.791 108.906 60.4004 109.062C56.4941 110.625 54.541 113.262 54.541 116.973L54.7168 166.25C54.7559 173.594 56.7871 180.215 60.8105 186.113C68.3105 195.371 78.0176 200 89.9316 200H90.166Z" fill="#5351FB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.0267 78.2397C25.0747 78.2397 19.5903 80.8572 16.1027 84.9716C13.2457 88.3419 8.19753 88.7581 4.82721 85.9011C1.45689 83.0442 1.04072 77.996 3.89766 74.6257C11.2138 65.9949 21.7471 62.2397 34.0267 62.2397C44.3462 62.2397 53.8044 64.8604 62.1984 70.1408C70.8948 64.9859 80.0577 62.2397 89.6031 62.2397C98.9359 62.2397 107.747 64.8667 115.938 69.8647C123.248 64.9426 132.023 62.6809 141.8 62.6809C154.063 62.6809 164.865 66.2318 173.642 73.7085C177.006 76.5734 177.41 81.6226 174.545 84.9861C171.68 88.3496 166.631 88.7538 163.267 85.8888C157.773 81.2084 150.804 78.6809 141.8 78.6809C133.295 78.6809 127.181 80.9311 122.714 84.787C119.22 87.8032 114.116 88.036 110.361 85.3504L110.22 85.2494C103.457 80.448 96.6344 78.2397 89.6031 78.2397C82.445 78.2397 75.1767 80.531 67.6755 85.6062C64.1535 87.9891 59.5099 87.8844 56.099 85.3452L55.8512 85.1607C49.5737 80.5653 42.3951 78.2397 34.0267 78.2397Z" fill="#5351FB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M133.969 32.9107C122.302 22.7916 107.261 17.6512 88.1548 18.0182C68.9596 18.3871 54.0522 23.9035 42.7387 34.0244C31.3296 44.2309 22.8201 59.7981 17.7954 81.5961C16.803 85.9015 12.5082 88.5872 8.20286 87.5947C3.89749 86.6023 1.21181 82.3076 2.20425 78.0022C7.71919 54.0773 17.4796 35.153 32.071 22.0997C46.7579 8.96082 65.5814 2.44902 87.8474 2.0212C110.203 1.59166 129.301 7.68281 144.452 20.8236C159.471 33.8502 169.858 53.1503 176.202 77.8057C177.303 82.0846 174.727 86.4459 170.448 87.5468C166.169 88.6478 161.808 86.0715 160.707 81.7926C154.876 59.1318 145.767 43.1438 133.969 32.9107Z" fill="#5351FB"/>
</svg>
<p><span class="spinner rotate"></span>Shutting down...</p>
<p>Please do not disconnect the power supply until the shutdown is complete.</p>
</div>
<div class="view shutdown-complete">
<svg class="logo dark" width="180" height="200" viewBox="0 0 180 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M90.166 200C93.4473 200 97.373 199.395 101.943 198.184C117.373 192.402 125.088 181.27 125.088 164.785V117.617C125.088 116.719 125.029 115.937 124.912 115.273C124.912 115.156 124.902 115.078 124.883 115.039C124.863 115 124.814 114.922 124.736 114.805C123.135 110.781 120.596 108.77 117.119 108.77H116.65L115.654 108.828H115.127C115.01 108.828 114.873 108.887 114.717 109.004C110.693 110.566 108.682 113.867 108.682 118.906V167.246C108.682 169.863 108.018 172.422 106.689 174.922C102.939 180.82 97.373 183.77 89.9902 183.77C84.4043 183.77 79.6777 182.031 75.8105 178.555C72.6465 175.977 71.0254 171.23 70.9473 164.316L70.8301 116.797C70.8301 116.367 70.791 115.918 70.7129 115.449C70.7129 115.293 70.7031 115.186 70.6836 115.127C70.6641 115.068 70.5957 114.961 70.4785 114.805C68.8379 110.703 66.2402 108.652 62.6855 108.652C61.5527 108.77 60.791 108.906 60.4004 109.062C56.4941 110.625 54.541 113.262 54.541 116.973L54.7168 166.25C54.7559 173.594 56.7871 180.215 60.8105 186.113C68.3105 195.371 78.0176 200 89.9316 200H90.166Z" fill="#5351FB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.0267 78.2397C25.0747 78.2397 19.5903 80.8572 16.1027 84.9716C13.2457 88.3419 8.19753 88.7581 4.82721 85.9011C1.45689 83.0442 1.04072 77.996 3.89766 74.6257C11.2138 65.9949 21.7471 62.2397 34.0267 62.2397C44.3462 62.2397 53.8044 64.8604 62.1984 70.1408C70.8948 64.9859 80.0577 62.2397 89.6031 62.2397C98.9359 62.2397 107.747 64.8667 115.938 69.8647C123.248 64.9426 132.023 62.6809 141.8 62.6809C154.063 62.6809 164.865 66.2318 173.642 73.7085C177.006 76.5734 177.41 81.6226 174.545 84.9861C171.68 88.3496 166.631 88.7538 163.267 85.8888C157.773 81.2084 150.804 78.6809 141.8 78.6809C133.295 78.6809 127.181 80.9311 122.714 84.787C119.22 87.8032 114.116 88.036 110.361 85.3504L110.22 85.2494C103.457 80.448 96.6344 78.2397 89.6031 78.2397C82.445 78.2397 75.1767 80.531 67.6755 85.6062C64.1535 87.9891 59.5099 87.8844 56.099 85.3452L55.8512 85.1607C49.5737 80.5653 42.3951 78.2397 34.0267 78.2397Z" fill="#5351FB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M133.969 32.9107C122.302 22.7916 107.261 17.6512 88.1548 18.0182C68.9596 18.3871 54.0522 23.9035 42.7387 34.0244C31.3296 44.2309 22.8201 59.7981 17.7954 81.5961C16.803 85.9015 12.5082 88.5872 8.20286 87.5947C3.89749 86.6023 1.21181 82.3076 2.20425 78.0022C7.71919 54.0773 17.4796 35.153 32.071 22.0997C46.7579 8.96082 65.5814 2.44902 87.8474 2.0212C110.203 1.59166 129.301 7.68281 144.452 20.8236C159.471 33.8502 169.858 53.1503 176.202 77.8057C177.303 82.0846 174.727 86.4459 170.448 87.5468C166.169 88.6478 161.808 86.0715 160.707 81.7926C154.876 59.1318 145.767 43.1438 133.969 32.9107Z" fill="#5351FB"/>
</svg>
<span class="text">Shutdown complete</span>
</div>
<div class="view error">
<svg class="logo danger" width="180" height="200" viewBox="0 0 180 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M90.166 200C93.4473 200 97.373 199.395 101.943 198.184C117.373 192.402 125.088 181.27 125.088 164.785V117.617C125.088 116.719 125.029 115.937 124.912 115.273C124.912 115.156 124.902 115.078 124.883 115.039C124.863 115 124.814 114.922 124.736 114.805C123.135 110.781 120.596 108.77 117.119 108.77H116.65L115.654 108.828H115.127C115.01 108.828 114.873 108.887 114.717 109.004C110.693 110.566 108.682 113.867 108.682 118.906V167.246C108.682 169.863 108.018 172.422 106.689 174.922C102.939 180.82 97.373 183.77 89.9902 183.77C84.4043 183.77 79.6777 182.031 75.8105 178.555C72.6465 175.977 71.0254 171.23 70.9473 164.316L70.8301 116.797C70.8301 116.367 70.791 115.918 70.7129 115.449C70.7129 115.293 70.7031 115.186 70.6836 115.127C70.6641 115.068 70.5957 114.961 70.4785 114.805C68.8379 110.703 66.2402 108.652 62.6855 108.652C61.5527 108.77 60.791 108.906 60.4004 109.062C56.4941 110.625 54.541 113.262 54.541 116.973L54.7168 166.25C54.7559 173.594 56.7871 180.215 60.8105 186.113C68.3105 195.371 78.0176 200 89.9316 200H90.166Z" fill="#5351FB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.0267 78.2397C25.0747 78.2397 19.5903 80.8572 16.1027 84.9716C13.2457 88.3419 8.19753 88.7581 4.82721 85.9011C1.45689 83.0442 1.04072 77.996 3.89766 74.6257C11.2138 65.9949 21.7471 62.2397 34.0267 62.2397C44.3462 62.2397 53.8044 64.8604 62.1984 70.1408C70.8948 64.9859 80.0577 62.2397 89.6031 62.2397C98.9359 62.2397 107.747 64.8667 115.938 69.8647C123.248 64.9426 132.023 62.6809 141.8 62.6809C154.063 62.6809 164.865 66.2318 173.642 73.7085C177.006 76.5734 177.41 81.6226 174.545 84.9861C171.68 88.3496 166.631 88.7538 163.267 85.8888C157.773 81.2084 150.804 78.6809 141.8 78.6809C133.295 78.6809 127.181 80.9311 122.714 84.787C119.22 87.8032 114.116 88.036 110.361 85.3504L110.22 85.2494C103.457 80.448 96.6344 78.2397 89.6031 78.2397C82.445 78.2397 75.1767 80.531 67.6755 85.6062C64.1535 87.9891 59.5099 87.8844 56.099 85.3452L55.8512 85.1607C49.5737 80.5653 42.3951 78.2397 34.0267 78.2397Z" fill="#5351FB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M133.969 32.9107C122.302 22.7916 107.261 17.6512 88.1548 18.0182C68.9596 18.3871 54.0522 23.9035 42.7387 34.0244C31.3296 44.2309 22.8201 59.7981 17.7954 81.5961C16.803 85.9015 12.5082 88.5872 8.20286 87.5947C3.89749 86.6023 1.21181 82.3076 2.20425 78.0022C7.71919 54.0773 17.4796 35.153 32.071 22.0997C46.7579 8.96082 65.5814 2.44902 87.8474 2.0212C110.203 1.59166 129.301 7.68281 144.452 20.8236C159.471 33.8502 169.858 53.1503 176.202 77.8057C177.303 82.0846 174.727 86.4459 170.448 87.5468C166.169 88.6478 161.808 86.0715 160.707 81.7926C154.876 59.1318 145.767 43.1438 133.969 32.9107Z" fill="#5351FB"/>
</svg>
<div class="error-text monitor-check">
<p><b class="text-dark">Error: External drive disconnected</b></p>
<p>The external drive was disconnected while Umbrel was running. This can sometimes happen when using an unofficial Raspberry Pi power supply.</p>
</div>
<div class="error-text semver-mismatch">
<p><b class="text-dark">Error: Version mismatch</b></p>
<p>The version of UmbrelOS on your microSD card is not compatible with the version of Umbrel on your external drive.</p>
</div>
<div class="error-text service-failed">
<p><b class="text-dark">Error: System service failed</b></p>
<p>Something went wrong when starting Umbrel. Need help? Feel free to jump on our <a href="https://community.getumbrel.com" target="_blank">community forum</a> or <a href="https://t.me/getumbrel" target="_blank">Telegram chat</a>.</p>
</div>
<div class="error-text docker-failed">
<p><b class="text-dark">Error: Failed to start containers</b></p>
<p>Something went wrong when starting Umbrel. Need help? Feel free to jump on our <a href="https://community.getumbrel.com" target="_blank">community forum</a> or <a href="https://t.me/getumbrel" target="_blank">Telegram chat</a>.</p>
</div>
<div class="error-text no-block-device">
<p><b class="text-dark">Error: No external drive found</b></p>
<p>Please connect an external drive (at least 1TB) to a USB 3.0 port (blue color) on your Raspberry Pi and restart your Umbrel.</p>
<p>Note: If it's not an external drive that you have used with Umbrel previously, all of the data on it (if any) will be erased.</p>
</div>
<div class="error-text multiple-block-devices">
<p><b class="text-dark">Error: Multiple external drives found</b></p>
<p>Umbrel only works with one external drive of atleast 1TB size. Please disconnect any additional external drives and restart your Umbrel.</p>
</div>
<div class="error-text rebinding-failed">
<p><b class="text-dark">Error: Failed to connect external drive</b></p>
<p>There was an error connecting your external drive. Please consider using the recommended hardware listed on getumbrel.com.</p>
</div>
<div class="error-text mount-failed">
<p><b class="text-dark">Error: Failed to mount external drive</b></p>
<p>This normally happens if you've experienced filesystem corruption. Need help? Feel free to jump on our <a href="https://community.getumbrel.com" target="_blank">community forum</a> or <a href="https://t.me/getumbrel" target="_blank">Telegram chat</a>.</p>
</div>
<br/><br/>
<a href="#" class="button restart"><svg width="12" height="11" viewBox="0 0 12 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.7449 4.90934C11.4816 4.73287 11.1258 4.80433 10.9503 5.06888L10.9332 5.09455C10.726 2.25044 8.3568 0 5.47409 0C2.45566 3.83785e-05 0 2.46731 0 5.50002C0 8.53273 2.45566 11 5.47409 11C5.89602 11 6.23805 10.6564 6.23805 10.2324C6.23805 9.8085 5.89602 9.46486 5.47409 9.46486C3.29815 9.46482 1.52791 7.68621 1.52791 5.50002C1.52791 3.31383 3.29815 1.53518 5.47409 1.53518C7.49086 1.53518 9.15884 3.06322 9.39208 5.02747C9.20823 4.79754 8.87694 4.74262 8.62812 4.90934C8.36482 5.08572 8.29374 5.44318 8.46929 5.70769L9.60384 7.4171C9.73773 7.61882 9.95557 7.73925 10.1866 7.73925C10.4175 7.73925 10.6353 7.61878 10.7691 7.4171L11.9037 5.70769C12.0792 5.44314 12.0081 5.08572 11.7449 4.90934Z" fill="#5351FB"/>
</svg>
Restart
</a>
<a href="#" class="button shutdown">
<svg width="11" height="12" viewBox="0 0 11 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.0243996 7.11592C-0.15516 5.21752 0.670316 3.49794 2.03946 2.40545C2.57315 1.97923 3.3712 2.34421 3.3712 3.01783C3.3712 3.26524 3.25149 3.49305 3.05697 3.64737C2.08684 4.42387 1.50328 5.64864 1.64543 7.00079C1.82997 8.76201 3.26645 10.1876 5.05707 10.3836C7.39384 10.6408 9.37897 8.84284 9.37897 6.59906C9.37897 5.40859 8.81785 4.34304 7.94249 3.64492C7.74797 3.4906 7.63076 3.26279 7.63076 3.01783C7.63076 2.35156 8.41633 1.97433 8.94504 2.39075C10.197 3.38282 11 4.89908 11 6.59906C11 9.70753 8.31159 12.2134 5.09697 11.9856C2.43848 11.8019 0.271294 9.72223 0.0243996 7.11592ZM5.49848 0C5.04958 0 4.68797 0.357633 4.68797 0.796101V4.46306C4.68797 4.90398 5.05208 5.25916 5.49848 5.25916C5.94489 5.25916 6.309 4.90153 6.309 4.46306V0.796101C6.309 0.357633 5.94738 0 5.49848 0Z" fill="#5351FB"/>
</svg>
Shutdown
</a>
<a class="button community" href="https://community.getumbrel.com" target="_blank">community</a>
</div>
</div>
<script src="/script.js"></script>
</body>
</html>
@@ -1,117 +0,0 @@
const isIframe = (window.self !== window.top);
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const on = (selector, eventName, callback) => {
for (element of document.querySelectorAll(selector)) {
element.addEventListener(eventName, event => {
event.preventDefault();
callback();
});
}
};
const setState = (key, value) => document.body.dataset[key] = value;
const getState = key => document.body.dataset[key];
const isUmbrelUp = async () => {
const response = await fetch('/manager-api/ping');
return response.status === 200 && response.redirected === false;
};
const checkForError = async () => {
const response = await fetch('/status');
const status = await response.json();
const errorCode = (status.find(service => service.status === 'errored') || {}).error;
return errorCode;
};
const isStatusServerUp = async () => {
try {
await checkForError();
return true;
} catch (e) {
return false;
}
};
const power = async action => {
try {
const token = await (await fetch('/token')).json();
const response = await fetch(`/${action}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({token}),
});
if (!response.ok) {
return false;
}
} catch (e) {} finally {
return true;
}
};
on('.shutdown', 'click', async () => {
if (! await power('shutdown')) {
alert('Failed to shutdown Umbrel');
return;
}
setState('status', 'shutting-down');
await delay(30000);
setState('status', 'shutdown-complete');
});
on('.restart', 'click', async () => {
if (! await power('restart')) {
alert('Failed to restart Umbrel');
return;
}
setState('status', 'restarting');
await delay(10000);
// Wait for Umbrel to come back up then reload the page.
while (true) {
try {
if (await isStatusServerUp() || await isUmbrelUp()) {
window.location.reload();
}
} catch (e) {}
await delay(1000);
}
});
const main = async () => {
// Protect against clickjacking
if (isIframe) {
document.body.innerText = 'For security reasons Umbrel doesn\'t work in an iframe.';
return;
}
// Set initial loading state
setState('status', 'starting');
// Start loop
while (getState('status') === 'starting') {
try {
// If Umbrel is ready, reload
if (await isUmbrelUp()) {
window.location.reload();
}
// If there are errors, set error state
const error = await checkForError();
if (error) {
setState('status', 'error');
setState('error', error);
}
} catch (e) {
console.error(e);
}
await delay(1000);
}
};
main();
@@ -1,116 +0,0 @@
body {
background-color: #F7F9FB;
font-family: system-ui, -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", "Segoe UI", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
margin: 0;
padding: 0;
font-size: 0.9rem;
font-weight: 400;
line-height: 1.5;
color: #6c757d;
text-align: center;
}
span, p {
margin: 0 0 1em 0;
}
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0 20px;
}
.view {
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
}
[data-status="starting"] .view.starting,
[data-status="restarting"] .view.restarting,
[data-status="shutting-down"] .view.shutting-down,
[data-status="shutdown-complete"] .view.shutdown-complete,
[data-status="error"] .view.error {
display: flex;
}
.error-text {
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
}
[data-error="monitor-check"] .error-text.monitor-check,
[data-error="semver-mismatch"] .error-text.semver-mismatch,
[data-error="service-failed"] .error-text.service-failed,
[data-error="docker-failed"] .error-text.docker-failed,
[data-error="no-block-device"] .error-text.no-block-device,
[data-error="multiple-block-devices"] .error-text.multiple-block-devices,
[data-error="rebinding-failed"] .error-text.rebinding-failed,
[data-error="mount-failed"] .error-text.mount-failed {
display: flex;
}
.logo {
max-height: 200px;
width: auto;
display: block;
margin-bottom: 80px;
}
.logo.danger path {
fill: #F46E6E;
}
.logo.dark path {
fill: #858997;
}
.text-dark {
color: #141821;
}
.spinner {
font-size: 80%;
font-weight: 400;
box-sizing: border-box;
display: inline-block;
vertical-align: text-bottom;
border: 0.25em solid currentColor;
border-right-color: transparent;
border-radius: 50%;
width: 1rem;
height: 1rem;
border-width: 0.2em;
margin: 0.17em 1em 0 0;
}
.button {
display: block;
margin-bottom: 1rem;
text-decoration: none;
color: #5351FB;
}
.button svg {
margin-right: 0.1rem;
}
.button.community {
position: absolute;
bottom: 0;
left: auto;
right: auto;
}
.rotate {
animation: rotate 1s linear infinite;
}
@keyframes rotate {
from {transform: rotate(0deg)}
to {transform: rotate(360deg)}
}
@@ -1,32 +0,0 @@
#!/usr/bin/env -S python3 -u
from pathlib import Path
import subprocess
from util.server import Server
from util.csrf import get_csrf_token, verify_csrf_token
import util.status_file as status_file
STATUS_FILE_PATH='/umbrel-status'
def csrf(request):
if not verify_csrf_token(request['post_data']['token']):
raise Exception("Invalid CSRF token")
def get_relative_path(path):
script_dir = Path(__file__).resolve().parent
return str(script_dir.joinpath(path))
def run(command):
if not status_file.contains_errors(STATUS_FILE_PATH):
raise Exception("Running commands is disabled if there are no status errors")
subprocess.run(command, check=True)
def main():
server = Server(directory=get_relative_path('static'))
server.get('/status', lambda _: status_file.parse(STATUS_FILE_PATH))
server.get('/token', lambda _: get_csrf_token())
server.post('/shutdown', csrf, lambda _: run(['poweroff']))
server.post('/restart', csrf, lambda _: run(['reboot']))
server.listen(8000)
main()
@@ -1,9 +0,0 @@
import secrets
csrf_token = secrets.token_hex(32)
def get_csrf_token():
return csrf_token
def verify_csrf_token(token):
return token == get_csrf_token()
@@ -1,88 +0,0 @@
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
import json
class Server():
def __init__(self, directory='.'):
self.directory = directory
self.get_routes = []
self.post_routes = []
# Register a GET handler
def get(self, path, *handlers):
self.get_routes.append({'path': path, 'handlers': handlers})
# Register a POST handler
def post(self, path, *handlers):
self.post_routes.append({'path': path, 'handlers': handlers})
# Create the server
def listen(self, port):
directory = self.directory
post_routes = self.post_routes
get_routes = self.get_routes
# Create request handler class
class Handler(SimpleHTTPRequestHandler):
# Patch the constructor with our directory
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=directory, **kwargs)
def send_error(self, code, message=None):
if code == 404 and self.path != '/':
self.send_response(302)
self.send_header('Location', '/')
self.end_headers()
else:
super().send_error(code, message)
# JSON helper
def send_json_response(self, status_code, data=None):
if status_code >= 400:
data = {'error': True}
self.send_response(status_code)
self.send_header('Content-type', 'application/json')
self.end_headers()
self.wfile.write(bytes(json.dumps(data), 'utf8'))
# Loop over routes and execute one if there's a match
def handle_routes(self, routes):
# Loop over the registered routes
for route in routes:
# Check if we have a match
if self.path == route['path']:
try:
request = {}
# Parse post data
if self.headers['Content-Length'] and self.headers['Content-Type'] == 'application/json':
content_length = int(self.headers['Content-Length'])
raw_post_data = self.rfile.read(content_length)
request['post_data'] = json.loads(raw_post_data.decode('utf-8'))
# Execute handlers
for handler in route['handlers']:
response = handler(request)
self.send_json_response(200, response)
except Exception as e:
# If it failed, send internal server error
print(f'Exception: {e}')
self.send_json_response(500)
# Route matched, return True
return True
# No routes matched, return False
return False
# Try to match a route aganst a GET request
# else fall back to static server
def do_GET(self):
if not self.handle_routes(get_routes):
super().do_GET()
# Try to match a route aganst a POST request
# else return 404
def do_POST(self):
if not self.handle_routes(post_routes):
self.send_json_response(404)
# Start HTTP server and attach handler
print(f'Server listening on port {port}...')
with ThreadingHTTPServer(('', port), Handler) as server:
server.serve_forever()
@@ -1,54 +0,0 @@
#!/usr/bin/env python3
from pathlib import Path
# A status file has status entries, one per line, in the format:
# $id:$status[:$error-code]
# e.g:
# foo:started
# foo:completed
# bar:started
# fizz:started
# fizz:completed
# bar:errored:no-network-access
# Creates an empty status file
def create_empty(status_file_path):
Path(status_file_path).open('w').close()
# Returns the index of the dict with a matching ID from a list
def _get_index(list, id):
for i, dict in enumerate(list):
if dict['id'] == id:
return i
return None
# Parses a status file
def parse(status_file_path):
# Empty list for holding the status entries
statuses = []
# Read the status file and loop over the entries
for entry in Path(status_file_path).read_text().split():
# Decode parts with ":" seperator
parts = entry.split(':')
parsed_entry = {
'id': parts[0],
'status': parts[1],
'error': parts[2] if len(parts) >2 else None
}
# Check if we already have an entry for this id
index = _get_index(statuses, parsed_entry['id'])
# If we don't, append the entry
if index == None:
statuses.append(parsed_entry)
# If we do, update the entry
else:
statuses[index] = parsed_entry
return statuses
# Checks if a status file contains errors
def contains_errors(status_file_path):
status = parse(status_file_path)
for entry in status:
if entry['status'] == 'errored':
return True
return False
-109
View File
@@ -1,109 +0,0 @@
#!/usr/bin/env python3
import sys
import os
import time
import subprocess
import io
import qrcode
umbrel_ascii = '''
,;###GGGGGGGGGGl#Sp
,##GGGlW""^' '`""%GGGG#S,
,#GGG" "lGG#o
#GGl^ '$GG#
,#GGb \GGG,
lGG" "GGG
#GGGlGGGl##p,,p##lGGl##p,,p###ll##GGGG
!GGGlW"""*GGGGGGG#""""WlGGGGG#W""*WGGGGS
"" "^ '" ""
@GGS lG#
!GGG !GGG
!GGG !GGG
!GGG !GGG
!GGG !GGG
!GGG !GGG
'GGG $GGl
"GGG#psqp##GG#
"%GGGGGG#"
'''.strip('\n')
def create_qr(data):
output_buffer = io.TextIOWrapper(io.BytesIO(), sys.stdout.encoding)
qr = qrcode.QRCode(border=0, error_correction=qrcode.constants.ERROR_CORRECT_Q)
qr.add_data(data)
qr.print_ascii(out=output_buffer)
output_buffer.seek(0)
qr_ascii = output_buffer.read().strip()
return qr_ascii
def combine_ascii(left_ascii, right_ascii, spacing=0):
left_lines = left_ascii.split('\n')
right_lines = right_ascii.split('\n')
no_left_lines = len(left_lines)
no_right_lines = len(right_lines)
left_width = max(list(map(lambda line: len(line), left_lines)))
max_height = max(no_left_lines, no_right_lines)
lines = []
for i in range(max_height):
left_line = left_lines[i] if i < no_left_lines else ""
right_line = right_lines[i] if i < no_right_lines else ""
padding = " " * ((left_width - len(left_line)) + spacing)
lines.append(left_line + padding + right_line)
return "\n".join(lines)
def read_file_when_available(file_path, timeout):
start = time.time()
while not os.path.exists(file_path):
if (time.time() - start) > timeout:
return False
time.sleep(1)
with open(file_path, "r") as file:
file_contents = file.read()
return file_contents
def run(command):
result = subprocess.run(command, stdout=subprocess.PIPE)
return result.stdout.decode('utf-8').rstrip("\n")
def main():
timeout = 30
tor_hostname_file = "/home/umbrel/umbrel/tor/data/web/hostname"
tor_hostname = read_file_when_available(tor_hostname_file, timeout)
ip = run(['hostname', '-I']).split(" ")[0]
if not tor_hostname or not ip:
print("Couldn't get connection details")
return
tor_hostname_qr_ascii = create_qr(tor_hostname.strip())
ascii_banner = combine_ascii(umbrel_ascii, tor_hostname_qr_ascii, spacing=10)
connection_details = f"""
{ascii_banner}
Your Umbrel is up and running at:
http://umbrel.local
http://{ip}
http://{tor_hostname}
"""
print(connection_details)
main()
+4
View File
@@ -1,6 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
# This script is used to bootstrap the mender update process
# The update server references it in the form:
# https://raw.githubusercontent.com/getumbrel/umbrel/<tag>/scripts/update-script
update_url=""
if ! command -v mender &> /dev/null
-76
View File
@@ -1,76 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# This script needs to be in this exact location for legacy reasons.
#
# It bootstraps the umbrelOS 1.0 update for old 0.5.x Umbrel installs.
# During OTA they download the latest release tarball from GitHub, extract
# it and then execute ./scripts/*-run.sh to process the update. So now we just
# have this ./scripts/bootstrap-run.sh in this exact location in the codebase
# so old Umbrel installs can bootstrap into the new mender based update system
# and reboot into the new OS.
RELEASE=$1
UMBREL_ROOT=$2
UPDATE_ROOT=$(readlink -f "${PWD}/../..")
# Make sure we correctly display an error if the bootstrap fails
function fail_update() {
reason="${1}"
echo "${reason}" | tee "${UMBREL_ROOT}/statuses/update-failure"
echo "Update failed!"
exit 1
}
function is_pi() {
cat /proc/cpuinfo | grep --quiet 'Raspberry Pi'
}
function is_home() {
dmidecode -t system | grep --silent 'Umbrel Home'
}
echo "Hardware support detection"
# Raspberry Pi
if is_pi
then
echo "Raspberry Pi detected"
fail_update "Please install umbrelOS 1.0 on your microSD card to update: https://link.umbrel.com/pi-update"
fi
# Not an Umbrel Home (custom Linux install)
if ! is_home
then
echo "Custon Linux install detected"
fail_update "umbrelOS 1.0 is not yet supported for custom Linux installs: https://link.umbrel.com/linux-update"
fi
# If we get here we're running on an Umbrel Home
echo "Umbrel Home detected"
# Flash new update
echo '{"state": "installing", "progress": 25, "description": "Installing umbrelOS 1.0, this may take a while", "updateTo": ""}' > "${UMBREL_ROOT}/statuses/update-status.json"
mender install https://download.umbrel.com/release/1.0.4/umbrelos-amd64.update
# Reboot
cat <<EOF > "$UMBREL_ROOT"/statuses/update-status.json
{"state": "installing", "progress": 97, "description": "Restarting Umbrel Home"}
EOF
# Sleep 5 seconds to give the frontend a chance to poll
# and pull in latest update status
echo "Sleeping 5 seconds"
sleep 5
# The following two cleanups shouldn't be needed since we'll reboot into a 1.0 install
# but we do it just incase something goes wrong and we end up back in 0.5.x
# Remove update lockfile so the next OTA update doesn't fail
rm -f "${UMBREL_ROOT}/statuses/update-in-progress"
# Write out a status file which will be used within scripts/start
# To complete the update when the system boots up again
touch "${UMBREL_ROOT}/statuses/umbrel-update-reboot-performed"
shutdown --reboot now
exit 0
-178
View File
@@ -1,178 +0,0 @@
#!/usr/bin/env bash
if [[ $UID != 0 ]]; then
echo "Update script must be run as root"
exit 1
fi
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
}
check_semver_range () {
local range="${1}"
local version="${2}"
"${UMBREL_ROOT}/scripts/umbrel-os/semver" -r "${range}" "${version}" | grep --quiet "^${version}$"
}
check_dependencies jq curl rsync
UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../..)"
update_type=""
if [[ "${1}" == "--ota" ]]; then
update_type="ota"
elif [[ "${1}" == "--path" ]]; then
update_type="path"
update_path="${2}"
elif [[ "${1}" == "--repo" ]]; then
update_type="repo"
descriptor="${2}"
if [[ "${descriptor}" != *"#"* ]]; then
descriptor="${descriptor}#master"
fi
repo="${descriptor%%#*}"
branch="${descriptor#*#}"
else
echo "Update requires \"--ota\", \"--path <path>\", or \"--repo user/repo[#branch]\" option."
exit 1
fi
if [[ "${update_type}" == "repo" ]]; then
repo_path="/tmp/umbrel-update/"
rm -rf "${repo_path}" 2> /dev/null || true
mkdir -p "${repo_path}"
git clone --single-branch --branch "${branch}" "https://github.com/${repo}.git" "${repo_path}"
# Patch version to show development build
commit=$(git --git-dir "${repo_path}/.git" rev-parse --short HEAD)
mv "${repo_path}/info.json" "${repo_path}/info.json.orig"
jq \
--arg commit "${commit}" \
'.build = $commit' \
"${repo_path}/info.json.orig" \
> "${repo_path}/info.json"
# Now hand over to the path updater
update_type="path"
update_path="${repo_path}"
fi
if [[ "${update_type}" == "path" ]] && [[ ! -f "${update_path}/.umbrel" ]]; then
echo "Update path doesn't seem to be an Umbrel install."
exit 1
fi
# This will cover only --repo and --path usage
if [[ "${update_type}" == "path" ]] && [[ "$*" != *"--skip-version-check"* ]]; then
current_version=$(cat "${UMBREL_ROOT}/info.json" | jq --raw-output '.version')
update_requires=$(cat "${update_path}/info.json" | jq --raw-output '.requires')
if ! check_semver_range "${update_requires}" "${current_version}"; then
echo "Error: Your Umbrel must first satisify this version: ${update_requires}"
echo " Your Umbrel version is: ${current_version}"
exit 1
fi
fi
if [[ "${update_type}" == "ota" ]]; then
RELEASE=$(cat "$UMBREL_ROOT"/statuses/update-status.json | jq .updateTo -r)
elif [[ "${update_type}" == "path" ]]; then
RELEASE=$(cat "$update_path"/info.json | jq .version -r)
fi
echo
echo "======================================="
echo "=============== UPDATE ================"
echo "======================================="
echo "========== Stage: Download ============"
echo "======================================="
echo
# Make sure an update is not in progres
if [[ -f "$UMBREL_ROOT/statuses/update-in-progress" ]]; then
echo "An update is already in progress. Exiting now."
exit 2
fi
echo "Creating lock"
touch "$UMBREL_ROOT"/statuses/update-in-progress
# Cleanup just in case there's temp stuff lying around from previous update
echo "Cleaning up any previous mess"
[[ -d "$UMBREL_ROOT"/.umbrel-"$RELEASE" ]] && rm -rf "$UMBREL_ROOT"/.umbrel-"$RELEASE"
# Update status file
cat <<EOF > "$UMBREL_ROOT"/statuses/update-status.json
{"state": "installing", "progress": 10, "description": "Downloading Umbrel $RELEASE", "updateTo": "$RELEASE"}
EOF
# Download new release
if [[ "${update_type}" == "ota" ]]; then
echo "Downloading Umbrel ${RELEASE}"
mkdir -p "${UMBREL_ROOT}/.umbrel-${RELEASE}"
cd "${UMBREL_ROOT}/.umbrel-${RELEASE}"
curl -L "https://github.com/getumbrel/umbrel/archive/$RELEASE.tar.gz" | tar -xz --strip-components=1
fi
# Copy over new release from path
if [[ "${update_type}" == "path" ]]; then
echo "Copying Umbrel ${RELEASE} from ${update_path}"
mkdir -p "${UMBREL_ROOT}/.umbrel-${RELEASE}"
cp --recursive \
--archive \
--no-target-directory \
"${update_path}" "${UMBREL_ROOT}/.umbrel-${RELEASE}"
cd "${UMBREL_ROOT}/.umbrel-${RELEASE}"
fi
# Run update scripts
echo "Running update install scripts of the new release"
cd scripts/update
UPDATE_INSTALL_SCRIPTS=$(ls *-run.sh)
for script in $UPDATE_INSTALL_SCRIPTS; do
if [[ -x $script ]]; then
echo
echo "== Begin Update Script $script =="
./"$script" "$RELEASE" "$UMBREL_ROOT"
echo "== End Update Script $script =="
echo
fi
done
# Delete cloned repo
echo "Deleting cloned repository"
[[ -d "$UMBREL_ROOT"/.umbrel-"$RELEASE" ]] && rm -rf "$UMBREL_ROOT"/.umbrel-"$RELEASE"
echo "Removing lock"
rm -f "$UMBREL_ROOT"/statuses/update-in-progress
# Make sure the update-status file isn't stuck in an
# unresolved state (i.e anything except "success" or "failed")
update_state=$(cat "${UMBREL_ROOT}/statuses/update-status.json" | jq .state -r)
# Show error
update_failure_status_file="${UMBREL_ROOT}/statuses/update-failure"
if [[ -f "${update_failure_status_file}" ]]; then
reason=$(cat "${update_failure_status_file}")
rm "${update_failure_status_file}"
cat <<EOF > "${UMBREL_ROOT}/statuses/update-status.json"
{"state": "failed", "progress": 100, "description": "${reason}", "updateTo": ""}
EOF
fi
if [[ "${update_state}" != "success" ]] && [[ "${update_state}" != "failed" ]]; then
# Sleep for a few seconds to make sure the user is on the update view
sleep 8
cat <<EOF > "${UMBREL_ROOT}/statuses/update-status.json"
{"state": "failed", "progress": 100, "description": "An unexpected error occured", "updateTo": ""}
EOF
fi
exit 0