From 3642af4cffbdd735a922b2c6667bd43c63ed5796 Mon Sep 17 00:00:00 2001 From: Andrew Rich Date: Fri, 12 Sep 2025 14:13:55 -0700 Subject: [PATCH 1/5] Fix keychain idempotency and enhance iTerm2 import reliability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix keychain cleanup breaking first-boot.sh re-runs by preserving keychain file - Improve iTerm2 detection from specific utility path to main app bundle - Add verification step to confirm iTerm2 preferences import succeeded - Enhance error messages with troubleshooting guidance - Warn when iTerm2 is running during import 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- scripts/server/first-boot.sh | 11 ++--------- scripts/server/operator-first-login.sh | 25 ++++++++++++++++++++----- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/scripts/server/first-boot.sh b/scripts/server/first-boot.sh index 0cb655f..7ccabaa 100755 --- a/scripts/server/first-boot.sh +++ b/scripts/server/first-boot.sh @@ -1342,15 +1342,8 @@ show_log " Bash shell and custom settings for both Administrator and Operator log "Removing temporary sudo timeout configuration" sudo rm -f /etc/sudoers.d/10_setup_timeout -# Clean up external keychain from setup directory (only after successful completion) -if [[ -n "${EXTERNAL_KEYCHAIN:-}" ]]; then - setup_keychain_file="${SETUP_DIR}/config/${EXTERNAL_KEYCHAIN}-db" - if [[ -f "${setup_keychain_file}" ]]; then - log "Cleaning up external keychain from setup directory" - rm -f "${setup_keychain_file}" - log "✅ Setup keychain file cleaned up" - fi -fi +# External keychain preserved in setup directory for idempotent re-runs +# (Previously removed keychain after completion, breaking re-run capability) # Clean up administrator password from memory if [[ -n "${ADMINISTRATOR_PASSWORD:-}" ]]; then diff --git a/scripts/server/operator-first-login.sh b/scripts/server/operator-first-login.sh index e148140..eb687b3 100755 --- a/scripts/server/operator-first-login.sh +++ b/scripts/server/operator-first-login.sh @@ -228,20 +228,35 @@ setup_iterm2_preferences() { return 0 fi - # Check if iTerm2 is installed - if [[ ! -f /Applications/iTerm.app/Contents/Resources/utilities/it2check ]]; then + # Check if iTerm2 is installed (more reliable detection method) + if [[ ! -d /Applications/iTerm.app ]]; then log "iTerm2 not installed - skipping preferences import" return 0 fi log "Importing iTerm2 preferences..." + # Ensure iTerm2 is not running during import for better reliability + if pgrep -f "iTerm.app" >/dev/null 2>&1; then + log "iTerm2 is currently running - preferences import may not take effect until restart" + fi + # Import preferences using defaults import if defaults import com.googlecode.iterm2 "${preferences_file}"; then - log "Successfully imported iTerm2 preferences" - log "iTerm2 preferences will be active when iTerm2 is next launched" + log "iTerm2 preferences import command succeeded" + + # Verify that import actually worked by checking for a key preference + if defaults read com.googlecode.iterm2 "Default Bookmark Guid" >/dev/null 2>&1; then + log "✅ Successfully imported and verified iTerm2 preferences" + log "Preferences will be active when iTerm2 is next launched" + else + log "⚠️ Import command succeeded but preferences verification failed" + log "iTerm2 preferences may not have been properly imported" + fi else - log "Warning: Failed to import iTerm2 preferences" + log "❌ Failed to import iTerm2 preferences" + log "Check that preferences file is valid: ${preferences_file}" + log "You can manually import by opening iTerm2 > Preferences > Profiles > Other Actions > Import JSON Profiles" fi } From 8309dcfc5020f7d9bb8c35368736ca20b677e88f Mon Sep 17 00:00:00 2001 From: Andrew Rich Date: Fri, 12 Sep 2025 14:56:45 -0700 Subject: [PATCH 2/5] use lowercase for plist name --- app-setup/plex-setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-setup/plex-setup.sh b/app-setup/plex-setup.sh index 61a376c..5df06cd 100755 --- a/app-setup/plex-setup.sh +++ b/app-setup/plex-setup.sh @@ -72,7 +72,7 @@ PLEX_MEDIA_MOUNT="/Users/${OPERATOR_USERNAME}/.local/mnt/${NAS_SHARE_NAME}" PLEX_SERVER_NAME="${PLEX_SERVER_NAME_OVERRIDE:-${HOSTNAME}}" PLEX_PREFS="com.plexapp.plexmediaserver" LAUNCH_AGENTS_DIR="${OPERATOR_HOME}/Library/LaunchAgents" -LAUNCH_AGENT="com.${HOSTNAME}.plexmediaserver" +LAUNCH_AGENT="com.${HOSTNAME_LOWER}.plexmediaserver" LAUNCH_AGENT_FILE="${LAUNCH_AGENTS_DIR}/${LAUNCH_AGENT}.plist" # Migration settings From 4e095607c7b860e74e166b5a4c8b6990137c2211 Mon Sep 17 00:00:00 2001 From: Andrew Rich Date: Sat, 13 Sep 2025 12:26:26 -0700 Subject: [PATCH 3/5] Add ProgressIndicator to casks configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added smartwatermelon/tap/progress-indicator to casks.txt - Uses full tap prefix for automatic tap + install workflow - ProgressIndicator provides real-time log monitoring for setup scripts - Positioned alphabetically between plex-media-server and stats 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- config/casks.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/config/casks.txt b/config/casks.txt index 301375d..e0dbbe9 100644 --- a/config/casks.txt +++ b/config/casks.txt @@ -16,6 +16,7 @@ lulu no-ip-duc notunes plex-media-server +smartwatermelon/tap/progress-indicator stats taskexplorer transmission From 85c98c27815c3a0ae22e409afefd949a39f7ed34 Mon Sep 17 00:00:00 2001 From: Andrew Rich Date: Sat, 13 Sep 2025 13:10:09 -0700 Subject: [PATCH 4/5] implement ProgressIndicator --- scripts/server/operator-first-login.sh | 108 ++++++++++++++----------- 1 file changed, 60 insertions(+), 48 deletions(-) diff --git a/scripts/server/operator-first-login.sh b/scripts/server/operator-first-login.sh index eb687b3..0d10317 100755 --- a/scripts/server/operator-first-login.sh +++ b/scripts/server/operator-first-login.sh @@ -41,10 +41,31 @@ fi CURRENT_USER=$(whoami) HOSTNAME_LOWER="$(tr '[:upper:]' '[:lower:]' <<<"${SERVER_NAME}")" LOG_FILE="${HOME}/.local/state/${HOSTNAME_LOWER}-operator-login.log" +PROGRESS_LOG_FILE="${HOME}/.local/state/${HOSTNAME_LOWER}-operator-login-progress.log" # Ensure log directory exists mkdir -p "$(dirname "${LOG_FILE}")" +# Progress Indicator function +# wraps log() +progress() { + if command -v ProgressIndicator; then + # tool is installed; else just skip to log() + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo "[${timestamp}] $*" | tee -a "${PROGRESS_LOG_FILE}" + if ! pgrep -f ProgressIndicator; then + # tool is not already running; launch watching PROGRESS_LOG_FILE + ProgressIndicator --watchfile="${PROGRESS_LOG_FILE}" + fi + fi + log "$*" +} + +stop_progress() { + pkill -f ProgressIndicator 2>/dev/null || true +} + # Logging function log() { local timestamp @@ -59,36 +80,31 @@ wait_for_network_mount() { local timeout=120 local elapsed=0 - log "Waiting for network mount at ${mount_path}..." + progress "Waiting for network mount at ${mount_path}..." while [[ ${elapsed} -lt ${timeout} ]]; do # Check if there's an active SMB mount owned by current user if mount | grep "${CURRENT_USER}" | grep -q "${mount_path}"; then - log "Network mount ready (active mount found for ${CURRENT_USER})" + progress "Network mount ready (active mount found for ${CURRENT_USER})" return 0 else - log "No active mount found for ${CURRENT_USER}, waiting... (${elapsed}s/${timeout}s)" - fi - - # Show progress dialog every 10 seconds - if [[ $((elapsed % 10)) -eq 0 && ${elapsed} -gt 0 ]]; then - osascript -e "display dialog \"Waiting for network storage to be ready...\\n\\nElapsed: ${elapsed}s / ${timeout}s\" buttons {\"OK\"} default button \"OK\" giving up after 3 with title \"Mac Mini Setup\"" >/dev/null 2>&1 || true + progress "No active mount found for ${CURRENT_USER}, waiting... (${elapsed}s/${timeout}s)" fi sleep 1 ((elapsed += 1)) done - log "Warning: Network mount not available after ${timeout} seconds" + progress "Warning: Network mount not available after ${timeout} seconds" return 1 } # Task: Dock cleanup setup_dock() { - log "Setting up dock for operator account..." + progress "Setting up dock for operator account..." if ! command -v dockutil; then - log "Warning: dockutil not found. Install: brew install dockutil" + progress "Warning: dockutil not found. Install: brew install dockutil" return 0 fi @@ -100,7 +116,7 @@ setup_dock() { done # Remove unwanted apps - log "Removing unwanted applications from dock..." + progress "Removing unwanted applications from dock..." local apps_to_remove=( "Messages" "Mail" @@ -123,19 +139,19 @@ setup_dock() { local elapsed=0 while dockutil --find "${app}" >/dev/null 2>&1 && [[ ${elapsed} -lt ${timeout} ]]; do - log "Removing ${app} from dock..." + progress "Removing ${app} from dock..." dockutil --remove "${app}" --no-restart 2>/dev/null || true sleep 1 ((elapsed += 1)) done if [[ ${elapsed} -ge ${timeout} ]]; then - log "Warning: Timeout removing ${app} from dock" + progress "Warning: Timeout removing ${app} from dock" fi done # Add desired items - log "Adding desired applications to dock..." + progress "Adding desired applications to dock..." local media_path="${HOME}/.local/mnt/${NAS_SHARE_NAME}/Media" local apps_to_add=() @@ -155,14 +171,14 @@ setup_dock() { local elapsed=0 while ! dockutil --find "${app}" >/dev/null 2>&1 && [[ ${elapsed} -lt ${timeout} ]]; do - log "Adding ${app} to dock..." + progress "Adding ${app} to dock..." dockutil --add "${app}" --no-restart 2>/dev/null || true sleep 1 ((elapsed += 1)) done if [[ ${elapsed} -ge ${timeout} ]]; then - log "Warning: Timeout adding ${app} to dock" + progress "Warning: Timeout adding ${app} to dock" fi done @@ -170,34 +186,34 @@ setup_dock() { killall Dock 2>/dev/null || true sleep 1 - log "Dock setup completed" + progress "Dock setup completed" } # Task: Configure Terminal profile setup_terminal_profile() { - log "Setting up Terminal profile..." + progress "Setting up Terminal profile..." local terminal_config_dir="${HOME}/.config/terminal" local profile_file="${terminal_config_dir}/Orangebrew.terminal" # Check if profile file exists if [[ ! -f "${profile_file}" ]]; then - log "No Terminal profile found at ${profile_file} - skipping terminal setup" + progress "No Terminal profile found at ${profile_file} - skipping terminal setup" return 0 fi # Extract profile name local profile_name if ! profile_name=$(plutil -extract name raw "${profile_file}" 2>/dev/null); then - log "Warning: Could not extract profile name from ${profile_file}" + progress "Warning: Could not extract profile name from ${profile_file}" return 0 fi - log "Registering Terminal profile '${profile_name}'..." + progress "Registering Terminal profile '${profile_name}'..." # Step 1: Open the profile file to register it with Terminal.app if open "${profile_file}"; then - log "Successfully opened Terminal profile file" + progress "Successfully opened Terminal profile file" # Step 2: Set as default window settings defaults write com.apple.Terminal "Default Window Settings" -string "${profile_name}" @@ -209,52 +225,52 @@ setup_terminal_profile() { sleep 2 # Brief pause to let Terminal register the profile killall Terminal 2>/dev/null || true - log "Terminal profile '${profile_name}' configured successfully" + progress "Terminal profile '${profile_name}' configured successfully" else - log "Warning: Failed to open Terminal profile file" + progress "Warning: Failed to open Terminal profile file" fi } # Task: Configure iTerm2 preferences setup_iterm2_preferences() { - log "Setting up iTerm2 preferences..." + progress "Setting up iTerm2 preferences..." local iterm2_config_dir="${HOME}/.config/iterm2" local preferences_file="${iterm2_config_dir}/iterm2.plist" # Check if preferences file exists if [[ ! -f "${preferences_file}" ]]; then - log "No iTerm2 preferences found at ${preferences_file} - skipping iTerm2 setup" + progress "No iTerm2 preferences found at ${preferences_file} - skipping iTerm2 setup" return 0 fi # Check if iTerm2 is installed (more reliable detection method) if [[ ! -d /Applications/iTerm.app ]]; then - log "iTerm2 not installed - skipping preferences import" + progress "iTerm2 not installed - skipping preferences import" return 0 fi - log "Importing iTerm2 preferences..." + progress "Importing iTerm2 preferences..." # Ensure iTerm2 is not running during import for better reliability if pgrep -f "iTerm.app" >/dev/null 2>&1; then - log "iTerm2 is currently running - preferences import may not take effect until restart" + progress "iTerm2 is currently running - preferences import may not take effect until restart" fi # Import preferences using defaults import if defaults import com.googlecode.iterm2 "${preferences_file}"; then - log "iTerm2 preferences import command succeeded" + progress "iTerm2 preferences import command succeeded" # Verify that import actually worked by checking for a key preference if defaults read com.googlecode.iterm2 "Default Bookmark Guid" >/dev/null 2>&1; then - log "✅ Successfully imported and verified iTerm2 preferences" + progress "✅ Successfully imported and verified iTerm2 preferences" log "Preferences will be active when iTerm2 is next launched" else - log "⚠️ Import command succeeded but preferences verification failed" + progress "⚠️ Import command succeeded but preferences verification failed" log "iTerm2 preferences may not have been properly imported" fi else - log "❌ Failed to import iTerm2 preferences" + progress "❌ Failed to import iTerm2 preferences" log "Check that preferences file is valid: ${preferences_file}" log "You can manually import by opening iTerm2 > Preferences > Profiles > Other Actions > Import JSON Profiles" fi @@ -262,27 +278,27 @@ setup_iterm2_preferences() { # Task: Start logrotate service setup_logrotate() { - log "Starting logrotate service for operator user..." + progress "Starting logrotate service for operator user..." brew services stop logrotate &>/dev/null || true if brew services start logrotate; then - log "Logrotate service started successfully" + progress "Logrotate service started successfully" else - log "Warning: Failed to start logrotate service - logs will not be rotated" + progress "Warning: Failed to start logrotate service - logs will not be rotated" fi } # Task: unload LaunchAgent unload_launchagent() { - log "Unloading LaunchAgent..." + progress "Unloading LaunchAgent..." local launch_agents_dir="${HOME}/Library/LaunchAgents" local launch_agent="com.${HOSTNAME_LOWER}.operator-first-login.plist" local operator_config_dir operator_config_dir="$(dirname "${CONFIG_FILE}")" if mv "${launch_agents_dir}/${launch_agent}" "${operator_config_dir}"; then - log "Moved LaunchAgent to ${operator_config_dir}" + progress "Moved LaunchAgent to ${operator_config_dir}" log "(Move back to ${launch_agents_dir} to re-run on next login)" else - log "Warning: Failed to rename LaunchAgent; it will probably reload on next login" + progress "Warning: Failed to rename LaunchAgent; it will probably reload on next login" fi } @@ -298,7 +314,7 @@ lock_screen_now() { # Main execution main() { - log "=== Operator First-Login Setup Started ===" + progress "=== Operator First-Login Setup Started ===" log "User: ${CURRENT_USER}" log "Server: ${SERVER_NAME}" @@ -308,9 +324,6 @@ main() { exit 1 fi - # Show setup notification to user - osascript -e 'display dialog "🔧 Setting up operator account...\n\nCustomizing dock and applications.\nThis will complete automatically in a few moments." buttons {"OK"} default button "OK" giving up after 8 with title "Mac Mini Setup"' - # Run setup tasks setup_dock setup_terminal_profile @@ -318,10 +331,9 @@ main() { setup_logrotate unload_launchagent - # Show setup notification to user - osascript -e 'display dialog "✅ Done setting up operator account!" buttons {"OK"} default button "OK" giving up after 8 with title "Mac Mini Setup"' - - log "=== Operator First-Login Setup Completed ===" + progress "=== Operator First-Login Setup Completed ===" + sleep 2 + stop_progress } main "$@" From d72f1949212b69ad2c967d98e94083bae9576a4f Mon Sep 17 00:00:00 2001 From: Andrew Rich Date: Sun, 14 Sep 2025 13:27:02 -0700 Subject: [PATCH 5/5] Fix ProgressIndicator integration issues in operator-first-login.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add proper PID tracking to prevent race conditions and multiple instances - Implement atomic process checking with kill -0 verification - Add graceful process cleanup with kill + wait pattern - Ensure both log directories are created before use - Reorder function definitions to satisfy shellcheck requirements - Add early ProgressIndicator availability detection with informative logging - Improve error handling with process launch verification - Make all progress messages consistent throughout iTerm2 setup 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- scripts/server/operator-first-login.sh | 74 +++++++++++++++++--------- 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/scripts/server/operator-first-login.sh b/scripts/server/operator-first-login.sh index 0d10317..c30fa6c 100755 --- a/scripts/server/operator-first-login.sh +++ b/scripts/server/operator-first-login.sh @@ -42,35 +42,61 @@ CURRENT_USER=$(whoami) HOSTNAME_LOWER="$(tr '[:upper:]' '[:lower:]' <<<"${SERVER_NAME}")" LOG_FILE="${HOME}/.local/state/${HOSTNAME_LOWER}-operator-login.log" PROGRESS_LOG_FILE="${HOME}/.local/state/${HOSTNAME_LOWER}-operator-login-progress.log" +PROGRESS_INDICATOR_PID="" -# Ensure log directory exists +# Ensure log directories exist mkdir -p "$(dirname "${LOG_FILE}")" +mkdir -p "$(dirname "${PROGRESS_LOG_FILE}")" + +# Logging function +log() { + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo "[${timestamp}] $*" | tee -a "${LOG_FILE}" +} + +# Check ProgressIndicator availability +if command -v ProgressIndicator >/dev/null 2>&1; then + log "ProgressIndicator available - GUI progress will be shown" +else + log "ProgressIndicator not available - using log-only progress tracking" +fi + +# Start ProgressIndicator if available and not already running +start_progress() { + if command -v ProgressIndicator >/dev/null 2>&1; then + if [[ -z "${PROGRESS_INDICATOR_PID}" ]] || ! kill -0 "${PROGRESS_INDICATOR_PID}" 2>/dev/null; then + ProgressIndicator --watchfile="${PROGRESS_LOG_FILE}" & + PROGRESS_INDICATOR_PID=$! + if kill -0 "${PROGRESS_INDICATOR_PID}" 2>/dev/null; then + log "Started ProgressIndicator (PID: ${PROGRESS_INDICATOR_PID})" + else + log "Warning: Failed to start ProgressIndicator GUI" + PROGRESS_INDICATOR_PID="" + fi + fi + fi +} # Progress Indicator function # wraps log() progress() { - if command -v ProgressIndicator; then - # tool is installed; else just skip to log() - local timestamp - timestamp=$(date '+%Y-%m-%d %H:%M:%S') - echo "[${timestamp}] $*" | tee -a "${PROGRESS_LOG_FILE}" - if ! pgrep -f ProgressIndicator; then - # tool is not already running; launch watching PROGRESS_LOG_FILE - ProgressIndicator --watchfile="${PROGRESS_LOG_FILE}" - fi - fi + start_progress + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo "[${timestamp}] $*" | tee -a "${PROGRESS_LOG_FILE}" log "$*" } stop_progress() { - pkill -f ProgressIndicator 2>/dev/null || true -} - -# Logging function -log() { - local timestamp - timestamp=$(date '+%Y-%m-%d %H:%M:%S') - echo "[${timestamp}] $*" | tee -a "${LOG_FILE}" + if [[ -n "${PROGRESS_INDICATOR_PID}" ]] && kill -0 "${PROGRESS_INDICATOR_PID}" 2>/dev/null; then + kill "${PROGRESS_INDICATOR_PID}" 2>/dev/null || true + wait "${PROGRESS_INDICATOR_PID}" 2>/dev/null || true + PROGRESS_INDICATOR_PID="" + log "Stopped ProgressIndicator" + fi + # Fallback cleanup + pkill -f "ProgressIndicator.*${PROGRESS_LOG_FILE}" 2>/dev/null || true } # Wait for network mount @@ -264,15 +290,15 @@ setup_iterm2_preferences() { # Verify that import actually worked by checking for a key preference if defaults read com.googlecode.iterm2 "Default Bookmark Guid" >/dev/null 2>&1; then progress "✅ Successfully imported and verified iTerm2 preferences" - log "Preferences will be active when iTerm2 is next launched" + progress "Preferences will be active when iTerm2 is next launched" else progress "⚠️ Import command succeeded but preferences verification failed" - log "iTerm2 preferences may not have been properly imported" + progress "iTerm2 preferences may not have been properly imported" fi else progress "❌ Failed to import iTerm2 preferences" - log "Check that preferences file is valid: ${preferences_file}" - log "You can manually import by opening iTerm2 > Preferences > Profiles > Other Actions > Import JSON Profiles" + progress "Check that preferences file is valid: ${preferences_file}" + progress "You can manually import by opening iTerm2 > Preferences > Profiles > Other Actions > Import JSON Profiles" fi } @@ -296,7 +322,7 @@ unload_launchagent() { operator_config_dir="$(dirname "${CONFIG_FILE}")" if mv "${launch_agents_dir}/${launch_agent}" "${operator_config_dir}"; then progress "Moved LaunchAgent to ${operator_config_dir}" - log "(Move back to ${launch_agents_dir} to re-run on next login)" + progress "(Move back to ${launch_agents_dir} to re-run on next login)" else progress "Warning: Failed to rename LaunchAgent; it will probably reload on next login" fi