mirror of
https://github.com/kuhyx/scripts.git
synced 2026-07-04 14:23:08 +02:00
fix: pacman hooks for hosts
This commit is contained in:
parent
f830f61392
commit
1e85350d5a
@ -10,6 +10,7 @@ Components:
|
||||
- hosts-guard.path (triggers on PathChanged=/etc/hosts)
|
||||
- hosts-bind-mount.service (bind mounts /etc/hosts read-only after boot)
|
||||
3. psychological/ directory – scripts that add delay + journaling before allowing a maintenance/unlock operation.
|
||||
4. pacman hooks – automatically unlock/re-lock /etc/hosts around package transactions so pacman never fails due to the read-only bind mount.
|
||||
|
||||
Install Flow (suggested):
|
||||
1. After generating /etc/hosts via your existing hosts/install.sh, copy it to /usr/local/share/locked-hosts.
|
||||
@ -19,6 +20,11 @@ Install Flow (suggested):
|
||||
systemctl enable --now hosts-guard.path
|
||||
systemctl enable --now hosts-bind-mount.service
|
||||
4. (Optional) Use psychological/unlock-hosts.sh as the ONLY sanctioned way to modify hosts (it removes protections temporarily, launches an editor after a delay, and re-enforces on close).
|
||||
5. Make pacman automatic (recommended):
|
||||
./install_pacman_hooks.sh
|
||||
This installs hooks under /etc/pacman.d/hooks that:
|
||||
- PreTransaction: temporarily disable guard and make /etc/hosts writable
|
||||
- PostTransaction: re-run enforcement and re-enable guard (bind mount + path watcher)
|
||||
|
||||
Limitations:
|
||||
- A root user can still disable units, remount, remove attributes.
|
||||
|
||||
0
hosts/guard/enforce-hosts.sh
Normal file → Executable file
0
hosts/guard/enforce-hosts.sh
Normal file → Executable file
49
hosts/guard/install_pacman_hooks.sh
Executable file
49
hosts/guard/install_pacman_hooks.sh
Executable file
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
require_root() { if [[ $EUID -ne 0 ]]; then exec sudo -E bash "$0" "$@"; fi }
|
||||
require_root "$@"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
HOOKS_DIR="/etc/pacman.d/hooks"
|
||||
|
||||
install -d -m 755 "$HOOKS_DIR"
|
||||
|
||||
# Pre-transaction hook
|
||||
cat >"$HOOKS_DIR/10-unlock-etc-hosts.hook" <<'HOOK'
|
||||
[Trigger]
|
||||
Operation = Upgrade
|
||||
Operation = Install
|
||||
Operation = Remove
|
||||
Type = Package
|
||||
Target = *
|
||||
|
||||
[Action]
|
||||
Description = Temporarily unlocking /etc/hosts for transaction
|
||||
When = PreTransaction
|
||||
Exec = /bin/bash /usr/local/share/hosts-guard/pacman-pre-unlock-hosts.sh
|
||||
NeedsTargets
|
||||
HOOK
|
||||
|
||||
# Post-transaction hook
|
||||
cat >"$HOOKS_DIR/90-relock-etc-hosts.hook" <<'HOOK'
|
||||
[Trigger]
|
||||
Operation = Upgrade
|
||||
Operation = Install
|
||||
Operation = Remove
|
||||
Type = Package
|
||||
Target = *
|
||||
|
||||
[Action]
|
||||
Description = Re-locking /etc/hosts after transaction
|
||||
When = PostTransaction
|
||||
Exec = /bin/bash /usr/local/share/hosts-guard/pacman-post-relock-hosts.sh
|
||||
NeedsTargets
|
||||
HOOK
|
||||
|
||||
# Place helper scripts into a shared location
|
||||
install -d -m 755 /usr/local/share/hosts-guard
|
||||
install -m 755 "$SCRIPT_DIR/pacman-hooks/pacman-pre-unlock-hosts.sh" /usr/local/share/hosts-guard/
|
||||
install -m 755 "$SCRIPT_DIR/pacman-hooks/pacman-post-relock-hosts.sh" /usr/local/share/hosts-guard/
|
||||
|
||||
echo "Pacman hooks installed into $HOOKS_DIR."
|
||||
50
hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh
Normal file
50
hosts/guard/pacman-hooks/pacman-post-relock-hosts.sh
Normal file
@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
# Post-transaction hook to re-apply hosts guard protections (single-layer ro bind)
|
||||
|
||||
TARGET=/etc/hosts
|
||||
ENFORCE=/usr/local/sbin/enforce-hosts.sh
|
||||
LOGTAG=hosts-guard-hook
|
||||
|
||||
mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0; }
|
||||
collapse_mounts() {
|
||||
local i=0
|
||||
if command -v mountpoint >/devnull 2>&1; then
|
||||
while mountpoint -q "$TARGET"; do
|
||||
umount -l "$TARGET" >/dev/null 2>&1 || break
|
||||
i=$((i+1))
|
||||
(( i > 20 )) && break
|
||||
done
|
||||
else
|
||||
local cnt
|
||||
cnt=$(mount_layers_count)
|
||||
while (( cnt > 1 )); do
|
||||
umount -l "$TARGET" >/dev/null 2>&1 || break
|
||||
i=$((i+1))
|
||||
(( i > 20 )) && break
|
||||
cnt=$(mount_layers_count)
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# Ensure we end with a single read-only bind mount layer
|
||||
logger -t "$LOGTAG" "post: relocking /etc/hosts (starting)"
|
||||
echo "$(date -Is) post-relock(start)" >> /run/hosts-guard-hook.log 2>/dev/null || true
|
||||
collapse_mounts
|
||||
|
||||
if [[ -x "$ENFORCE" ]]; then
|
||||
"$ENFORCE" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# Apply exactly one ro bind layer
|
||||
mount --bind "$TARGET" "$TARGET" >/dev/null 2>&1 || true
|
||||
mount -o remount,ro,bind "$TARGET" >/dev/null 2>&1 || true
|
||||
|
||||
# Start only the path watcher; avoid bind-mount service (we already bound once)
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
systemctl start hosts-guard.path >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
logger -t "$LOGTAG" "post: relocking /etc/hosts (done)"
|
||||
echo "$(date -Is) post-relock(done)" >> /run/hosts-guard-hook.log 2>/dev/null || true
|
||||
|
||||
exit 0
|
||||
66
hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh
Normal file
66
hosts/guard/pacman-hooks/pacman-pre-unlock-hosts.sh
Normal file
@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
# Non-interactive pre-transaction hook to temporarily unlock /etc/hosts
|
||||
|
||||
TARGET=/etc/hosts
|
||||
LOGTAG=hosts-guard-hook
|
||||
|
||||
stop_units_if_present() {
|
||||
local units=(hosts-bind-mount.service hosts-guard.path)
|
||||
for u in "${units[@]}"; do
|
||||
if command -v systemctl >/dev/null 2>&1; then
|
||||
if systemctl list-unit-files 2>/dev/null | grep -q "^$u"; then
|
||||
systemctl stop "$u" >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
is_ro_mount() { findmnt -no OPTIONS -T "$TARGET" 2>/dev/null | grep -qw ro; }
|
||||
is_bind_mount() { findmnt -no OPTIONS -T "$TARGET" 2>/dev/null | grep -qw bind; }
|
||||
|
||||
mount_layers_count() { awk '$5=="/etc/hosts"{c++} END{print c+0}' /proc/self/mountinfo 2>/dev/null || echo 0; }
|
||||
cleanup_mount_stacks() {
|
||||
local i=0
|
||||
# Unmount bind layers until /etc/hosts is no longer a mountpoint
|
||||
if command -v mountpoint >/dev/null 2>&1; then
|
||||
while mountpoint -q "$TARGET"; do
|
||||
umount -l "$TARGET" >/dev/null 2>&1 || break
|
||||
i=$((i+1))
|
||||
(( i > 20 )) && break
|
||||
done
|
||||
else
|
||||
# Fallback to best-effort using mountinfo count
|
||||
local cnt
|
||||
cnt=$(mount_layers_count)
|
||||
while (( cnt > 1 )); do
|
||||
umount -l "$TARGET" >/dev/null 2>&1 || break
|
||||
i=$((i+1))
|
||||
(( i > 20 )) && break
|
||||
cnt=$(mount_layers_count)
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# Drop protective attributes if present
|
||||
if command -v lsattr >/dev/null 2>&1; then
|
||||
attrs=$(lsattr -d "$TARGET" 2>/dev/null || true)
|
||||
echo "$attrs" | grep -q " i " && chattr -i "$TARGET" >/dev/null 2>&1 || true
|
||||
echo "$attrs" | grep -q " a " && chattr -a "$TARGET" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
stop_units_if_present
|
||||
|
||||
logger -t "$LOGTAG" "pre: unlocking /etc/hosts (starting)"
|
||||
echo "$(date -Is) pre-unlock" >> /run/hosts-guard-hook.log 2>/dev/null || true
|
||||
|
||||
# Always collapse any existing layers; we'll operate on the plain file
|
||||
cleanup_mount_stacks
|
||||
|
||||
# If someone managed a ro single-layer mount, ensure rw by remounting or collapsing again
|
||||
if is_ro_mount; then
|
||||
mount -o remount,rw "$TARGET" >/dev/null 2>&1 || cleanup_mount_stacks
|
||||
fi
|
||||
|
||||
logger -t "$LOGTAG" "pre: unlocking /etc/hosts (done)"
|
||||
|
||||
exit 0
|
||||
0
hosts/guard/setup_hosts_guard.sh
Normal file → Executable file
0
hosts/guard/setup_hosts_guard.sh
Normal file → Executable file
@ -12,6 +12,36 @@ BOLD='\033[1m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
PACMAN_BIN="/usr/bin/pacman"
|
||||
# Determine if this invocation may perform a transaction (upgrade/install/remove)
|
||||
needs_unlock() {
|
||||
# If args include -S (install/upgrade), -U (local install), or -R (remove), we unlock
|
||||
# Also include -Su/-Syu/-Syuu when -S is part of the combined flag
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
-S*|-U|-R|--sync|--upgrade|--remove)
|
||||
return 0 ;;
|
||||
esac
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Run pre/post hooks for /etc/hosts guard if present
|
||||
pre_unlock_hosts() {
|
||||
local pre="/usr/local/share/hosts-guard/pacman-pre-unlock-hosts.sh"
|
||||
if [[ -x "$pre" ]]; then
|
||||
echo -e "${CYAN}[hosts-guard] Preparing /etc/hosts for transaction...${NC}" >&2
|
||||
/bin/bash "$pre" || true
|
||||
fi
|
||||
}
|
||||
|
||||
post_relock_hosts() {
|
||||
local post="/usr/local/share/hosts-guard/pacman-post-relock-hosts.sh"
|
||||
if [[ -x "$post" ]]; then
|
||||
/bin/bash "$post" || true
|
||||
echo -e "${CYAN}[hosts-guard] Protections re-applied to /etc/hosts.${NC}" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# Ensure periodic system services (timer/monitor) are set up; if not, trigger setup
|
||||
ensure_periodic_maintenance() {
|
||||
@ -155,7 +185,7 @@ function has_noconfirm_flag() {
|
||||
|
||||
# Cleanup: remove any installed blocked packages (in addition to the queued operation)
|
||||
function remove_installed_blocked_packages() {
|
||||
local user_args=("$@")
|
||||
# args not used; kept for future policy extension
|
||||
# List installed package names
|
||||
mapfile -t installed_names < <("$PACMAN_BIN" -Qq 2>/dev/null)
|
||||
local to_remove=()
|
||||
@ -223,8 +253,10 @@ function check_for_steam() {
|
||||
|
||||
# Function to check if current day is a weekday (after 4PM Friday until midnight Sunday)
|
||||
function is_weekday() {
|
||||
local day_of_week=$(date +%u) # %u gives 1-7 (Monday is 1, Sunday is 7)
|
||||
local hour=$(date +%H) # %H gives hour in 24-hour format (00-23)
|
||||
local day_of_week
|
||||
day_of_week=$(date +%u) # %u gives 1-7 (Monday is 1, Sunday is 7)
|
||||
local hour
|
||||
hour=$(date +%H) # %H gives hour in 24-hour format (00-23)
|
||||
|
||||
# Monday through Thursday are always weekdays
|
||||
if [[ $day_of_week -ge 1 && $day_of_week -le 4 ]]; then
|
||||
@ -248,7 +280,8 @@ function prompt_for_steam_challenge() {
|
||||
|
||||
# Check if it's a weekday and block completely
|
||||
if is_weekday; then
|
||||
local day_name=$(date +%A)
|
||||
local day_name
|
||||
day_name=$(date +%A)
|
||||
echo -e "${RED}Steam installation BLOCKED: Steam cannot be installed on weekdays.${NC}"
|
||||
echo -e "${RED}Today is $day_name. Please try again on the weekend (Saturday or Sunday).${NC}"
|
||||
return 1
|
||||
@ -522,15 +555,23 @@ fi
|
||||
display_operation "$1"
|
||||
|
||||
# Echo the command that's about to be executed
|
||||
echo -e "${GREEN}Executing:${NC} $PACMAN_BIN $@" >&2
|
||||
echo -e "${GREEN}Executing:${NC} $PACMAN_BIN $*" >&2
|
||||
|
||||
# Record start time for statistics
|
||||
start_time=$(date +%s)
|
||||
|
||||
# Execute the real pacman command
|
||||
# Execute the real pacman command (with /etc/hosts guard handling)
|
||||
if needs_unlock "$@"; then
|
||||
pre_unlock_hosts
|
||||
fi
|
||||
|
||||
"$PACMAN_BIN" "$@"
|
||||
exit_code=$?
|
||||
|
||||
if needs_unlock "$@"; then
|
||||
post_relock_hosts
|
||||
fi
|
||||
|
||||
# Record end time for statistics
|
||||
end_time=$(date +%s)
|
||||
duration=$((end_time - start_time))
|
||||
|
||||
103
scripts/toggle_window_manager.sh
Executable file
103
scripts/toggle_window_manager.sh
Executable file
@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Configuration -----------------------------------------------------------------
|
||||
TARGET_SESSION_NAME="Xfce Session"
|
||||
TARGET_PACKAGES=(
|
||||
xfwm4 # Compositing window manager with XFCE integration
|
||||
xfce4-session # Provides the Xfce session entry for display managers
|
||||
xfce4-panel # Panel with system tray support
|
||||
xfce4-settings # Settings daemon (enables compositing toggle, theming, etc.)
|
||||
xfce4-terminal # Handy default terminal for the new environment
|
||||
)
|
||||
|
||||
# Utility functions --------------------------------------------------------------
|
||||
info() { echo "[INFO] $*"; }
|
||||
warn() { echo "[WARN] $*" >&2; }
|
||||
error() { echo "[ERROR] $*" >&2; exit 1; }
|
||||
|
||||
require_command() {
|
||||
local cmd="$1" pkg_hint="${2:-}"
|
||||
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||
if [[ -n "$pkg_hint" ]]; then
|
||||
warn "Install '$pkg_hint' to obtain the '$cmd' command."
|
||||
fi
|
||||
error "Required command '$cmd' not found."
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_pacman() {
|
||||
require_command pacman "pacman"
|
||||
if ! grep -qi "arch" /etc/os-release 2>/dev/null; then
|
||||
warn "This script was designed for Arch Linux; continuing anyway."
|
||||
fi
|
||||
}
|
||||
|
||||
install_packages() {
|
||||
local missing=()
|
||||
for pkg in "${TARGET_PACKAGES[@]}"; do
|
||||
if ! pacman -Qi "$pkg" >/dev/null 2>&1; then
|
||||
missing+=("$pkg")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing[@]} -eq 0 ]]; then
|
||||
info "All target packages are already installed."
|
||||
return
|
||||
fi
|
||||
|
||||
if ! command -v sudo >/dev/null 2>&1; then
|
||||
error "sudo is required to install packages. Install sudo or run this script as root."
|
||||
fi
|
||||
|
||||
info "Installing missing packages: ${missing[*]}"
|
||||
sudo pacman -S --needed --noconfirm "${missing[@]}"
|
||||
}
|
||||
|
||||
print_post_install_tips() {
|
||||
cat <<EOF
|
||||
|
||||
------------------------------------------------------------------------
|
||||
XFCE session installed.
|
||||
|
||||
• i3 remains your default window manager. We did not modify ~/.xinitrc,
|
||||
display manager defaults, or systemd targets.
|
||||
• At your next graphical login, pick "${TARGET_SESSION_NAME}" (or "Xfce"),
|
||||
then log in to enjoy compositing via xfwm4.
|
||||
• Once you are done testing Unity, simply log out and choose i3 again.
|
||||
|
||||
We'll log you out now so you can switch sessions safely.
|
||||
------------------------------------------------------------------------
|
||||
EOF
|
||||
}
|
||||
|
||||
logout_user() {
|
||||
local session_id="${XDG_SESSION_ID:-}"
|
||||
|
||||
if [[ -n "$session_id" ]] && loginctl show-session "$session_id" >/dev/null 2>&1; then
|
||||
info "Terminating current session (ID: $session_id) via loginctl."
|
||||
loginctl terminate-session "$session_id"
|
||||
return
|
||||
fi
|
||||
|
||||
if loginctl list-sessions 2>/dev/null | awk '{print $1" "$3}' | grep -q " $USER$"; then
|
||||
info "Terminating all sessions for user '$USER' via loginctl."
|
||||
loginctl terminate-user "$USER"
|
||||
return
|
||||
fi
|
||||
|
||||
warn "loginctl could not terminate the session; attempting fallback logout."
|
||||
pkill -KILL -u "$USER" || error "Failed to terminate user sessions. Please log out manually."
|
||||
}
|
||||
|
||||
main() {
|
||||
ensure_pacman
|
||||
install_packages
|
||||
print_post_install_tips
|
||||
|
||||
# Give the user a moment to read the instructions before logging out.
|
||||
logout_user
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Reference in New Issue
Block a user