Skip to content

Add setup script and README for Brother HL-L1222 Android print server on Raspberry Pi (openSUSE)#30

Open
ib-bsb-br wants to merge 1 commit into
mainfrom
codex/create-tutorial-for-brother-hl-l1222-v-setup-on-raspberry-pi-6shgxo
Open

Add setup script and README for Brother HL-L1222 Android print server on Raspberry Pi (openSUSE)#30
ib-bsb-br wants to merge 1 commit into
mainfrom
codex/create-tutorial-for-brother-hl-l1222-v-setup-on-raspberry-pi-6shgxo

Conversation

@ib-bsb-br

@ib-bsb-br ib-bsb-br commented Apr 29, 2026

Copy link
Copy Markdown
Owner

Motivation

  • Provide a one-step, repeatable setup to turn a Raspberry Pi 4B+ running openSUSE Tumbleweed into a network print server for a USB-connected Brother HL-L1222/HL-L1222-V so Android devices can print via IPP/Mopria.
  • Automate installation and configuration of required services (cups, avahi, firewalld, optional Bluetooth) and create a usable CUPS queue with sensible defaults and safety backups.
  • Offer convenient fallback workflows (hotfolder auto-print watcher and optional LAN upload page) and systemd units to make the service suitable for headless operation.

Description

  • Added scripts/setup_hl_l1222_android_print.sh, an executable shell script that installs dependencies, enables services, configures firewall and CUPS sharing, detects USB printer URIs, selects an appropriate model (with everywhere/generic fallbacks), and creates/recreates the CUPS queue with confirmation and backups.
  • The script installs two helper services: a Python hotfolder watcher (hotfolder_watcher.py) and an optional LAN upload HTTP server (upload_server.py), and it installs corresponding systemd units (hll1222-hotfolder.service and hll1222-upload.service).
  • The script includes safety measures such as backing up /etc/cups/cupsd.conf, requiring explicit confirmation before destructive operations, resolving required binaries, and providing a --status diagnostic mode and CLI flags to customize behavior.
  • Updated README.md to document the new setup, usage (sudo bash scripts/setup_hl_l1222_android_print.sh), how to connect Android devices over Wi‑Fi using ipp://<raspberrypi-ip>/printers/Brother_HL_L1222_V, and notes about Bluetooth and driver alternatives.

Testing

  • No automated tests or CI were added or executed for this change.

Codex Task


Open in Devin Review

Summary by Sourcery

Introduce a one-step setup script and documentation to configure a Raspberry Pi running openSUSE as a CUPS-based network print server for a USB-connected Brother HL-L1222/HL-L1222-V, including Android-friendly IPP access and headless-friendly helper services.

New Features:

  • Add a setup shell script that installs and configures CUPS, Avahi, firewalld, and optional Bluetooth to expose a shared Brother HL-L1222/HL-L1222-V printer over IPP.
  • Provide optional hotfolder auto-print and LAN upload helper services, each managed via systemd units for headless operation.

Enhancements:

  • Document end-to-end setup and Android IPP usage for the Brother HL-L1222/HL-L1222-V Raspberry Pi print server workflow in the README.

Summary by cubic

Add a one-step setup script and docs to turn a Raspberry Pi 4B+ (openSUSE Tumbleweed) into an Android‑friendly IPP print server for a USB Brother HL‑L1222/HL‑L1222‑V. It automates CUPS sharing, firewall and service setup, and adds headless helpers (hotfolder auto‑print and optional LAN upload).

  • New Features

    • Add scripts/setup_hl_l1222_android_print.sh to install and configure cups, avahi, firewalld (and optional Bluetooth), open firewall, enable sharing, detect USB URI, pick a model (brlaser/everywhere/generic), and create/recreate the CUPS queue with safe backups and confirmations.
    • Add hotfolder watcher and optional LAN upload server with systemd units hll1222-hotfolder.service and hll1222-upload.service.
    • Include --status and flags for queue/device/model selection and upload port; update README.md with run command and Android IPP URI.
  • Migration

    • Run: sudo bash scripts/setup_hl_l1222_android_print.sh.
    • On Android (same Wi‑Fi), add printer via ipp://<raspberrypi-ip>/printers/Brother_HL_L1222_V (Mopria or equivalent).

