Skip to content

Instantly share code, notes, and snippets.

@RGreeneRI
Created February 26, 2026 06:56
Show Gist options
  • Select an option

  • Save RGreeneRI/cae2341222687d97cb45571f282d04e5 to your computer and use it in GitHub Desktop.

Select an option

Save RGreeneRI/cae2341222687d97cb45571f282d04e5 to your computer and use it in GitHub Desktop.
Create an embedded LIVI instance on a tablet from a clean base Debian install.
#!/bin/bash
set -e
# Run this after installing a minimal debian 13 with only standard system utilities and SSH server.
# Be sure to create user "carplay" during install.
# Define variables
CARPLAY_USER="carplay"
USER_HOME=$(getent passwd "$CARPLAY_USER" | cut -d: -f6)
PROFILE_FILE="$USER_HOME/.profile"
AUTOLOGIN_FILE_PATH="/etc/systemd/system/getty@tty1.service.d"
AUTOLOGIN_FILE="/etc/systemd/system/getty@tty1.service.d/autologin.conf"
RULE_FILE="/etc/udev/rules.d/99-LIVI.rules"
START_SH="$USER_HOME/LIVI/start.sh"
GRUB_FILE="/etc/default/grub"
KERNEL_PRINTK_FILE="/etc/sysctl.d/20-quiet-printk.conf"
GRUBD_FILE_10="/etc/grub.d/10_linux"
LIVI_URL="https://github.com/f-io/LIVI/releases/download/v5.2.1/LIVI-5.2.1-x86_64.AppImage"
# Check if carplay user exists, exit if it doesn't.
echo "Ensuring carplay user exists..."
if ! id "$CARPLAY_USER" >/dev/null 2>&1; then
echo "User $CARPLAY_USER does not exist."
exit 1
fi
# Install required packages
echo "Installing required packages..."
apt update && apt install -y -qq --no-install-recommends \
cron wget sudo ca-certificates \
linux-image-amd64 firmware-linux \
libinput10 wayland-protocols \
cage foot acpi mesa-vulkan-drivers \
mesa-utils xserver-xorg-input-libinput \
fuse libfuse2t64 libnspr4 libnss3 \
libatk1.0-0t64 libatk-bridge2.0-0t64 \
libcups2t64 libcairo2 libgtk-3-0t64 \
libinput-tools wlr-randr libinput-bin
# Add carplay user to sudo group
echo "Adding $CARPLAY_USER to sudo group..."
usermod -aG sudo $CARPLAY_USER
# Create autologin file
echo "Creating autologin file..."
mkdir -p "$AUTOLOGIN_FILE_PATH"
cat <<EOF > "$AUTOLOGIN_FILE"
[Service]
ExecStart=
ExecStart=-/sbin/agetty --autologin $CARPLAY_USER --noclear %I \$TERM
EOF
# Create LIVI dir and download LIVI AppImage.
echo "Downloading LIVI..."
sudo -u "$CARPLAY_USER" mkdir -p "$USER_HOME/LIVI"
sudo -u "$CARPLAY_USER" wget -O "$USER_HOME/LIVI/LIVI.AppImage" "$LIVI_URL"
sudo -u "$CARPLAY_USER" chmod +x "$USER_HOME/LIVI/LIVI.AppImage"
# Set udev rules for dongle and disabling the trackpad
echo "Creating udev rules for dongle and Touchpad."
echo "SUBSYSTEM==\"usb\", ATTR{idVendor}==\"1314\", ATTR{idProduct}==\"152*\", MODE=\"0660\", OWNER=\"$CARPLAY_USER\"" > "$RULE_FILE"
cat <<'EOF' >> "$RULE_FILE"
ATTRS{name}=="*Touchpad*", ENV{LIBINPUT_IGNORE_DEVICE}="1"
ATTRS{name}=="*Mouse*", ENV{LIBINPUT_IGNORE_DEVICE}="1"
EOF
udevadm control --reload-rules
udevadm trigger
# Ensure profile exists
echo "Setting up profile file..."
touch "$PROFILE_FILE"
chown "$CARPLAY_USER:$CARPLAY_USER" "$PROFILE_FILE"
# Remove existing LIVI_LAUNCH block if present
sed -i '/# >>> LIVI_LAUNCH START >>>/,/# <<< LIVI_LAUNCH END <<</d' "$PROFILE_FILE"
# Append fresh block
cat <<EOF >> "$PROFILE_FILE"
# >>> LIVI_LAUNCH START >>>
alias ll='ls -lah'
# Launch LIVI
if [ -z "\$WAYLAND_DISPLAY" ] && [ "\$(tty)" = "/dev/tty1" ]; then
exec cage "$START_SH"
fi
# <<< LIVI_LAUNCH END <<<
EOF
# Create start script with crash-loop protection
echo "Creating start script..."
sudo -u "$CARPLAY_USER" touch "$START_SH"
sudo -u "$CARPLAY_USER" chmod +x "$START_SH"
cat <<'EOF' > "$START_SH"
#!/bin/bash
# Maximum number of quick restarts allowed
MAX_RESTARTS=5
# Time window in seconds to count restarts
WINDOW=10
restart_count=0
first_restart_time=$(date +%s)
while true; do
"$HOME/LIVI/LIVI.AppImage"
# Check how quickly it exited
now=$(date +%s)
if (( now - first_restart_time <= WINDOW )); then
((restart_count++))
else
# Reset count if enough time has passed
restart_count=1
first_restart_time=$now
fi
# If it crashes too fast repeatedly, pause longer to avoid CPU burn
if (( restart_count > MAX_RESTARTS )); then
echo "LIVI crashed $restart_count times within $WINDOW seconds. Waiting before restart..."
sleep 10
restart_count=0
first_restart_time=$(date +%s)
else
# Short pause before normal restart
sleep 1
fi
done
EOF
# Reduce boot time and screen clutter by tweaking grub
echo "Tweaking grub, etc for quicker and cleaner boot..."
# Set GRUB_TIMEOUT=0
if grep -q "^GRUB_TIMEOUT=" "$GRUB_FILE"; then
sed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/' "$GRUB_FILE"
else
echo 'GRUB_TIMEOUT=0' >> "$GRUB_FILE"
fi
# Set GRUB_CMDLINE_LINUX_DEFAULT
NEW_CMDLINE='GRUB_CMDLINE_LINUX_DEFAULT="quiet loglevel=1 vt.global_cursor_default=0 systemd.show_status=false rd.udev.log_level=3 nowatchdog mitigations=off video=efifb:nobgrt console=tty3"'
if grep -q "^GRUB_CMDLINE_LINUX_DEFAULT=" "$GRUB_FILE"; then
sed -i "s|^GRUB_CMDLINE_LINUX_DEFAULT=.*|$NEW_CMDLINE|" "$GRUB_FILE"
else
echo "$NEW_CMDLINE" >> "$GRUB_FILE"
fi
echo 'GRUB_TIMEOUT_STYLE=hidden' >> "$GRUB_FILE"
# Ensure GRUB_TERMINAL=console is set in $GRUB_FILE
if grep -qE '^[[:space:]]*#?[[:space:]]*GRUB_TERMINAL=' "$GRUB_FILE"; then
# Replace existing (commented or not) line
sed -i 's|^[[:space:]]*#\?[[:space:]]*GRUB_TERMINAL=.*|GRUB_TERMINAL=console|' "$GRUB_FILE"
else
# Add it to the end of the file
echo 'GRUB_TERMINAL=console' >> "$GRUB_FILE"
fi
sed -i 's/quiet_boot="0"/quiet_boot="1"/g' "$GRUBD_FILE_10"
echo "kernel.printk = 3 3 3 3" > "$KERNEL_PRINTK_FILE"
update-grub
# more screen clutter removal
echo "Clearing MOTD..."
> /etc/motd
rm /etc/update-motd.d/10-uname
# Create power monitor script
echo "Creating power monitor script..."
touch /root/power-monitor.sh
chmod +x /root/power-monitor.sh
cat <<'EOF' > "/root/power-monitor.sh"
#!/bin/bash
ACPI_CMD="/usr/bin/acpi"
SHUTDOWN_SECS="5"
RECHECK_SECS="2"
# Loop to check power status
while true; do
# Get uptime
UPTIME_SECS=$(cut -d. -f1 /proc/uptime)
# Get the battery status
STATUS=$($ACPI_CMD -a)
# Check if the tablet is plugged in (AC connected)
if [[ "$STATUS" != *"on-line"* ]]; then
# If not plugged in, initiate shutdown
echo "AC adapter is unplugged."
if [[ $UPTIME_SECS -ge 300 ]]; then
echo "Initiating shutdown in $SHUTDOWN_SECS seconds..."
sleep $SHUTDOWN_SECS
/sbin/shutdown -P now
exit 0
fi
echo "Uptime under 5 minutes, staying on in case you booted up unplugged."
echo "Disabling auto-shutdown."
exit 0
fi
if [[ "$STATUS" == *"on-line"* ]]; then
echo "AC adapter is plugged in. Checking again in $RECHECK_SECS seconds."
fi
# Sleep for X seconds before checking again
sleep $RECHECK_SECS
done
EOF
# Add power monitor cron job
echo "Creating power monitor cron job..."
CRON_CMD="/root/power-monitor.sh"
CRON_JOB="@reboot $CRON_CMD"
EXISTING_CRON=$(crontab -l -u root 2>/dev/null || true)
if ! echo "$EXISTING_CRON" | grep -Fq "$CRON_CMD"; then
printf "%s\n%s\n" "$EXISTING_CRON" "$CRON_JOB" | crontab -u root -
echo "Cron job added."
else
echo "Cron job already exists."
fi
echo "Done setting up LIVI embedded tablet! Reboot to load LIVI."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment