Add setup script and README for Brother HL-L1222 Android print server on Raspberry Pi (openSUSE)#30
Conversation
Reviewer's GuideAdds 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 workflowsequenceDiagram
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
Class diagram for setup script, hotfolder watcher, and upload serverclassDiagram
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
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- The
--statusflag is effectively broken becauseSTATUS_ONLYis declaredlocalinsidemain()afterparse_argshas already set the global variable, so the checkif [[ "${STATUS_ONLY:-no}" == "yes" ]]will never be true; either remove thelocalor pass the flag back in a more explicit way. - In
print_summary, the lineecho " "$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>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'" |
There was a problem hiding this comment.
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'"| if __name__ == "__main__": | ||
| host = "0.0.0.0" | ||
| port = int(os.environ.get("UPLOAD_PORT", "8088")) | ||
| ThreadingHTTPServer((host, port), H).serve_forever() |
There was a problem hiding this comment.
🚨 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.1with 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.
| 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() |
There was a problem hiding this comment.
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)}) |
There was a problem hiding this comment.
💡 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 |
There was a problem hiding this comment.
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 👍 / 👎.
| exit 1 | ||
| fi | ||
|
|
||
| TMP_DIR="" |
There was a problem hiding this comment.
🟡 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.
| TMP_DIR="" | |
| rm -rf "${TMP_DIR}" | |
| TMP_DIR="" |
Was this helpful? React with 👍 or 👎 to provide feedback.
| exit 1 | ||
| fi | ||
|
|
||
| TMP_DIR="" |
There was a problem hiding this comment.
🟡 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.
| TMP_DIR="" | |
| rm -rf "${TMP_DIR}" | |
| TMP_DIR="" |
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] |
There was a problem hiding this comment.
🟡 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.
| 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('.') |
Was this helpful? React with 👍 or 👎 to provide feedback.
| 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)}) |
There was a problem hiding this comment.
🚩 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
|
|
||
| ## Run | ||
| ```bash | ||
| sudo bash scripts/setup_hl_l1222_android_print.sh |
There was a problem hiding this comment.
📝 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
| --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 |
There was a problem hiding this comment.
📝 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
| local STATUS_ONLY="no" | ||
| parse_args "$@" |
There was a problem hiding this comment.
📝 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
| echo " $LPSTAT_BIN -t" | ||
| echo " lpinfo -v" | ||
| echo " "$LPINFO_BIN" -m | grep -Ei 'brother|hl|brlaser|everywhere|generic|laser|mono|pcl'" |
There was a problem hiding this comment.
📝 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
| 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 |
There was a problem hiding this comment.
📝 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
Motivation
cups,avahi,firewalld, optional Bluetooth) and create a usable CUPS queue with sensible defaults and safety backups.Description
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 (witheverywhere/generic fallbacks), and creates/recreates the CUPS queue with confirmation and backups.hotfolder_watcher.py) and an optional LAN upload HTTP server (upload_server.py), and it installs corresponding systemd units (hll1222-hotfolder.serviceandhll1222-upload.service)./etc/cups/cupsd.conf, requiring explicit confirmation before destructive operations, resolving required binaries, and providing a--statusdiagnostic mode and CLI flags to customize behavior.README.mdto document the new setup, usage (sudo bash scripts/setup_hl_l1222_android_print.sh), how to connect Android devices over Wi‑Fi usingipp://<raspberrypi-ip>/printers/Brother_HL_L1222_V, and notes about Bluetooth and driver alternatives.Testing
Codex Task
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:
Enhancements:
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
scripts/setup_hl_l1222_android_print.shto install and configurecups,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.hll1222-hotfolder.serviceandhll1222-upload.service.--statusand flags for queue/device/model selection and upload port; updateREADME.mdwith run command and Android IPP URI.Migration
scripts/setup_hl_l1222_android_print.sh.ipp://<raspberrypi-ip>/printers/Brother_HL_L1222_V(Mopria or equivalent).Written for commit f13ab3f. Summary will update on new commits. Review in cubic