MintOS - Post Script

MintOS - Post Script
Linux MIntOS

The following code is shared and is based on the BazziteOS script that was rewritten to take into account the uniquie changes in Bazzite.

The script was then reengineered back to MintOS with a few changes

  1. MintOS is my daily driver
  2. MintOS has advantages for me that BazziteOS doesnt accommodate easily
  3. This code has numerous changes to accommodate my needs
  4. Clam AV hardening is not in this script - but is to be rewritten shortly

Post MintOS installation script

#!/bin/bash

# ================================================================================
# Base Setup Script for Linux Mint (LMDE 7 / Ubuntu Edition)
# Filename : setup-laptop-mintos.sh
# Updated  : 2026-05-17
# Version  : 3.0.0
# ================================================================================
#
# PURPOSE:
#   Post-install hardening and configuration for Linux Mint laptops.
#   Supports two Mint variants, detected automatically at runtime:
#
#     • Linux Mint LMDE 7   — Debian base (apt / dpkg)
#     • Linux Mint Ubuntu   — Ubuntu base (apt / dpkg)
#
#   The script aborts with a clear error if run on any other OS.
#   All operations are idempotent — safe to re-run.
#
# WHAT THIS SCRIPT DOES:
#
#   SECURITY & HARDENING:
#     - AppArmor installation and activation
#       (LMDE 7: installs and enables; Ubuntu Mint: already active, verifies)
#     - UFW firewall: default-deny inbound, default-allow outbound,
#       WireGuard egress rule (UDP 51820 out)
#     - Hardened SSH via sshd_config.d drop-in (update-resistant)
#     - SSH host key regeneration (ED25519 + RSA 4096)
#     - ClamAV antivirus with freshclam initial update and weekly timer
#     - UDM SE block-page certificate installed to system trust store
#
#   PACKAGE MANAGEMENT:
#     - Bluetooth package held to prevent driver updates breaking Dell BT
#     - Firefox, inetutils-telnet, and system LibreOffice purged
#       (LibreOffice Flatpak installed instead for current version)
#     - i386 architecture added (Wine / Steam compatibility)
#
#   SOFTWARE INSTALLATION:
#     - Base tools: curl, wget, nmap, smartmontools, ffmpeg, rsyslog, mtr,
#       fwupd, openssh-server, openssh-client, gnupg, cups, avahi-daemon
#     - WireGuard VPN (wireguard + wireguard-tools via apt)
#     - Flatpak apps: GIMP, Inkscape, Krita, LibreOffice, VLC, Pinta,
#       Google Chrome
#     - Insync (Google Drive/OneDrive client) via official Debian/Ubuntu
#       apt repo — native .deb install with file manager integration
#
#   USER-SPECIFIC (braedach only):
#     - Android Studio via Flatpak
#     - Pods (Podman GUI) via Flatpak
#     - Google Antigravity IDE via official APT repo
#
#   SYSTEM CONFIGURATION:
#     - Epson WF-4830 printer via IPP Everywhere
#     - IPv6 stable address preference (disables temp addresses on wlo1)
#     - Unattended-upgrades enabled for automatic security updates
#     - Custom MOTD (OS, IP, uptime, IPv4/IPv6)
#
#   FIRMWARE UPDATES (fwupd):
#     - LVFS metadata refresh
#     - AC power and battery pre-flight checks
#     - BIOS/UEFI capsule gating on AC power
#     - Non-BIOS firmware applied unconditionally
#     - Fallback instructions if no LVFS updates found
#
#   DIAGNOSTICS:
#     - Final health check covering firewall, SSH, AppArmor, ClamAV,
#       Insync, Flatpak apps, auto-updates, and failed systemd units
#
# SUPPORTED DISTROS:
#   - Linux Mint LMDE 7 (Debian Trixie base)   ID=linuxmint  ID_LIKE=debian
#   - Linux Mint 22.x  (Ubuntu 24.04 base)     ID=linuxmint  ID_LIKE=ubuntu
#   Any other OS causes an immediate abort.
#
# NOTES:
#   a) INSYNC: Commercial Google Drive/OneDrive sync client ($29.99/account,
#      15-day trial). Uses the official apt.insync.io repo for .deb packages.
#      File manager integration installed for Nemo (Mint default).
#
#   b) APPARMOR: On Ubuntu-based Mint it is already enabled in the kernel.
#      The script installs extra profiles and verifies status on both variants.
#      On LMDE 7 it installs, enables, and activates AppArmor fully.
#
#   c) BLUETOOTH HOLD: Dell laptops have known breakage when the bluetooth
#      package updates pull new firmware. The hold prevents this.
#
#   d) AUTOMATIC UPDATES: unattended-upgrades is configured for security
#      updates. Full upgrades remain manual (apt upgrade) to avoid surprise
#      reboots on a laptop.
#
# ================================================================================

set -euo pipefail

# -------------------------------------------------------------------------------
# Privilege check

if [[ "${EUID}" -ne 0 ]]; then
    echo "[ERROR] This script must be run as root (sudo ./setup-laptop-mintos.sh)"
    exit 1
