diff --git a/.gitignore b/.gitignore
index f964293..e8c8e25 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,10 @@
.ssh
.viminfo
+# Python bytecode
+__pycache__
+*.py[cod]
+
# umbrel-dev
docker-compose.override.yml
diff --git a/scripts/start b/scripts/start
index ae140bc..751add1 100755
--- a/scripts/start
+++ b/scripts/start
@@ -31,8 +31,13 @@ check_dependencies rsync jq curl
UMBREL_ROOT="$(dirname $(readlink -f "${BASH_SOURCE[0]}"))/.."
UMBREL_LOGS="${UMBREL_ROOT}/logs"
+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
@@ -109,3 +114,5 @@ echo " http://${DEVICE_IP}"
if [[ ! -z "${hidden_service_url:-}" ]]; then
echo " http://${hidden_service_url}"
fi
+
+$set_status umbrel completed
diff --git a/scripts/umbrel-os/external-storage/monitor b/scripts/umbrel-os/external-storage/monitor
index 2da070a..62a540b 100755
--- a/scripts/umbrel-os/external-storage/monitor
+++ b/scripts/umbrel-os/external-storage/monitor
@@ -6,6 +6,8 @@ UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../../..)"
block_device="${1}"
mount_point="${2}"
+set_status="/sd-root/${UMBREL_ROOT}/scripts/umbrel-os/status-server/set-status"
+
check_if_not_already_running() {
if ps ax | grep $0 | grep -v $$ | grep bash | grep -v grep
then
@@ -42,6 +44,7 @@ main () {
done
echo "Stopping Umbrel due to failed storage device check..."
+ $set_status mount errored monitor-check
docker kill $(docker ps -aq)
}
diff --git a/scripts/umbrel-os/external-storage/mount b/scripts/umbrel-os/external-storage/mount
index 6d7b4a7..80cf485 100755
--- a/scripts/umbrel-os/external-storage/mount
+++ b/scripts/umbrel-os/external-storage/mount
@@ -21,6 +21,8 @@ EXTERNAL_DOCKER_DIR="${MOUNT_POINT}/docker"
SWAP_DIR="/swap"
SWAP_FILE="${SWAP_DIR}/swapfile"
+set_status="${UMBREL_ROOT}/scripts/umbrel-os/status-server/set-status"
+
check_root () {
if [[ $UID != 0 ]]; then
echo "This script must be run as root"
@@ -143,6 +145,7 @@ copy_docker_to_external_storage () {
}
main () {
+ $set_status mount started
echo "Running external storage mount script..."
check_root
check_dependencies sed wipefs parted mount sync umount
@@ -164,6 +167,7 @@ main () {
if [[ $retry_for_block_devices -gt 20 ]]; then
echo "No block devices found in 20 tries..."
echo "Exiting mount script without doing anything"
+ $set_status mount errored no-block-device
exit 1
fi
@@ -172,6 +176,7 @@ main () {
if [[ $no_of_block_devices -gt 1 ]]; then
echo "Multiple block devices found, only one drive is supported"
echo "Exiting mount script without doing anything"
+ $set_status mount errored multiple-block-devices
exit 1
fi
@@ -192,6 +197,7 @@ main () {
if [[ $retry_for_usb_devices -gt 10 ]]; then
echo "USB devices weren't registered after 10 tries..."
echo "Exiting mount script without doing anything"
+ $set_status mount errored rebinding-failed
exit 1
fi
@@ -206,16 +212,16 @@ main () {
mount_partition "${partition_path}"
echo "Checking if device contains an Umbrel install..."
-
+
if [[ -f "${EXTERNAL_UMBREL_ROOT}"/.umbrel ]]; then
echo "Yes, it contains an Umbrel install"
- else
+ else
echo "No, it doesn't contain an Umbrel install"
echo "Unmounting partition..."
unmount_partition
setup_new_device $block_device $partition_path
fi
-
+
else
echo "No, it's not ext4"
setup_new_device $block_device $partition_path
@@ -266,6 +272,7 @@ main () {
${UMBREL_ROOT}/scripts/umbrel-os/external-storage/monitor ${block_device} ${MOUNT_POINT} &
echo "Mount script completed successfully!"
+ $set_status mount completed
}
main
diff --git a/scripts/umbrel-os/external-storage/update-from-sdcard b/scripts/umbrel-os/external-storage/update-from-sdcard
index 3a93c82..3092446 100755
--- a/scripts/umbrel-os/external-storage/update-from-sdcard
+++ b/scripts/umbrel-os/external-storage/update-from-sdcard
@@ -6,6 +6,8 @@ UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../../..)"
SD_MOUNT_POINT="/sd-root"
SD_UMBREL_ROOT="${SD_MOUNT_POINT}${UMBREL_ROOT}"
+set_status="${UMBREL_ROOT}/scripts/umbrel-os/status-server/set-status"
+
check_root () {
if [[ $UID != 0 ]]; then
echo "This script must be run as root"
@@ -31,12 +33,14 @@ check_semver_range () {
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
@@ -48,11 +52,13 @@ main () {
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
echo "Yes, it does, attempting an automatic update..."
"${UMBREL_ROOT}/scripts/update/update" --path "${SD_UMBREL_ROOT}"
+ $set_status sdcard-update completed
}
main
diff --git a/scripts/umbrel-os/services/umbrel-status-server-iptables-update.service b/scripts/umbrel-os/services/umbrel-status-server-iptables-update.service
new file mode 100644
index 0000000..14434ea
--- /dev/null
+++ b/scripts/umbrel-os/services/umbrel-status-server-iptables-update.service
@@ -0,0 +1,25 @@
+# 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
diff --git a/scripts/umbrel-os/services/umbrel-status-server.service b/scripts/umbrel-os/services/umbrel-status-server.service
new file mode 100644
index 0000000..4133277
--- /dev/null
+++ b/scripts/umbrel-os/services/umbrel-status-server.service
@@ -0,0 +1,21 @@
+# 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
diff --git a/scripts/umbrel-os/status-server/set-status b/scripts/umbrel-os/status-server/set-status
new file mode 100755
index 0000000..0231de0
--- /dev/null
+++ b/scripts/umbrel-os/status-server/set-status
@@ -0,0 +1,31 @@
+#!/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}"
diff --git a/scripts/umbrel-os/status-server/setup b/scripts/umbrel-os/status-server/setup
new file mode 100755
index 0000000..fe9ee22
--- /dev/null
+++ b/scripts/umbrel-os/status-server/setup
@@ -0,0 +1,24 @@
+#!/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"
diff --git a/scripts/umbrel-os/status-server/setup-iptables b/scripts/umbrel-os/status-server/setup-iptables
new file mode 100755
index 0000000..12951b5
--- /dev/null
+++ b/scripts/umbrel-os/status-server/setup-iptables
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+UMBREL_ROOT="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/../../..)"
+
+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
+ iptables --append ${rule[@]}
+ echo "Appended new iptables entry."
+}
+
+main
diff --git a/scripts/umbrel-os/status-server/static/favicon.png b/scripts/umbrel-os/status-server/static/favicon.png
new file mode 100644
index 0000000..2ee9d95
Binary files /dev/null and b/scripts/umbrel-os/status-server/static/favicon.png differ
diff --git a/scripts/umbrel-os/status-server/static/index.html b/scripts/umbrel-os/status-server/static/index.html
new file mode 100644
index 0000000..34ec82f
--- /dev/null
+++ b/scripts/umbrel-os/status-server/static/index.html
@@ -0,0 +1,114 @@
+
+
+
+
+ Umbrel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Starting Umbrel...
+
+
+
+
+
Restarting...
+
Please do not disconnect the power supply while the restart is in progress.
+
+
+
+
+
Shutting down...
+
Please do not disconnect the power supply until the shutdown is complete.
+
+
+
+
+ Shutdown complete
+
+
+
+
+
+
+
Error: External drive disconnected
+
The external drive was disconnected while Umbrel was running. This can sometimes happen when using an unofficial Raspberry Pi power supply.
+
+
+
+
Error: Version mismatch
+
The version of UmbrelOS on your microSD card is not compatible with the version of Umbrel on your external drive.
+
+
+
+
Error: No external drive found
+
Please connect an external drive (at least 1TB) to a USB 3.0 port (blue color) on your Raspberry Pi and restart your Umbrel.
+
+
+
+
Error: Multiple external drives found
+
Umbrel only works with one external drive of atleast 1TB size. Please disconnect any additional external drives and restart your Umbrel.
+
+
+
+
Error: Failed to connect external drive
+
There was an error connecting your external drive. Please consider using the recommended hardware listed on getumbrel.com.