Written for commit f13ab3f. Summary will update on new commits. Review in cubic

@sourcery-ai

sourcery-ai Bot commented Apr 29, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a one-shot setup script plus documentation to turn a Raspberry Pi 4B+ running openSUSE Tumbleweed into a networked CUPS print server for a USB Brother HL-L1222/HL-L1222-V, including optional hotfolder and upload HTTP services and associated systemd units for headless operation.

Sequence diagram for optional upload-to-print fallback workflow

sequenceDiagram
  actor AndroidUser
  participant Browser as Android_browser
  participant UploadServer as upload_server.py
  participant HotfolderWatcher as hotfolder_watcher.py
  participant CUPS as CUPS_daemon
  participant USBPrinter as Brother_HL_L1222_V
  participant HotfolderDir as Hotfolder_directory

  AndroidUser ->> Browser: Open http://raspberrypi:8088/
  Browser ->> UploadServer: GET /
  UploadServer -->> Browser: HTML_upload_form

  AndroidUser ->> Browser: Select_file_and_submit
  Browser ->> UploadServer: POST /upload (multipart_file)
  UploadServer ->> UploadServer: Validate_size_and_extension
  UploadServer ->> HotfolderDir: Save_file_in_hotfolder
  UploadServer -->> Browser: 200_OK_uploaded_successfully

  loop Poll_hotfolder
    HotfolderWatcher ->> HotfolderDir: Scan_for_new_files
    HotfolderWatcher ->> HotfolderDir: Check_file_stability
  end

  HotfolderWatcher ->> CUPS: lp -d Brother_HL_L1222_V file
  CUPS ->> USBPrinter: Send_print_job
  USBPrinter -->> CUPS: Printing_completed
  CUPS -->> HotfolderWatcher: Job_status_OK
  HotfolderWatcher ->> HotfolderDir: Move_file_to_printed_subdir
Loading

Class diagram for setup script, hotfolder watcher, and upload server

classDiagram

  class SetupScript {
    +QUEUE_NAME
    +DEVICE_URI
    +MODEL_NAME
    +ENABLE_BLUETOOTH
    +ENABLE_UPLOAD
    +UPLOAD_PORT
    +MAX_UPLOAD_BYTES
    +SCRIPT_DIR
    +HOTFOLDER
    +HOTFOLDER_SERVICE
    +UPLOAD_SERVICE
    +CUPSD_CONF
    +main(args)
    +parse_args(args)
    +status_report()
    +install_dependencies()
    +enable_services()
    +configure_firewall()
    +configure_cups_sharing()
    +detect_usb_uri()
    +select_model()
    +create_or_replace_queue()
    +install_hotfolder_service()
    +install_upload_service_optional()
    +print_summary()
  }

  class ShellHelpers {
    +log(message)
    +warn(message)
    +err(message)
    +resolve_bin(name)
    +cleanup()
    +require_command(cmd, pkg)
    +confirm_high_risk(message)
    +usage()
  }

  class HotfolderWatcherScript {
    +hot_path
    +queue_name
    +allowed_extensions
    +done_dir
    +failed_dir
    +stable(path)
    +scan_and_print_loop()
  }

  class UploadServerScript {
    +hot_path
    +queue_name
    +max_bytes
    +allowed_extensions
    +page_html
    +serve_forever()
  }

  class UploadHandlerH {
    +send_body(code, body, ctype)
    +do_GET()
    +do_POST()
  }

  class SystemdServiceHotfolder {
    +Unit_Description
    +After_cups_service
    +Requires_cups_service
    +Service_User_lp
    +Service_Group_lp
    +Environment_PRINT_QUEUE
    +Environment_PRINT_HOTFOLDER
    +ExecStart_hotfolder_watcher.py
    +Restart_always
  }

  class SystemdServiceUpload {
    +Unit_Description
    +After_network_online_and_cups
    +Requires_cups_service
    +Wants_network_online
    +Service_User_lp
    +Service_Group_lp
    +Environment_PRINT_QUEUE
    +Environment_PRINT_HOTFOLDER
    +Environment_UPLOAD_PORT
    +Environment_MAX_UPLOAD_BYTES
    +ExecStart_upload_server.py
    +Restart_always
  }

  SetupScript ..> ShellHelpers : uses
  SetupScript ..> HotfolderWatcherScript : installs_and_configures
  SetupScript ..> UploadServerScript : optionally_installs
  SetupScript ..> SystemdServiceHotfolder : writes_unit_file
  SetupScript ..> SystemdServiceUpload : writes_unit_file

  UploadServerScript ..> UploadHandlerH : HTTP_request_handler