fi

# -------------------------------------------------------------------------------
# Logging

LOGFILE="/var/log/mintos-setup.log"
BACKUP_TS="$(date +%Y%m%d-%H%M%S)"

exec > >(tee -a "$LOGFILE") 2>&1

info()  { echo -e "\033[0;32m[INFO]\033[0m  $*"; }
warn()  { echo -e "\033[0;33m[WARN]\033[0m  $*" >&2; }
error() { echo -e "\033[0;31m[ERROR]\033[0m $*" >&2; exit 1; }
step()  { echo -e "\n\033[1;34m[====]\033[0m $*\n"; }

info "MintOS setup started at $(date)"
info "Log file: ${LOGFILE}"

# -------------------------------------------------------------------------------
# OS detection and validation
#
# Abort immediately if not running on a supported Linux Mint variant.
# We check ID and ID_LIKE from /etc/os-release.

step "OS Detection"

if [[ ! -f /etc/os-release ]]; then
    error "/etc/os-release not found — cannot determine OS. Aborting."
fi

source /etc/os-release

OS_ID="${ID:-unknown}"
OS_ID_LIKE="${ID_LIKE:-unknown}"
OS_PRETTY="${PRETTY_NAME:-unknown}"
OS_CODENAME="${VERSION_CODENAME:-unknown}"

info "Detected OS: ${OS_PRETTY}"
info "ID: ${OS_ID}  |  ID_LIKE: ${OS_ID_LIKE}  |  Codename: ${OS_CODENAME}"

if [[ "${OS_ID}" != "linuxmint" ]]; then
    error "Unsupported OS: '${OS_ID}'. This script requires Linux Mint (linuxmint)."
    error "For Bazzite, use setup-laptop-bazzite.sh instead."
fi

# Determine Mint variant for distro-specific behaviour
if echo "${OS_ID_LIKE}" | grep -qi "debian"; then
    MINT_BASE="debian"
    info "Mint base: Debian (LMDE)"
elif echo "${OS_ID_LIKE}" | grep -qi "ubuntu"; then
    MINT_BASE="ubuntu"
    info "Mint base: Ubuntu"
else
    error "Linux Mint detected but base distro '${OS_ID_LIKE}' is not recognised."
    error "Expected 'debian' (LMDE) or 'ubuntu'. Aborting."
fi

# -------------------------------------------------------------------------------
# Resolve the actual logged-in user (not root, even when run via sudo)

CURRENT_USER=$(logname 2>/dev/null || echo "${SUDO_USER:-root}")
CURRENT_HOME=$(getent passwd "${CURRENT_USER}" | cut -d: -f6)
info "Running as root. Detected user context: ${CURRENT_USER} (home: ${CURRENT_HOME})"

# -------------------------------------------------------------------------------
# Network connectivity check

step "Network Connectivity"

info "Checking network connectivity..."
apt-get update -qq 2>/dev/null \
    || error "apt-get update failed — check DNS/network before continuing."
info "Network OK."

# -------------------------------------------------------------------------------
# Systemd housekeeping

step "Systemd Housekeeping"

info "Checking and resetting failed systemd units..."
systemctl reset-failed || true

# ===============================================================================
# SECTION 1: Package Management — Hold, Purge, and Base Install
# ===============================================================================

step "Package Management"

# ---- i386 architecture (Wine / Steam compatibility) ----
info "Adding i386 architecture..."
if ! dpkg --print-foreign-architectures | grep -q i386; then
    dpkg --add-architecture i386 || warn "Failed to add i386 architecture."
    apt-get update -qq || true
else
    info "i386 architecture already present."
fi

# ---- Hold Bluetooth to prevent Dell BT breakage on driver updates ----
info "Holding bluetooth package (prevents Dell BT breakage on updates)..."
apt-mark hold bluetooth 2>/dev/null || warn "bluetooth package not found — skipping hold."

# ---- Purge unwanted packages ----
# Firefox: replaced by Flatpak Chrome
# inetutils-telnet: insecure, replaced by SSH
# LibreOffice (system): replaced by Flatpak edition (current version, sandboxed)
info "Purging unwanted packages (Firefox, telnet, system LibreOffice)..."
apt-get purge -y --ignore-missing \
    firefox \
    firefox-esr \
    firefox-locale-en \
    inetutils-telnet \
    'libreoffice*' \
    || true
apt-get autoremove --purge -y
apt-get autoclean -y
apt-get clean -y

# ---- Install base system packages ----
info "Installing base packages..."
apt-get install -y -qq \
    curl \
    wget \
    gnupg \
    nmap \
    mtr \
    smartmontools \
    gsmartcontrol \
    ffmpeg \
    rsyslog \
    fwupd \
    openssh-server \
    openssh-client \
    cups \
    avahi-daemon \
    unattended-upgrades \
    apt-listchanges \
    || error "Base package installation failed."

# ===============================================================================
# SECTION 2: WireGuard VPN
#
# The WireGuard kernel module is included in the Linux kernel since 5.6.
# We install the userspace tools only.
# ===============================================================================

step "WireGuard VPN"

info "Installing WireGuard userspace tools..."
apt-get install -y wireguard wireguard-tools \
    || warn "WireGuard installation failed — kernel module may still be available."

info "WireGuard installed. To connect using a config file:"
info "  sudo wg-quick up /etc/wireguard/wg0.conf"
info "Or use Settings > Network > VPN to add a WireGuard connection."

# ===============================================================================
# SECTION 3: UFW Firewall
#
# UFW is Linux Mint's standard firewall frontend (sits on top of iptables/nftables).
#
# Policy:
#   - Default incoming: DENY (block all unsolicited inbound)
#   - Default outgoing: ALLOW (all egress permitted)
#   - SSH allowed from LAN (192.168.0.0/16) only
#   - WireGuard egress UDP 51820 explicitly permitted outbound
#   - All rules persistent across reboots
# ===============================================================================

step "UFW Firewall"

info "Installing UFW..."
apt-get install -y ufw || error "UFW installation failed."

info "Configuring UFW policy (default deny inbound, allow outbound)..."
ufw --force reset
ufw default deny incoming
ufw default allow outgoing

info "Adding SSH rule: permit from LAN (192.168.0.0/16) only..."
ufw allow from 192.168.0.0/16 to any port 22 proto tcp comment "SSH LAN only"

info "Adding WireGuard egress rule (UDP 51820 out)..."
ufw allow out 51820/udp comment "WireGuard egress"

info "Adding mDNS rule for printer discovery (LAN only)..."
ufw allow from 192.168.0.0/16 to any port 5353 proto udp comment "mDNS LAN printer discovery"

info "Enabling UFW..."
ufw --force enable

info "UFW status:"
ufw status verbose

# ===============================================================================
# SECTION 4: AppArmor
#
# LMDE 7 (Debian base): AppArmor is available but not always active by default.
#                        Script installs, enables, and starts it.
# Ubuntu Mint:           AppArmor is active by default in the Ubuntu kernel.
#                        Script installs extra profiles and verifies status.
#
# Either way: apparmor-profiles-extra provides community-maintained profiles
# for common applications beyond the base set.
# ===============================================================================

step "AppArmor"

info "Installing AppArmor packages..."
apt-get install -y \
    apparmor \
    apparmor-utils \
    apparmor-profiles \
    apparmor-profiles-extra \
    || error "AppArmor installation failed."

if [[ "${MINT_BASE}" == "debian" ]]; then
    info "LMDE base: enabling and starting AppArmor service..."
    systemctl enable apparmor || warn "Failed to enable AppArmor."
    systemctl start  apparmor || warn "Failed to start AppArmor."
else
    info "Ubuntu base: AppArmor is kernel-integrated — verifying service status..."
    systemctl is-active --quiet apparmor \
        && info "AppArmor is active." \
        || warn "AppArmor service not active — a reboot may be required."
fi

if aa-enabled 2>/dev/null; then
    info "AppArmor is enabled and running."
    aa-status 2>/dev/null | head -5 || true
else
    warn "AppArmor may not be fully active. A reboot may be required."
fi

# ===============================================================================
# SECTION 5: Insync (Google Drive / OneDrive client)
#
# Insync is a commercial sync client ($29.99/account, 15-day trial).
# It provides an official apt repo at apt.insync.io for Debian and Ubuntu.
#
# On Mint the correct base distro must be specified:
#   LMDE 7  → debian  / codename: trixie
#   Mint 22 → ubuntu  / codename: noble   (Ubuntu 24.04 base)
#   Mint 21 → ubuntu  / codename: jammy   (Ubuntu 22.04 base)
#
# File manager integration: Nemo is Mint's default file manager.
# insync-nemo is installed for both variants.
# ===============================================================================

step "Insync (Google Drive / OneDrive Client)"

INSYNC_GPG_DST="/etc/apt/trusted.gpg.d/insynchq.gpg"
INSYNC_LIST="/etc/apt/sources.list.d/insync.list"
INSYNC_GPG_URL="https://apt.insync.io/insynchq.gpg"

info "Adding Insync GPG key..."
if curl -fsSL "${INSYNC_GPG_URL}" | gpg --dearmor | tee "${INSYNC_GPG_DST}" > /dev/null; then
    info "Insync GPG key installed to ${INSYNC_GPG_DST}"
else
    warn "Failed to download Insync GPG key — Insync install may fail."
fi

# Determine repo line based on Mint base and codename
if [[ "${MINT_BASE}" == "debian" ]]; then
    INSYNC_DISTRO="debian"
    INSYNC_CODENAME="${OS_CODENAME}"   # e.g. trixie
else
    INSYNC_DISTRO="ubuntu"
    # Ubuntu Mint reports its own codename; we need the Ubuntu base codename
    # Mint 22.x = noble (24.04), Mint 21.x = jammy (22.04)
    case "${OS_CODENAME}" in
        wilma|virginia)   INSYNC_CODENAME="noble"  ;;   # Mint 22.x
        vera|vanessa|una) INSYNC_CODENAME="jammy"  ;;   # Mint 21.x
        *)                INSYNC_CODENAME="noble"
                          warn "Unknown Mint codename '${OS_CODENAME}' — defaulting to noble." ;;
    esac
