— Testing Rolling, Anti-Cinnamon Guardrails
This is a 2025 refresh of my Debian + Fluxbox notes, updated specifically for Debian Testing (rolling). The major addition this year is a set of guardrails to prevent Cinnamon from being installed during upgrades or via meta/task packages, plus a pre-flight step that removes Cinnamon if it already snuck in.
What’s new in this 2025 Testing update
- Targets Debian Testing (rolling) rather than a fixed stable snapshot.
- Pre-guardrail cleanup: detect and purge Cinnamon if present.
- Two-layer anti-Cinnamon safety:
- Hard APT pin refuses Cinnamon and its task/meta packages.
- Lean APT defaults disable automatic Recommends/Suggests to prevent surprise DE pulls.
- Audio modernized: media keys now use
wpctl(PipeWire-native), notamixer. - Completes the daily-driver baseline: adds CUPS and a Debian-repo browser.
Assumptions
- Fresh minimal install of Debian Testing.
- You want Xorg + Fluxbox (not a full desktop environment).
- You want NetworkManager to own networking.
- You want a clean, stable lightweight workflow even as Testing evolves.
Install command
Save the script below as debian-fluxbox-setup-testing-2025.sh, then run:
sudo bash debian-fluxbox-setup-testing-2025.sh <username>
One-shot script (2025 Testing rolling)
#!/usr/bin/env bash
###############################################################################
# Debian Testing + Fluxbox setup helper (2025, data-driven components)
#
# PURPOSE
# Turn a minimal Debian Testing install into "my normal workstation"
# using a small set of data-driven components and profiles.
#
# KEY FEATURES
# - Components are defined in a single list (COMPONENT_SPECS).
# - Profiles: workstation (default), full, minimal, guardrails.
# - Generic per-component flags: --no-, --enable-, --with-.
# - Interactive wizard: --interactive.
# - Dry-run: --dry-run (print plan, no changes).
# - Per-user defaults config: ~/.config/debian-fluxbox-setup.conf
#
# HOW TO ADD A NEW COMPONENT / OPTION
# 1) Pick an ID (no spaces, used in flags & config), e.g. "dev-tools".
# 2) Decide:
# - A short human description.
# - Which apt packages to install (space-separated, or empty).
# - An optional hook function name to run after install (or empty).
# - Which profiles enable this by default:
# workstation, full, minimal, guardrails
#
# 3) Add ONE LINE to COMPONENT_SPECS with this format:
# "id|Description|pkg1 pkg2 ...|hook_function|profile1,profile2,..."
#
# Examples:
# - Package-only component:
# "dev-tools|Developer tools (git, vim, htop)|git vim htop||workstation,full"
#
# - Component with a post-install hook:
# "my-net-tweaks|Custom network tweaks||my_net_hook|full"
#
# and somewhere above in the script:
# my_net_hook() {
# # do custom sysctl, config files, etc.
# }
#
# 4) After that, you automatically get:
# - CLI flags:
# --no-dev-tools (disable)
# --enable-dev-tools (enable)
# --with-dev-tools (alias for --enable-dev-tools)
# - Interactive prompt line.
# - Config-file override:
# dev-tools=true or dev-tools=false
# - Inclusion in any profile(s) you listed.
#
# CONFIG FILE (OPTIONAL)
# Path: ~/.config/debian-fluxbox-setup.conf (for the user invoking sudo)
#
# Supported keys:
# PROFILE= # workstation, full, minimal, guardrails
# =true|false # e.g. audio=false, printing=true
#
# Example:
# PROFILE=minimal
# audio=true
# browsers=true
#
# PROFILES
# workstation (default) 2025 blog behavior: guardrails + X + NM + utils +
# bt/batt tray + PipeWire audio + printing + browsers +
# config + dev-tools.
# full workstation + extras (vlc, libreoffice, etc.) +
# media-graphics, media-audio, games.
# minimal guardrails + X + NM + basic utils, but:
# - NO audio
# - NO printing
# - NO browsers
# - NO bt/batt tray
# - NO extras
# guardrails Cinnamon cleanup + APT guardrails only.
#
# AUDIO BACKENDS
# - Default in workstation/full: PipeWire stack (component: audio).
# - Optional non-default ALSA stack (component: audio-alsa).
# - Convenience flag:
# --alsa / --alsa-audio => disable PipeWire audio, enable ALSA audio.
#
# EXAMPLES
# Default workstation profile for $SUDO_USER:
# sudo ./debian-fluxbox-setup-2025.sh
#
# Explicit user + full profile:
# sudo ./debian-fluxbox-setup-2025.sh --user myuser --profile full
#
# Minimal profile:
# sudo ./debian-fluxbox-setup-2025.sh --user myuser --profile minimal
#
# Guardrails only:
# sudo ./debian-fluxbox-setup-2025.sh --profile guardrails
#
# Drop specific components:
# sudo ./debian-fluxbox-setup-2025.sh --profile full --no-printing --no-audio
#
# Use ALSA audio instead of PipeWire:
# sudo ./debian-fluxbox-setup-2025.sh --profile workstation --alsa
#
# Dry-run (no changes, just a plan):
# ./debian-fluxbox-setup-2025.sh --profile minimal --dry-run
#
# Interactive:
# sudo ./debian-fluxbox-setup-2025.sh --interactive
###############################################################################
set -euo pipefail
SCRIPT_NAME="${0##*/}"
usage() {
cat < Disable a component (e.g. --no-audio, --no-printing).
--enable- Enable a component (e.g. --enable-audio).
--with- Alias for --enable-.
--extras Alias for --enable-extras.
Audio backend convenience:
--alsa
--alsa-audio Disable PipeWire audio, enable ALSA audio stack.
Other:
-h, --help Show this help and exit.
Config file:
~/.config/debian-fluxbox-setup.conf (for the user invoking sudo)
Supported keys:
PROFILE=
=true|false (e.g. audio=false, printing=true)
EOF
}
###############################################################################
# Low-level helpers
###############################################################################
os_hint() {
if [[ -r /etc/os-release ]]; then
if ! grep -qiE 'testing' /etc/os-release; then
echo "[!] /etc/os-release does not appear to say 'testing'."
echo " Continuing anyway (you may be on a testing-derived snapshot)."
fi
fi
}
pre_clean_cinnamon() {
echo "[*] Checking for existing Cinnamon install..."
# SAFETY CHECK: Prevent purging if inside a GUI
if [[ -n "${DISPLAY:-}" ]]; then
echo "[!] WARNING: You appear to be running inside a graphical session (DISPLAY=$DISPLAY)."
echo " Purging Cinnamon while logged into it (or a DM) might crash your session."
echo " It is STRONGLY recommended to run this component from a TTY (Ctrl+Alt+F3)."
echo
read -r -p " Press Enter to continue anyway (risk of crash), or Ctrl+C to abort." || exit 1
fi
local CINN_PKGS_INSTALLED
CINN_PKGS_INSTALLED="$(
dpkg -l 2>/dev/null | awk '{print $2}' | \
grep -E '^cinnamon($|-)|^task-cinnamon-desktop$|^cinnamon-desktop-environment$' || true
)"
if [[ -n "${CINN_PKGS_INSTALLED}" ]]; then
echo "[!] Cinnamon-related packages detected:"
echo "${CINN_PKGS_INSTALLED}" | sed 's/^/ - /'
echo "[*] Purging Cinnamon-related packages..."
apt-get purge -y \
task-cinnamon-desktop \
cinnamon-desktop-environment \
'cinnamon*' || true
echo "[*] Autoremoving orphaned dependencies..."
apt-get autoremove -y || true
echo "[*] Cinnamon removal complete."
else
echo "[*] No Cinnamon packages detected."
fi
}
setup_guardrails() {
echo "[*] Creating APT guardrails..."
cat > /etc/apt/apt.conf.d/99lean-desktop <<'EOF'
APT::Install-Recommends "false";
APT::Install-Suggests "false";
EOF
cat > /etc/apt/preferences.d/no-cinnamon.pref <<'EOF'
Package: cinnamon
Pin: release *
Pin-Priority: -1
Package: cinnamon-*
Pin: release *
Pin-Priority: -1
Package: task-cinnamon-desktop
Pin: release *
Pin-Priority: -1
Package: cinnamon-desktop-environment
Pin: release *
Pin-Priority: -1
EOF
}
apt_update() {
echo "[*] Updating apt..."
apt-get update
}
network_post_install() {
echo "[*] Enabling NetworkManager service..."
systemctl enable --now NetworkManager
echo "[*] Setting /etc/network/interfaces to loopback-only (NM-friendly)..."
if [[ -f /etc/network/interfaces ]]; then
cp -a /etc/network/interfaces "/etc/network/interfaces.bak.$(date +%Y%m%d%H%M%S)"
fi
cat > /etc/network/interfaces <<'EOF'
# NetworkManager-friendly baseline.
# Loopback only.
source /etc/network/interfaces.d/*
auto lo
iface lo inet loopback
EOF
}
setup_fluxbox_config() {
if [[ -z "${USER_NAME:-}" || -z "${USER_HOME:-}" ]]; then
echo "[!] Fluxbox config requested but USER_NAME/USER_HOME not set; skipping."
return 0
fi
# Check which components are enabled so we can conditionally add menu entries
# and keybindings that actually match the installed toolset.
local utils_enabled="${COMP_ENABLED[utils]:-false}"
local browsers_enabled="${COMP_ENABLED[browsers]:-false}"
local network_enabled="${COMP_ENABLED[network]:-false}"
local audio_pipewire_enabled="${COMP_ENABLED[audio]:-false}"
local audio_alsa_enabled="${COMP_ENABLED[audio-alsa]:-false}"
local printing_enabled="${COMP_ENABLED[printing]:-false}"
echo "[*] Creating Fluxbox config skeleton for ${USER_NAME}..."
install -d -m 0755 "${USER_HOME}/.fluxbox"
chown -R "${USER_NAME}:${USER_NAME}" "${USER_HOME}/.fluxbox"
###########################################################################
# Menu
###########################################################################
if [[ ! -f "${USER_HOME}/.fluxbox/menu" ]]; then
# Begin menu and always-present terminal
cat > "${USER_HOME}/.fluxbox/menu" <<'EOF'
[begin] (fluxbox)
[exec] (Terminal) {xterm}
EOF
# File manager + display tools if utils=true
if [[ "${utils_enabled}" == "true" ]]; then
cat >> "${USER_HOME}/.fluxbox/menu" <<'EOF'
[exec] (File Manager) {thunar}
[exec] (Display Settings) {arandr}
EOF
fi
# Browsers if browsers=true
if [[ "${browsers_enabled}" == "true" ]]; then
cat >> "${USER_HOME}/.fluxbox/menu" <<'EOF'
[exec] (Web Browser - Chromium) {chromium}
[exec] (Web Browser - Firefox ESR) {firefox-esr}
EOF
fi
# Network connections editor if network=true
if [[ "${network_enabled}" == "true" ]]; then
cat >> "${USER_HOME}/.fluxbox/menu" <<'EOF'
[exec] (Network Connections) {nm-connection-editor}
EOF
fi
# Audio control if any audio backend is enabled
if [[ "${audio_pipewire_enabled}" == "true" ]]; then
cat >> "${USER_HOME}/.fluxbox/menu" <<'EOF'
[exec] (Audio Control) {pavucontrol}
EOF
elif [[ "${audio_alsa_enabled}" == "true" ]]; then
cat >> "${USER_HOME}/.fluxbox/menu" <<'EOF'
[exec] (Audio Mixer) {alsamixer}
EOF
fi
# Printing GUI if printing=true
if [[ "${printing_enabled}" == "true" ]]; then
cat >> "${USER_HOME}/.fluxbox/menu" <<'EOF'
[exec] (Printers) {system-config-printer}
EOF
fi
# System submenu (always present)
cat >> "${USER_HOME}/.fluxbox/menu" <<'EOF'
[submenu] (System)
[exec] (Reconfigure) {fluxbox-remote reconfigure}
[exec] (Restart) {fluxbox-remote restart}
[exec] (Logout) {fluxbox-remote exit}
[end]
[end]
EOF
fi
###########################################################################
# Keys
###########################################################################
if [[ ! -f "${USER_HOME}/.fluxbox/keys" ]]; then
# Base keybindings (non-audio)
cat > "${USER_HOME}/.fluxbox/keys" <<'EOF'
# Basic Fluxbox keybindings
Mod1 Tab :NextWindow (workspace=[current])
Mod1 Shift Tab :PrevWindow (workspace=[current])
Mod1 F4 :Close
Mod1 z :RootMenu
Mod1 Shift z :HideMenus
Mod1 x :ExecCommand xterm
Control Shift n :ExecCommand chromium --incognito
XF86MonBrightnessUp :ExecCommand /usr/bin/xbacklight -inc 5
XF86MonBrightnessDown :ExecCommand /usr/bin/xbacklight -dec 5
EOF
# Audio keybindings (PipeWire) if enabled
if [[ "${audio_pipewire_enabled}" == "true" ]]; then
cat >> "${USER_HOME}/.fluxbox/keys" <<'EOF'
# Audio volume keys (PipeWire via wpctl)
XF86AudioLowerVolume :ExecCommand wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
XF86AudioRaiseVolume :ExecCommand wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+
XF86AudioMute :ExecCommand wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
EOF
fi
# Audio keybindings (ALSA) if enabled
if [[ "${audio_alsa_enabled}" == "true" ]]; then
cat >> "${USER_HOME}/.fluxbox/keys" <<'EOF'
# Audio volume keys (ALSA via amixer)
XF86AudioLowerVolume :ExecCommand amixer set Master 5%- unmute
XF86AudioRaiseVolume :ExecCommand amixer set Master 5%+ unmute
XF86AudioMute :ExecCommand amixer set Master toggle
EOF
fi
fi
###########################################################################
# Startup and .xinitrc
###########################################################################
cat > "${USER_HOME}/.fluxbox/startup" <<'EOF'
#!/bin/sh
xset -b
command -v numlockx >/dev/null 2>&1 && numlockx on &
command -v nm-applet >/dev/null 2>&1 && nm-applet &
command -v blueman-applet >/dev/null 2>&1 && blueman-applet &
command -v cbatticon >/dev/null 2>&1 && cbatticon &
command -v pasystray >/dev/null 2>&1 && pasystray &
command -v fbsetbg >/dev/null 2>&1 && fbsetbg -l &
xterm -geometry 120x55+0+0 &
exec /usr/bin/fluxbox
EOF
chmod +x "${USER_HOME}/.fluxbox/startup"
# .xinitrc
if [[ ! -f "${USER_HOME}/.xinitrc" ]]; then
cat > "${USER_HOME}/.xinitrc" <<'EOF'
#!/bin/sh
exec startfluxbox
EOF
chmod +x "${USER_HOME}/.xinitrc"
fi
chown -R "${USER_NAME}:${USER_NAME}" \
"${USER_HOME}/.fluxbox" \
"${USER_HOME}/.xinitrc"
}
postrun_summary() {
echo
echo "=================================================================="
echo "[*] Post-run verification"
echo "=================================================================="
echo
echo "[1/7] Cinnamon packages installed (should be none if cleanup ran):"
# Use if/else to avoid grep failure triggering set -e
if dpkg -l | awk '{print $2}' | grep -qE '^cinnamon($|-)|^task-cinnamon-desktop$|^cinnamon-desktop-environment$'; then
echo " [!] Cinnamon packages still present:"
dpkg -l | awk '{print $2}' | grep -E '^cinnamon($|-)|^task-cinnamon-desktop$|^cinnamon-desktop-environment$' || true
else
echo " [✓] none"
fi
echo
echo "[2/7] APT policy for cinnamon (expect Pin-Priority -1 if guardrails enabled):"
apt-cache policy cinnamon 2>/dev/null || echo " (package not visible in current cache)"
echo
echo "[3/7] APT guardrail files:"
ls -l /etc/apt/apt.conf.d/99lean-desktop /etc/apt/preferences.d/no-cinnamon.pref 2>/dev/null || \
echo " (guardrail files not present)"
echo
echo "[4/7] CUPS packages:"
dpkg -l | awk '{print $2}' | grep -E '^cups$|^cups-client$' || echo " (cups not installed)"
echo
echo "[5/7] Browser packages:"
dpkg -l | awk '{print $2}' | grep -E '^chromium$|^firefox-esr$' || echo " (no Debian-repo browser installed)"
echo
echo "[6/7] PipeWire control tool (wpctl):"
if command -v wpctl >/dev/null 2>&1; then
echo " [✓] found at: $(command -v wpctl)"
else
echo " [!] wpctl not found (likely using ALSA-only or no audio component)"
fi
echo
echo "[7/7] wpctl status (may be minimal pre-login):"
if command -v wpctl >/dev/null 2>&1 && [[ -n "${USER_NAME:-}" ]]; then
sudo -u "${USER_NAME}" wpctl status 2>/dev/null || echo " (no active user PipeWire session yet)"
else
echo " (skipped)"
fi
echo
echo "[✓] Setup complete."
echo
echo "Next steps:"
if [[ -n "${USER_N
AME:-}" ]]; then
echo " - Log in as ${USER_NAME}"
fi
echo " - If not using a display manager, run: startx"
echo " - Use nm-applet to join Wi-Fi (if installed)"
echo
echo "Guardrails (if enabled):"
echo " - /etc/apt/preferences.d/no-cinnamon.pref"
echo " - /etc/apt/apt.conf.d/99lean-desktop"
echo
echo "To re-enable Recommends/Suggests globally:"
echo " sudo rm /etc/apt/apt.conf.d/99lean-desktop"
echo
echo "To remove the Cinnamon block:"
echo " sudo rm /etc/apt/preferences.d/no-cinnamon.pref"
echo "=================================================================="
}
###############################################################################
# Data-driven component definitions
#
# Format of each entry:
# "id|Description|packages|hook_function|default_profiles"
#
# id : short token, used in flags (--no-id, --enable-id)
# Description : human-readable description shown in prompts/plan
# packages : space-separated apt package names (or empty)
# hook_function : shell function name to run after packages (or empty)
# default_profiles : comma-separated profiles that enable this by default:
# workstation,full,minimal,guardrails
###############################################################################
COMPONENT_SPECS=(
"cinnamon-clean|Pre-clean Cinnamon packages||pre_clean_cinnamon|workstation,full,minimal,guardrails"
"guardrails|APT guardrails (no Cinnamon, no Recommends/Suggests)||setup_guardrails|workstation,full,minimal,guardrails"
"x|Xorg + Fluxbox core|xorg xinit x11-xserver-utils xterm fluxbox fonts-dejavu-core xbacklight||workstation,full,minimal"
"network|NetworkManager and NM-friendly config|network-manager network-manager-gnome|network_post_install|workstation,full,minimal"
"utils|Thunar & desktop utilities|thunar thunar-volman arandr numlockx||workstation,full,minimal"
"bt-batt|Bluetooth & battery tray|blueman cbatticon||workstation,full"
"audio|Audio (PipeWire stack)|pipewire-audio pipewire-pulse wireplumber pavucontrol pasystray||workstation,full"
"audio-alsa|Audio (ALSA stack)|alsa-utils alsa-tools alsa-oss||"
"printing|Printing stack (CUPS)|cups cups-client system-config-printer||workstation,full"
"browsers|Browsers (Chromium & Firefox ESR)|chromium firefox-esr||workstation,full"
"extras|Extras (evince, vlc, libreoffice, etc.)|evince vlc libreoffice xtrlock xfburn simple-scan calibre||full"
"config|Fluxbox user config skeleton (per-user)||setup_fluxbox_config|workstation,full,minimal"
"dev-tools|Developer tools (git, vim, tmux, htop, build-essential)|git vim tmux htop build-essential curl||workstation,full"
"media-graphics|Graphics tools (GIMP, Inkscape)|gimp inkscape||full"
"media-audio|Audio editor tools (Audacity)|audacity||full"
"dosbox|Retro DOSBox gaming (DOSBox Staging)|dosbox-staging||full"
"casual-games|Lightweight games (ScummVM, Xboard, XMahjongg, XGalaga)|scummvm xboard gnuchess xmahjongg xgalaga||full"
)
# Arrays to hold parsed component data
declare -a COMPONENT_IDS
declare -A COMP_DESC COMP_PKGS COMP_HOOK COMP_DEFAULTS COMP_ENABLED
for spec in "${COMPONENT_SPECS[@]}"; do
IFS='|' read -r id desc pkgs hook defaults <<<"$spec"
COMPONENT_IDS+=("$id")
COMP_DESC["$id"]="$desc"
COMP_PKGS["$id"]="$pkgs"
COMP_HOOK["$id"]="$hook"
COMP_DEFAULTS["$id"]="$defaults"
done
###############################################################################
# Profiles & config
###############################################################################
INTERACTIVE="false"
DRY_RUN="false"
PROFILE="workstation"
apply_profile() {
local id defaults
for id in "${COMPONENT_IDS[@]}"; do
defaults="${COMP_DEFAULTS[$id]}"
if [[ ",${defaults}," == *",${PROFILE},"* ]]; then
COMP_ENABLED["$id"]="true"
else
COMP_ENABLED["$id"]="false"
fi
done
}
load_config() {
local base_user base_home cfg_path line key value val_lc
base_user="${SUDO_USER:-root}"
base_home="$(eval echo "~${base_user}")"
cfg_path="${base_home}/.config/debian-fluxbox-setup.conf"
if [[ ! -r "${cfg_path}" ]]; then
return 0
fi
echo "[*] Loading config from ${cfg_path}"
while IFS= read -r line || [[ -n "$line" ]]; do
line="${line%%#*}"
line="${line#"${line%%[![:space:]]*}"}"
line="${line%"${line##*[![:space:]]}"}"
[[ -z "$line" ]] && continue
[[ "$line" != *=* ]] && continue
key="${line%%=*}"
value="${line#*=}"
key="${key%"${key##*[![:space:]]}"}"
key="${key#"${key%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
value="${value#"${value%%[![:space:]]}"}"
case "$key" in
PROFILE)
PROFILE="$value"
apply_profile
;;
*)
if [[ -n "${COMP_DESC[$key]+_}" ]]; then
val_lc="$(printf '%s' "$value" | tr 'A-Z' 'a-z')"
case "$val_lc" in
true|1|yes|on) COMP_ENABLED["$key"]="true" ;;
false|0|no|off) COMP_ENABLED["$key"]="false" ;;
*)
echo "[*] Ignoring invalid boolean for ${key} in config: ${value}"
;;
esac
else
echo "[*] Ignoring unknown config key: $key"
fi
;;
esac
done < "${cfg_path}"
}
# Initialize from default profile, then config
apply_profile
load_config
###############################################################################
# CLI parsing
###############################################################################
USER_NAME="${SUDO_USER:-}"
while [[ $# -gt 0 ]]; do
case "$1" in
-u|--user)
if [[ $# -lt 2 ]]; then
echo "Error: --user requires an argument" >&2
usage
exit 1
fi
USER_NAME="$2"
shift 2
;;
-i|--interactive)
INTERACTIVE="true"
shift
;;
-p|--profile)
if [[ $# -lt 2 ]]; then
echo "Error: --profile requires an argument" >&2
usage
exit 1
fi
PROFILE="$2"
apply_profile
shift 2
;;
--profile=*)
PROFILE="${1#*=}"
apply_profile
shift
;;
--alsa|--alsa-audio)
# Switch audio backend: disable PipeWire audio, enable ALSA audio.
if [[ -n "${COMP_DESC[audio]+_}" ]]; then
COMP_ENABLED["audio"]="false"
fi
if [[ -n "${COMP_DESC[audio-alsa]+_}" ]]; then
COMP_ENABLED["audio-alsa"]="true"
fi
shift
;;
--dry-run)
DRY_RUN="true"
shift
;;
--no-*)
{
id="${1#--no-}"
if [[ -n "${COMP_DESC[$id]+_}" ]]; then
COMP_ENABLED["$id"]="false"
shift
else
echo "Unknown component in flag: $1" >&2
usage
exit 1
fi
}
;;
--enable-*|--with-*)
{
id="${1#--enable-}"
id="${id#--with-}"
if [[ -n "${COMP_DESC[$id]+_}" ]]; then
COMP_ENABLED["$id"]="true"
shift
else
echo "Unknown component in flag: $1" >&2
usage
exit 1
fi
}
;;
--extras)
if [[ -n "${COMP_DESC[extras]+_}" ]]; then
COMP_ENABLED["extras"]="true"
fi
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
break
;;
*)
echo "Unknown option: $1" >&2
usage
exit 1
;;
esac
done
###############################################################################
# Interactive wizard
###############################################################################
ask_component() {
local id="$1"
local current="${COMP_ENABLED[$id]:-false}"
local desc="${COMP_DESC[$id]}"
local def_str reply
if [[ "$current" == "true" ]]; then
def_str="Y/n"
else
def_str="y/N"
fi
while true; do
read -r -p " - ${desc}? [$def_str] " reply || { echo; echo "Aborted."; exit 1; }
reply="${reply:-}"
if [[ -z "$reply" ]]; then
break
fi
case "$reply" in
[Yy]*) COMP_ENABLED["$id"]="true"; break ;;
[Nn]*) COMP_ENABLED["$id"]="false"; break ;;
*) echo "Please answer y or n." ;;
esac
done
}
if [[ "${INTERACTIVE}" == "true" ]]; then
echo "=== Debian + Fluxbox setup interactive mode ==="
echo
if [[ -z "${USER_NAME}" ]]; then
read -r -p "Target username (must already exist, or leave blank for system-only): " USER_NAME
else
tmp_user=""
read -r -p "Target username [${USER_NAME}] (blank to keep): " tmp_user || true
if [[ -n "${tmp_user:-}" ]]; then
USER_NAME="${tmp_user}"
fi
fi
echo
echo "Profile base (current: ${PROFILE}):"
echo " workstation, full, minimal, guardrails"
tmp_profile=""
read -r -p "Profile name (or blank to keep '${PROFILE}'): " tmp_profile || true
if [[ -n "${tmp_profile:-}" ]]; then
PROFILE="${tmp_profile}"
apply_profile
fi
echo
echo "Toggle components (Y = include, n = drop):"
for id in "${COMPONENT_IDS[@]}"; do
ask_component "$id"
done
echo
fi
###############################################################################
# Pre-flight checks and plan
###############################################################################
USER_HOME=""
if [[ -n "${USER_NAME:-}" ]]; then
if ! id "${USER_NAME}" >/dev/null 2>&1; then
echo "User '${USER_NAME}' does not exist." >&2
exit 1
fi
USER_HOME="$(eval echo "~${USER_NAME}")"
fi
# Root check (skip for dry-run so you can inspect plans as non-root).
if [[ "${DRY_RUN}" != "true" && "$(id -u)" -ne 0 ]]; then
echo "Please run as root (use sudo)." >&2
exit 1
fi
# Config component needs a user when actually writing files.
if [[ "${DRY_RUN}" != "true" && "${COMP_ENABLED[config]:-false}" == "true" && -z "${USER_NAME:-}" ]]; then
echo "Error: 'config' component enabled but no target username was provided." >&2
echo "Specify --user USER or disable with --no-config or use guardrails-only profile." >&2
exit 1
fi
os_hint
echo "=================================================================="
echo "Debian + Fluxbox setup - plan"
echo " Target user: ${USER_NAME:-}"
echo " Profile: ${PROFILE}"
echo " Dry-run mode: ${DRY_RUN}"
echo
echo "Components:"
for id in "${COMPONENT_IDS[@]}"; do
printf " - %-12s : %s [%s]\n" "$id" "${COMP_DESC[$id]}" "${COMP_ENABLED[$id]:-false}"
done
echo "=================================================================="
echo
# Dry-run: show high-level steps and exit before touching the system.
if [[ "${DRY_RUN}" == "true" ]]; then
echo "Dry-run mode: no changes will be made."
echo "Steps that WOULD be executed:"
echo " - apt-get update"
for id in "${COMPONENT_IDS[@]}"; do
if [[ "${COMP_ENABLED[$id]:-false}" == "true" ]]; then
pkgs="${COMP_PKGS[$id]}"
hook="${COMP_HOOK[$id]}"
echo " - ${COMP_DESC[$id]}"
if [[ -n "$pkgs" ]]; then
echo " packages: $pkgs"
fi
if [[ -n "$hook" ]]; then
echo " hook: $hook"
fi
fi
done
echo
echo "No packages will be installed or removed, and no files will be written in dry-run mode."
exit 0
fi
###############################################################################
# Component runner
###############################################################################
run_component() {
local id="$1"
local pkgs="${COMP_PKGS[$id]}"
local hook="${COMP_HOOK[$id]}"
if [[ "${COMP_ENABLED[$id]:-false}" != "true" ]]; then
echo "[*] Skipping ${COMP_DESC[$id]}."
return 0
fi
if [[ -n "$pkgs" ]]; then
echo "[*] Installing packages for ${COMP_DESC[$id]}..."
# shellcheck disable=SC2086
apt-get install -y $pkgs
fi
if [[ -n "$hook" ]]; then
"$hook"
fi
}
###############################################################################
# Execution
###############################################################################
apt_update
for id in "${COMPONENT_IDS[@]}"; do
run_component "$id"
done
postrun_summary
Additional software (optional, still fits the Fluxbox ethos)
If you want parity with the older “daily driver” list, these still pair nicely with a lightweight setup:
- Document/PDF:
evince - Media:
vlc - Office:
libreoffice - Locking:
xtrlock - Optical:
xfburn(if you still burn discs) - Scanning:
simple-scan(if you need it) - E-books:
calibre
Why I’m comfortable with these guardrails
- Testing changes quickly. The pin ensures Cinnamon can’t be reintroduced via drift.
- Lean APT defaults reduce surprises. Install “nice-to-haves” explicitly, when you want them.
- You can disable this any time. Removing two small files restores default behavior.
Quick troubleshooting
- Wi-Fi not showing up? Confirm your interfaces aren’t declared in
/etc/network/interfacesbeyond loopback and that NetworkManager is enabled. - wpctl keys not working on first boot? Log into Fluxbox once so your user PipeWire session is active; then test again.
- Printing not detected? Install model-specific drivers if needed and confirm CUPS is running.
No comments:
Post a Comment