Loading

File-Level Changes

Change Details Files
Introduce a comprehensive Bash setup script that provisions the Raspberry Pi as an Android‑compatible IPP/CUPS print server for a USB‑connected Brother HL‑L1222/HL‑L1222‑V, including dependency installation, CUPS/firewalld/Avahi configuration, printer queue creation, and optional Bluetooth and upload service toggles.
  • Add argument parsing to allow customization of queue name, device URI, model, Bluetooth and upload enablement, upload port, and a diagnostics-only mode.
  • Install and validate core packages (CUPS, cups-filters, avahi, firewalld, usbutils, python3, optional brlaser and Bluetooth stack) via zypper with non-interactive flags and error handling.
  • Enable and start core system services (cups, avahi-daemon, firewalld, optionally bluetooth) with systemd and fail fast if they cannot be started.
  • Configure firewalld to permanently allow IPP and mDNS traffic, and optionally open the custom upload port, then reload the firewall.
  • Back up the existing CUPS configuration file, adjust sharing-related directives via cupsctl and direct edits (WebInterface, Browsing, BrowseLocalProtocols), and restart CUPS.
  • Auto-detect a suitable USB device URI for the Brother printer via lpinfo, or fail with guidance if not found.
  • Auto-select a printer model/PPD preferring brlaser/Brother models, then an everywhere driver, then a generic mono laser model, and fall back with diagnostics if none are found.
  • Create or recreate the target CUPS queue with confirmation before destructive actions, set sane default options (A4, one-sided, shared), enable and accept the queue, and set it as the system default.
  • Add a status-report mode that prints system, USB, and CUPS diagnostics without making changes.
  • Emit a final summary with discovered IP address and ready-to-use IPP URI for Android clients, plus optional upload/Bluetooth notes.
scripts/setup_hl_l1222_android_print.sh
Add a Python-based hotfolder watcher service and optional LAN upload HTTP service, both installed by the setup script and wired up via systemd units for headless, resilient operation.
  • Create a Python hotfolder watcher script that monitors a spool directory, waits for files to become stable, filters by allowed extensions, prints them to the configured CUPS queue, and moves them into printed/failed subfolders.
  • Install the watcher into a dedicated script directory, create the hotfolder with lp ownership and proper permissions, and set up a systemd service that runs as user/group lp, restarts on failure, and depends on CUPS.
  • Create an optional Python HTTP upload service that exposes a minimal upload form, validates file size and extensions, writes uploads into the hotfolder with safe filenames and permissions, and relies on the watcher for actual printing.
  • Install the upload server into the script directory and define a systemd service that runs as lp, depends on network-online, CUPS, and the hotfolder service, and is parameterized by environment variables for queue, hotfolder, port, and max upload size.
  • Wire both services into the main setup flow so they are installed, systemd is reloaded, and the units are enabled and started when requested.
scripts/setup_hl_l1222_android_print.sh
Replace the placeholder README with documentation describing the new Raspberry Pi print-server setup flow and Android usage.
  • Document the purpose of the repository as a Brother HL-L1222-V Android print server on Raspberry Pi 4B+ running openSUSE Tumbleweed.
  • Describe the capabilities enabled by the setup (Android IPP/Mopria printing, CUPS sharing, firewall/discovery configuration).
  • Clarify Bluetooth limitations for Android printing and position Wi‑Fi IPP as the supported path, noting that Bluetooth services are enabled only for experimentation.
  • Provide basic usage instructions for running the setup script with sudo and outline the steps Android users should follow to add the printer using an ipp:// URI referencing the created CUPS queue.
  • Note driver/model behavior, including use of the CUPS everywhere model by default and how users can substitute an official Brother PPD if available.
README.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • The --status flag is effectively broken because STATUS_ONLY is declared local inside main() after parse_args has already set the global variable, so the check if [[ "${STATUS_ONLY:-no}" == "yes" ]] will never be true; either remove the local or pass the flag back in a more explicit way.
  • In print_summary, the line echo " "$LPINFO_BIN" -m | grep -Ei 'brother|hl|brlaser|everywhere|generic|laser|mono|pcl'" has mismatched/fragmented quoting and will not echo the intended command; wrap the entire command in one double-quoted string (e.g. echo " $LPINFO_BIN -m | grep -Ei 'brother|hl|brlaser|everywhere|generic|laser|mono|pcl'").
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `--status` flag is effectively broken because `STATUS_ONLY` is declared `local` inside `main()` after `parse_args` has already set the global variable, so the check `if [[ "${STATUS_ONLY:-no}" == "yes" ]]` will never be true; either remove the `local` or pass the flag back in a more explicit way.
- In `print_summary`, the line `echo "  "$LPINFO_BIN" -m | grep -Ei 'brother|hl|brlaser|everywhere|generic|laser|mono|pcl'"` has mismatched/fragmented quoting and will not echo the intended command; wrap the entire command in one double-quoted string (e.g. `echo "  $LPINFO_BIN -m | grep -Ei 'brother|hl|brlaser|everywhere|generic|laser|mono|pcl'"`).

## Individual Comments

### Comment 1
<location path="scripts/setup_hl_l1222_android_print.sh" line_range="649" />
<code_context>
+  echo "Validate:"
+  echo "  $LPSTAT_BIN -t"
+  echo "  lpinfo -v"
+  echo "  "$LPINFO_BIN" -m | grep -Ei 'brother|hl|brlaser|everywhere|generic|laser|mono|pcl'"
+  echo "  echo 'test page from pi' | lp -d ${QUEUE_NAME}"
+  echo
</code_context>
<issue_to_address>
**issue (bug_risk):** The echo/pipe line is parsed incorrectly due to split quoting, so the suggested command won’t actually be printed as intended.

Because the argument is split across quoted segments, the shell parses this as `echo  /path/to/lpinfo -m | grep ...`, so the pipe is executed instead of being shown. The user will only see `grep` output, not the full command they should run. Quote the entire example command as a single string, e.g.

```bash
echo "  $LPINFO_BIN -m | grep -Ei 'brother|hl|brlaser|everywhere|generic|laser|mono|pcl'"
```
</issue_to_address>

### Comment 2
<location path="scripts/setup_hl_l1222_android_print.sh" line_range="574-577" />
<code_context>
+        self.send_body(200, "<p>Uploaded successfully. File queued.</p><p><a href='/'>Back</a></p>")
+
+if __name__ == "__main__":
+    host = "0.0.0.0"
+    port = int(os.environ.get("UPLOAD_PORT", "8088"))
+    ThreadingHTTPServer((host, port), H).serve_forever()
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Binding the upload server to 0.0.0.0 with no auth exposes it on all interfaces, which may be broader than intended.

Given there’s no authentication and the firewall opens `${UPLOAD_PORT}/tcp`, any host that can reach the Pi on that port can upload files. To better match the "LAN only" / fallback intent, consider tightening exposure by:

- Binding to a safer address (e.g. `127.0.0.1` with SSH port forwarding, or only the LAN IP), and/or
- Clearly documenting that this should only run on trusted LANs, and/or
- Adding a minimal guard (e.g. shared secret token in the URL).

Even just binding to `127.0.0.1` would significantly reduce the attack surface.

