Skip to content

Instantly share code, notes, and snippets.

@tapickell
Created September 7, 2026 23:20
Show Gist options
  • Select an option

  • Save tapickell/df131f22c4a0bf834da1205abce24de4 to your computer and use it in GitHub Desktop.

Select an option

Save tapickell/df131f22c4a0bf834da1205abce24de4 to your computer and use it in GitHub Desktop.
printer_setup_x220.sh
#!/usr/bin/env bash
#
# Ender 3 / Klipper host bootstrap
# Target: Debian 13 x86_64
#
# Installs:
# - Klipper
# - Moonraker
# - Mainsail
# - Crowsnest
# - nginx
# - required Python/system packages
#
# Does NOT install printer.cfg or flash the SKR.
# We will migrate those from the existing Pi afterward.
#
set -Eeuo pipefail
KLIPPER_REPO="https://github.com/Klipper3d/klipper.git"
MOONRAKER_REPO="https://github.com/Arksine/moonraker.git"
CROWSNEST_REPO="https://github.com/mainsail-crew/crowsnest.git"
MAINSAIL_URL="https://github.com/mainsail-crew/mainsail/releases/latest/download/mainsail.zip"
PRINTER_DATA="$HOME/printer_data"
log() {
printf '\n\033[1;32m==> %s\033[0m\n' "$*"
}
die() {
printf '\n\033[1;31mERROR: %s\033[0m\n' "$*" >&2
exit 1
}
# ---------------------------------------------------------------------------
# Sanity checks
# ---------------------------------------------------------------------------
if [[ $EUID -eq 0 ]]; then
die "Run this as your normal user, NOT as root. The script will use sudo."
fi
command -v sudo >/dev/null 2>&1 ||
die "sudo is not installed."
case "$(uname -m)" in
x86_64)
;;
*)
die "Expected x86_64 Debian host; found $(uname -m)"
;;
esac
# ---------------------------------------------------------------------------
# Base OS packages
# ---------------------------------------------------------------------------
log "Updating Debian packages"
sudo apt update
sudo DEBIAN_FRONTEND=noninteractive apt upgrade -y
log "Installing dependencies"
sudo apt install -y \
git \
curl \
wget \
unzip \
nginx \
python3 \
python3-dev \
python3-venv \
python3-pip \
python3-setuptools \
python3-wheel \
build-essential \
libffi-dev \
libncurses-dev \
libusb-dev \
libusb-1.0-0-dev \
pkg-config \
avrdude \
gcc-avr \
binutils-avr \
avr-libc \
stm32flash \
dfu-util \
virtualenv \
policykit-1 \
zlib1g-dev \
libjpeg-dev
# ---------------------------------------------------------------------------
# User permissions
# ---------------------------------------------------------------------------
log "Adding $USER to serial-device groups"
sudo usermod -aG dialout "$USER"
# ---------------------------------------------------------------------------
# Printer data layout
# ---------------------------------------------------------------------------
log "Creating printer_data directories"
mkdir -p \
"$PRINTER_DATA/config" \
"$PRINTER_DATA/gcodes" \
"$PRINTER_DATA/logs" \
"$PRINTER_DATA/comms" \
"$PRINTER_DATA/systemd"
# ---------------------------------------------------------------------------
# Klipper
# ---------------------------------------------------------------------------
log "Installing Klipper"
if [[ ! -d "$HOME/klipper/.git" ]]; then
git clone "$KLIPPER_REPO" "$HOME/klipper"
else
log "Klipper repository already exists -- updating"
git -C "$HOME/klipper" pull --ff-only
fi
python3 -m venv "$HOME/klippy-env"
"$HOME/klippy-env/bin/pip" install --upgrade pip wheel
"$HOME/klippy-env/bin/pip" install \
-r "$HOME/klipper/scripts/klippy-requirements.txt"
# ---------------------------------------------------------------------------
# Moonraker
# ---------------------------------------------------------------------------
log "Installing Moonraker"
if [[ ! -d "$HOME/moonraker/.git" ]]; then
git clone "$MOONRAKER_REPO" "$HOME/moonraker"
else
log "Moonraker repository already exists -- updating"
git -C "$HOME/moonraker" pull --ff-only
fi
python3 -m venv "$HOME/moonraker-env"
"$HOME/moonraker-env/bin/pip" install --upgrade pip wheel
"$HOME/moonraker-env/bin/pip" install \
-r "$HOME/moonraker/scripts/moonraker-requirements.txt"
# ---------------------------------------------------------------------------
# Klipper systemd service
# ---------------------------------------------------------------------------
log "Creating Klipper systemd service"
sudo tee /etc/systemd/system/klipper.service >/dev/null <<EOF
[Unit]
Description=Klipper 3D Printer Firmware
Documentation=https://www.klipper3d.org/
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=$USER
WorkingDirectory=$HOME/klipper
ExecStart=$HOME/klippy-env/bin/python \
$HOME/klipper/klippy/klippy.py \
$PRINTER_DATA/config/printer.cfg \
-l $PRINTER_DATA/logs/klippy.log \
-I $PRINTER_DATA/comms/klippy.serial \
-a $PRINTER_DATA/comms/klippy.sock
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# ---------------------------------------------------------------------------
# Moonraker systemd service
# ---------------------------------------------------------------------------
log "Creating Moonraker systemd service"
sudo tee /etc/systemd/system/moonraker.service >/dev/null <<EOF
[Unit]
Description=Moonraker API Server
After=network-online.target klipper.service
Wants=network-online.target
[Service]
Type=simple
User=$USER
WorkingDirectory=$HOME/moonraker
ExecStart=$HOME/moonraker-env/bin/python -m moonraker \
-d $PRINTER_DATA
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# ---------------------------------------------------------------------------
# Mainsail
# ---------------------------------------------------------------------------
log "Installing Mainsail"
sudo mkdir -p /var/www/mainsail
TMP_MAINSAIL="$(mktemp -d)"
curl -L "$MAINSAIL_URL" \
-o "$TMP_MAINSAIL/mainsail.zip"
sudo rm -rf /var/www/mainsail/*
sudo unzip -q "$TMP_MAINSAIL/mainsail.zip" \
-d /var/www/mainsail
rm -rf "$TMP_MAINSAIL"
sudo chown -R www-data:www-data /var/www/mainsail
# ---------------------------------------------------------------------------
# nginx
# ---------------------------------------------------------------------------
log "Configuring nginx"
sudo tee /etc/nginx/sites-available/mainsail >/dev/null <<'EOF'
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/mainsail;
index index.html;
client_max_body_size 1G;
location / {
try_files $uri $uri/ /index.html;
}
location /websocket {
proxy_pass http://127.0.0.1:7125/websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
}
location ~ ^/(printer|api|access|machine|server)/ {
proxy_pass http://127.0.0.1:7125;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
EOF
sudo rm -f /etc/nginx/sites-enabled/default
sudo ln -sf \
/etc/nginx/sites-available/mainsail \
/etc/nginx/sites-enabled/mainsail
sudo nginx -t
sudo systemctl restart nginx
# ---------------------------------------------------------------------------
# Crowsnest
# ---------------------------------------------------------------------------
log "Installing Crowsnest"
if [[ ! -d "$HOME/crowsnest/.git" ]]; then
git clone "$CROWSNEST_REPO" "$HOME/crowsnest"
else
log "Crowsnest repository already exists -- updating"
git -C "$HOME/crowsnest" pull --ff-only
fi
#
# Crowsnest's own installer handles its multimedia dependencies and service.
#
if [[ -x "$HOME/crowsnest/tools/pkglist.sh" ]]; then
sudo "$HOME/crowsnest/tools/pkglist.sh" || true
fi
# ---------------------------------------------------------------------------
# Initial Moonraker config
# ---------------------------------------------------------------------------
if [[ ! -f "$PRINTER_DATA/config/moonraker.conf" ]]; then
log "Creating initial Moonraker configuration"
cat > "$PRINTER_DATA/config/moonraker.conf" <<EOF
[server]
host: 0.0.0.0
port: 7125
klippy_uds_address: $PRINTER_DATA/comms/klippy.sock
[file_manager]
enable_object_processing: True
[authorization]
trusted_clients:
127.0.0.0/8
192.168.0.0/16
10.0.0.0/8
172.16.0.0/12
cors_domains:
http://*.local
http://*
https://*
[history]
[octoprint_compat]
[update_manager]
enable_auto_refresh: True
[update_manager mainsail]
type: web
channel: stable
repo: mainsail-crew/mainsail
path: /var/www/mainsail
EOF
fi
# ---------------------------------------------------------------------------
# Enable services
# ---------------------------------------------------------------------------
log "Enabling services"
sudo systemctl daemon-reload
#
# Klipper will probably fail until we copy printer.cfg over.
# That's expected.
#
sudo systemctl enable klipper
sudo systemctl enable moonraker
sudo systemctl enable nginx
sudo systemctl restart moonraker || true
# ---------------------------------------------------------------------------
# Disable sleep/suspend/hibernate
# ---------------------------------------------------------------------------
log "Disabling automatic sleep/hibernate"
sudo systemctl mask \
sleep.target \
suspend.target \
hibernate.target \
hybrid-sleep.target
# ---------------------------------------------------------------------------
# Finish
# ---------------------------------------------------------------------------
IP_ADDR="$(hostname -I | awk '{print $1}')"
printf '\n'
printf '============================================================\n'
printf ' Klipper host bootstrap complete\n'
printf '============================================================\n'
printf '\n'
printf 'Host: %s\n' "$(hostname)"
printf 'IP: %s\n' "${IP_ADDR:-unknown}"
printf 'Mainsail: http://%s/\n' "${IP_ADDR:-HOST-IP}"
printf 'Printer data: %s\n' "$PRINTER_DATA"
printf '\n'
printf 'IMPORTANT:\n'
printf ' 1. Log out/reboot so dialout group membership takes effect.\n'
printf ' 2. We still need to migrate printer.cfg and Moonraker config.\n'
printf ' 3. Do NOT reflash the SKR Mini E3 V3 yet.\n'
printf ' 4. The existing Klipper MCU firmware should remain usable.\n'
printf '\n'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment