Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
```codex_answers
<!-- placeholder for codex's responses textual data -->
# Brother HL-L1222-V Android Print Server (Raspberry Pi 4B+ / openSUSE Tumbleweed)

This repository now provides an executable setup script that configures a Raspberry Pi 4B+ (openSUSE Tumbleweed) as a print server for a USB-connected Brother HL-L1222/HL-L1222-V.

## What this enables
- Android phones on the **same Wi-Fi network** can print through IPP (Mopria-compatible workflow).
- USB Brother printer is shared by CUPS over the local network.
- Firewall and discovery services are configured for LAN printing.

## Important Bluetooth note
Android-to-Raspberry-Pi Bluetooth printing into CUPS is generally not reliable/supported in modern Android/CUPS workflows. The script still enables Bluetooth service for optional experimentation, but the production path is **Wi-Fi IPP**.

## Script
- `scripts/setup_hl_l1222_android_print.sh`

## Run
```bash
sudo bash scripts/setup_hl_l1222_android_print.sh
```

## After script completes
1. Keep printer connected via USB to Raspberry Pi.
2. Connect Android phone to same Wi-Fi as Raspberry Pi.
3. Enable Mopria Print Service (or equivalent Android print service).
4. Add printer with:
- `ipp://<raspberrypi-ip>/printers/Brother_HL_L1222_V`

## Notes
- Script uses CUPS `everywhere` model as default for broad compatibility.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (typo): Consider adding an article: "as the default" for smoother grammar.

Suggestion: "Script uses CUPS everywhere model as the default for broad compatibility."

Suggested change
- Script uses CUPS `everywhere` model as default for broad compatibility.
- Script uses CUPS `everywhere` model as the default for broad compatibility.

- If you have an official Brother HL-L1222-V Linux PPD/driver package, install it and adjust queue model if desired.
118 changes: 118 additions & 0 deletions scripts/setup_hl_l1222_android_print.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#!/usr/bin/env bash
set -euo pipefail

# Setup script: Brother HL-L1222(-V) USB printer on openSUSE Tumbleweed (Raspberry Pi)
# Goal: receive print jobs from Android phones over the same Wi-Fi (IPP/Mopria) via CUPS.
# Note: Bluetooth printing from Android to CUPS is generally unsupported/restricted; script enables
# practical network printing path and optional USB OTG fallback guidance.

PRINTER_NAME="Brother_HL_L1222_V"
PPD_MODEL="everywhere"

if [[ ${EUID} -ne 0 ]]; then
echo "Please run as root: sudo bash $0"
exit 1
fi

REQ_CMDS=(zypper systemctl lpinfo lpadmin lpstat)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add cupsctl to the list of required commands to ensure it is available for the configuration steps.

Suggested change
REQ_CMDS=(zypper systemctl lpinfo lpadmin lpstat)
REQ_CMDS=(zypper systemctl lpinfo lpadmin lpstat cupsctl)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Preflight checks require CUPS commands before CUPS is installed, so the script can fail immediately on fresh systems.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/setup_hl_l1222_android_print.sh, line 17:

<comment>Preflight checks require CUPS commands before CUPS is installed, so the script can fail immediately on fresh systems.</comment>

<file context>
@@ -0,0 +1,118 @@
+  exit 1
+fi
+
+REQ_CMDS=(zypper systemctl lpinfo lpadmin lpstat)
+for c in "${REQ_CMDS[@]}"; do
+  command -v "$c" >/dev/null || { echo "Missing command: $c"; exit 1; }
</file context>
Suggested change
REQ_CMDS=(zypper systemctl lpinfo lpadmin lpstat)
REQ_CMDS=(zypper systemctl)

for c in "${REQ_CMDS[@]}"; do
command -v "$c" >/dev/null || { echo "Missing command: $c"; exit 1; }
Comment on lines +17 to +19

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove CUPS CLI prereq checks before package install

The script checks for lpinfo, lpadmin, and lpstat before running zypper install, so on a fresh openSUSE host (the primary bootstrap case) it exits with “Missing command” and never reaches the package installation step that would provide those binaries. This makes the setup non-functional unless CUPS client tools are already preinstalled.

