#!/bin/bash
################################################################################
# File Name : debian-hplip-named-diposable.sh
# Description : This script creates a disposable qube for HP printers via USB
# using Debian's packaged hplip and hpli-gui with download and
# installation of hp-plugin from Open Printing. This is script
# requires interation to accept EULA policies. Note, you must
# use plugin version that matches Debian HPLIP version.
# Usage : • Transfer from appvm to dom0:
# qvm-run -p appvm 'cat ~/debian-hplip-named-diposable.sh' > ~/debian-hplip-named-diposable.sh
# • Make executable:
# chmod +x ~/debian-hplip-named-diposable.sh
# • Run:
# bash ~/debian-hplip-named-diposable.sh
# Author : Me and the bois
# License : Free of charge, no warranty
################################################################################
# Security first
set -e
# Configuration
BASE_TEMPLATE="debian-12-minimal"
CUSTOM_TEMPLATE="debian-hplip-template"
DISP_TEMPLATE="debian-hplip-template-dvm"
NAMED_DISP_VM="disp-hplip"
# Plugin URL for Debian HPLIP version
PLUGIN_URL="https://www.openprinting.org/download/printdriver/auxfiles/HP/plugins/hplip-3.22.10-plugin.run"
# Ensure printer is connected via USB and powered on
check_printer_attached() {
echo -e "\nPrinter Check..."
if qvm-usb 2>/dev/null | grep -q -i -E "03f0|Hewlett"; then
echo "HP printer detected"
return 0
else
echo "No HP printer detected"
echo "Please connect HP printer via USB and ensure it's powered on"
return 1
fi
}
# Step 1: Verify and create base template
create_base_template() {
echo -e "\nStep 1: Setting up base template $BASE_TEMPLATE..."
if ! qvm-check "$BASE_TEMPLATE" 2>/dev/null; then
echo "Installing $BASE_TEMPLATE..."
sudo qubes-dom0-update "qubes-template-$BASE_TEMPLATE"
fi
# Ensure template is shut down before updating
qvm-shutdown --wait "$BASE_TEMPLATE" 2>/dev/null || true
if qvm-check "$BASE_TEMPLATE"; then
echo "Updating $BASE_TEMPLATE..."
sudo qubesctl --show-output --skip-dom0 --targets="$BASE_TEMPLATE" state.sls update.qubes-vm
fi
echo -e "\nBase template setup complete"
}
# Step 2: Create custom base template
create_custom_template() {
echo -e "\nStep 2: Creating $CUSTOM_TEMPLATE..."
if ! qvm-check "$CUSTOM_TEMPLATE"; then
qvm-clone "$BASE_TEMPLATE" "$CUSTOM_TEMPLATE"
fi
qvm-prefs "$CUSTOM_TEMPLATE" label black
qvm-prefs "$CUSTOM_TEMPLATE" include_in_backups false
qvm-service "$CUSTOM_TEMPLATE" cups on
qvm-service "$CUSTOM_TEMPLATE" avahi on
qvm-start "$CUSTOM_TEMPLATE"
}
# Step 3: Install packages in template (change as needed)
install_system_dependencies() {
echo -e "\nStep 3: Installing packages in $CUSTOM_TEMPLATE..."
# Basic system configuration
echo "Configuring system environment..."
qvm-run -p -u root "$CUSTOM_TEMPLATE" "echo 'TERM=xterm' >> /etc/environment"
qvm-run -p -u root "$CUSTOM_TEMPLATE" "sed -i 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen"
qvm-run -p -u root "$CUSTOM_TEMPLATE" "locale-gen en_US.UTF-8"
# Install core packages
echo "Installing core packages..."
qvm-run -p -u root "$CUSTOM_TEMPLATE" "
apt-get install -y --no-install-recommends \
qubes-core-agent-passwordless-root \
qubes-core-agent-networking \
qubes-usb-proxy \
dbus \
dbus-x11 \
python3 \
thunar \
qubes-core-agent-thunar \
mousepad \
qpdfview \
less \
psmisc \
bash-completion \
xfce4-terminal
"
}
# Step 4: Install packages
install_hplip_from_apt() {
echo -e "\nStep 4: Installing CUPS and essential libraries..."
# Install CUPS and essential libraries
echo "Installing CUPS and essential libraries..."
qvm-run -p -u root "$CUSTOM_TEMPLATE" "
apt-get install -y --no-install-recommends \
cups \
cups-client \
libcups2 \
libcupsimage2 \
libdbus-1-3 \
libjpeg62-turbo \
libusb-1.0-0 \
libsane1 \
avahi-daemon \
avahi-utils
"
# Install HPLIP CLI
echo "Installing HPLIP CLI..."
qvm-run -p -u root "$CUSTOM_TEMPLATE" "
apt-get install -y --no-install-recommends \
hplip \
python3-dbus \
python3-pil \
python3-reportlab
"
# Installing HPLIP GUI
echo "Installing HPLIP GUI..."
qvm-run -p -u root "$CUSTOM_TEMPLATE" "
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
curl \
pkexec \
polkitd \
policykit-1 \
cups-pk-helper \
python3-pyqt5 \
hplip-gui
"
}
# Step 5: Installing HP plugin from remote source
download_and_install_plugin() {
echo -e "\nStep 5: Installing HP plugin..."
echo "Using plugin version that matches Debian HPLIP version"
echo "Downloading plugin from: $PLUGIN_URL"
qvm-start --skip-if-running "$CUSTOM_TEMPLATE"
# Attach HP printer if available (TODO: use variables for device id)
DEVICE_ID=$(qvm-usb list | grep -i -E "03f0|Hewlett" | awk '{print $1}' | head -n 1)
if [ -n "$DEVICE_ID" ]; then
qvm-usb attach "$CUSTOM_TEMPLATE" "$DEVICE_ID"
echo "Attached printer $DEVICE_ID to $CUSTOM_TEMPLATE"
fi
# Download plugin as root (TODO: dinamically download matching hplip version)
qvm-run -p -u root "$CUSTOM_TEMPLATE" "
cd /tmp && curl -x http://127.0.0.1:8082/ \
--retry 5 \
--retry-delay 2 \
--retry-max-time 30 \
--connect-timeout 20 \
--max-time 300 \
--continue-at - \
--output hplip-3.22.10-plugin.run \
'$PLUGIN_URL'
"
# Extract and install plugin with interactive prompts (TODO: noninteractive dont work, even extracting)
echo "Installing plugin as root with interactive prompts..."
qvm-run -p -u root "$CUSTOM_TEMPLATE" "
chmod +x /tmp/hplip-3.22.10-plugin.run
DEBIAN_FRONTEND=noninteractive bash /tmp/hplip-3.22.10-plugin.run
"
# Cleanup files
qvm-run -p "$CUSTOM_TEMPLATE" "rm -rf /tmp/hplip-*" 2>/dev/null || true
echo "Plugin installation completed successfully"
}
# Step 6: Service permissions and authentication configuration
setup_printer_services() {
echo -e "\nStep 6: Configuring printer services..."
# Create proper user
echo "Configuring CUPS services..."
qvm-run -p -u root "$CUSTOM_TEMPLATE" "
# Create cups user in correct group
useradd -r -c 'CUPS printing service' -d /var/spool/cups -g lp -s /bin/false cups
# Add cups user to lp group
usermod -a -G lp cups
# Add user to lp group for printer administration
usermod -a -G lp user
# Stop any running cups services
systemctl stop cups cups.socket cups.path 2>/dev/null || true
systemctl reset-failed cups cups.socket cups.path 2>/dev/null || true
"
# Create cups-files.conf using mktemp
echo "Configuring CUPS settings..."
local cups_files_conf=$(mktemp)
cat > "$cups_files_conf" << 'EOF'
# Default user and group for filters/backends/helper programs
User cups
Group lp
# Administrator user group
SystemGroup root
# Log file group
LogFileGroup adm
# Spool directory
RequestRoot /var/spool/cups
EOF
qvm-copy-to-vm "$CUSTOM_TEMPLATE" "$cups_files_conf"
qvm-run -p -u root "$CUSTOM_TEMPLATE" "mv /home/user/QubesIncoming/dom0/$(basename "$cups_files_conf") /etc/cups/cups-files.conf"
rm -f "$cups_files_conf"
# Create cupsd.conf using mktemp
echo "Configuring CUPS daemon settings..."
local cupsd_conf=$(mktemp)
cat > "$cupsd_conf" << 'EOF'
LogLevel warn
MaxLogSize 0
Listen localhost:631
Listen /run/cups/cups.sock
Browsing Off
DefaultAuthType Basic
WebInterface No
# Default print settings
DefaultPageSize A4
Order allow,deny
Allow all
Order allow,deny
Allow all
AuthType Default
Require user @SYSTEM
Order allow,deny
Allow all
JobPrivateAccess default
JobPrivateValues default
SubscriptionPrivateAccess default
SubscriptionPrivateValues default
Order allow,deny
Allow all
EOF
qvm-copy-to-vm "$CUSTOM_TEMPLATE" "$cupsd_conf"
qvm-run -p -u root "$CUSTOM_TEMPLATE" "mv /home/user/QubesIncoming/dom0/$(basename "$cupsd_conf") /etc/cups/cupsd.conf"
rm -f "$cupsd_conf"
# Directory permissions with correct ownership
echo "Configuring CUPS permissions and ownership..."
qvm-run -p -u root "$CUSTOM_TEMPLATE" "
chmod 644 /etc/cups/cupsd.conf /etc/cups/cups-files.conf
mkdir -p /var/log/cups /var/cache/cups /var/spool/cups
chown -R cups:lp /var/log/cups /var/cache/cups /var/spool/cups
chown -R root:lp /etc/cups
chmod 755 /var/log/cups /var/cache/cups /var/spool/cups
# Ensure cups spool directory has correct permissions
chown cups:lp /var/spool/cups
chmod 755 /var/spool/cups
"
# Configure udev rules for USB printer devices using mktemp
echo "Configuring USB permissions..."
local udev_rules=$(mktemp)
cat > "$udev_rules" << 'EOF'
# HP USB printer permissions
SUBSYSTEM=="usb", ATTRS{idVendor}=="03f0", GROUP="lp", MODE="0664"
SUBSYSTEM=="usb", ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="*", GROUP="lp", MODE="0664"
EOF
qvm-copy-to-vm "$CUSTOM_TEMPLATE" "$udev_rules"
qvm-run -p -u root "$CUSTOM_TEMPLATE" "
mv /home/user/QubesIncoming/dom0/$(basename "$udev_rules") /etc/udev/rules.d/99-hp-usb-printer.rules
udevadm control --reload-rules
udevadm trigger
"
rm -f "$udev_rules"
# Configure PolicyKit for passwordless printer administration
echo "Configuring PolicyKit..."
qvm-run -p -u root "$CUSTOM_TEMPLATE" "mkdir -p /etc/polkit-1/rules.d"
# Create PolicyKit rules using mktemp
local polkit_rules=$(mktemp)
cat > "$polkit_rules" << 'EOF'
// Allow any action for qubes users and printer groups
polkit.addRule(function(action, subject) {
if (subject.isInGroup("user") || subject.isInGroup("lp") || subject.user == "root") {
return polkit.Result.YES;
}
});
EOF
qvm-copy-to-vm "$CUSTOM_TEMPLATE" "$polkit_rules"
qvm-run -p -u root "$CUSTOM_TEMPLATE" "
mv /home/user/QubesIncoming/dom0/$(basename "$polkit_rules") /etc/polkit-1/rules.d/99-qubes-allow-all.rules
chmod 644 /etc/polkit-1/rules.d/99-qubes-allow-all.rules
rm -f /home/user/QubesIncoming/dom0/$(basename "$polkit_rules")
"
rm -f "$polkit_rules"
# Configure and start services
echo "Starting printer services..."
qvm-run -p -u root "$CUSTOM_TEMPLATE" "
# Verify configuration
echo 'Verifying CUPS configuration...'
if cupsd -t; then
echo 'CUPS configuration test PASSED'
else
echo 'CUPS configuration test FAILED, checking details...'
cupsd -t 2>&1
exit 1
fi
# Reset any failed services
systemctl reset-failed cups cups.socket cups.path 2>/dev/null || true
# Enable services
systemctl enable cups.socket
systemctl enable cups.path
systemctl enable avahi-daemon
systemctl enable dbus
# Start services in correct order
systemctl start dbus
systemctl start avahi-daemon
systemctl start cups.socket
systemctl start cups.path
sleep 3
systemctl start cups
sleep 3
systemctl status cups --no-pager -l
"
echo "Printer services configuration completed"
}
# Step 7: Create DVM template
create_dvm_template() {
echo -e "\nStep 7: Creating DVM template..."
qvm-shutdown --wait "$CUSTOM_TEMPLATE"
if ! qvm-check "$DISP_TEMPLATE" 2>/dev/null; then
qvm-create --template "$CUSTOM_TEMPLATE" --label red "$DISP_TEMPLATE"
fi
qvm-prefs "$DISP_TEMPLATE" template_for_dispvms True
qvm-prefs "$DISP_TEMPLATE" include_in_backups false
qvm-service "$DISP_TEMPLATE" cups on
qvm-service "$DISP_TEMPLATE" avahi on
echo -e "\nTemplate for disposables '$DISP_TEMPLATE' created successfully"
}
# Step 8: Create named disposable VM
create_named_disposable() {
echo -e "\nStep 8: Creating named disposable VM..."
if ! qvm-check "$NAMED_DISP_VM" 2>/dev/null; then
qvm-create --template "$DISP_TEMPLATE" --class DispVM --label blue \
--property netvm="" \
--property vcpus=1 \
--property memory=128 \
--property maxmem=512 \
--property autostart=False \
--property include_in_backups=False \
"$NAMED_DISP_VM"
fi
qvm-service "$NAMED_DISP_VM" cups on
qvm-service "$NAMED_DISP_VM" avahi on
echo -e "\nNamed disposable VM '$NAMED_DISP_VM' created successfully"
}
# Finalization
finalize() {
echo -e "\nStep 9: Finalizing setup..."
# Sync appmenus and shutdown
qvm-start --skip-if-running "$CUSTOM_TEMPLATE"
qvm-sync-appmenus "$CUSTOM_TEMPLATE"
qvm-shutdown --wait "$CUSTOM_TEMPLATE"
qvm-start --skip-if-running "$NAMED_DISP_VM"
qvm-sync-appmenus "$NAMED_DISP_VM"
qvm-shutdown --wait "$NAMED_DISP_VM"
# Configure menu items
qvm-features "$CUSTOM_TEMPLATE" menu-items "xfce4-terminal.desktop"
qvm-features "$DISP_TEMPLATE" menu-items "xfce4-terminal.desktop"
qvm-features "$NAMED_DISP_VM" menu-items "hplip.desktop thunar.desktop xfce4-terminal.desktop"
}
# Main execution
main() {
echo "Starting HP USB Printer Setup"
check_printer_attached
create_base_template
create_custom_template
install_system_dependencies
install_hplip_from_apt
download_and_install_plugin
setup_printer_services
create_dvm_template
create_named_disposable
finalize
echo -e "\nSetup completed successfully!"
}
main "$@"