fi

info "Configuring Insync repo (${INSYNC_DISTRO} / ${INSYNC_CODENAME})..."
echo "deb [signed-by=${INSYNC_GPG_DST}] https://apt.insync.io/${INSYNC_DISTRO} ${INSYNC_CODENAME} non-free contrib" \
    > "${INSYNC_LIST}"

apt-get update -qq || warn "apt-get update after Insync repo addition failed."

info "Installing Insync and Nemo integration..."
apt-get install -y insync insync-nemo \
    || warn "Insync install failed — install manually: sudo apt install insync insync-nemo"

# ===============================================================================
# SECTION 6: Flatpak Applications
#
# Flatpak is used for:
#   - Google Chrome  (avoids the Google APT repo entirely — easier maintenance)
#   - LibreOffice    (current version, replacing the purged system package)
#   - Creative apps  (GIMP, Inkscape, Krita, VLC, Pinta)
#
# Flatpak is pre-installed on Linux Mint. Flathub is the default remote.
# ===============================================================================

step "Flatpak Application Installation"

info "Ensuring Flathub remote is present..."
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo || true

FLATPAK_APPS=(
    "org.gimp.GIMP"
    "org.inkscape.Inkscape"
    "org.kde.krita"
    "org.libreoffice.LibreOffice"
    "org.videolan.VLC"
    "com.github.PintaProject.Pinta"
    "com.google.Chrome"
)

for app in "${FLATPAK_APPS[@]}"; do
    info "Installing Flatpak: ${app}..."
    if flatpak install -y --noninteractive flathub "${app}" 2>/dev/null; then
        info "  ✓ ${app}"
    else
        warn "  ✗ ${app} — install failed or already installed."
    fi
done

# ===============================================================================
# SECTION 7: ClamAV
#
# Native host install — no container needed on a mutable Mint system.
# freshclam downloads the initial virus definitions.
# clamtk provides a GUI frontend.
# A weekly systemd timer keeps definitions current.
# ===============================================================================

step "ClamAV Antivirus"

info "Installing ClamAV and ClamTK..."
apt-get install -y clamav clamav-daemon clamtk \
    || error "ClamAV installation failed."

info "Stopping freshclam service for initial update..."
systemctl stop clamav-freshclam 2>/dev/null || true

info "Downloading initial ClamAV virus definitions (this may take a moment)..."
freshclam || warn "ClamAV database update failed — definitions may be out of date."

info "Restarting clamav-freshclam service..."
systemctl enable --now clamav-freshclam || warn "clamav-freshclam service failed to start."

info "ClamAV daemon status:"
systemctl is-active --quiet clamav-daemon \
    && info "clamav-daemon is running." \
    || warn "clamav-daemon not running — may need manual start: sudo systemctl start clamav-daemon"

# ===============================================================================
# SECTION 8: SSH Hardening
#
# Uses sshd_config.d/ drop-in so the hardened config survives package updates.
# Debian/Ubuntu sftp subsystem path: /usr/lib/openssh/sftp-server
# Service name on Debian/Ubuntu: ssh (not sshd)
# ===============================================================================

step "SSH Hardening"

SSHD_DIR="/etc/ssh"
SSHD_CFG="${SSHD_DIR}/sshd_config"
SSHD_CUSTOM_DIR="${SSHD_DIR}/sshd_config.d"
SSHD_CUSTOM_CFG="${SSHD_CUSTOM_DIR}/99-hardened-custom.conf"
BACKUP_DIR="${SSHD_DIR}/backup-${BACKUP_TS}"

mkdir -p "${BACKUP_DIR}"
mkdir -p "${SSHD_CUSTOM_DIR}"

info "Backing up sshd_config and host keys to ${BACKUP_DIR}..."
cp -a "${SSHD_CFG}" "${BACKUP_DIR}/sshd_config.bak" 2>/dev/null || \
    warn "sshd_config not found — openssh-server may not yet be installed."

for key in ssh_host_ed25519_key ssh_host_rsa_key ssh_host_ecdsa_key; do
    [[ -f "${SSHD_DIR}/${key}"     ]] && cp -a "${SSHD_DIR}/${key}"     "${BACKUP_DIR}/" || true
    [[ -f "${SSHD_DIR}/${key}.pub" ]] && cp -a "${SSHD_DIR}/${key}.pub" "${BACKUP_DIR}/" || true
done

info "Removing old SSH host keys..."
rm -f /etc/ssh/ssh_host_*

info "Generating new ED25519 host key..."
ssh-keygen -t ed25519 -f "${SSHD_DIR}/ssh_host_ed25519_key" -N "" -o -a 100

info "Generating new RSA 4096 host key..."
ssh-keygen -t rsa -b 4096 -f "${SSHD_DIR}/ssh_host_rsa_key" -N "" -o