Useful? React with 👍 / 👎.

done
Comment on lines +17 to +20

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 CUPS commands (lpinfo, lpadmin, lpstat) are checked before CUPS is installed, blocking fresh installs

Lines 17-19 check that lpinfo, lpadmin, and lpstat are available via command -v, but these are CUPS binaries provided by the cups package which isn't installed until lines 24-26. On a fresh system (the primary target for a setup script), the prerequisite check fails with "Missing command: lpinfo" and exits before the zypper install cups step ever runs. The check should either move these CUPS-specific commands after the install step, or only verify base OS commands (zypper, systemctl) before installation.

Suggested change
REQ_CMDS=(zypper systemctl lpinfo lpadmin lpstat)
for c in "${REQ_CMDS[@]}"; do
command -v "$c" >/dev/null || { echo "Missing command: $c"; exit 1; }
done
REQ_CMDS=(zypper systemctl)
for c in "${REQ_CMDS[@]}"; do
command -v "$c" >/dev/null || { echo "Missing command: $c"; exit 1; }
done
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


echo "[1/8] Installing print/network discovery stack..."
zypper --non-interactive refresh
zypper --non-interactive install \
cups cups-filters ghostscript avahi nss-mdns \
firewalld ipp-usb bluez bluez-tools

echo "[2/8] Enabling required services..."
systemctl enable --now cups.service
systemctl enable --now avahi-daemon.service
systemctl enable --now firewalld.service
systemctl enable --now bluetooth.service

# Ensure cupsd listens on local network for Android clients using IPP/Mopria.
CUPSD_CONF="/etc/cups/cupsd.conf"
if ! grep -q '^Port 631' "$CUPSD_CONF"; then
sed -i 's/^Listen localhost:631/# &/' "$CUPSD_CONF" || true
echo 'Port 631' >> "$CUPSD_CONF"
fi

if ! grep -q '^Browsing Yes' "$CUPSD_CONF"; then
echo 'Browsing Yes' >> "$CUPSD_CONF"
fi

if ! grep -q '^DefaultShared Yes' "$CUPSD_CONF"; then
echo 'DefaultShared Yes' >> "$CUPSD_CONF"
fi

if ! grep -q '^WebInterface Yes' "$CUPSD_CONF"; then
echo 'WebInterface Yes' >> "$CUPSD_CONF"
fi
Comment on lines +36 to +51

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Appending directives to cupsd.conf creates ordering issues

Lines 36-51 append Port 631, Browsing Yes, DefaultShared Yes, and WebInterface Yes to the end of cupsd.conf. CUPS processes directives in order, and some of these may conflict with or be overridden by earlier directives already in the file. For example, if the original file has Browsing No near the top, appending Browsing Yes at the bottom means CUPS sees both — and the last value typically wins. While this happens to work in CUPS's favor for this script, it leaves behind contradictory entries. A cleaner approach would be sed -i to replace existing directives in-place rather than appending duplicates.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


if ! grep -q '<Location />' "$CUPSD_CONF"; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Logic error: grep -q '<Location />' will match the existing <Location /> block present in default CUPS configurations (which typically has restrictive ACLs). Since the block already exists, this check passes and the script never adds Allow @LOCAL, leaving network access blocked (HTTP 403 Forbidden for LAN clients).

Additionally, the sed on line 37 only targets Listen localhost:631 — if the config uses 127.0.0.1:631 or *:631, it won't be commented out, and appending Port 631 will cause a port binding conflict that prevents CUPS from starting.

Consider using cupsctl --remote-any --share-printers which handles these cases safely and idempotently.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/setup_hl_l1222_android_print.sh, line 53:

<comment>Logic error: `grep -q '<Location />'` will match the existing `<Location />` block present in default CUPS configurations (which typically has restrictive ACLs). Since the block already exists, this check passes and the script never adds `Allow @LOCAL`, leaving network access blocked (HTTP 403 Forbidden for LAN clients).

Additionally, the `sed` on line 37 only targets `Listen localhost:631` — if the config uses `127.0.0.1:631` or `*:631`, it won't be commented out, and appending `Port 631` will cause a port binding conflict that prevents CUPS from starting.

