Initial commit

This commit is contained in:
2024-12-30 11:42:12 -07:00
commit 09ba4114c1
86 changed files with 7522 additions and 0 deletions

View File

@@ -0,0 +1,198 @@
#
# Configuration file for the CUPS scheduler. See "man cupsd.conf" for a
# complete description of this file.
#
# Log general information in error_log - change "warn" to "debug"
# for troubleshooting...
LogLevel warn
PageLogFormat
# Specifies the maximum size of the log files before they are rotated. The value "0" disables log rotation.
MaxLogSize 0
# Default error policy for printers
ErrorPolicy retry-job
# Allow remote access
Listen *:631 # important
ServerAlias * # important
# Show shared printers on the local network.
Browsing Yes
BrowseLocalProtocols dnssd
# Default authentication type, when authentication is required...
DefaultAuthType Basic
DefaultEncryption IfRequested
# Web interface setting...
WebInterface Yes
# Timeout after cupsd exits if idle (applied only if cupsd runs on-demand - with -l)
IdleExitTimeout 60
# Restrict access to the server...
<Location />
Order allow,deny
Allow all
</Location>
# Restrict access to the admin pages...
<Location /admin>
Order allow,deny
Allow all
</Location>
# Restrict access to configuration files...
<Location /admin/conf>
AuthType Default
Require user @SYSTEM
Order allow,deny
Allow all
</Location>
# Restrict access to log files...
<Location /admin/log>
AuthType Default
Require user @SYSTEM
Order allow,deny
Allow all
</Location>
# Set the default printer/job policies...
<Policy default>
# Job/subscription privacy...
JobPrivateAccess default
JobPrivateValues default
SubscriptionPrivateAccess default
SubscriptionPrivateValues default
# Job-related operations must be done by the owner or an administrator...
<Limit Create-Job Print-Job Print-URI Validate-Job>
Order deny,allow
# Allow all # mine...
</Limit>
<Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job Cancel-My-Jobs Close-Job CUPS-Move-Job CUPS-Get-Document>
Require user @OWNER @SYSTEM
Order deny,allow
</Limit>
# All administration operations require an administrator to authenticate...
<Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default CUPS-Get-Devices>
AuthType Default
Require user @SYSTEM
Order deny,allow
</Limit>
# All printer operations require a printer operator to authenticate...
<Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Jobs CUPS-Accept-Jobs CUPS-Reject-Jobs>
AuthType Default
Require user @SYSTEM
Order deny,allow
</Limit>
# Only the owner or an administrator can cancel or authenticate a job...
<Limit Cancel-Job CUPS-Authenticate-Job>
Require user @OWNER @SYSTEM
Order deny,allow
</Limit>
<Limit All>
Order deny,allow
# Allow all # mine
</Limit>
</Policy>
# Set the authenticated printer/job policies...
<Policy authenticated>
# Job/subscription privacy...
JobPrivateAccess default
JobPrivateValues default
SubscriptionPrivateAccess default
SubscriptionPrivateValues default
# Job-related operations must be done by the owner or an administrator...
<Limit Create-Job Print-Job Print-URI Validate-Job>
AuthType Default
Order deny,allow
</Limit>
<Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job Cancel-My-Jobs Close-Job CUPS-Move-Job CUPS-Get-Document>
AuthType Default
Require user @OWNER @SYSTEM
Order deny,allow
</Limit>
# All administration operations require an administrator to authenticate...
<Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default>
AuthType Default
Require user @SYSTEM
Order deny,allow
</Limit>
# All printer operations require a printer operator to authenticate...
<Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Jobs CUPS-Accept-Jobs CUPS-Reject-Jobs>
AuthType Default
Require user @SYSTEM
Order deny,allow
</Limit>
# Only the owner or an administrator can cancel or authenticate a job...
<Limit Cancel-Job CUPS-Authenticate-Job>
AuthType Default
Require user @OWNER @SYSTEM
Order deny,allow
</Limit>
<Limit All>
Order deny,allow
</Limit>
</Policy>
# Set the kerberized printer/job policies...
<Policy kerberos>
# Job/subscription privacy...
JobPrivateAccess default
JobPrivateValues default
SubscriptionPrivateAccess default
SubscriptionPrivateValues default
# Job-related operations must be done by the owner or an administrator...
<Limit Create-Job Print-Job Print-URI Validate-Job>
AuthType Negotiate
Order deny,allow
</Limit>
<Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job Cancel-My-Jobs Close-Job CUPS-Move-Job CUPS-Get-Document>
AuthType Negotiate
Require user @OWNER @SYSTEM
Order deny,allow
</Limit>
# All administration operations require an administrator to authenticate...
<Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default>
AuthType Default
Require user @SYSTEM
Order deny,allow
</Limit>
# All printer operations require a printer operator to authenticate...
<Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After Cancel-Jobs CUPS-Accept-Jobs CUPS-Reject-Jobs>
AuthType Default
Require user @SYSTEM
Order deny,allow
</Limit>
# Only the owner or an administrator can cancel or authenticate a job...
<Limit Cancel-Job CUPS-Authenticate-Job>
AuthType Negotiate
Require user @OWNER @SYSTEM
Order deny,allow
</Limit>
<Limit All>
Order deny,allow
</Limit>
</Policy>