chmod 600 "${SSHD_DIR}/ssh_host_ed25519_key" "${SSHD_DIR}/ssh_host_rsa_key"
chmod 644 "${SSHD_DIR}/ssh_host_ed25519_key.pub" "${SSHD_DIR}/ssh_host_rsa_key.pub"

# Ensure main sshd_config includes the drop-in directory
if [[ -f "${SSHD_CFG}" ]] && ! grep -q "^Include ${SSHD_CUSTOM_DIR}/\*.conf" "${SSHD_CFG}"; then
    if grep -q "^Include" "${SSHD_CFG}"; then
        info "Include directive already present in sshd_config."
    else
        sed -i "1i Include ${SSHD_CUSTOM_DIR}/*.conf" "${SSHD_CFG}"
        info "Added Include directive to ${SSHD_CFG}."
    fi
fi

info "Writing hardened SSH drop-in to ${SSHD_CUSTOM_CFG}..."

cat > "${SSHD_CUSTOM_CFG}" <<EOF
# Hardened SSH Configuration — Linux Mint
# Managed by setup-laptop-mintos.sh (${BACKUP_TS})
# Drop-in: survives openssh-server package updates.

Protocol 2
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
HostbasedAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
UsePAM yes
LoginGraceTime 30
MaxAuthTries 4
MaxSessions 10

HostKey ${SSHD_DIR}/ssh_host_ed25519_key
HostKey ${SSHD_DIR}/ssh_host_rsa_key

AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
UseDNS no
PrintMotd no
PrintLastLog yes
AcceptEnv LANG LC_*

# Subsystem path on Debian/Ubuntu (differs from Fedora)
Subsystem sftp /usr/lib/openssh/sftp-server

# Permit password auth from LAN (RFC1918), ULA, and link-local IPv6 only
Match Address 192.168.0.0/16,fe80::/10,fc00::/7
    PermitRootLogin no
    PasswordAuthentication yes
    AllowTcpForwarding no

EOF

chmod 644 "${SSHD_CUSTOM_CFG}"

info "Validating sshd_config..."
if sshd -t; then
    info "sshd_config syntax OK — restarting SSH service..."
    # Debian/Ubuntu service is named 'ssh', not 'sshd'
    systemctl restart ssh 2>/dev/null \
        || systemctl restart sshd 2>/dev/null \
        || warn "SSH service restart failed — check service status manually."
    info "SSH service restarted."
else
    warn "sshd_config test failed — not restarting SSH."
    warn "Backup available at: ${BACKUP_DIR}/sshd_config.bak"
fi

# ===============================================================================
# SECTION 9: UDM SE Block-Page Certificate
#
# Installs the Ubiquiti UDM SE block-page CA certificate.
# On Debian/Ubuntu the tool is update-ca-certificates.
# Certificate store: /usr/local/share/ca-certificates/
# ===============================================================================

step "UDM SE Block-Page Certificate"

CERT_URL="http://ghost.baden.braedach.com/content/files/2025/12/UniFi-SSL-Certificate.cer"
CERT_TMP="/tmp/UniFi-SSL-Certificate.cer"
CERT_DST="/usr/local/share/ca-certificates/udmse-blockpage-ca.crt"

info "Downloading UDM SE certificate from ${CERT_URL}..."

if ! wget -q -O "${CERT_TMP}" "${CERT_URL}"; then
    warn "Failed to download certificate from ${CERT_URL}"
    warn "Ensure ghost.baden.braedach.com is reachable from this machine."
else
    if ! grep -q "BEGIN CERTIFICATE" "${CERT_TMP}"; then
        warn "Downloaded file is not a valid PEM certificate — skipping install."
        rm -f "${CERT_TMP}"
    else
        cp "${CERT_TMP}" "${CERT_DST}"
        if update-ca-certificates; then
            info "UDM SE block-page certificate installed successfully."
            info "Installed to: ${CERT_DST}"
        else
            warn "update-ca-certificates failed — check if the file is valid PEM."
        fi
        rm -f "${CERT_TMP}"
    fi
fi

# ===============================================================================
# SECTION 10: IPv6 Stable Address Configuration
# ===============================================================================

step "IPv6 Configuration"

info "Configuring IPv6 stable addressing (disabling temp/privacy addresses)..."

cat > /etc/sysctl.d/99-ipv6-laptop.conf <<'EOF'
# Prefer stable IPv6 addresses over temporary privacy addresses.
# Reduces mDNS/Avahi resolution failures caused by address churn on wlo1.
net.ipv6.conf.wlo1.use_tempaddr=0
net.ipv6.conf.all.use_tempaddr=0
net.ipv6.conf.default.use_tempaddr=0
EOF

sysctl -p /etc/sysctl.d/99-ipv6-laptop.conf || warn "IPv6 sysctl apply failed."

# ===============================================================================
# SECTION 11: Epson WF-4830 Printer
# ===============================================================================

step "Epson WF-4830 Printer"

info "Enabling CUPS and Avahi for printer discovery..."
systemctl enable --now cups    || warn "CUPS service failed to start."
systemctl enable --now avahi-daemon || warn "Avahi service failed to start."