Consider using `cupsctl --remote-any --share-printers` which handles these cases safely and idempotently.</comment>

<file context>
@@ -0,0 +1,118 @@
+  echo 'WebInterface Yes' >> "$CUPSD_CONF"
+fi
+
+if ! grep -q '<Location />' "$CUPSD_CONF"; then
+  cat >> "$CUPSD_CONF" <<'CUPSLOC'
+<Location />
</file context>

cat >> "$CUPSD_CONF" <<'CUPSLOC'
<Location />
Order allow,deny
Allow @LOCAL
</Location>

<Location /admin>
Order allow,deny
Allow @LOCAL
</Location>
CUPSLOC
fi
Comment on lines +34 to +65

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The manual modification of /etc/cups/cupsd.conf is fragile and contains logic errors:

  1. Logic Error: The check grep -q '<Location />' will return true on most default CUPS installations because the block usually exists (though restricted). Consequently, the script will skip adding the Allow @LOCAL directive, and network printing will remain blocked ('Forbidden').
  2. Port Conflict: The sed command only targets localhost:631. If the configuration uses 127.0.0.1:631 or *:631, it won't be commented out, and appending Port 631 will cause CUPS to fail to start due to a port binding conflict.

Using cupsctl is the standard, safe, and idempotent way to configure these settings.

Suggested change
# Ensure cupsd listens on local network for Android clients using IPP/Mopria.
CUPSD_CONF="/etc/cups/cupsd.conf"
if ! grep -q '^Port 631' "$CUPSD_CONF"; then
sed -i 's/^Listen localhost:631/# &/' "$CUPSD_CONF" || true
echo 'Port 631' >> "$CUPSD_CONF"
fi
if ! grep -q '^Browsing Yes' "$CUPSD_CONF"; then
echo 'Browsing Yes' >> "$CUPSD_CONF"
fi
if ! grep -q '^DefaultShared Yes' "$CUPSD_CONF"; then
echo 'DefaultShared Yes' >> "$CUPSD_CONF"
fi
if ! grep -q '^WebInterface Yes' "$CUPSD_CONF"; then
echo 'WebInterface Yes' >> "$CUPSD_CONF"
fi
if ! grep -q '<Location />' "$CUPSD_CONF"; then
cat >> "$CUPSD_CONF" <<'CUPSLOC'
<Location />
Order allow,deny
Allow @LOCAL
</Location>
<Location /admin>
Order allow,deny
Allow @LOCAL
</Location>
CUPSLOC
fi
# Configure CUPS for network access and sharing
cupsctl --remote-any --share-printers --remote-admin WebInterface=Yes

Comment on lines +53 to +65

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 guard skips adding Allow @Local when default CUPS block already exists

Line 53 checks whether <Location /> already exists in cupsd.conf. The default CUPS installation ships with a <Location /> block that typically does not include Allow @LOCAL. Because the grep finds the existing block, the script skips appending the new block with Allow @LOCAL, so LAN clients (i.e. Android phones) are still denied access. This silently defeats the script's primary purpose — enabling network printing over IPP/Mopria. The fix should either inject Allow @LOCAL into the existing <Location /> block, or replace the existing block rather than simply checking for its presence.

Prompt for agents
The guard on line 53 checks for the presence of `<Location />` in cupsd.conf and skips the entire block if found. However, the default CUPS cupsd.conf already contains a `<Location />` directive — typically without `Allow @LOCAL`. This means the script never adds the crucial `Allow @LOCAL` rule that enables LAN printing.

The fix should handle two scenarios:
1. If `<Location />` already exists but lacks `Allow @LOCAL`, inject `Allow @LOCAL` into the existing block (e.g. using sed to insert after the `Order allow,deny` line within `<Location />`). Do the same for `<Location /admin>`.
2. If `<Location />` doesn't exist at all, append the full block as currently done.

Also consider checking for `Allow @LOCAL` specifically (e.g., `grep -q 'Allow @LOCAL' cupsd.conf`) rather than checking for `<Location />`.

Relevant function: the block at scripts/setup_hl_l1222_android_print.sh lines 53-65.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


