Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions bin/omarchy-doctor
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#!/bin/bash

# Request sudo upfront to avoid prompts during checks
sudo -v

# Keep sudo alive during the script
(while true; do sudo -v; sleep 50; done) &
SUDO_REFRESH_PID=$!
trap "kill $SUDO_REFRESH_PID 2>/dev/null" EXIT

# Define Omarchy locations
export OMARCHY_PATH="/home/ryan/Work/omarchy/omarchy-installer"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use $HOME here instead?

export OMARCHY_INSTALL="$OMARCHY_PATH/install"

# Track results
failed_checks=0
passed_checks=0
skipped_checks=0
total_checks=0

# Function to display a message with icon
show_message() {
local type="$1"
local msg="$2"
case "$type" in
error) printf " ❌ ERROR %s\n" "$msg" ;;
warning) printf " ⚠️ WARNING %s\n" "$msg" ;;
info) printf " ✅ OK %s\n" "$msg" ;;
esac
}

# Function to check a single script
check_script() {
local script="$1"
local script_name="${script#$OMARCHY_INSTALL/}"

# Check if script has verify function
if ! bash -c "source '$script' && declare -f omarchy_verify" >/dev/null 2>&1; then
((skipped_checks++))
return
fi

((total_checks++))

# Get friendly name
local friendly_name=$(bash -c "source '$script' 2>/dev/null; echo \"\${OMARCHY_DESCRIPTION:-$script_name}\"")

# Run verification in subshell and capture output
local output
output=$(
errors=()
warnings=()
infos=()

add_error() { errors+=("$1"); }
add_warning() { warnings+=("$1"); }
add_info() { infos+=("$1"); }

source "$script"
omarchy_verify
exit_code=$?

# Output results with delimiters
echo "EXIT:$exit_code"
for e in "${errors[@]}"; do echo "ERROR:$e"; done
for w in "${warnings[@]}"; do echo "WARNING:$w"; done
for i in "${infos[@]}"; do echo "INFO:$i"; done
)

# Parse output
local exit_code errors=() warnings=() infos=()
while IFS= read -r line; do
case "$line" in
EXIT:*) exit_code="${line#EXIT:}" ;;
ERROR:*) errors+=("${line#ERROR:}") ;;
WARNING:*) warnings+=("${line#WARNING:}") ;;
INFO:*) infos+=("${line#INFO:}") ;;
esac
done <<< "$output"

# Handle skipped (return code 2)
if [[ $exit_code -eq 2 ]]; then
((skipped_checks++))
return
fi