info "Installing Epson WF-4830 via IPP Everywhere..."
if lpadmin -p "EPSON-WF4830" \
    -E \
    -v "ipp://192.168.1.11/ipp/print" \
    -m everywhere \
    -D "EPSON WF-4830 Series"; then
    lpoptions -d EPSON-WF4830 || true
    info "Printer installed and set as default."
else
    warn "lpadmin failed — CUPS may not be fully started. Retry after reboot:"
    warn "  sudo lpadmin -p EPSON-WF4830 -E -v ipp://192.168.1.11/ipp/print -m everywhere -D 'EPSON WF-4830 Series'"
fi

# ===============================================================================
# SECTION 12: Firmware Updates (fwupd)
# ===============================================================================

step "Firmware Updates (fwupd)"

if ! command -v fwupdmgr &>/dev/null; then
    warn "fwupdmgr not found — skipping firmware updates."
else

    # AC power pre-flight
    AC_ONLINE=0
    for ps in /sys/class/power_supply/AC*/online /sys/class/power_supply/ADP*/online; do
        [[ -f "${ps}" ]] && AC_ONLINE=$(cat "${ps}") && break
    done

    if [[ "${AC_ONLINE}" -eq 1 ]]; then
        info "AC power confirmed — BIOS/UEFI capsule updates eligible."
    else
        warn "AC power NOT detected. BIOS/UEFI firmware updates will be blocked by fwupd."
        warn "Connect AC adapter if a BIOS update is needed, then re-run: sudo fwupdmgr update"
    fi

    # Battery pre-flight
    BAT_CAPACITY=""
    for bat in /sys/class/power_supply/BAT*/capacity; do
        [[ -f "${bat}" ]] && BAT_CAPACITY=$(cat "${bat}") && break
    done

    if [[ -n "${BAT_CAPACITY}" ]]; then
        if [[ "${BAT_CAPACITY}" -ge 10 ]]; then
            info "Battery level: ${BAT_CAPACITY}% — meets fwupd minimum (10%)."
        else
            warn "Battery: ${BAT_CAPACITY}% — below fwupd minimum. Charge before updating BIOS."
        fi
    else
        info "No battery detected — skipping battery check."
    fi

    # Refresh LVFS metadata
    info "Refreshing LVFS firmware metadata..."
    if ! fwupdmgr refresh --force; then
        warn "LVFS metadata refresh failed — check internet connectivity."
    else
        info "Querying devices visible to fwupd..."
        fwupdmgr get-devices || warn "Could not enumerate fwupd devices."

        info "Checking for available firmware updates..."
        UPDATE_OUTPUT=$(fwupdmgr get-updates 2>&1 || true)

        if echo "${UPDATE_OUTPUT}" | grep -qi "no upgrades\|nothing to do\|no.*update"; then
            info "System firmware is already up to date."
            info "If a BIOS update is required but not showing via LVFS:"
            info "  1. Download BIOS from the manufacturer support page"
            info "  2. Boot Hiren's BootCD PE from USB"
            info "  3. Run the flasher from within Windows PE"
        else
            info "Firmware updates available:"
            echo "${UPDATE_OUTPUT}"

            if echo "${UPDATE_OUTPUT}" | grep -qi "system firmware\|uefi\|bios"; then
                info "BIOS/UEFI firmware update detected."
                if [[ "${AC_ONLINE}" -ne 1 ]]; then
                    warn "Skipping BIOS update — AC power required."
                    warn "Connect AC and run: sudo fwupdmgr update"
                else
                    info "Applying BIOS update (will apply on next reboot via EFI capsule)..."
                    fwupdmgr update -y || warn "Firmware update failed — check output above."
                    info "IMPORTANT: Reboot required to apply BIOS/UEFI update."
                    info "Do NOT interrupt power during the reboot cycle."
                fi
            else
                info "Applying non-BIOS firmware updates..."
                fwupdmgr update -y || warn "One or more firmware updates failed."
            fi
        fi
    fi

fi

# ===============================================================================
# SECTION 13: Automatic Updates
#
# unattended-upgrades handles security updates automatically.
# Full upgrades (apt upgrade) remain manual to avoid surprise reboots.
# ===============================================================================

step "Automatic Security Updates"

info "Configuring unattended-upgrades for automatic security updates..."

cat > /etc/apt/apt.conf.d/20auto-upgrades <<'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";
EOF

# Ensure unattended-upgrades service is running
systemctl enable --now unattended-upgrades \
    || warn "unattended-upgrades service failed to enable."

info "Automatic security updates configured."
info "Full upgrades remain manual: sudo apt upgrade"

# ===============================================================================
# SECTION 14: User-Specific Software (The programmer only)
#
# Additional development and productivity tools installed only for the
# The programmer user account. Skipped for all other users.
# ===============================================================================

step "User-Specific Software"