View File

@@ -0,0 +1,19 @@
version: "3.8"
services:
cups:
image: olbat/cupsd:stable-2024-01-19 # admin user/password: print/print
container_name: cups
privileged: true
volumes:
- "/dev/bus/usb:/dev/bus/usb" # keep this under volumes, not devices
- "/run/dbus:/run/dbus"
- "./cupsd.conf:/etc/cups/cupsd.conf:ro"
#- "./data/printers.conf:/etc/cups/printers.conf:ro"
ports:
- "631:631/tcp" # CUPS
restart: "always"
cups-webpage:
buid: server
ports:
- 6311:6311

View File

@@ -0,0 +1,24 @@
# Printer configuration file for CUPS v2.4.2
# Written by cupsd
# DO NOT EDIT THIS FILE WHEN CUPSD IS RUNNING
NextPrinterId 2
<Printer Brother_HL-L2300D_series>
PrinterId 1
UUID urn:uuid:8ac038d7-8659-3de9-57d0-0a7f97b956cc
Info Brother HL-L2300D series
Location
MakeModel Brother HL-L2300D series, using brlaser v6
DeviceURI usb://Brother/HL-L2300D%20series?serial=U63878J0N375067
State Idle
StateTime 1714021523
ConfigTime 1714021523
Type 4180
Accepting Yes
Shared Yes
JobSheets none none
QuotaPeriod 0
PageLimit 0
KLimit 0
OpPolicy default
ErrorPolicy retry-job
</Printer>

View File

@@ -0,0 +1,13 @@
## what I am running on office server
```bash
sudo apt install python3-pip cups python3-cups hplip
pip install pycups fastapi "uvicorn[standard]" python-multipart
sudo hp-setup -i # manually configure printer...
python -m uvicorn print_api:app --reload --host 0.0.0.0
```
url: http://100.103.75.97:8000/

View File

@@ -0,0 +1,11 @@
FROM python:3
RUN apt-get update \
&& apt-get install -y libcups2-dev python3-pip cups python3-cups gcc \
&& pip install pycups fastapi "uvicorn[standard]" python-multipart
WORKDIR /app
COPY ./src .
CMD python -m uvicorn print_api:app --reload --host 0.0.0.0 --port 6311

27
home-server/printing/server/flake.lock generated Normal file
View File

@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1725634671,
"narHash": "sha256-v3rIhsJBOMLR8e/RNWxr828tB+WywYIoajrZKFM+0Gg=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "574d1eac1c200690e27b8eb4e24887f8df7ac27c",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -0,0 +1,38 @@
{
description = "Printer Server Flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
};
outputs = { self, nixpkgs, ... }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
myPython = pkgs.python3.withPackages (python-pkgs: with pkgs; [
python312Packages.fastapi
python312Packages.fastapi-cli
python312Packages.pycups
python312Packages.python-multipart
python312Packages.uvicorn
]);
in
{
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [
python312Packages.fastapi
python312Packages.fastapi-cli
python312Packages.pycups
python312Packages.python-multipart
python312Packages.uvicorn
];
};
packages.${system} = rec {
fastapi-server = pkgs.writeShellScriptBin "start-server" ''
${myPython}/bin/fastapi run ${self}/src/print_api.py
'';
default = fastapi-server;
};
};
}

View File

