17 Commits
1.0.0 ... 2.0.0

Author SHA1 Message Date
René Preuß
621b6cb5c0 Add autowire
Add logs command
Improve print jobs handling
Improve installation/documentation
2025-07-29 22:16:32 +02:00
René Preuß
a152074975 Update raspberry.md 2025-07-28 21:18:46 +02:00
René Preuß
c6f98d9d1e Update orangepi5plus.md 2025-07-21 00:11:59 +02:00
René Preuß
54a8ef2abd Add init commands 2025-07-20 12:52:13 +02:00
René Preuß
06765121cb Update orangepi5plus.md 2025-07-20 12:48:12 +02:00
René Preuß
fd0c32b32e Update packages 2025-07-20 11:49:56 +02:00
René Preuß
a6804c306b Create orangepi5plus.md 2025-07-20 11:46:06 +02:00
René Preuß
402ece3e55 Update raspberry.md 2025-03-14 23:46:02 +01:00
René Preuß
06c5ea9224 Update raspberry.md 2025-03-14 23:32:04 +01:00
René Preuß
d3e1eccfa5 Fix sleep 2024-08-22 10:59:38 +02:00
René Preuß
7c8086fea3 Remove license key 2024-08-18 22:40:12 +02:00
René Preuß
bbc50dc6dd Merge branch 'main' of https://github.com/bitinflow/print-cli 2024-08-18 21:21:00 +02:00
René Preuß
5d0caeda1c Improve error handling 2024-08-18 21:20:11 +02:00
René Preuß
a4451e3556 Update raspberry.md 2024-08-18 20:06:18 +02:00
René Preuß
60ace31331 Add raspberry tutorial 2024-08-18 20:03:22 +02:00
René Preuß
cc20006f3d Update config path to cwd 2024-08-18 18:52:36 +02:00
René Preuß
2433ec846c Update composer 2024-08-18 18:43:16 +02:00
18 changed files with 1773 additions and 1240 deletions

View File