if [[ "${CURRENT_USER}" == "The programmer" ]]; then
    info "User is The programmer — installing user-specific packages..."

    info "Installing Android Studio via Flatpak..."
    flatpak install -y --noninteractive flathub com.google.AndroidStudio \
        || warn "AndroidStudio Flatpak install failed."

    info "Installing Pods (Podman GUI) via Flatpak..."
    flatpak install -y --noninteractive flathub com.github.marhkb.Pods \
        || warn "Pods Flatpak install failed."

    info "Installing Google Antigravity IDE..."
    mkdir -p /etc/apt/keyrings
    curl -fsSL https://us-central1-apt.pkg.dev/doc/repo-signing-key.gpg \
        | gpg --dearmor -o /etc/apt/keyrings/antigravity-repo-key.gpg \
        || warn "Failed to import Antigravity GPG key."

    echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/antigravity-repo-key.gpg] \
https://us-central1-apt.pkg.dev/projects/antigravity-auto-updater-dev/ antigravity-debian main" \
        > /etc/apt/sources.list.d/antigravity.list

    apt-get update -y
    apt-get install -y antigravity \
        || warn "Antigravity install failed."

else
    info "User is ${CURRENT_USER} — skipping braedach-specific installs."
fi

# ===============================================================================
# SECTION 15: MOTD
# ===============================================================================

step "MOTD Configuration"

info "Configuring MOTD system information display..."