```suggestion
if __name__ == "__main__":
    # Bind to localhost by default to avoid exposing the upload server on all interfaces.
    # Override with UPLOAD_HOST (e.g. a specific LAN IP) if you explicitly want wider access.
    host = os.environ.get("UPLOAD_HOST", "127.0.0.1")
    port = int(os.environ.get("UPLOAD_PORT", "8088"))
    ThreadingHTTPServer((host, port), H).serve_forever()
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

echo "Validate:"
echo " $LPSTAT_BIN -t"
echo " lpinfo -v"
echo " "$LPINFO_BIN" -m | grep -Ei 'brother|hl|brlaser|everywhere|generic|laser|mono|pcl'"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): The echo/pipe line is parsed incorrectly due to split quoting, so the suggested command won’t actually be printed as intended.

Because the argument is split across quoted segments, the shell parses this as echo /path/to/lpinfo -m | grep ..., so the pipe is executed instead of being shown. The user will only see grep output, not the full command they should run. Quote the entire example command as a single string, e.g.

echo "  $LPINFO_BIN -m | grep -Ei 'brother|hl|brlaser|everywhere|generic|laser|mono|pcl'"

Comment on lines +574 to +577
if __name__ == "__main__":
host = "0.0.0.0"
port = int(os.environ.get("UPLOAD_PORT", "8088"))
ThreadingHTTPServer((host, port), H).serve_forever()

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 (security): Binding the upload server to 0.0.0.0 with no auth exposes it on all interfaces, which may be broader than intended.

Given there’s no authentication and the firewall opens ${UPLOAD_PORT}/tcp, any host that can reach the Pi on that port can upload files. To better match the "LAN only" / fallback intent, consider tightening exposure by:

  • Binding to a safer address (e.g. 127.0.0.1 with SSH port forwarding, or only the LAN IP), and/or
  • Clearly documenting that this should only run on trusted LANs, and/or
  • Adding a minimal guard (e.g. shared secret token in the URL).

Even just binding to 127.0.0.1 would significantly reduce the attack surface.

Suggested change
if __name__ == "__main__":
host = "0.0.0.0"
port = int(os.environ.get("UPLOAD_PORT", "8088"))
ThreadingHTTPServer((host, port), H).serve_forever()
if __name__ == "__main__":
# Bind to localhost by default to avoid exposing the upload server on all interfaces.
# Override with UPLOAD_HOST (e.g. a specific LAN IP) if you explicitly want wider access.
host = os.environ.get("UPLOAD_HOST", "127.0.0.1")
port = int(os.environ.get("UPLOAD_PORT", "8088"))
ThreadingHTTPServer((host, port), H).serve_forever()

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive setup script and updated documentation for configuring a Raspberry Pi as a print server for the Brother HL-L1222-V printer on openSUSE Tumbleweed. The script automates the installation of CUPS, Avahi, and firewall rules, while also providing Python-based services for hotfolder monitoring and optional web-based file uploads. Feedback was provided regarding the use of the deprecated cgi.FieldStorage class in the upload service, suggesting a transition to more modern libraries for better long-term compatibility with Python 3.11+.

if clen <= 0 or clen > maxb:
self.send_body(413, "Invalid upload size")
return
form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ={"REQUEST_METHOD": "POST", "CONTENT_TYPE": self.headers.get("Content-Type", ""), "CONTENT_LENGTH": str(clen)})

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

The cgi.FieldStorage class is deprecated since Python 3.11. Consider using the email.message module or a modern web framework for handling multipart form data to ensure long-term compatibility.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f13ab3fb86

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


cat > "$upload_py" <<'PY'
#!/usr/bin/env python3
import cgi

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 Replace removed cgi dependency in upload service

The optional upload service will crash on startup on hosts using Python 3.13+ because it imports cgi (import cgi), and that stdlib module was removed after Python 3.12. In this script’s target environment (rolling openSUSE Tumbleweed), enabling --enable-upload can therefore produce a systemd unit that immediately fails with ModuleNotFoundError, leaving the advertised upload fallback unusable.

Useful? React with 👍 / 👎.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 9 potential issues.

Open in Devin Review

exit 1
fi

TMP_DIR=""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Temporary directories leaked on success path because TMP_DIR is cleared without rm

Both install_hotfolder_service (line 494) and install_upload_service_optional (line 630) set TMP_DIR="" after finishing, but never actually remove the temporary directory created by mktemp -d at lines 396 and 504. The cleanup trap (lines 62-66) only deletes the directory if TMP_DIR is non-empty when the trap fires. On the successful path, TMP_DIR is empty by the time the EXIT trap runs, so both temp directories persist on disk indefinitely.

Suggested change
TMP_DIR=""
rm -rf "${TMP_DIR}"
TMP_DIR=""
Open in Devin Review

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

exit 1
fi

TMP_DIR=""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Same temp directory leak in install_upload_service_optional

Same issue as in install_hotfolder_service: line 630 clears TMP_DIR without first removing the directory created at scripts/setup_hl_l1222_android_print.sh:504. The directory and its contents remain in /tmp after a successful run.

Suggested change
TMP_DIR=""
rm -rf "${TMP_DIR}"
TMP_DIR=""
Open in Devin Review

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

if ext not in allowed:
self.send_body(400, f"Unsupported file type: {html.escape(ext)}")
return
safe = "".join(ch if ch.isalnum() or ch in "._-" else "_" for ch in name)[:160]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Upload server accepts dot-prefixed filenames that hotfolder watcher silently ignores

The upload server's filename sanitizer at line 559 permits . as a valid character, so a file named .report.pdf is saved as-is into the hotfolder. However, the hotfolder watcher explicitly skips dotfiles at scripts/setup_hl_l1222_android_print.sh:430 (f.name.startswith('.')). The user receives "Uploaded successfully. File queued." but the file is never printed and sits in the hotfolder indefinitely. The fix is to strip leading dots from the sanitized filename in the upload server (e.g., safe = safe.lstrip('.')) or to reject such filenames.

Suggested change
safe = "".join(ch if ch.isalnum() or ch in "._-" else "_" for ch in name)[:160]
safe = "".join(ch if ch.isalnum() or ch in "._-" else "_" for ch in name)[:160].lstrip('.')
Open in Devin Review

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

Comment on lines +510 to +549
import cgi
import html
import os
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import urlparse

hot = Path(os.environ.get("PRINT_HOTFOLDER", "/var/spool/hll1222-hotfolder"))
queue = os.environ.get("PRINT_QUEUE", "Brother_HL_L1222_V")
maxb = int(os.environ.get("MAX_UPLOAD_BYTES", "52428800"))
allowed = {".pdf", ".txt", ".png", ".jpg", ".jpeg", ".tif", ".tiff"}

hot.mkdir(parents=True, exist_ok=True)

page = f"""<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>HL-L1222 Upload</title></head><body><h1>HL-L1222 Upload</h1><p>Queue: <b>{html.escape(queue)}</b></p><form method='post' enctype='multipart/form-data' action='/upload'><input type='file' name='file' required><button type='submit'>Upload</button></form><p>LAN only. Allowed: PDF/TXT/PNG/JPG/TIFF</p></body></html>"""

class H(BaseHTTPRequestHandler):
def send_body(self, code, body, ctype="text/html; charset=utf-8"):
data = body.encode("utf-8")
self.send_response(code)
self.send_header("Content-Type", ctype)
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)

def do_GET(self):
if urlparse(self.path).path == "/":
self.send_body(200, page)
else:
self.send_body(404, "Not found")

def do_POST(self):
if urlparse(self.path).path != "/upload":
self.send_body(404, "Not found")
return
clen = int(self.headers.get("Content-Length", "0"))
if clen <= 0 or clen > maxb:
self.send_body(413, "Invalid upload size")
return
form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ={"REQUEST_METHOD": "POST", "CONTENT_TYPE": self.headers.get("Content-Type", ""), "CONTENT_LENGTH": str(clen)})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚩 Upload server uses deprecated cgi module (removed in Python 3.13)

The embedded upload_server.py imports cgi and uses cgi.FieldStorage at line 549. The cgi module was deprecated in Python 3.11 (PEP 594) and removed from the standard library in Python 3.13 (released October 2024). Since the script targets openSUSE Tumbleweed — a rolling release distro — and installs python3 without version pinning (scripts/setup_hl_l1222_android_print.sh:192), the installed Python will almost certainly be 3.13+ in 2026, causing an ImportError at service startup. This would make the upload server non-functional. Per review guidelines I did not flag this as a bug since I cannot verify the exact Python version from the repo context alone, but this is a significant concern worth investigating.

Open in Devin Review

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

Comment thread README.md

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

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: README instructs running with sudo but script header says normal user

The README at line 18 instructs sudo bash scripts/setup_hl_l1222_android_print.sh, but the script's own header comment (line 15) says "Run as a normal user from home directory" and uses sudo internally for privileged operations. Running with sudo is functionally fine (the internal sudo calls become no-ops when already root), but this creates a contradiction in documentation. The confirm_high_risk prompts (which use read -r -p) still work correctly under sudo bash since stdin is inherited.

Open in Devin Review

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

Comment on lines +114 to +144
--queue)
QUEUE_NAME="$2"
shift 2
;;
--device)
DEVICE_URI="$2"
shift 2
;;
--model)
MODEL_NAME="$2"
shift 2
;;
--enable-bluetooth)
ENABLE_BLUETOOTH="yes"
shift
;;
--disable-bluetooth)
ENABLE_BLUETOOTH="no"
shift
;;
--enable-upload)
ENABLE_UPLOAD="yes"
shift
;;
--disable-upload)
ENABLE_UPLOAD="no"
shift
;;
--upload-port)
UPLOAD_PORT="$2"
shift 2

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: parse_args uses $2 without checking argument count

Options that require values (--queue, --device, --model, --upload-port) access $2 via shift 2 without first verifying $# -ge 2. Under set -u (line 2), if the user passes e.g. --queue as the last argument with no value, $2 triggers an "unbound variable" error. This is acceptable fail-fast behavior, but the error message will be cryptic ("unbound variable") rather than a helpful "missing argument for --queue". Not a bug per se, but worth noting for usability.

Open in Devin Review

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

Comment on lines +664 to +665
local STATUS_ONLY="no"
parse_args "$@"

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: STATUS_ONLY scoping works correctly via bash dynamic scoping

At first glance, STATUS_ONLY appears problematic: it's declared local in main() (line 664) but assigned in parse_args() (line 147). However, bash uses dynamic scoping — called functions can see and modify the caller's local variables. So parse_args correctly modifies main's local STATUS_ONLY. The ${STATUS_ONLY:-no} default at line 682 is defensive but correct. Not a bug.

Open in Devin Review

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

Comment on lines +647 to +649
echo " $LPSTAT_BIN -t"
echo " lpinfo -v"
echo " "$LPINFO_BIN" -m | grep -Ei 'brother|hl|brlaser|everywhere|generic|laser|mono|pcl'"

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: Quoting inconsistency in print_summary display output

Line 649 has echo " "$LPINFO_BIN" -m | grep ..." where $LPINFO_BIN sits between two double-quoted segments, making it unquoted and subject to word-splitting. Since LPINFO_BIN is a simple path like /usr/sbin/lpinfo (no spaces), this works correctly in practice. Compare with line 647 which correctly uses echo " $LPSTAT_BIN -t" with the variable inside quotes. Also, line 648 hardcodes lpinfo instead of using $LPINFO_BIN, breaking the pattern. These are cosmetic inconsistencies, not functional bugs.

Open in Devin Review

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

Comment on lines +272 to +280
if ! sudo grep -qi '^WebInterface[[:space:]]\+Yes' "${CUPSD_CONF}"; then
echo 'WebInterface Yes' | sudo tee -a "${CUPSD_CONF}" >/dev/null
fi
if ! sudo grep -qi '^Browsing[[:space:]]\+Yes' "${CUPSD_CONF}"; then
echo 'Browsing Yes' | sudo tee -a "${CUPSD_CONF}" >/dev/null
fi
if ! sudo grep -qi '^BrowseLocalProtocols[[:space:]]\+dnssd' "${CUPSD_CONF}"; then
echo 'BrowseLocalProtocols dnssd' | sudo tee -a "${CUPSD_CONF}" >/dev/null
fi

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: CUPS config grep patterns could match existing directives with different values

Lines 272-279 use grep -qi to check if directives like WebInterface Yes already exist before appending. The pattern ^WebInterface[[:space:]]\+Yes specifically matches the directive with value Yes. If CUPS already has WebInterface No, the grep won't match and the script will append WebInterface Yes — resulting in a duplicate directive with conflicting values. CUPS typically uses the last directive, so the appended value would win, which is the intended behavior. However, this leaves a messy config file. cupsctl --share-printers --remote-any (line 266) may already set some of these, reducing the risk.

Open in Devin Review

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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 2 files

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant