Add ubuntu-desktop-prune.sh script for cleanup
This commit is contained in:
@@ -0,0 +1,510 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ubuntu-desktop-prune.sh
|
||||||
|
#
|
||||||
|
# Copyright (C) 2026 LINUXexpert.org
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by the
|
||||||
|
# Free Software Foundation, version 3 of the License.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
# for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along
|
||||||
|
# with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Version
|
||||||
|
# =========================
|
||||||
|
SCRIPT_VERSION="1.1"
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Globals / Defaults
|
||||||
|
# =========================
|
||||||
|
LOG_DIR="/var/log/ubuntu-to-mint"
|
||||||
|
mkdir -p "$LOG_DIR"
|
||||||
|
LOG_FILE="${LOG_DIR}/ubuntu-desktop-prune-$(date +%Y%m%d-%H%M%S).log"
|
||||||
|
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||||
|
|
||||||
|
CMD=""
|
||||||
|
ASSUME_YES="no"
|
||||||
|
WITH_RECOMMENDS="no"
|
||||||
|
SKIP_DM_FIX="no"
|
||||||
|
ROLLBACK_DIR=""
|
||||||
|
|
||||||
|
# For safety gates
|
||||||
|
MAX_REMOVALS_DEFAULT=75
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Pretty output
|
||||||
|
# =========================
|
||||||
|
is_tty() { [[ -t 1 ]]; }
|
||||||
|
|
||||||
|
if is_tty && command -v tput >/dev/null 2>&1; then
|
||||||
|
RED="$(tput setaf 1)"
|
||||||
|
GREEN="$(tput setaf 2)"
|
||||||
|
YELLOW="$(tput setaf 3)"
|
||||||
|
BLUE="$(tput setaf 4)"
|
||||||
|
BOLD="$(tput bold)"
|
||||||
|
RESET="$(tput sgr0)"
|
||||||
|
else
|
||||||
|
RED=""; GREEN=""; YELLOW=""; BLUE=""; BOLD=""; RESET=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
info() { echo "${BLUE}INFO:${RESET} $*"; }
|
||||||
|
ok() { echo "${GREEN}OK:${RESET} $*"; }
|
||||||
|
warn() { echo "${YELLOW}WARN:${RESET} $*"; }
|
||||||
|
die() { echo "${RED}ERROR:${RESET} $*"; exit 1; }
|
||||||
|
|
||||||
|
have_cmd() { command -v "$1" >/dev/null 2>&1; }
|
||||||
|
|
||||||
|
need_root() {
|
||||||
|
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
||||||
|
die "This must be run as root. Use: sudo bash $0 ..."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
on_err() {
|
||||||
|
local rc=$?
|
||||||
|
local line="${BASH_LINENO[0]:-unknown}"
|
||||||
|
echo "ERROR: FAILED at line ${line} (exit ${rc})."
|
||||||
|
echo "ERROR: Command: ${BASH_COMMAND}"
|
||||||
|
echo "ERROR: Log: ${LOG_FILE}"
|
||||||
|
exit "$rc"
|
||||||
|
}
|
||||||
|
trap on_err ERR
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
ubuntu-desktop-prune.sh (v${SCRIPT_VERSION})
|
||||||
|
|
||||||
|
Goal:
|
||||||
|
Best-effort, "gentle" removal of Ubuntu GNOME/Ubuntu-session desktop components
|
||||||
|
AFTER you have a working non-Ubuntu desktop (e.g., Mint Cinnamon/MATE/Xfce).
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
sudo bash $0 doctor
|
||||||
|
sudo bash $0 plan
|
||||||
|
sudo bash $0 prune --yes
|
||||||
|
sudo bash $0 rollback /root/ubuntu-desktop-prune-backup-YYYYMMDD-HHMMSS
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--yes Non-interactive / proceed (required for prune)
|
||||||
|
--with-recommends Allow recommends (default: off)
|
||||||
|
--skip-dm-fix Do not attempt to set LightDM as default before pruning
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# APT sanity
|
||||||
|
# =========================
|
||||||
|
ensure_no_apt_locks() {
|
||||||
|
local locks=(/var/lib/dpkg/lock-frontend /var/lib/dpkg/lock /var/lib/apt/lists/lock /var/cache/apt/archives/lock)
|
||||||
|
for l in "${locks[@]}"; do
|
||||||
|
if fuser "$l" >/dev/null 2>&1; then
|
||||||
|
die "APT/dpkg lock is held (lock file: $l). Close package managers and try again."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
apt_fix_broken() {
|
||||||
|
info "Attempting basic dpkg/apt remediation (best-effort)..."
|
||||||
|
DEBIAN_FRONTEND=noninteractive dpkg --configure -a || true
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get -y -f install || true
|
||||||
|
}
|
||||||
|
|
||||||
|
apt_opts_common() {
|
||||||
|
local -a opts
|
||||||
|
opts=(-y -o Dpkg::Use-Pty=0 -o Acquire::Retries=3)
|
||||||
|
if [[ "$WITH_RECOMMENDS" != "yes" ]]; then
|
||||||
|
opts+=(--no-install-recommends)
|
||||||
|
fi
|
||||||
|
echo "${opts[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# OS / environment checks
|
||||||
|
# =========================
|
||||||
|
detect_os_minimal() {
|
||||||
|
[[ -r /etc/os-release ]] || die "Cannot read /etc/os-release"
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
. /etc/os-release
|
||||||
|
|
||||||
|
info "Script v${SCRIPT_VERSION}"
|
||||||
|
info "Log: ${LOG_FILE}"
|
||||||
|
info "Detected OS: ${NAME:-unknown} (ID=${ID:-?}, VERSION_ID=${VERSION_ID:-?}, CODENAME=${VERSION_CODENAME:-?})"
|
||||||
|
|
||||||
|
# We allow Ubuntu base systems (including “Ubuntu-with-Mint-repos” setups).
|
||||||
|
if [[ "${ID:-}" != "ubuntu" && "${ID:-}" != "linuxmint" ]]; then
|
||||||
|
warn "OS ID is not ubuntu/linuxmint. Proceeding anyway, but this script is designed for Ubuntu base systems."
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! have_cmd apt-get || ! have_cmd dpkg; then
|
||||||
|
die "apt-get/dpkg not found. This script requires Debian/Ubuntu-style package management."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Backup / rollback (limited)
|
||||||
|
# =========================
|
||||||
|
backup_state() {
|
||||||
|
local backup_dir="/root/ubuntu-desktop-prune-backup-$(date +%Y%m%d-%H%M%S)"
|
||||||
|
mkdir -p "$backup_dir"
|
||||||
|
|
||||||
|
info "Creating backup at: ${backup_dir}"
|
||||||
|
mkdir -p "${backup_dir}/etc"
|
||||||
|
cp -a /etc/apt "${backup_dir}/etc/" || true
|
||||||
|
cp -a /etc/lightdm 2>/dev/null "${backup_dir}/etc/" || true
|
||||||
|
cp -a /etc/X11/default-display-manager 2>/dev/null "${backup_dir}/etc/" || true
|
||||||
|
cp -a /etc/systemd/system/display-manager.service 2>/dev/null "${backup_dir}/etc/" || true
|
||||||
|
|
||||||
|
dpkg-query -W -f='${Package}\t${Version}\n' > "${backup_dir}/dpkg-packages.tsv" || true
|
||||||
|
apt-mark showmanual > "${backup_dir}/apt-manual.txt" || true
|
||||||
|
apt-mark showhold > "${backup_dir}/apt-holds.txt" || true
|
||||||
|
systemctl list-unit-files --state=enabled > "${backup_dir}/enabled-services.txt" 2>/dev/null || true
|
||||||
|
|
||||||
|
ok "Backup complete."
|
||||||
|
echo "$backup_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
rollback() {
|
||||||
|
need_root
|
||||||
|
local dir="${1:-}"
|
||||||
|
[[ -n "$dir" ]] || die "rollback requires a backup directory argument"
|
||||||
|
[[ -d "$dir" ]] || die "backup directory not found: $dir"
|
||||||
|
[[ -d "$dir/etc/apt" ]] || die "backup does not contain etc/apt: $dir/etc/apt"
|
||||||
|
|
||||||
|
info "Restoring /etc/apt from: $dir/etc/apt"
|
||||||
|
rm -rf /etc/apt
|
||||||
|
cp -a "$dir/etc/apt" /etc/apt
|
||||||
|
|
||||||
|
if [[ -d "$dir/etc/lightdm" ]]; then
|
||||||
|
info "Restoring /etc/lightdm from backup..."
|
||||||
|
rm -rf /etc/lightdm
|
||||||
|
cp -a "$dir/etc/lightdm" /etc/lightdm
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$dir/etc/default-display-manager" ]]; then
|
||||||
|
info "Restoring /etc/X11/default-display-manager from backup..."
|
||||||
|
mkdir -p /etc/X11
|
||||||
|
cp -a "$dir/etc/default-display-manager" /etc/X11/default-display-manager
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$dir/etc/display-manager.service" ]]; then
|
||||||
|
info "Restoring /etc/systemd/system/display-manager.service from backup..."
|
||||||
|
mkdir -p /etc/systemd/system
|
||||||
|
cp -a "$dir/etc/display-manager.service" /etc/systemd/system/display-manager.service
|
||||||
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
ok "Rollback completed (APT + DM configs)."
|
||||||
|
warn "Rollback does NOT automatically reinstall removed packages."
|
||||||
|
info "Now run:"
|
||||||
|
echo " sudo apt-get update"
|
||||||
|
echo " sudo apt-get -f install"
|
||||||
|
}
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Display manager hardening (noninteractive-safe)
|
||||||
|
# =========================
|
||||||
|
ensure_lightdm_default_noninteractive() {
|
||||||
|
if [[ "$SKIP_DM_FIX" == "yes" ]]; then
|
||||||
|
warn "--skip-dm-fix set; skipping LightDM default selection."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -d /run/systemd/system ]]; then
|
||||||
|
warn "systemd not detected; skipping LightDM enablement."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Ensuring LightDM is default display manager (debconf preseed + dpkg-reconfigure)..."
|
||||||
|
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get $(apt_opts_common) install lightdm slick-greeter debconf-utils >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# Preseed the DM selection so noninteractive installs switch away from gdm3.
|
||||||
|
# Key: shared/default-x-display-manager
|
||||||
|
echo "lightdm shared/default-x-display-manager select lightdm" | debconf-set-selections || true
|
||||||
|
|
||||||
|
# Apply selection
|
||||||
|
DEBIAN_FRONTEND=noninteractive dpkg-reconfigure -f noninteractive lightdm >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# Debian/Ubuntu mechanism
|
||||||
|
mkdir -p /etc/X11
|
||||||
|
echo "/usr/sbin/lightdm" > /etc/X11/default-display-manager || true
|
||||||
|
|
||||||
|
if have_cmd update-alternatives; then
|
||||||
|
update-alternatives --set x-display-manager /usr/sbin/lightdm >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If gdm3 exists, disable it so it can't take control
|
||||||
|
if systemctl list-unit-files | awk '{print $1}' | grep -qx 'gdm3.service'; then
|
||||||
|
systemctl disable --now gdm3 >/dev/null 2>&1 || true
|
||||||
|
systemctl mask gdm3 >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure LightDM enabled
|
||||||
|
systemctl unmask lightdm >/dev/null 2>&1 || true
|
||||||
|
systemctl set-default graphical.target >/dev/null 2>&1 || true
|
||||||
|
systemctl enable lightdm >/dev/null 2>&1 || true
|
||||||
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# Best-effort start
|
||||||
|
systemctl restart lightdm >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
ok "LightDM default selection/enablement applied."
|
||||||
|
}
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Target selection
|
||||||
|
# =========================
|
||||||
|
pkg_installed() {
|
||||||
|
dpkg-query -W -f='${Status}\n' "$1" 2>/dev/null | grep -q "install ok installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_prune_list() {
|
||||||
|
# Conservative list: remove Ubuntu GNOME session + Ubuntu desktop meta packages + GDM.
|
||||||
|
# We intentionally avoid removing libs/toolkit packages that Cinnamon/Xfce/MATE may still rely on.
|
||||||
|
local -a candidates=(
|
||||||
|
ubuntu-desktop
|
||||||
|
ubuntu-desktop-minimal
|
||||||
|
ubuntu-session
|
||||||
|
ubuntu-session-minimal
|
||||||
|
gdm3
|
||||||
|
gnome-shell
|
||||||
|
gnome-shell-common
|
||||||
|
gnome-shell-extension-ubuntu-dock
|
||||||
|
yaru-theme-gnome
|
||||||
|
yaru-theme-gtk
|
||||||
|
yaru-theme-icon
|
||||||
|
yaru-theme-sound
|
||||||
|
ubuntu-wallpapers
|
||||||
|
ubuntu-wallpapers-noble
|
||||||
|
ubuntu-wallpapers-jammy
|
||||||
|
gnome-software
|
||||||
|
snap-store
|
||||||
|
)
|
||||||
|
|
||||||
|
local -a selected=()
|
||||||
|
local p
|
||||||
|
for p in "${candidates[@]}"; do
|
||||||
|
if pkg_installed "$p"; then
|
||||||
|
selected+=("$p")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%s\n' "${selected[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_target_summary() {
|
||||||
|
local -a pkgs=("$@")
|
||||||
|
if [[ ${#pkgs[@]} -eq 0 ]]; then
|
||||||
|
warn "No target packages from the conservative prune list are currently installed."
|
||||||
|
warn "Nothing to do."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Prune targets (installed):"
|
||||||
|
printf ' - %s\n' "${pkgs[@]}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Simulation + safety gates
|
||||||
|
# (FIXED: exact-match critical package detection)
|
||||||
|
# =========================
|
||||||
|
apt_simulate_purge() {
|
||||||
|
local -a pkgs=("$@")
|
||||||
|
local sim="/tmp/ubuntu-desktop-prune-sim.$$.txt"
|
||||||
|
|
||||||
|
info "Simulating purge (no changes)..."
|
||||||
|
set +e
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get -s purge --autoremove "${pkgs[@]}" 2>&1 | tee "$sim"
|
||||||
|
local rc=${PIPESTATUS[0]}
|
||||||
|
set -e
|
||||||
|
|
||||||
|
[[ $rc -eq 0 ]] || die "APT simulation failed (exit $rc). See: $sim"
|
||||||
|
|
||||||
|
# Build an exact package list from the simulation.
|
||||||
|
# APT prints: "Remv <pkg>:amd64 ..." or "Purg <pkg> ..."
|
||||||
|
mapfile -t sim_removed < <(
|
||||||
|
awk '/^(Remv|Purg)[[:space:]]+/{print $2}' "$sim" \
|
||||||
|
| sed -E 's/:[a-z0-9]+$//' \
|
||||||
|
| sort -u
|
||||||
|
)
|
||||||
|
|
||||||
|
# Hard stops: ONLY if these exact packages are removed.
|
||||||
|
# NOTE: do NOT treat "systemd-*" as a hard stop.
|
||||||
|
local -a critical_exact=(
|
||||||
|
sudo
|
||||||
|
systemd
|
||||||
|
systemd-sysv
|
||||||
|
network-manager
|
||||||
|
openssh-server
|
||||||
|
linux-image-generic
|
||||||
|
linux-image-amd64
|
||||||
|
)
|
||||||
|
|
||||||
|
local c r
|
||||||
|
for c in "${critical_exact[@]}"; do
|
||||||
|
for r in "${sim_removed[@]}"; do
|
||||||
|
if [[ "$r" == "$c" ]]; then
|
||||||
|
die "Simulation shows removal of critical package '${c}'. Aborting. See: $sim"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
local remv_count
|
||||||
|
remv_count="$(grep -cE '^(Remv|Purg)[[:space:]]' "$sim" || true)"
|
||||||
|
info "Simulation removal count: ${remv_count}"
|
||||||
|
|
||||||
|
if [[ "${ASSUME_YES}" != "yes" && "${remv_count}" -gt "${MAX_REMOVALS_DEFAULT}" ]]; then
|
||||||
|
die "Simulation wants to remove ${remv_count} packages (too many for 'gentle'). Re-run with --yes only if you reviewed $sim."
|
||||||
|
fi
|
||||||
|
|
||||||
|
ok "Simulation looks acceptable. Review: $sim"
|
||||||
|
}
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Commands
|
||||||
|
# =========================
|
||||||
|
doctor() {
|
||||||
|
need_root
|
||||||
|
ensure_no_apt_locks
|
||||||
|
detect_os_minimal
|
||||||
|
|
||||||
|
info "Doctor checks..."
|
||||||
|
apt_fix_broken
|
||||||
|
|
||||||
|
local holds
|
||||||
|
holds="$(apt-mark showhold || true)"
|
||||||
|
if [[ -n "$holds" ]]; then
|
||||||
|
warn "Held packages detected (may affect pruning):"
|
||||||
|
echo "$holds"
|
||||||
|
else
|
||||||
|
ok "No held packages detected."
|
||||||
|
fi
|
||||||
|
|
||||||
|
local -a pkgs=()
|
||||||
|
mapfile -t pkgs < <(build_prune_list || true)
|
||||||
|
|
||||||
|
if show_target_summary "${pkgs[@]}"; then
|
||||||
|
ok "Doctor complete."
|
||||||
|
else
|
||||||
|
ok "Doctor complete (nothing obvious to prune)."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
plan() {
|
||||||
|
need_root
|
||||||
|
ensure_no_apt_locks
|
||||||
|
detect_os_minimal
|
||||||
|
apt_fix_broken
|
||||||
|
|
||||||
|
local -a pkgs=()
|
||||||
|
mapfile -t pkgs < <(build_prune_list || true)
|
||||||
|
show_target_summary "${pkgs[@]}" || return 0
|
||||||
|
|
||||||
|
# LightDM check (plan-time warning)
|
||||||
|
if [[ "$SKIP_DM_FIX" != "yes" ]]; then
|
||||||
|
if ! pkg_installed lightdm; then
|
||||||
|
warn "lightdm is not installed. prune will attempt to install/activate it first."
|
||||||
|
fi
|
||||||
|
if [[ -f /etc/X11/default-display-manager ]]; then
|
||||||
|
info "Current default-display-manager: $(cat /etc/X11/default-display-manager || true)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
apt_simulate_purge "${pkgs[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
prune() {
|
||||||
|
need_root
|
||||||
|
ensure_no_apt_locks
|
||||||
|
detect_os_minimal
|
||||||
|
|
||||||
|
if [[ "$ASSUME_YES" != "yes" ]]; then
|
||||||
|
die "prune is destructive and requires --yes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
apt_fix_broken
|
||||||
|
|
||||||
|
local -a pkgs=()
|
||||||
|
mapfile -t pkgs < <(build_prune_list || true)
|
||||||
|
show_target_summary "${pkgs[@]}" || return 0
|
||||||
|
|
||||||
|
# Ensure DM is safe before removing gdm3/gnome session packages
|
||||||
|
ensure_lightdm_default_noninteractive
|
||||||
|
|
||||||
|
# Re-run simulation immediately before changes
|
||||||
|
apt_simulate_purge "${pkgs[@]}"
|
||||||
|
|
||||||
|
local backup_dir
|
||||||
|
backup_dir="$(backup_state)"
|
||||||
|
info "Backup dir: ${backup_dir}"
|
||||||
|
|
||||||
|
info "Proceeding with purge..."
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get $(apt_opts_common) purge --autoremove "${pkgs[@]}"
|
||||||
|
|
||||||
|
info "Post-purge remediation..."
|
||||||
|
apt_fix_broken
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get $(apt_opts_common) autoremove --purge || true
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get $(apt_opts_common) clean || true
|
||||||
|
|
||||||
|
ok "Prune completed."
|
||||||
|
info "Log: ${LOG_FILE}"
|
||||||
|
info "Backup (limited): ${backup_dir}"
|
||||||
|
info "Next:"
|
||||||
|
echo " 1) Reboot"
|
||||||
|
echo " 2) Confirm you can login to your desired session"
|
||||||
|
echo " 3) If you hit issues, restore via Timeshift/snapshot (preferred) or rollback APT/DM configs:"
|
||||||
|
echo " sudo bash $0 rollback ${backup_dir}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# =========================
|
||||||
|
# Arg parsing (command-first OR options-first)
|
||||||
|
# =========================
|
||||||
|
parse_args_any_order() {
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
doctor|plan|prune|rollback)
|
||||||
|
[[ -z "$CMD" ]] || die "Multiple commands specified: '${CMD}' and '$1'"
|
||||||
|
CMD="$1"
|
||||||
|
shift 1
|
||||||
|
if [[ "$CMD" == "rollback" ]]; then
|
||||||
|
if [[ $# -gt 0 && "${1:-}" != --* ]]; then
|
||||||
|
ROLLBACK_DIR="$1"
|
||||||
|
shift 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
--yes) ASSUME_YES="yes"; shift 1;;
|
||||||
|
--with-recommends) WITH_RECOMMENDS="yes"; shift 1;;
|
||||||
|
--skip-dm-fix) SKIP_DM_FIX="yes"; shift 1;;
|
||||||
|
-h|--help|help) usage; exit 0;;
|
||||||
|
*) die "Unknown argument: $1" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
parse_args_any_order "$@"
|
||||||
|
[[ -n "$CMD" ]] || { usage; exit 1; }
|
||||||
|
|
||||||
|
case "$CMD" in
|
||||||
|
doctor) doctor ;;
|
||||||
|
plan) plan ;;
|
||||||
|
prune) prune ;;
|
||||||
|
rollback)
|
||||||
|
[[ -n "$ROLLBACK_DIR" ]] || die "rollback requires a directory argument"
|
||||||
|
rollback "$ROLLBACK_DIR"
|
||||||
|
;;
|
||||||
|
*) die "Unknown command: $CMD" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user