echo "[3/8] Opening firewall for IPP + mDNS..."
firewall-cmd --permanent --add-service=ipp || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Do not swallow firewall command failures; this can produce a false successful setup with broken network printing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/setup_hl_l1222_android_print.sh, line 68:

<comment>Do not swallow firewall command failures; this can produce a false successful setup with broken network printing.</comment>

<file context>
@@ -0,0 +1,118 @@
+fi
+
+echo "[3/8] Opening firewall for IPP + mDNS..."
+firewall-cmd --permanent --add-service=ipp || true
+firewall-cmd --permanent --add-service=mdns || true
+firewall-cmd --reload || true
</file context>

firewall-cmd --permanent --add-service=mdns || true
firewall-cmd --reload || true

echo "[4/8] Detecting USB printer URI..."
Comment on lines +67 to +72

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Avoid fully swallowing firewall-cmd failures or at least surface them to the user.

Using || true on all three firewall-cmd calls means the script always reports success even if firewalld is missing or the rules fail to apply, which can leave network printing broken without any indication. Consider only relaxing failures when firewalld is intentionally inactive (e.g., via systemctl is-active firewalld) and/or logging a clear warning when firewall-cmd fails so users know IPP/mDNS might not be open.

Suggested change
echo "[3/8] Opening firewall for IPP + mDNS..."
firewall-cmd --permanent --add-service=ipp || true
firewall-cmd --permanent --add-service=mdns || true
firewall-cmd --reload || true
echo "[4/8] Detecting USB printer URI..."
echo "[3/8] Opening firewall for IPP + mDNS..."
if ! command -v firewall-cmd >/dev/null 2>&1; then
echo "Warning: firewall-cmd not found; skipping firewall configuration. Network printing may not be reachable from other devices."
elif ! systemctl is-active --quiet firewalld; then
echo "Warning: firewalld service is not active; skipping firewall configuration. Network printing may not be reachable from other devices."
else
if ! firewall-cmd --permanent --add-service=ipp; then
echo "Warning: failed to add IPP service to firewalld. Network printing may not be reachable from other devices."
fi
if ! firewall-cmd --permanent --add-service=mdns; then
echo "Warning: failed to add mDNS service to firewalld. Network printer discovery may not work."
fi
if ! firewall-cmd --reload; then
echo "Warning: failed to reload firewalld. Newly added firewall rules may not be active."
fi
fi
echo "[4/8] Detecting USB printer URI..."

USB_URI="$(lpinfo -v | awk '/^direct usb:\/\/Brother\//{print $2; exit}')"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 ipp-usb package may conflict with direct USB backend for printer detection

The script installs ipp-usb at scripts/setup_hl_l1222_android_print.sh:26, which provides an IPP-over-USB bridge. Once running, ipp-usb may take exclusive ownership of the USB device, causing the lpinfo -v detection at line 73 to fail to find the direct usb://Brother/... URI. The printer might instead appear only as an ipp://localhost:60000/... endpoint. If this happens, the awk filter /^direct usb:\/\/Brother\// wouldn't match and the script would exit at line 78 claiming no printer was found. Whether this actually occurs depends on the ipp-usb daemon activation timing and USB device locking behavior on the target platform — it warrants testing.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

if [[ -z "$USB_URI" ]]; then
echo "No Brother USB URI auto-detected. Available devices:"
lpinfo -v
echo "Connect the printer via USB and re-run this script."
exit 1
fi

echo "Detected URI: $USB_URI"

echo "[5/8] Creating/updating CUPS queue..."
if lpstat -p "$PRINTER_NAME" >/dev/null 2>&1; then
lpadmin -x "$PRINTER_NAME"
fi
lpadmin -p "$PRINTER_NAME" -E -v "$USB_URI" -m "$PPD_MODEL"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The everywhere model (IPP Everywhere) is incompatible with a raw usb:// URI for the Brother HL-L1222-V, as this printer does not support IPP natively over USB. Using this combination will result in a non-functional print queue.

