Skip to content

Commit

Permalink
Umbrel Home hardware reset
Browse files Browse the repository at this point in the history
  • Loading branch information
lukechilds committed Jun 17, 2023
1 parent bd330d7 commit fb4b8d1
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 0 deletions.
26 changes: 26 additions & 0 deletions server/update/index.js
@@ -1,8 +1,14 @@
import fse from 'fs-extra'
import {$} from 'execa'
import pRetry from 'p-retry'

import isUmbrelHome from '../utilities/is-umbrel-home.js'

async function copyFromOverlay({updateRoot, path}) {
const overlayRoot = `${updateRoot}/server/update/umbrel-os-overlay`
return fse.copy(`${overlayRoot}${path}`, path)
}

async function updateNetworkManager() {
console.log('Patching network-manager to use dhclient...')

Expand All @@ -25,6 +31,22 @@ managed=false
await $`systemctl restart NetworkManager`
}

async function activatePowerButtonRecovery({updateRoot}) {
console.log('Activating power button recovery...')

console.log('Registering acpi event handlers...')
await copyFromOverlay({updateRoot, path: '/etc/acpi/events/power-button'})
await copyFromOverlay({updateRoot, path: '/etc/acpi/power-button.sh'})
console.log('Installing acpid...')
await $`apt-get update --yes`
await $`apt-get install --yes acpid`
await $`systemctl restart acpid`

console.log('Telling logind to ignore power button presses...')
await copyFromOverlay({updateRoot, path: '/etc/systemd/logind.conf.d/power-button.conf'})
await $`systemctl restart systemd-logind`
}

export default async function update({updateRoot, umbrelRoot}) {
console.log(`Running migrations from "${updateRoot}" on "${umbrelRoot}"`)

Expand All @@ -48,5 +70,9 @@ export default async function update({updateRoot, umbrelRoot}) {
console.log('Running Umbrel Home specific migrations...')

await updateNetworkManager()

await pRetry(() => activatePowerButtonRecovery({updateRoot}), {
retries: 3,
})
}
}
2 changes: 2 additions & 0 deletions server/update/umbrel-os-overlay/etc/acpi/events/power-button
@@ -0,0 +1,2 @@
event=button/power.*PBTN
action=systemd-cat -t umbrel-power-button /etc/acpi/power-button.sh &
87 changes: 87 additions & 0 deletions server/update/umbrel-os-overlay/etc/acpi/power-button.sh
@@ -0,0 +1,87 @@
#!/bin/bash

# Configuration
RECOVERY_SEQUENCE_COUNT=10
LISTEN_TIME=1
STATE_FILE="/tmp/power_button_state"
PASSWORD_RESET_FLAG="/tmp/password_reset_flag"

reset_umbrel_password() {
user_json="/home/umbrel/umbrel/db/user.json"
new_password='$2b$10$PDwSSnPmfCQJh3x72KjKs.Nb7NgU62gftuic991GkRyFMcIowpTv2'

# The seed is no longer used for anything but it needs to be present in the user.json file
# and decryptable by the password for the legacy password change logic to work.
new_seed='TgXPdw/2PAVunpgU/gC+Mw==$87da593006e88d358f2c8ea6fb060faf$8QPHQcfgevnn$9f16c056e076b30c2d9232c8d945033c12a3c35e97f0f43e50e99d7087adb027$250000$cbc'

echo "umbrel:umbrel" | chpasswd

if ! [ -f "$user_json" ]; then
echo "Error: File not found."
return
fi

JSON=$(cat "$user_json" 2>/dev/null)

if ! jq -e . >/dev/null 2>&1 <<<"$JSON"; then
echo "Error: Invalid JSON file."
return
fi

if ! jq -e '.password' >/dev/null 2>&1 <<<"$JSON"; then
echo "Error: Password property not found in the JSON file."
return
fi

jq --arg new_password "$new_password" --arg new_seed "$new_seed" '.password = $new_password | .seed = $new_seed' <<<"$JSON" >"$user_json"
echo "Password updated successfully."
}

# Function to handle power button presses
handle_press() {
echo "Power button pressed!"
# Append the current timestamp to the state file
echo "$(date +%s)" >> "${STATE_FILE}"

# Check if the last n button presses happened recently
num_entries=$(wc -l < "${STATE_FILE}")
last_timestamps=$(tail -n "${RECOVERY_SEQUENCE_COUNT}" "${STATE_FILE}")
first_timestamp=$(echo "${last_timestamps}" | head -n 1)
last_timestamp=$(echo "${last_timestamps}" | tail -n 1)
total_allowed_duration=$((LISTEN_TIME * RECOVERY_SEQUENCE_COUNT))
if [[ "${num_entries}" -ge "${RECOVERY_SEQUENCE_COUNT}" ]] && [[ $((last_timestamp - first_timestamp)) -lt "${total_allowed_duration}" ]]; then
echo "Recovery sequence detected. Initiating factory reset."
# This flag indicates that a password reset has been initiated so the previous button presses
# don't also initiate a reset
touch "${PASSWORD_RESET_FLAG}"
reset_umbrel_password

# Remove the password reset flag after all previous button press event handlers have died
# so future resets will work.
sleep "${LISTEN_TIME}"
rm "${PASSWORD_RESET_FLAG}"
exit
fi

# Listen for additional button presses
echo "Listening for additional button presses for ${LISTEN_TIME} seconds..."
sleep "${LISTEN_TIME}"

# Read the latest timestamp
latest_timestamp=$(tail -n 1 "${STATE_FILE}")

if [[ "${latest_timestamp}" -ne "${last_timestamp}" ]] || [[ -e "${PASSWORD_RESET_FLAG}" ]]; then
# Another button press has been registered or a recovery has just been initiated.
# Exit this handler.
exit
fi

echo "Nothing else happened."
}

# Check if we should do any special handling and exit early if we do.
handle_press

# If we get here no special handling is needed and we should trigger a shutdown.
echo "Shutting down the system."
poweroff
@@ -0,0 +1,6 @@
# We want logind to ignore the power button press events.
# We will register custom acpi event handlers to handle this
# ourselves.

[Login]
HandlePowerKey=ignore

0 comments on commit fb4b8d1

Please sign in to comment.