-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add recovery password reset menu #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 6 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
e2c6083
Add recovery password reset menu
SimonFair 85768e0
Fix recovery dataset enumeration
SimonFair 2914bc0
Show password recovery progress
SimonFair 110f93a
Fix recovery menu shellcheck warnings
SimonFair cb03aad
Show password recovery log on completion
SimonFair ab890b9
Use A for recovery password reset
SimonFair 66a7633
Harden recovery menu config lookup
SimonFair 9689699
Prevent recovery config symlink escape
SimonFair File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| #!/bin/bash | ||
| set -euo pipefail | ||
|
|
||
| ui_backend="" | ||
| RECOVERY_LOG=() | ||
| SCRIPT_SELF_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| COMMON_MENU_LIB="" | ||
|
|
||
| for candidate in "${MENU_GUI_COMMON_LIB:-}" "/boot/install/menu_gui_common.sh" "$SCRIPT_SELF_DIR/menu_gui_common.sh"; do | ||
| [[ -n "$candidate" && -f "$candidate" ]] || continue | ||
| COMMON_MENU_LIB="$candidate" | ||
| break | ||
| done | ||
|
|
||
| if [[ -z "$COMMON_MENU_LIB" ]]; then | ||
| echo "Missing common menu library (menu_gui_common.sh)." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| # shellcheck disable=SC1090 | ||
| . "$COMMON_MENU_LIB" | ||
|
|
||
| recovery_status() { | ||
| local message="$1" | ||
|
|
||
| RECOVERY_LOG+=("$(date '+%H:%M:%S') $message") | ||
|
|
||
| case "$ui_backend" in | ||
| whiptail) | ||
| whiptail --title "Password Reset" --infobox "$message" 8 80 | ||
| ;; | ||
| dialog) | ||
| dialog --title "Password Reset" --infobox "$message" 8 80 | ||
| ;; | ||
| *) | ||
| printf '[Password Reset] %s\n' "$message" | ||
| ;; | ||
| esac | ||
| } | ||
|
|
||
| recovery_log_text() { | ||
| printf '%s\n' "${RECOVERY_LOG[@]}" | ||
| } | ||
|
|
||
| reset_unraid_password() { | ||
| local pool_name="${RECOVERY_BOOT_POOL:-flash}" | ||
| local mount_root="" | ||
| local dataset_list="" | ||
| local config_dir="" | ||
| local dataset mountpoint | ||
| local mount_failed=0 | ||
|
|
||
| if ! command -v zpool >/dev/null 2>&1 || ! command -v zfs >/dev/null 2>&1; then | ||
| ui_msg "Password Reset" "ZFS tools are not available in this image." | ||
| return 1 | ||
| fi | ||
|
|
||
| if zpool list -H -o name "$pool_name" >/dev/null 2>&1; then | ||
| ui_msg "Password Reset" "The '$pool_name' boot pool is already imported. Refusing to modify an active pool." | ||
| return 1 | ||
| fi | ||
|
|
||
| if ! ui_confirm "Reset Password" "This removes config/passwd and config/shadow from the '$pool_name' boot pool. The next Unraid boot will have no web password. Continue?"; then | ||
| return 0 | ||
| fi | ||
|
|
||
| mount_root="$(mktemp -d /mnt/unraid-recovery.XXXXXX)" | ||
| recovery_status "Importing ZFS boot pool '$pool_name'..." | ||
| if ! zpool import -N -R "$mount_root" "$pool_name"; then | ||
| rmdir "$mount_root" 2>/dev/null || true | ||
| ui_msg "Password Reset" "Unable to import ZFS boot pool '$pool_name'. Ensure the target boot device is connected and not in use." | ||
| return 1 | ||
| fi | ||
|
|
||
| dataset_list="$(mktemp /tmp/unraid-recovery-datasets.XXXXXX)" | ||
| recovery_status "Reading datasets from '$pool_name'..." | ||
| if ! zfs list -H -o name,mountpoint -r "$pool_name" > "$dataset_list"; then | ||
| rm -f "$dataset_list" | ||
| zpool export "$pool_name" >/dev/null 2>&1 || true | ||
| rmdir "$mount_root" 2>/dev/null || true | ||
| ui_msg "Password Reset" "Unable to list datasets in the '$pool_name' boot pool." | ||
| return 1 | ||
| fi | ||
|
|
||
| while IFS=$'\t' read -r dataset mountpoint; do | ||
| [[ -n "$dataset" ]] || continue | ||
| [[ "$mountpoint" == "legacy" || "$mountpoint" == "none" || "$mountpoint" == "-" ]] && continue | ||
| recovery_status "Mounting dataset '$dataset'..." | ||
| if ! zfs mount "$dataset"; then | ||
| mount_failed=1 | ||
| break | ||
| fi | ||
| done < "$dataset_list" | ||
| rm -f "$dataset_list" | ||
|
|
||
| if [[ "$mount_failed" -eq 1 ]]; then | ||
| zpool export "$pool_name" >/dev/null 2>&1 || true | ||
| rmdir "$mount_root" 2>/dev/null || true | ||
| ui_msg "Password Reset" "The '$pool_name' boot dataset could not be mounted." | ||
| return 1 | ||
| fi | ||
|
|
||
| recovery_status "Locating the Unraid config directory..." | ||
| config_dir="$(find -P "$mount_root" -type d -name config -print -quit 2>/dev/null || true)" | ||
| if [[ -z "$config_dir" ]]; then | ||
| zpool export "$pool_name" >/dev/null 2>&1 || true | ||
| rmdir "$mount_root" 2>/dev/null || true | ||
| ui_msg "Password Reset" "No config directory was found on the '$pool_name' boot pool." | ||
| return 1 | ||
| fi | ||
|
|
||
| recovery_status "Deleting config/passwd and config/shadow..." | ||
| if ! rm -f -- "$config_dir/passwd" "$config_dir/shadow"; then | ||
| zpool export "$pool_name" >/dev/null 2>&1 || true | ||
| rmdir "$mount_root" 2>/dev/null || true | ||
| ui_msg "Password Reset" "Unable to remove the saved password files." | ||
| return 1 | ||
| fi | ||
|
|
||
| recovery_status "Verifying password files are absent..." | ||
| if [[ -e "$config_dir/passwd" || -e "$config_dir/shadow" ]]; then | ||
| zpool export "$pool_name" >/dev/null 2>&1 || true | ||
| rmdir "$mount_root" 2>/dev/null || true | ||
| ui_msg "Password Reset" "The password files are still present in the config directory." | ||
| return 1 | ||
| fi | ||
|
|
||
| recovery_status "Syncing changes to '$pool_name'..." | ||
| if ! sync; then | ||
| zpool export "$pool_name" >/dev/null 2>&1 || true | ||
| rmdir "$mount_root" 2>/dev/null || true | ||
| ui_msg "Password Reset" "Unable to sync the password reset changes." | ||
| return 1 | ||
| fi | ||
|
|
||
| recovery_status "Exporting ZFS boot pool '$pool_name'..." | ||
| if ! zpool export "$pool_name"; then | ||
| ui_msg "Password Reset" "Password files were removed, but the '$pool_name' pool could not be exported. Export it before rebooting." | ||
| return 1 | ||
| fi | ||
| rmdir "$mount_root" 2>/dev/null || true | ||
| recovery_status "Password reset complete." | ||
| ui_msg "Password Reset Complete" "$(recovery_log_text) | ||
|
|
||
| Password files were removed successfully. You can now boot Unraid and set a new password." | ||
| } | ||
|
|
||
| recovery_menu() { | ||
| local choice="" | ||
|
|
||
| if ! choice="$(ui_menu "Recovery" "Select a recovery action" A "Reset password" B "Back")"; then | ||
| choice="$(ui_hotkey_select "Recovery" "Select a recovery action" A "Reset password" B "Back")" | ||
| fi | ||
| choice="${choice//$'\r'/}" | ||
| choice="${choice//[[:space:]]/}" | ||
| choice="${choice^^}" | ||
|
|
||
| case "$choice" in | ||
| A) reset_unraid_password ;; | ||
| *) ;; | ||
| esac | ||
| } | ||
|
|
||
| detect_ui_backend | ||
| recovery_menu | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.