# Count messages
local error_count=${#errors[@]}
local warning_count=${#warnings[@]}

# Determine status
local status_icon status_color
if [[ $error_count -gt 0 ]]; then
status_icon="❌"
status_color="1" # Red
((failed_checks++))
elif [[ $warning_count -gt 0 ]]; then
status_icon="⚠️"
status_color="3" # Yellow
((passed_checks++))
else
status_icon="✅"
status_color="2" # Green
((passed_checks++))
fi

# Display result
printf "%s %s\n" "$status_icon" "$(gum style --foreground "$status_color" --bold "$friendly_name")"

# Display messages if any errors or warnings
if [[ $error_count -gt 0 || $warning_count -gt 0 ]]; then
for msg in "${errors[@]}"; do
show_message error "$msg"
done

for msg in "${warnings[@]}"; do
show_message warning "$msg"
done

for msg in "${infos[@]}"; do
show_message info "$msg"
done
fi

echo
}

# Main execution
echo
gum style --italic --foreground 7 "Running health checks..."
echo

# Process all scripts
while IFS= read -r script; do
# Skip certain directories
if [[ "$script" == */all.sh ]] ||
[[ "$script" == */helpers/* ]] ||
[[ "$script" == */preflight/* ]] ||
[[ "$script" == */first-run/* ]] ||
[[ "$script" == */post-install/* ]]; then
continue
fi

check_script "$script"
done < <(find "$OMARCHY_INSTALL" -type f -name "*.sh" | sort)

# Display summary
echo
echo "Summary:"
echo " Passed: $passed_checks/$total_checks"
[[ $failed_checks -gt 0 ]] && echo " Failed: $failed_checks/$total_checks"
[[ $skipped_checks -gt 0 ]] && echo " Skipped: $skipped_checks"

echo

if [[ $failed_checks -gt 0 ]]; then
echo "❌ Some checks failed. Please review the errors above."
exit 1
else
echo "✅ All checks passed!"
fi
21 changes: 21 additions & 0 deletions install/checks/firewall.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
OMARCHY_DESCRIPTION="Firewall Configuration"

omarchy_verify() {
# Check if UFW is enabled
sudo ufw status | grep -q "Status: active" || add_error "UFW firewall not active"

# Check if UFW service is enabled
systemctl is-enabled ufw &>/dev/null || add_error "UFW service not enabled"

# Check default policies - they're on one line as "Default: deny (incoming), allow (outgoing), deny (routed)"
sudo ufw status verbose | grep -q "Default:.*deny (incoming)" || add_error "UFW default incoming policy not set to deny"
sudo ufw status verbose | grep -q "Default:.*allow (outgoing)" || add_error "UFW default outgoing policy not set to allow"

# Check specific rules are present
sudo ufw status numbered | grep -q "53317/udp" || add_error "LocalSend UDP port 53317 not allowed"
sudo ufw status numbered | grep -q "53317/tcp" || add_error "LocalSend TCP port 53317 not allowed"
sudo ufw status numbered | grep -q "22/tcp" || add_error "SSH port 22 not allowed"

# Check Docker DNS rule
sudo ufw status numbered | grep -q "allow-docker-dns" || add_error "Docker DNS rule not configured"
}
9 changes: 9 additions & 0 deletions install/checks/gnome-theme.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
OMARCHY_DESCRIPTION="GNOME Theme Settings"

omarchy_verify() {
gsettings get org.gnome.desktop.interface gtk-theme &>/dev/null || add_error "Cannot access GTK theme setting"
gsettings get org.gnome.desktop.interface color-scheme &>/dev/null || add_error "Cannot access color scheme setting"
gsettings get org.gnome.desktop.interface icon-theme &>/dev/null || add_error "Cannot access icon theme setting"

[[ -d /usr/share/icons ]] || add_error "Icon themes directory missing"
}
13 changes: 13 additions & 0 deletions install/checks/power-profiles.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
OMARCHY_DESCRIPTION="Power Profile & Battery Settings"

omarchy_verify() {
if ls /sys/class/power_supply/BAT* &>/dev/null; then
current_profile=$(powerprofilesctl get 2>/dev/null)
[[ "$current_profile" == "balanced" ]] || add_error "Power profile not set to balanced for battery device"

systemctl --user is-enabled omarchy-battery-monitor.timer &>/dev/null || add_error "Battery monitor timer not enabled"
else
current_profile=$(powerprofilesctl get 2>/dev/null)
[[ "$current_profile" == "performance" ]] || add_error "Power profile not set to performance for AC device"
fi
}
17 changes: 13 additions & 4 deletions install/config/branding.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
# Allow the user to change the branding for fastfetch and screensaver
mkdir -p ~/.config/omarchy/branding
cp ~/.local/share/omarchy/icon.txt ~/.config/omarchy/branding/about.txt
cp ~/.local/share/omarchy/logo.txt ~/.config/omarchy/branding/screensaver.txt
OMARCHY_DESCRIPTION="Branding Config"

omarchy_install() {
mkdir -p ~/.config/omarchy/branding
cp ~/.local/share/omarchy/icon.txt ~/.config/omarchy/branding/about.txt
cp ~/.local/share/omarchy/logo.txt ~/.config/omarchy/branding/screensaver.txt
}

omarchy_verify() {
[[ -d ~/.config/omarchy/branding ]] || add_error "Branding directory missing"
[[ -f ~/.config/omarchy/branding/about.txt ]] || add_error "About branding file missing"
[[ -f ~/.config/omarchy/branding/screensaver.txt ]] || add_error "Screensaver branding file missing"
}
22 changes: 17 additions & 5 deletions install/config/config.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
# Copy over Omarchy configs
mkdir -p ~/.config
cp -R ~/.local/share/omarchy/config/* ~/.config/
OMARCHY_DESCRIPTION="Config Files"

# Use default bashrc from Omarchy
cp ~/.local/share/omarchy/default/bashrc ~/.bashrc
omarchy_install() {
# Copy over Omarchy configs
mkdir -p ~/.config
cp -R ~/.local/share/omarchy/config/* ~/.config/

# Use default bashrc from Omarchy
cp ~/.local/share/omarchy/default/bashrc ~/.bashrc
}

omarchy_verify() {
[[ -d ~/.config ]] || add_error "Config directory missing"
[[ -f ~/.bashrc ]] || add_error "Bashrc file missing"

[[ -d ~/.config/hypr ]] || add_error "Hypr config missing"
[[ -d ~/.config/waybar ]] || add_error "Waybar config missing"
}
42 changes: 29 additions & 13 deletions install/config/detect-keyboard-layout.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
# Copy over the keyboard layout that's been set in Arch during install to Hyprland
conf="/etc/vconsole.conf"
hyprconf="$HOME/.config/hypr/input.conf"

if grep -q '^XKBLAYOUT=' "$conf"; then
layout=$(grep '^XKBLAYOUT=' "$conf" | cut -d= -f2 | tr -d '"')
sed -i "/^[[:space:]]*kb_options *=/i\ kb_layout = $layout" "$hyprconf"
fi

if grep -q '^XKBVARIANT=' "$conf"; then
variant=$(grep '^XKBVARIANT=' "$conf" | cut -d= -f2 | tr -d '"')
sed -i "/^[[:space:]]*kb_options *=/i\ kb_variant = $variant" "$hyprconf"
fi
OMARCHY_DESCRIPTION="Keyboard Layout Config"

omarchy_install() {
# Copy over the keyboard layout that's been set in Arch during install to Hyprland
conf="/etc/vconsole.conf"
hyprconf="$HOME/.config/hypr/input.conf"

if grep -q '^XKBLAYOUT=' "$conf"; then
layout=$(grep '^XKBLAYOUT=' "$conf" | cut -d= -f2 | tr -d '"')
sed -i "/^[[:space:]]*kb_options *=/i\ kb_layout = $layout" "$hyprconf"
fi

if grep -q '^XKBVARIANT=' "$conf"; then
variant=$(grep '^XKBVARIANT=' "$conf" | cut -d= -f2 | tr -d '"')
sed -i "/^[[:space:]]*kb_options *=/i\ kb_variant = $variant" "$hyprconf"
fi
}

omarchy_verify() {
[[ -f "$HOME/.config/hypr/input.conf" ]] || add_error "Hyprland input config missing"

# If vconsole.conf has keyboard layout, check if it's in hypr config
if [[ -f "/etc/vconsole.conf" ]] && grep -q '^XKBLAYOUT=' "/etc/vconsole.conf"; then
layout=$(grep '^XKBLAYOUT=' "/etc/vconsole.conf" | cut -d= -f2 | tr -d '"')
if [[ -f "$HOME/.config/hypr/input.conf" ]]; then
grep -q "kb_layout = $layout" "$HOME/.config/hypr/input.conf" || add_error "Keyboard layout not configured in Hyprland"
fi
fi
}
62 changes: 41 additions & 21 deletions install/config/docker.sh
Original file line number Diff line number Diff line change
@@ -1,32 +1,52 @@
# Configure Docker daemon:
# - limit log size to avoid running out of disk
# - use host's DNS resolver
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
OMARCHY_DESCRIPTION="Docker Configuration"

omarchy_install() {
# Configure Docker daemon:
# - limit log size to avoid running out of disk
# - use host's DNS resolver
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "5" },
"dns": ["172.17.0.1"],
"bip": "172.17.0.1/16"
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "5" },
"dns": ["172.17.0.1"],
"bip": "172.17.0.1/16"
}
EOF

# Expose systemd-resolved to our Docker network
sudo mkdir -p /etc/systemd/resolved.conf.d
echo -e '[Resolve]\nDNSStubListenerExtra=172.17.0.1' | sudo tee /etc/systemd/resolved.conf.d/20-docker-dns.conf >/dev/null
sudo systemctl restart systemd-resolved
# Expose systemd-resolved to our Docker network
sudo mkdir -p /etc/systemd/resolved.conf.d
echo -e '[Resolve]\nDNSStubListenerExtra=172.17.0.1' | sudo tee /etc/systemd/resolved.conf.d/20-docker-dns.conf >/dev/null
sudo systemctl restart systemd-resolved

# Start Docker automatically
sudo systemctl enable docker
# Start Docker automatically
sudo systemctl enable docker

# Give this user privileged Docker access
sudo usermod -aG docker ${USER}
# Give this user privileged Docker access
sudo usermod -aG docker ${USER}

# Prevent Docker from preventing boot for network-online.target
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/no-block-boot.conf <<'EOF'
# Prevent Docker from preventing boot for network-online.target
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/no-block-boot.conf <<'EOF'
[Unit]
DefaultDependencies=no
EOF

sudo systemctl daemon-reload
sudo systemctl daemon-reload
}

omarchy_verify() {
[[ -f /etc/docker/daemon.json ]] || add_error "Docker daemon.json missing"
[[ -f /etc/systemd/resolved.conf.d/20-docker-dns.conf ]] || add_error "Docker DNS config missing"
[[ -f /etc/systemd/system/docker.service.d/no-block-boot.conf ]] || add_error "Docker boot config missing"

getent group docker >/dev/null 2>&1 || add_error "Docker group does not exist"

groups "$USER" | grep -q docker || add_error "User $USER not in docker group"

if systemctl list-unit-files | grep -q docker.service; then
systemctl is-enabled docker >/dev/null 2>&1 || add_error "Docker service not enabled"

systemctl is-active docker >/dev/null 2>&1 || add_warning "Docker service is not running (may be intentional)"
fi
}
16 changes: 14 additions & 2 deletions install/config/fix-powerprofilesctl-shebang.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
# Ensure we use system python3 and not mise's python3
sudo sed -i '/env python3/ c\#!/bin/python3' /usr/bin/powerprofilesctl
OMARCHY_DESCRIPTION="Powerprofilesctl"

omarchy_install() {
# Ensure we use system python3 and not mise's python3
sudo sed -i '/env python3/ c\#!/bin/python3' /usr/bin/powerprofilesctl
}

omarchy_verify() {
[[ -f /usr/bin/powerprofilesctl ]] || add_error "powerprofilesctl not found"

if [[ -f /usr/bin/powerprofilesctl ]]; then
head -n1 /usr/bin/powerprofilesctl | grep -q "^#!/bin/python3$" || add_error "powerprofilesctl shebang not fixed"
fi
}
Loading