@@ -1,34 +1,3 @@
<p align="center">
<img title="Laravel Zero" height="100" src="https://raw.githubusercontent.com/laravel-zero/docs/master/images/logo/laravel-zero-readme.png" alt="Laravel Zero Logo" />
</p>
# Print CLI
<p align="center">
<a href="https://github.com/laravel-zero/framework/actions"><img src="https://github.com/laravel-zero/laravel-zero/actions/workflows/tests.yml/badge.svg" alt="Build Status" /></a>
<a href="https://packagist.org/packages/laravel-zero/framework"><img src="https://img.shields.io/packagist/dt/laravel-zero/framework.svg" alt="Total Downloads" /></a>
<a href="https://packagist.org/packages/laravel-zero/framework"><img src="https://img.shields.io/packagist/v/laravel-zero/framework.svg?label=stable" alt="Latest Stable Version" /></a>
<a href="https://packagist.org/packages/laravel-zero/framework"><img src="https://img.shields.io/packagist/l/laravel-zero/framework.svg" alt="License" /></a>
</p>
Laravel Zero was created by [Nuno Maduro](https://github.com/nunomaduro) and [Owen Voke](https://github.com/owenvoke), and is a micro-framework that provides an elegant starting point for your console application. It is an **unofficial** and customized version of Laravel optimized for building command-line applications.
- Built on top of the [Laravel](https://laravel.com) components.
- Optional installation of Laravel [Eloquent](https://laravel-zero.com/docs/database/), Laravel [Logging](https://laravel-zero.com/docs/logging/) and many others.
- Supports interactive [menus](https://laravel-zero.com/docs/build-interactive-menus/) and [desktop notifications](https://laravel-zero.com/docs/send-desktop-notifications/) on Linux, Windows & MacOS.
- Ships with a [Scheduler](https://laravel-zero.com/docs/task-scheduling/) and a [Standalone Compiler](https://laravel-zero.com/docs/build-a-standalone-application/).
- Integration with [Collision](https://github.com/nunomaduro/collision) - Beautiful error reporting
------
## Documentation
For full documentation, visit [laravel-zero.com](https://laravel-zero.com/).
## Support the development
**Do you like this project? Support it by donating**
- PayPal: [Donate](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L)
- Patreon: [Donate](https://www.patreon.com/nunomaduro)
## License
Laravel Zero is an open-source software licensed under the MIT license.
Print CLI is the official repository for the print driver for Anikeen Events.

90
app/Commands/Autowire.php Normal file
View File

@@ -0,0 +1,90 @@
<?php
namespace App\Commands;
use App\Support\PrinterManager;
use App\Traits\HasConfig;
use Exception;
use Illuminate\Support\Facades\Http;
use LaravelZero\Framework\Commands\Command;
use Smalot\Cups\Model\Printer;
use Throwable;
class Autowire extends Command
{
use HasConfig;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'autowire {--init : Initialize the configuration when no config file exists}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Automatically wire up the application';
/**
* Execute the console command.
*/
public function handle(PrinterManager $manager): int
{
$this->info('Autowiring the application...');
$printers = $manager->getList()->map(fn(Printer $printer) => [
'name' => $printer->getName(),
'status' => $printer->getStatus(),
'attributes' => $printer->getAttributes(),
'uri' => $printer->getUri(),
]);
try {
if ($this->option('init') && !file_exists($this->getYamlFilename())) {
$response = Http::acceptJson()->post('https://events.anikeen.com/api/printers/autoinit', [
'hostname' => gethostname(),
]);
if ($response->successful()) {
$this->setConfig($response->json());
} else {
$this->warn('Unable to auto initialize configuration! Please use `print-cli init` to create a configuration file.');
}
}
$contig = $this->getConfig(expectedVersion: 2);
$response = Http::acceptJson()->patch(sprintf('%s/api/printers/autowire', $contig->getBaseUrl()), [
'id' => $contig->getId(),
'hostname' => gethostname(),
'printers' => $printers->toArray(),
]);
if ($response->successful()) {
$newPrinters = $response->json('printers', []);
if (empty($newPrinters)) {
$this->info('No new printers found to autowire.');
} else {
foreach ($newPrinters as $printer) {
$this->info(sprintf('Printer: %s', $printer['uri']));
}
}
$this->setConfig([
...$contig->toArray(),
'printers' => $newPrinters,
]);
} else {
throw new Exception('Failed to autowire printers: ' . $response->body());
}
} catch (Throwable $exception) {
$this->error('Failed to autowire printers: ' . $exception->getMessage());
return self::FAILURE;
}
$this->info('Autowiring completed successfully.');
return self::SUCCESS;
}
}

72
app/Commands/Init.php Normal file
View File

@@ -0,0 +1,72 @@
<?php
namespace App\Commands;
use App\Traits\HasConfig;
use Illuminate\Support\Collection;
use LaravelZero\Framework\Commands\Command;
use Smalot\Cups\Builder\Builder;
use Smalot\Cups\Manager\PrinterManager;
use Smalot\Cups\Transport\Client;
use Smalot\Cups\Transport\ResponseParser;
class Init extends Command
{
use HasConfig;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'init';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a print-cli configuration file';
/**
* Execute the console command.
*/
public function handle(): void
{
$username = getenv('USER') ?: get_current_user();
$client = new Client();
$builder = new Builder();
$responseParser = new ResponseParser();
$printerManager = new PrinterManager($builder, $client, $responseParser);
$printers = Collection::make($printerManager->getList());
if ($printers->isEmpty()) {
$this->error('We could not find any printers! Setup them first :)');
return;
}
$this->info('Please register the print-server first at:');
$this->info('https://events.anikeen.com/console/resources/print-servers');
$id = $this->ask('What is your print-server ID?');
$password = $this->ask('What is your password?', match ($username) {
'print-cli' => 'print-cli',
'orangepi' => 'orangepi',
default => null,
});
$this->setConfig([
'version' => 2,
'id' => $id,
'base_url' => 'https://events.anikeen.com',
'default_credentials' => [
'username' => $username,
'password' => $password,
],
]);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Commands;
use LaravelZero\Framework\Commands\Command;
class InitSupervisor extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'init:supervisor';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a supervisor configuration file';
/**
* Execute the console command.
*/
public function handle(): void
{
$username = getenv('USER') ?: get_current_user();
$home = getenv('HOME');
$ini = <<<INI
[program:print-cli]
directory = $home
command = /usr/bin/php $home/.config/composer/vendor/bin/print-cli serve
autostart = true
autorestart = true
redirect_stderr = true
stdout_logfile = /var/log/print-cli.log
stopwaitsecs = 3600
user = $username
INI;
echo $ini;
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Commands;
use App\Support\PrinterManager;
use Illuminate\Console\Scheduling\Schedule;
use LaravelZero\Framework\Commands\Command;
class InitWireguard extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'init:wireguard';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Initialize WireGuard configuration';
/**
* Execute the console command.
*/
public function handle(PrinterManager $printer)
{
//
}
}

64
app/Commands/Logs.php Normal file
View File

@@ -0,0 +1,64 @@
<?php
namespace App\Commands;
use App\Traits\HasConfig;
use LaravelZero\Framework\Commands\Command;
class Logs extends Command
{
use HasConfig;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'logs';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Display the print server logs in real-time';
/**
* Execute the console command.
*/
public function handle(): void
{
$logFile = '/var/log/print-cli.log';
if (!file_exists($logFile)) {
$this->error('Log file not found. Please ensure the print server is running.');
return;
}
$cmd = "tail -f $logFile";
$descriptorspec = [
1 => ['pipe', 'w'], // stdout
2 => ['pipe', 'w'], // stderr
];
$process = proc_open($cmd, $descriptorspec, $pipes);
if (is_resource($process)) {
stream_set_blocking($pipes[1], false);
stream_set_blocking($pipes[2], false);
while (true) {
$out = stream_get_contents($pipes[1]);
$err = stream_get_contents($pipes[2]);
if ($out) $this->info(trim($out));
if ($err) $this->error(trim($err));
usleep(200000); // 200ms delay to avoid CPU overuse
}
// Never reached, but if you need to stop manually:
// fclose($pipes[1]);
// fclose($pipes[2]);
// proc_close($process);
}
}
}

View File

@@ -2,13 +2,10 @@
namespace App\Commands;
use App\Support\PrinterManager;
use LaravelZero\Framework\Commands\Command;
use Smalot\Cups\Builder\Builder;
use Smalot\Cups\Manager\PrinterManager;
use Smalot\Cups\Transport\Client;
use Smalot\Cups\Transport\ResponseParser;
class ListCommand extends Command
class Printers extends Command
{
/**
* The name and signature of the console command.
@@ -26,20 +23,17 @@ class ListCommand extends Command
/**
* Execute the console command.
*
* @return int
*/
public function handle(): int
public function handle(PrinterManager $printers): int
{
$client = new Client();
$builder = new Builder();
$responseParser = new ResponseParser();
$printers = $printers->getList();
$printerManager = new PrinterManager($builder, $client, $responseParser);
if ($printers->isEmpty()) {
$this->error('We could not find any printers! Please register them first in CUPS.');
return 1;
}
$printers = $printerManager->getList();
$this->info('Printers:');
$this->info('Printer:');
foreach ($printers as $printer) {
$this->info($printer->getName());

198
app/Commands/Serve.php Normal file
View File

@@ -0,0 +1,198 @@
<?php
namespace App\Commands;
use App\Support\Config;
use App\Traits\HasConfig;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use LaravelZero\Framework\Commands\Command;
use Smalot\Cups\Builder\Builder;
use Smalot\Cups\Manager\JobManager;
use Smalot\Cups\Manager\PrinterManager;
use Smalot\Cups\Model\Job;
use Smalot\Cups\Transport\Client;
use Smalot\Cups\Transport\ResponseParser;
use Throwable;
class Serve extends Command
{
use HasConfig;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'serve';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
private $counter = 0;
/**
* Execute the console command.
*/
public function handle(): void
{
$this->info('Starting service...');
$this->monitor();
}
/**
* Monitor the service and handle jobs.
*/
private function monitor(): void
{
while (true) {
// autowire every 10 iterations
if ($this->counter % 10 === 0) {
try {
$this->call('autowire', [
'--init' => $this->counter === 0, // Initialize only on the first run
]);
} catch (Throwable $e) {
$this->error('Autowire failed: ' . $e->getMessage());
}
}
try {
$config = $this->getConfig(expectedVersion: 2);
$this->fetchJobs($config);
sleep(2);
} catch (ConnectionException $e) {
$this->error('Connection error: ' . $e->getMessage());
sleep(2);
} catch (RequestException $e) {
$this->error('Request error: ' . $e->getMessage());
sleep(2);
} catch (Throwable $e) {
$this->error('An unexpected error occurred: ' . $e->getMessage());
sleep(2);
}
$this->counter++;
}
}
/**
* @throws RequestException
* @throws ConnectionException
*/
private function fetchJobs(Config $config): void
{
$printerIds = $config->getPrinters()->pluck('id')->toArray();
$response = Http::acceptJson()->get(sprintf('%s/api/printers/jobs', $config->getBaseUrl()), [
'printer_ids' => $printerIds,
]);
if ($response->failed()) {
throw new RequestException($response);
}
$jobs = $response->json();
if (empty($jobs)) {
return;
}
$this->info('Printing jobs...');
foreach ($jobs as $job) {
try {
$this->handleJob($config, $job);
} catch (Throwable $e) {
$this->error($e->getMessage());
}
}
}
/**
* @throws RequestException
* @throws ConnectionException
*/
private function handleJob(Config $config, array $job): void
{
$printer = $config->getPrinters()->firstWhere('id', $job['printer_id']);
[$username, $password] = $this->getConfig()->getPrinterCredentials($printer);
if (!empty($job['data']['preview'])) {
$this->info(sprintf('Job %s is a preview', $job['id']));
$this->markCompleted($config, $job, 0);
return;
} elseif (empty($job['file_url'])) {
$this->info(sprintf('Job %s has no file', $job['id']));
$this->markFailed($config, $job, 'No file provided');
return;
}
$paperWidth = $job['data']['paper']['width'] ?? 75;
$paperHeight = $job['data']['paper']['height'] ?? 75;
$pointWidth = round($paperWidth * 2.83465, 2);
$pointHeight = round($paperHeight * 2.83465, 2);
$client = new Client($username, $password);
$builder = new Builder();
$responseParser = new ResponseParser();
$printerManager = new PrinterManager($builder, $client, $responseParser);
$printer = $printerManager->findByUri($printer['uri']);
$jobManager = new JobManager($builder, $client, $responseParser);
$content = file_get_contents($job['file_url']);
Storage::put($filename = sprintf('pdfs/%s.pdf', Str::random(16)), $content);
$printerJob = new Job();
$printerJob->setName(sprintf('job-%s', $job['id']));
$printerJob->setCopies(1);
$printerJob->setPageRanges('1');
$printerJob->addFile(Storage::path($filename));
$printerJob->addAttribute('media', "Custom.{$pointWidth}x{$pointHeight}");
$printerJob->addAttribute('fit-to-page', true);
if (!$jobManager->send($printer, $printerJob)) {
$this->markFailed($config, $job, 'Failed to print job');
$this->error(sprintf('Failed to print job %s', $job['id']));
return;
}
$this->markCompleted($config, $job, $printerJob->getId());
$this->info(sprintf('Job %s completed as %s', $job['id'], $printerJob->getId()));
}
/**
* @throws RequestException
* @throws ConnectionException
*/
private function markCompleted(Config $config, array $job, int $jobId): void
{
$response = Http::acceptJson()
->patch(sprintf('%s/api/printers/jobs/%s/complete', $config->getBaseUrl(), $job['id']), [
'job_id' => $jobId,
]);
$response->throw();
}
/**
* @throws RequestException
* @throws ConnectionException
*/
private function markFailed(Config $config, array $job, string $reason): void
{
Http::acceptJson()
->patch(sprintf('%s/api/printers/jobs/%s/fail', $config->getBaseUrl(), $job['id']), [
'reason' => $reason,
])
->throw();
}
}

View File

@@ -1,172 +0,0 @@
<?php
namespace App\Commands;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use LaravelZero\Framework\Commands\Command;
use Smalot\Cups\Builder\Builder;
use Smalot\Cups\Manager\JobManager;
use Smalot\Cups\Manager\PrinterManager;
use Smalot\Cups\Model\Job;
use Smalot\Cups\Transport\Client;
use Smalot\Cups\Transport\ResponseParser;
use Symfony\Component\Yaml\Yaml;
use Throwable;
class ServeCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'serve';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
* @throws RequestException
*/
public function handle(): void
{
$this->info('Starting service...');
$yaml = $this->getConfiguration();
$printerIds = array_map(fn($printer) => $printer['id'], $yaml['printers']);
$response = Http::patch(sprintf('%s/api/printers/register', $yaml['base_url']), [
'printers' => $yaml['printers'],
]);
$response->throw();
$this->info('Service started!');
do {
try {
$response = Http::get(sprintf('%s/api/printers/jobs', $yaml['base_url']), [
'printer_ids' => $printerIds,
]);
if ($response->failed()) {
$this->error('Failed to fetch jobs, error: ' . $response->status());
sleep(2);
continue;
}
$jobs = $response->json();
if (empty($jobs)) {
continue;
}
$this->info('Printing jobs...');
foreach ($jobs as $job) {
try {
$this->handleJob($job, $yaml);
} catch (Throwable $e) {
$this->error($e->getMessage());
}
}
sleep(2);
} catch (Throwable $e) {
$this->error($e->getMessage());
sleep(2);
}
} while (true);
}
private function getConfiguration(): array
{
$this->info('Reading configuration...');
$yaml = file_get_contents(base_path('print-cli.yml'));
return Yaml::parse($yaml);
}
/**
* @throws RequestException
*/
private function handleJob(array $job, mixed $yaml): void
{
$printer = Collection::make($yaml['printers'])->firstWhere('id', $job['printer_id']);
if (!empty($job['data']['preview'])) {
$this->info(sprintf('Job %s is a preview', $job['id']));
$this->markCompleted($job, $yaml, 0);
return;
} elseif (empty($job['file_url'])) {
$this->info(sprintf('Job %s has no file', $job['id']));
$this->markFailed($job, $yaml, 'No file provided');
return;
}
$paperWidth = $job['data']['paper']['width'] ?? 75;
$paperHeight = $job['data']['paper']['height'] ?? 75;
$pointWidth = round($paperWidth * 2.83465, 2);
$pointHeight = round($paperHeight * 2.83465, 2);
$client = new Client($printer['username'], $printer['password']);
$builder = new Builder();
$responseParser = new ResponseParser();
$printerManager = new PrinterManager($builder, $client, $responseParser);
$printer = $printerManager->findByUri($printer['address']);
$jobManager = new JobManager($builder, $client, $responseParser);
$content = file_get_contents($job['file_url']);
Storage::put($filename = sprintf('pdfs/%s.pdf', Str::random(16)), $content);
$printerJob = new Job();
$printerJob->setName(sprintf('job-%s', $job['id']));
$printerJob->setCopies(1);
$printerJob->setPageRanges('1');
$printerJob->addFile(Storage::path($filename));
$printerJob->addAttribute('media', "Custom.{$pointWidth}x{$pointHeight}");
$printerJob->addAttribute('fit-to-page', true);
if (!$jobManager->send($printer, $printerJob)) {
$this->markFailed($job, $yaml, 'Failed to print job');
$this->error(sprintf('Failed to print job %s', $job['id']));
return;
}
$this->markCompleted($job, $yaml, $printerJob->getId());
$this->info(sprintf('Job %s completed as %s', $job['id'], $printerJob->getId()));
}
/**
* @throws RequestException
*/
private function markCompleted(array $job, mixed $yaml, int $jobId): void
{
$response = Http::patch(sprintf('%s/api/printers/jobs/%s/complete', $yaml['base_url'], $job['id']), [
'job_id' => $jobId,
]);
$response->throw();
}
/**
* @throws RequestException
*/
private function markFailed(array $job, mixed $yaml, string $reason): void
{
$response = Http::patch(sprintf('%s/api/printers/jobs/%s/fail', $yaml['base_url'], $job['id']), [
'reason' => $reason,
]);
$response->throw();
}
}

View File

@@ -1,8 +0,0 @@
<?php
namespace App;
class Printer
{
}

68
app/Support/Config.php Normal file
View File

@@ -0,0 +1,68 @@
<?php
namespace App\Support;
use Illuminate\Support\Collection;
class Config
{
public function __construct(protected array $config)
{
//
}
public function get(string $key, mixed $default = null): mixed
{
return $this->config[$key] ?? $default;
}
public function set(string $key, mixed $value): void
{
$this->config[$key] = $value;
}
public function all(): array
{
return $this->config;
}
public function has(string $key): bool
{
return array_key_exists($key, $this->config);
}
public function remove(string $key): void
{
unset($this->config[$key]);
}
public function toArray(): array
{
return $this->config;
}
public function getId(): string
{
return $this->get('id');
}
public function getBaseUrl(): string
{
return $this->get('base_url');
}
public function getPrinters(): Collection
{
return Collection::make($this->get('printers', []));
}
public function getPrinterCredentials(array $printer): array
{
$defaultCredentials = $this->get('default_credentials');
return [
$printer['username'] ?? $defaultCredentials['username'] ?? null,
$printer['password'] ?? $defaultCredentials['password'] ?? null,
];
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Support;
use Illuminate\Support\Collection;
use Smalot\Cups\Builder\Builder;
use Smalot\Cups\Manager\PrinterManager as CupsPrinterManager;
use Smalot\Cups\Transport\Client;
use Smalot\Cups\Transport\ResponseParser;
class PrinterManager
{
protected CupsPrinterManager $printerManager;
public function __construct()
{
$client = new Client();
$builder = new Builder();
$responseParser = new ResponseParser();
$this->printerManager = new CupsPrinterManager($builder, $client, $responseParser);
}
public function getList(): Collection
{
return Collection::make($this->printerManager->getList());
}
}

78
app/Traits/HasConfig.php Normal file
View File

@@ -0,0 +1,78 @@
<?php
namespace App\Traits;
use App\Support\Config;
use Illuminate\Console\Command;
use RuntimeException;
use Symfony\Component\Yaml\Yaml;
use Throwable;
/**
* @mixin Command
*/
trait HasConfig
{
protected function getYamlFilename(): string
{
$home = getenv('HOME') ?: getenv('USERPROFILE');
if (!$home && function_exists('posix_getpwuid')) {
$home = posix_getpwuid(posix_getuid())['dir'] ?? null;
}
if (!$home) {
throw new \RuntimeException("Unable to determine user's home directory.");
}
return sprintf('%s/print-cli.yml', $home);
}
protected function getConfig($expectedVersion = null): Config
{
try {
$yaml = file_get_contents($this->getYamlFilename());
} catch (Throwable) {
throw new RuntimeException(sprintf(
'Unable to read configuration file %s',
$this->getYamlFilename(),
));
}
$config = Yaml::parse($yaml);
if ($expectedVersion !== null) {
if (!isset($config['version'])) {
throw new RuntimeException('Configuration file does not have a version.');
}
if ($config['version'] !== $expectedVersion) {
throw new RuntimeException(sprintf(
'Configuration file version %s does not match expected version %s.',
$config['version'],
$expectedVersion
));
}
}
if (!isset($config['base_url'])) {
throw new RuntimeException('Configuration file does not have a base_url.');
}
return new Config($config);
}
protected function setConfig(array $config): void
{
$yaml = Yaml::dump(
input: $config,
inline: 100,
indent: 2,
flags: Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK
);
file_put_contents(
filename: $this->getYamlFilename(),
data: preg_replace('/-\n\s+/', '- ', $yaml)
);
}
}

40
bin/install Normal file
View File

@@ -0,0 +1,40 @@
#!/bin/sh
# Prevent running as root
if [ "$EUID" -eq 0 ]; then
echo "This script must NOT be run as root or with sudo. Please run as a normal user."
exit 1
fi
# Stage 1: Install dependencies
sudo apt-get update
sudo apt-get upgrade -y
sudo add-apt-repository -y ppa:ondrej/php
sudo apt-get install -y git cups zip unzip supervisor \
php-cli php-zip php-curl php-xml php-mbstring \
printer-driver-gutenprint
# Stage 2: Install Composer
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php composer-setup.php
php -r "unlink('composer-setup.php');"
sudo mv composer.phar /usr/local/bin/composer
# Stage 3: Setup Composer global vendor bin directory
COMPOSER_BIN_DIR='export PATH="$PATH:$HOME/.config/composer/vendor/bin"'
grep -qxF "$COMPOSER_BIN_DIR" ~/.bashrc || echo "$COMPOSER_BIN_DIR" >> ~/.bashrc
# Stage 4: Configure CUPS server
sudo cupsctl --remote-admin --remote-any
sudo usermod -aG lpadmin "$USER"
sudo /etc/init.d/cups restart
# Stage 5: Install Print CLI application
composer global require anikeen/print-cli
print-cli init --no-interaction
print-cli init:supervisor | sudo tee /etc/supervisor/conf.d/print-cli.conf > /dev/null
# Stage 6: Start Supervisor service
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl restart print-cli

View File

@@ -20,20 +20,15 @@
"guzzlehttp/guzzle": "^7.8",
"illuminate/http": "^11.5",
"laravel-zero/framework": "^11.0.0",
"smalot/cups-ipp": "dev-master",
"symfony/yaml": "^7.1"
"ghostzero/cups-ipp": "^1.0",
"symfony/yaml": "^7.1",
"ext-posix": "*"
},
"require-dev": {
"laravel/pint": "^1.15.2",
"mockery/mockery": "^1.6.11",
"pestphp/pest": "^2.34.7"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/ghostzero/cups-ipp.git"
}
],
"autoload": {
"psr-4": {
"App\\": "app/",

1927
composer.lock generated

File diff suppressed because it is too large Load Diff

76
docs/orangepi5plus.md Normal file
View File

@@ -0,0 +1,76 @@
# Installation
Installing Print-CLI on a Orange Pi 5 Plus is a straightforward process but requires some time to install the necessary
packages. This guide will help you to install Print-CLI on a Orange Pi 5 Plus.
Estimated time: 30-60 minutes
## Step 1: Install the Orange Pi OS
Before we start, make sure you have created a bootable SD card with the **Orange Pi 1.2.0 Jammy** image. The
simplest way is to use the Balena Etcher which helps you flash the Orange Pi image onto your SD card.
Recommended configuration:
- **OS**: Orange Pi 1.2.0 Jammy with Linux 5.10.160-rockchip-rk3588
- **Username**: orangepi
## Step 2: Install Required Packages
**Tested Printers:**
- EPSON ET-2750 Series with driver: Epson Expression ET-2750 EcoTank - CUPS+Gutenprint v5.3.3 (color)
- EPSON ET-2860 Series with driver: Epson Expression ET-2750 EcoTank - CUPS+Gutenprint v5.3.3 (color)
With the Orange Pi OS installed, we need to install the required packages for Print-CLI to work. Run the following
command and grab a coffee while the packages are being installed (it may take a while):
> **Note:** It may ask you for your password during the installation process.
```bash
curl -sfL https://print-cli.b-cdn.net/install | bash -
```
## Step 3: Configuration
After the installation is complete, you can configure your printer and other settings. Just run the following command:
```bash
print-cli init
```
# Troubleshooting
## WLAN
https://www.makeuseof.com/connect-to-wifi-with-nmcli/
# Printer Offsets
### EPSON ET-2750 Series (with Gutenprint @ OrangePI)
> Some other OS and drivers need other offsets.
```json
{
"badge": {
"offset": {
"y": 0,
"x": 0
}
}
}
```
### EPSON ET-2860 Series (with Gutenprint @ OrangePI)
```json
{
"badge": {
"offset": {
"y": -80,
"x": 0
}
}
}
```

46
docs/raspberry.md Normal file
View File

@@ -0,0 +1,46 @@
# Installation
Installing Print-CLI on a Raspberry Pi is a quite simple process but requires some time installing the required
packages. This guide will help you to install Print-CLI on a Raspberry Pi.
Estimated time: 30-60 minutes
## Step 1: Install the Raspberry Pi OS
Before we start, make sure you have created a bootable SD card with the **Ubuntu Server 22.04 LTS 64-bit** image. The
simplest way is to use the Raspberry Pi Imager which enables you to select an Ubuntu image when flashing your SD card.
Recommended configuration:
- **OS**: Ubuntu Server 22.04 LTS 64-bit
- **Username**: print-cli
## Step 2: Install Required Packages
**Tested Printers:**
- EPSON ET-2750 Series with driver: Epson Expression ET-2750 EcoTank - CUPS+Gutenprint v5.3.3 (color)
- EPSON ET-2860 Series with driver: Epson Expression ET-2750 EcoTank - CUPS+Gutenprint v5.3.3 (color)
With the Raspberry Pi OS installed, we need to install the required packages for Print-CLI to work. Run the following
command and grab a coffee while the packages are being installed (it may take a while):
> **Note:** It may ask you for your password during the installation process.
```bash
curl -sfL https://print-cli.b-cdn.net/install | bash -
```
## Step 3: Configuration
After the installation is complete, you can configure your printer and other settings. Just run the following command:
```bash
print-cli init
```
# Troubleshooting
## WLAN
https://www.makeuseof.com/connect-to-wifi-with-nmcli/