rm -f /etc/update-motd.d/*

cat > /etc/update-motd.d/80-sysinfo <<'EOF'
#!/bin/bash
echo "=== System Information ==="
echo ""
echo "  🖥️  Operating System : $(lsb_release -ds 2>/dev/null || grep -oP '(?<=PRETTY_NAME=\").*(?=\")' /etc/os-release)"
echo "  🌐  IPv4 Address     : $(hostname -I 2>/dev/null | awk '{print $1}')"
echo "  🔗  IPv6 Global      : $(ip -6 addr show scope global 2>/dev/null | grep inet6 | awk '{print $2}' | cut -d'/' -f1 | head -n1)"
echo "  🪢  IPv6 Link-local  : $(ip -6 addr show scope link  2>/dev/null | grep inet6 | awk '{print $2}' | cut -d'/' -f1 | head -n1)"
echo "  ⏱️  System Uptime    : $(uptime -p 2>/dev/null)"
echo ""
EOF

chmod +x /etc/update-motd.d/80-sysinfo

# ===============================================================================
# SECTION 16: Final Diagnostics
# ===============================================================================

step "Final Diagnostics"

DIAG_PASS=0
DIAG_WARN=0
DIAG_FAIL=0

diag_ok()   { echo -e "  \033[0;32m[PASS]\033[0m $*"; (( DIAG_PASS++ )); }
diag_warn() { echo -e "  \033[0;33m[WARN]\033[0m $*"; (( DIAG_WARN++ )); }
diag_fail() { echo -e "  \033[0;31m[FAIL]\033[0m $*"; (( DIAG_FAIL++ )); }

echo ""
echo "  ── OS Detection ────────────────────────────────────────"
diag_ok "Running on: ${OS_PRETTY} (base: ${MINT_BASE})"

echo ""
echo "  ── Firewall (UFW) ──────────────────────────────────────"

if systemctl is-active --quiet ufw 2>/dev/null || ufw status | grep -q "Status: active"; then
    diag_ok "UFW is active"
    if ufw status | grep -q "deny (incoming)"; then
        diag_ok "Default incoming policy: deny"
    else
        diag_warn "UFW incoming policy not confirmed as deny"
    fi
else
    diag_fail "UFW is NOT active"
fi

echo ""
echo "  ── AppArmor ────────────────────────────────────────────"

if aa-enabled 2>/dev/null; then
    diag_ok "AppArmor is enabled"
    PROFILES=$(aa-status 2>/dev/null | grep "profiles are loaded" | awk '{print $1}' || echo "unknown")
    diag_ok "${PROFILES} AppArmor profiles loaded"
else
    diag_warn "AppArmor not confirmed active — reboot may be required"
fi

echo ""
echo "  ── SSH ─────────────────────────────────────────────────"

if [[ -f "${SSHD_CUSTOM_CFG}" ]]; then
    diag_ok "SSH hardened drop-in: ${SSHD_CUSTOM_CFG}"
else
    diag_fail "SSH drop-in config NOT found"
fi

if [[ -f "${SSHD_DIR}/ssh_host_ed25519_key" ]]; then
    diag_ok "ED25519 host key present"
else
    diag_fail "ED25519 host key NOT found"
fi

if systemctl is-active --quiet ssh 2>/dev/null || systemctl is-active --quiet sshd 2>/dev/null; then
    diag_ok "SSH service is running"
else
    diag_warn "SSH service not active"
fi

echo ""
echo "  ── ClamAV ──────────────────────────────────────────────"

if command -v clamscan &>/dev/null; then
    diag_ok "clamscan binary present"
else
    diag_fail "clamscan not found"
fi

if systemctl is-active --quiet clamav-freshclam 2>/dev/null; then
    diag_ok "clamav-freshclam (definition updater) is running"
else
    diag_warn "clamav-freshclam not running"
fi

if [[ -d /var/lib/clamav ]] && ls /var/lib/clamav/*.cvd /var/lib/clamav/*.cld 2>/dev/null | head -1 &>/dev/null; then
    diag_ok "ClamAV virus definitions present"
else
    diag_warn "ClamAV definitions not found — freshclam may still be downloading"
fi

echo ""
echo "  ── Insync ──────────────────────────────────────────────"

if command -v insync &>/dev/null; then
    diag_ok "insync binary present"
else
    diag_warn "insync not found — install may have failed"
fi

if [[ -f "${INSYNC_LIST}" ]]; then
    diag_ok "Insync apt repo configured: ${INSYNC_LIST}"
else
    diag_warn "Insync apt repo not found"
fi

echo ""
echo "  ── UDM SE Certificate ──────────────────────────────────"

if [[ -f "${CERT_DST}" ]]; then
    diag_ok "UDM SE certificate installed: ${CERT_DST}"
else
    diag_warn "UDM SE certificate NOT installed"
fi

echo ""
echo "  ── Flatpak Apps ────────────────────────────────────────"

for app in org.gimp.GIMP org.inkscape.Inkscape org.kde.krita \
           com.google.Chrome org.libreoffice.LibreOffice \
           org.videolan.VLC com.github.PintaProject.Pinta; do
    if flatpak info "${app}" &>/dev/null; then
        diag_ok "${app}"
    else
        diag_warn "${app} — not installed"
    fi
done

echo ""
echo "  ── WireGuard ───────────────────────────────────────────"

if command -v wg &>/dev/null; then
    diag_ok "wg (WireGuard tools) present"
else
    diag_warn "wg not found — WireGuard tools may not be installed"
fi

echo ""
echo "  ── Auto-Updates ────────────────────────────────────────"

if systemctl is-active --quiet unattended-upgrades; then
    diag_ok "unattended-upgrades service is active"
else
    diag_warn "unattended-upgrades not active"
fi

if [[ -f /etc/apt/apt.conf.d/20auto-upgrades ]]; then
    diag_ok "auto-upgrades config present"
else
    diag_warn "auto-upgrades config not found"
fi

echo ""
echo "  ── IPv6 Configuration ──────────────────────────────────"

if [[ -f /etc/sysctl.d/99-ipv6-laptop.conf ]]; then
    diag_ok "IPv6 stable address sysctl installed"
else
    diag_warn "IPv6 sysctl config not found"
fi

echo ""
echo "  ── Bluetooth Hold ──────────────────────────────────────"

if apt-mark showhold 2>/dev/null | grep -q "bluetooth"; then
    diag_ok "bluetooth package is held"
else
    diag_warn "bluetooth package hold not confirmed"
fi

echo ""
echo "  ── Systemd Failed Units ────────────────────────────────"

FAILED_UNITS=$(systemctl --failed --no-legend --plain 2>/dev/null | awk '{print $1}')
if [[ -z "${FAILED_UNITS}" ]]; then
    diag_ok "No failed systemd units detected"
else
    while IFS= read -r unit; do
        diag_warn "Failed unit: ${unit}"
    done <<< "${FAILED_UNITS}"
fi

echo ""
echo "════════════════════════════════════════════════════════════"
echo "  DIAGNOSTIC SUMMARY"
echo "  Pass: ${DIAG_PASS}  |  Warn: ${DIAG_WARN}  |  Fail: ${DIAG_FAIL}"
echo "════════════════════════════════════════════════════════════"

# ===============================================================================
# POST-SCRIPT INSTRUCTIONS
# ===============================================================================

cat <<POSTINSTALL

════════════════════════════════════════════════════════════════
  IMPORTANT POST-SCRIPT ACTIONS
════════════════════════════════════════════════════════════════

1. REBOOT RECOMMENDED
   AppArmor and sysctl changes take full effect after a reboot.
   Run:  sudo reboot

2. INSYNC SETUP (after reboot)
   Open Insync from the application launcher.
   Sign in with your Google account(s) and configure sync folders.
   Trial: 15 days free.  Licence: \$29.99/Google account.

3. WIREGUARD VPN
   Place your .conf file in /etc/wireguard/ then:
     sudo wg-quick up /etc/wireguard/wg0.conf
   Or use Settings > Network > VPN to add a WireGuard connection.

4. CLAMAV SCANNING
   Scan home directory:
     clamscan --recursive --infected ~/
   Update definitions manually:
     sudo freshclam

5. PRINTER (if lpadmin failed above)
   sudo lpadmin -p EPSON-WF4830 -E \\
     -v ipp://192.168.1.11/ipp/print \\
     -m everywhere -D "EPSON WF-4830 Series"

6. MANUAL SYSTEM UPDATE
   sudo apt update && sudo apt upgrade

════════════════════════════════════════════════════════════════

POSTINSTALL

info "Setup complete. Log: ${LOGFILE}"
info "SSH backup: ${BACKUP_DIR}"
echo ""

#endscript

Hope this helps someone

#enoughsaid