@@ -0,0 +1,149 @@
<!DOCTYPE html>
<html>
<head>
<title>Document Upload</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
height: 100vh;
background: #09373e;
color: #85bfc8;
}
#form-container {
display: flex;
justify-content: center;
align-items: center;
}
form {
border: 1px solid #ccc;
padding: 20px;
background: #05252a;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h2 {
margin-bottom: 20px;
text-align: center;
}
input[type="file"] {
display: none; /* Hide the file input */
}
input[type="submit"] {
padding: 10px 20px;
border: none;
background: #007bff;
color: #fff;
font-size: 16px;
cursor: pointer;
}
input[type="submit"]:hover {
background: #0056b3;
}
#dropzone {
border: 2px dashed #ccc;
padding: 20px;
width: 300px;
text-align: center;
color: #ccc;
cursor: pointer;
}
#dropzone.dragover {
border-color: #000;
color: #000;
}
</style>
</head>
<body>
<h2>Upload Document</h2>
<br />
<section id="form-container">
<form
id="printForm"
action="/print/"
method="post"
enctype="multipart/form-data"
>
<div id="dropzone">Drop file to upload or click to select</div>
<input type="file" id="fileInput" />
<br />
<input type="submit" value="Upload Document" name="submit" />
</form>
</section>
<script>
var stagedFile = undefined;
const formElement = document.getElementById("printForm");
const fileInputElement = document.getElementById("fileInput");
formElement.addEventListener("submit", async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("file", stagedFile);
const response = await fetch("/print/", {
method: "POST",
body: formData,
});
const data = await response.json();
console.log(data);
});
document
.getElementById("dropzone")
.addEventListener("dragover", function (event) {
event.preventDefault(); // Prevent default behavior (Prevent file from being opened)
event.stopPropagation();
event.target.classList.add("dragover"); // Optional: add a style change
});
document
.getElementById("dropzone")
.addEventListener("dragleave", function (event) {
event.preventDefault();
event.stopPropagation();
event.target.classList.remove("dragover"); // Optional: revert style change
});
document
.getElementById("dropzone")
.addEventListener("drop", function (event) {
event.preventDefault();
event.stopPropagation();
event.target.classList.remove("dragover"); // Optional: revert style change
// Process files
var files = event.dataTransfer.files;
handleFiles(files);
});
// Handle file selection when clicked
document
.getElementById("dropzone")
.addEventListener("click", function () {
fileInputElement.click(); // Trigger the hidden file input's click
});
fileInputElement.addEventListener("change", function (event) {
const files = event.target.files;
handleFiles(files);
});
const handleFiles = (files) => {
stagedFile = files[0];
renderStagedFile();
};
const renderStagedFile = () => {
const element = document.getElementById("dropzone");
if (!stagedFile) {
element.textContent = "Drop file to upload or click to select";
} else {
element.textContent = `FILE: ${stagedFile.name}`;
}
};
</script>
</body>
</html>

View File

@@ -0,0 +1,69 @@
import os
from pprint import pprint
import tempfile
from fastapi import FastAPI, File, UploadFile, Request
import cups
from fastapi.responses import HTMLResponse
app = FastAPI()
# @app.post("/print/")
# async def print_document(file: UploadFile = File(...)):
# temp_file = tempfile.NamedTemporaryFile(delete=False)
# temp_file.write(await file.read())
# temp_file.close()
# conn = cups.Connection(host="server.alexmickelson.guru")
# printers = conn.getPrinters()
# print(file.filename)
# print(temp_file.name)
# pprint(printers)
# for printer in printers:
# print(printer, printers[printer]["device-uri"])
# default_printer = list(printers.keys())[0]
# job_id = conn.printFile(default_printer, temp_file.name, f"FastAPI Print Job for {temp_file.name}", {})
# os.unlink(temp_file.name)
# return {"job_id": job_id, "file_name": file.filename}
@app.post("/print/")
async def print_document(file: UploadFile = File(...)):
# Save the uploaded file to a temporary file
temp_file = tempfile.NamedTemporaryFile(delete=False)
temp_file.write(await file.read())
temp_file.close()
# Connect to the CUPS server on the host (use default CUPS connection)
conn = cups.Connection() # This will connect to localhost CUPS
# Get the list of available printers
printers = conn.getPrinters()
print(file.filename)
print(temp_file.name)
pprint(printers)
for printer in printers:
print(printer, printers[printer]["device-uri"])
# Use the default printer (first one in the list)
default_printer = list(printers.keys())[0]
# Print the file
job_id = conn.printFile(default_printer, temp_file.name, f"FastAPI Print Job for {temp_file.name}", {})
# Clean up the temporary file
os.unlink(temp_file.name)
return {"job_id": job_id, "file_name": file.filename}
@app.get("/", response_class=HTMLResponse)
async def read_root(request: Request):
with open('src/index.html', 'r') as f:
html_content = f.read()
return HTMLResponse(content=html_content, status_code=200)