Since ipp-usb is installed (line 26), it may claim the device and provide an ipp:// or driverless:// URI. If you intend to use the everywhere model, you should use one of those URIs discovered via lpinfo. Otherwise, for this specific model, you likely need the brlaser driver (e.g., -m drv:///brlaser.drv/brl1222.ppd).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use IPP URI when configuring the everywhere model

The queue is created with -m everywhere, but USB_URI is constrained to direct usb://Brother/...; per lpadmin(8), everywhere queries the printer via a specified IPP device URI. With a direct USB URI this frequently fails queue creation (or produces a non-working driverless setup), breaking the script’s default path for this printer.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: The everywhere model requires an IPP device URI to query the printer's capabilities (per lpadmin(8): "the model 'everywhere' queries the printer referred to by the specified IPP device-uri"). The USB_URI here is a usb:// scheme which cannot be queried via IPP, so this will either fail queue creation or produce a non-functional driverless queue.

Since ipp-usb is installed, you should detect the ipp:// or driverless:// URI it provides (via lpinfo -v), or fall back to a compatible driver like brlaser (e.g., -m drv:///brlaser.drv/brl1222.ppd) for the USB path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/setup_hl_l1222_android_print.sh, line 87:

<comment>The `everywhere` model requires an IPP device URI to query the printer's capabilities (per `lpadmin(8)`: "the model 'everywhere' queries the printer referred to by the specified IPP device-uri"). The `USB_URI` here is a `usb://` scheme which cannot be queried via IPP, so this will either fail queue creation or produce a non-functional driverless queue.

Since `ipp-usb` is installed, you should detect the `ipp://` or `driverless://` URI it provides (via `lpinfo -v`), or fall back to a compatible driver like `brlaser` (e.g., `-m drv:///brlaser.drv/brl1222.ppd`) for the USB path.</comment>

<file context>
@@ -0,0 +1,118 @@
+if lpstat -p "$PRINTER_NAME" >/dev/null 2>&1; then
+  lpadmin -x "$PRINTER_NAME"
+fi
+lpadmin -p "$PRINTER_NAME" -E -v "$USB_URI" -m "$PPD_MODEL"
+lpoptions -d "$PRINTER_NAME"
+
</file context>

lpoptions -d "$PRINTER_NAME"

# Disable duplex (hardware lacks automatic duplex)
lpadmin -p "$PRINTER_NAME" -o sides=one-sided -o media=A4

# Enable sharing explicitly
lpadmin -p "$PRINTER_NAME" -o printer-is-shared=true

echo "[6/8] Restarting CUPS..."
systemctl restart cups.service

echo "[7/8] Printing a test page..."
lp -d "$PRINTER_NAME" /usr/share/cups/data/testprint || true
Comment on lines +97 to +100

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Test print may silently fail due to CUPS restart timing

At line 97, CUPS is restarted with systemctl restart cups.service, and the very next command at line 100 submits a test print job via lp. There is no delay or readiness check between the restart and the print submission. If CUPS hasn't fully started accepting connections, lp would fail. The || true prevents the script from aborting, but the test page would silently not print. This is non-critical (all configuration is already persisted), but could confuse users who expect to see a test page.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


echo "[8/8] Final status"
lpstat -t

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: lpstat -t at end of script can abort under set -e if CUPS is still restarting

Line 103 runs lpstat -t to display final status. Unlike the test print on line 100 which has || true, this command has no error suppression. Under set -e, if CUPS hasn't fully restarted yet (same timing concern as ANALYSIS-0002), lpstat -t would fail and cause the script to exit with an error after all setup is already complete. This would give a misleading impression of failure. Adding || true or a brief sleep before status commands would make the script more robust.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


IP_ADDR="$(hostname -I | awk '{print $1}')"
cat <<MSG

Setup complete.

Android printing method (recommended):
1) Connect phone to same Wi-Fi as Raspberry Pi.
2) Ensure Mopria Print Service is enabled on Android.
3) Add/find printer: ipp://$IP_ADDR/printers/$PRINTER_NAME

Bluetooth note:
- Direct Android->CUPS Bluetooth printing is typically not supported on modern Android/CUPS stacks.
- Use Wi-Fi IPP/Mopria path above, or USB-OTG direct phone-to-printer where supported by phone/app.
MSG