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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
workflow_dispatch:
concurrency:
group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}"
cancel-in-progress: true
cancel-in-progress: ${{ github.event_name == 'pull_request' }}


permissions: {}
Expand Down
11 changes: 7 additions & 4 deletions .github/workflows/update-fsdk-sources.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ permissions:
jobs:
update:
runs-on: ubuntu-24.04
timeout-minutes: 180
timeout-minutes: 360
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All @@ -30,14 +30,17 @@ jobs:
- name: Check for updates
id: changes
run: |
if git diff --quiet -- VERSION elements; then
if git diff --quiet -- VERSION README.md elements; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Verify CUPS patch compatibility
if: steps.changes.outputs.changed == 'true'
run: just verify-cups-patch-chain
- name: Fetch updated sources with retries
if: steps.changes.outputs.changed == 'true'
run: just fetch
- name: Verify updated appliance
if: steps.changes.outputs.changed == 'true'
run: just verify
Expand All @@ -57,9 +60,9 @@ jobs:
set -euo pipefail
branch=auto/update-fsdk-sources
git config user.name 'mergeraptor[bot]'
git config user.email '${{ secrets.MERGERAPTOR_APP_ID }}+mergeraptor[bot]@users.noreply.github.com'
git config user.email '267480593+mergeraptor[bot]@users.noreply.github.com'
git switch --create "$branch"
git add VERSION elements
git add VERSION README.md elements
git commit -m 'chore(deps): update FSDK appliance sources'
basic="$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 -w0)"
echo "::add-mask::$basic"
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
*.snap
*.rock
*.spdx.json
.DS_Store
.scratch/
.worktrees/
Expand Down
215 changes: 55 additions & 160 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Your contributions are welcome. Please post [issues and pull
requests](https://github.com/OpenPrinting/ghostscript-printer-app).


### Contained Printer Drivers (in the Snap)
### Contained Printer Drivers

- **Ghostscript built-in**:
```
Expand Down Expand Up @@ -59,8 +59,8 @@ requests](https://github.com/OpenPrinting/ghostscript-printer-app).

- **`pnm2ppa`**: Driver for some older HP printers with proprietary
protocol, probably the only HP printers **NOT supported by
HPLIP**. even not with HP's proprietary plugin. The configuration
file in the Snap is user-editable, see below.
HPLIP**. even not with HP's proprietary plugin. Its configuration file is
user-editable in persistent application state, see below.

- **`pxljr`**: For HP Color LaserJet 3500/3550/3600, should give
better output quality than HPLIP.
Expand All @@ -71,8 +71,8 @@ requests](https://github.com/OpenPrinting/ghostscript-printer-app).
facility for HP is not included in this Printer Application, use the
[HPLIP Printer Application](https://snapcraft.io/hplip-printer-app)
(download proprietary plugin in-app, via web interface) for these
printers, the firmware is proprietary anyway. In the Snap the user
can add color profiles, see below.
printers, the firmware is proprietary anyway. Users can add color profiles
in persistent application state, see below.

- **`SpliX`**: Driver for laser printers with proprietary languages,
from Dell, Lexmark, Samsung, Toshiba, Xerox
Expand All @@ -93,8 +93,8 @@ requests](https://github.com/OpenPrinting/ghostscript-printer-app).
- **`ptouch`**: Driver for Brother's P-Touch label printers

- **`c2050`, `cjet`, `min12xxw`, `m2300w`**: Drivers for older
Lexmark, Canon, and Minolta printers. For `m2300w` in the Snap the
user can add color profiles, see below.
Lexmark, Canon, and Minolta printers. Users can add `m2300w` color profiles
in persistent application state, see below.

- **`CUPS`, `cups-filters`**: Included drivers for PCL, dot-matrix
(Oki, Epson), label printers (Dymo, Intellitech, Zebra), and some
Expand Down Expand Up @@ -340,177 +340,72 @@ If you mess up any configuration file under
`/var/snap/ghostscript-printer-app/common/`, simply delete it (or move
it away) and restart the Snap to get it restored.

## THE ROCK (OCI CONTAINER IMAGE)
## OCI CONTAINER IMAGE

### Install from Docker Hub
#### Prerequisites
The OCI appliance is published only to the GitHub Container Registry under
immutable application-version tags. The version is recorded in [`VERSION`](VERSION);
there is no `latest`, `edge`, or `stable` OCI tag.

1. **Docker Installed**: Ensure Docker is installed on your system. You can download it from the [official Docker website](https://www.docker.com/get-started).
```sh
sudo snap install docker
```

#### Step-by-Step Guide

You can pull the `ghostscript-printer-app` Docker image from either the GitHub Container Registry or Docker Hub.

**From GitHub Container Registry** <br>
To pull the image from the GitHub Container Registry, run the following command:
```sh
sudo docker pull ghcr.io/openprinting/ghostscript-printer-app:latest
```

Create a Docker volume:
```sh
sudo docker volume create ghostscript-printer-app
```

To run the container after pulling the image from the GitHub Container Registry, use:
```sh
sudo docker run -d \
--name ghostscript-printer-app \
--network host \
-e PORT=<port> \
-v ghostscript-printer-app:/var/lib/ghostscript-printer-app \
-v /dev/bus/usb:/dev/bus/usb:ro \
--device-cgroup-rule='c 189:* rmw' \
ghcr.io/openprinting/ghostscript-printer-app:latest
```

**From Docker Hub** <br>
Alternatively, you can pull the image from Docker Hub, by running:
```sh
sudo docker pull openprinting/ghostscript-printer-app
```

Create a Docker volume:
```sh
sudo docker volume create ghostscript-printer-app
```

To run the container after pulling the image from Docker Hub, use:
```sh
sudo docker run -d \
--name ghostscript-printer-app \
--network host \
-e PORT=<port> \
-v ghostscript-printer-app:/var/lib/ghostscript-printer-app \
-v /dev/bus/usb:/dev/bus/usb:ro \
--device-cgroup-rule='c 189:* rmw' \
openprinting/ghostscript-printer-app:latest
```
### Run the published image

- `PORT` is an optional environment variable used to start the printer-app on a specified port. If not provided, it will start on the default port 8000 or, if port 8000 is busy, on 8001 and so on.
- **The container must be started in `--network host` mode** to allow the Printer-Application instance inside the container to access and discover printers available in the local network where the host system is in.
- Alternatively using the internal network of the Docker instance (`-p <port>:8000` instead of `--network host -e PORT=<port>`) only gives access to local printers running on the host system itself.
- `-v ghostscript-printer-app:/var/lib/ghostscript-printer-app` maps a volume for persistent storage.
- The following volume and device settings are crucial for USB printer access:
- `-v /dev/bus/usb:/dev/bus/usb:ro` mounts the host's USB device directory read-only inside the container for USB printer access.
- `--device-cgroup-rule='c 189:* rmw'` allows the container to read, write, and mknod to USB devices.
Install Podman with a working rootless user namespace, then select an explicit release:

### Setting Up and Running ghostscript-printer-app locally

#### Prerequisites

**Docker Installed**: Ensure Docker is installed on your system. You can download it from the [official Docker website](https://www.docker.com/get-started) or from the Snap Store:
```sh
sudo snap install docker
```

**Rockcraft**: Rockcraft should be installed. You can install Rockcraft using the following command:
```sh
sudo snap install rockcraft --classic
```

**Skopeo**: Skopeo should be installed to compile `*.rock` files into Docker images. It comes bundled with Rockcraft, so no separate installation is required.

#### Step-by-Step Guide

**Build ghostscript-printer-app rock**

The first step is to build the Rock from the `rockcraft.yaml`. This image will contain all the configurations and dependencies required to run ghostscript-printer-app.

Open your terminal and navigate to the directory containing your `rockcraft.yaml`, then run the following command:
version=10.07.1-1
image="ghcr.io/projectbluefin/ghostscript-printer-app:${version}"
podman pull "$image"
podman volume create ghostscript-printer-app
podman run -d \
--name ghostscript-printer-app \
--network host \
-e PORT=8000 \
-v ghostscript-printer-app:/var/lib/ghostscript-printer-app:Z,U \
"$image"
```

Open `http://localhost:8000/` and use the web interface to add a printer.
`PORT` is optional; without it, the application selects port 8000 or the next
available port. Host networking is required for local-network printer discovery.
The named volume preserves Printer Application state and user-edited driver
configuration across upgrades.

For USB printers, add these options to `podman run`:

```sh
rockcraft pack -v
--device /dev/bus/usb --group-add keep-groups
```

**Compile to Docker Image**
The host user must already have permission to access the printer device. Do not
work around host permissions by running the appliance as root.

Once the rock is built, you need to compile docker image from it.
### Build and verify locally

```sh
sudo rockcraft.skopeo --insecure-policy copy oci-archive:<rock_image> docker-daemon:ghostscript-printer-app:latest
```
The OCI image is built with BuildStream inside the pinned builder container.
Install Podman and `just`, then run:

Create a Docker volume:
```sh
sudo docker volume create ghostscript-printer-app
just build
just verify
```

**Run the ghostscript-printer-app Docker Container**

```sh
sudo docker run -d \
--name ghostscript-printer-app \
--network host \
-e PORT=<port> \
-v ghostscript-printer-app:/var/lib/ghostscript-printer-app \
-v /dev/bus/usb:/dev/bus/usb:ro \
--device-cgroup-rule='c 189:* rmw' \
ghostscript-printer-app:latest
```
- `PORT` is an optional environment variable used to start the printer-app on a specified port. If not provided, it will start on the default port 8000 or, if port 8000 is busy, on 8001 and so on.
- **The container must be started in `--network host` mode** to allow the Printer-Application instance inside the container to access and discover printers available in the local network where the host system is in.
- Alternatively using the internal network of the Docker instance (`-p <port>:8000` instead of `--network host -e PORT=<port>`) only gives access to local printers running on the host system itself.
- `-v ghostscript-printer-app:/var/lib/ghostscript-printer-app` maps a volume for persistent storage.
- The following volume and device settings are crucial for USB printer access:
- `-v /dev/bus/usb:/dev/bus/usb:ro` mounts the host's USB device directory read-only inside the container for USB printer access.
- `--device-cgroup-rule='c 189:* rmw'` allows the container to read, write, and mknod to USB devices.
`just build` exports and tags the local image as
`ghcr.io/projectbluefin/ghostscript-printer-app:build`. `just verify` is the
authoritative appliance gate: it checks the BuildStream graph and patch chain,
starts the real image, exercises every driver slice, verifies lifecycle and
persistence behavior, audits the advertised payload and complete ELF closure,
and enforces the uncompressed size ceiling.

#### Setting up
For real hardware, follow the separate
[USB and network printer validation procedure](docs/oci-physical-validation.md).
Synthetic CI results are not physical-printer evidence.

Enter the web interface
### Releases

```sh
http://localhost:<port>/
```

Use the web interface to add a printer. Supply a name, select the
discovered printer, then select make and model. Also set the installed
accessories, loaded media and the option defaults. If the printer is a
PostScript printer, accessory configuration and option defaults can
also often get polled from the printer.

<!-- Begin Included Components -->
## Included Components
- pappl v1.4.10
- qpdf v11.10.1
- ghostscript ghostpdl-10.06.0rc1_test001
- cups v2.4.16
- libcupsfilters 2.1.1
- libppd 2.1.1
- cups-filters 2.0.1
- pyppd release-1-1-0
- foomatic-db 20240504
- hplip debian/3.22.10+dfsg0-8
- c2050 debian/0.3-7
- cjet debian/0.8.9-11
- min12xxw debian/0.0.9-11
- pnm2ppa debian/1.13-14
- c2esp debian/27-11
- dymo-cups-drivers debian/1.4.0-12
- foo2zjs debian/20200505dfsg0-4
- fxlinuxprint debian/1.1.0+ds-4
- m2300w debian/0.51-15
- printer-driver-oki 1.0.2
- pxljr debian/1.4+repack0-6
- rastertosag-gdi debian/0.1-8
- splix debian/2.0.1-1
- brlaser v6
- ptouch-driver debian/1.7-1
<!-- End Included Components -->
Maintainers publish by pushing a Git tag exactly matching `v$(cat VERSION)`.
The tag workflow builds and verifies native amd64 and arm64 images, publishes
the matching immutable GHCR multi-architecture index, and verifies its SPDX
SBOM, keyless signatures, GitHub provenance, and OCI metadata. It never
publishes a mutable channel alias.

## BUILDING WITHOUT PACKAGING OR INSTALLATION

Expand Down
4 changes: 2 additions & 2 deletions docs/oci-physical-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ Run:
just verify
```

The x86_64 image has a 500 MiB (524,288,000-byte) uncompressed ceiling. The implementation baseline measured 449,525,807 bytes on 2026-09-16. `tests/appliance-parity.sh` measures the local image through Podman and fails above that ceiling. The gate also checks OCI metadata, payload inventory, absence of development content, interpreter policy, and every ELF dependency.
The x86_64 image has a 500 MiB (524,288,000-byte) uncompressed ceiling. The implementation baseline measured 444,975,225 bytes on 2026-09-16. `tests/appliance-parity.sh` measures the local image through Podman and fails above that ceiling. The gate also checks OCI metadata, payload inventory, absence of development content, interpreter policy, and every ELF dependency.

The OCI driver contract is the complete list under [Contained Printer Drivers](../README.md#contained-printer-drivers-in-the-snap), not a sample. Despite the historical heading, the FSDK image must retain every listed Ghostscript/Foomatic driver and every named external driver family. The parity gate requires each legacy Ghostscript name as either a compiled Ghostscript device or an exact Foomatic PPD entry, then separately checks all external driver, filter, backend, and PPD-provider families.
The OCI driver contract is the complete list under [Contained Printer Drivers](../README.md#contained-printer-drivers), not a sample. The FSDK image must retain every listed Ghostscript/Foomatic driver and every named external driver family. The parity gate requires each legacy Ghostscript name as either a compiled Ghostscript device or an exact Foomatic PPD entry, then separately checks all external driver, filter, backend, and PPD-provider families.

## USB printer

Expand Down
3 changes: 3 additions & 0 deletions docs/skills/ci-tooling.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ metadata:
8. Keep the Snap update/build lanes independent from FSDK OCI publication.
9. Give every external BuildStream source a project alias. Prefer an authoritative, checksummed release archive over a personal Git mirror when upstream Git is unreliable.
10. Give pull-request CI a PR-scoped concurrency group with `cancel-in-progress: true`; stacked force-pushes must not leave duplicate multi-hour architecture jobs consuming the runner pool.
11. Attribute App-authored commits with the bot account's numeric GitHub user ID in its noreply address. The GitHub App ID is a different identifier and does not link commits to the bot account.

## Common Rationalizations

Expand All @@ -53,6 +54,7 @@ metadata:
- An SBOM generated for only the runner's architecture.
- An unaliased external source URL or a source pinned only to a personal fork.
- Pull-request CI without cancellation of superseded runs.
- A bot noreply email built from the GitHub App ID instead of the bot account user ID.

## Verification

Expand All @@ -61,6 +63,7 @@ metadata:
- [ ] Pull-request CI completes on native amd64 and arm64 runners without registry credentials.
- [ ] BuildStream resolves and fetches every repository-owned source without `[unaliased-url]` warnings.
- [ ] Pushing a replacement commit cancels the superseded run for the same pull request.
- [ ] App-authored commits use the verified bot account ID in `<user-id>+<app-slug>[bot]@users.noreply.github.com`.
- [ ] A mismatched tag fails in the metadata job before any write-capable job.
- [ ] The published index contains exactly amd64 and arm64 and has the required annotations.
- [ ] `cosign verify` succeeds for the index and SBOM artifact.
Expand Down
6 changes: 3 additions & 3 deletions docs/skills/fsdk-cups-patching.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ metadata:

## Core Process

1. Treat `rockcraft.yaml`, `snap/snapcraft.yaml`, and root `patches/` as the existing behavior contract.
1. Treat `snap/snapcraft.yaml`, the FSDK elements, and root `patches/` as the current behavior contract; consult Git history only when auditing the retired OCI implementation.
2. Keep one CUPS artifact owner. The FSDK junction must continue to own CUPS so its reverse dependencies build against the same libraries.
3. Keep CUPS-only source patches under `patches/cups/`. The `patch_queue` plugin applies every file in its directory, so unrelated patches must stay elsewhere.
4. Stage `patches/cups/` into the FSDK junction with a `local` source. Apply `patches/freedesktop-sdk/` at the junction project level; that project patch injects the nested CUPS source `patch_queue` and adjusts FSDK's CUPS configuration.
5. Do not use `config.overrides` for small CUPS patches or feature switches. BuildStream documents overrides as complete downstream ownership that stops inheriting upstream element updates.
6. Do not stage a second CUPS implementation. Duplicate `libcups.so*` ownership creates an artifact overlap and can compile reverse dependencies against a different library than the application receives.
7. When moving a shared patch, update both Snap and Rock references while Rock remains. Apply patches from the source root when their paths start with `a/backend/` and use `-p1`.
7. Shared CUPS patches remain under `patches/cups/` for both Snap and FSDK. Apply patches from the source root when their paths start with `a/backend/` and use `-p1`.
8. Cross-junction source checkouts nest under `<junction>/<element-path>/`; the CUPS probe therefore checks `freedesktop-sdk/components-_private-cups-base/`, not the checkout root.
9. Match FSDK's multiarch install layout for every repository-built library. Define `gcc-triplet`, `lib`, and `libdir` in the root project and pass `--libdir=%{libdir}` to Autotools; FSDK's `pkg-config` searches `/usr/lib/<gcc-triplet>/pkgconfig`, not `/usr/lib/pkgconfig`.
10. Do not `chown` high numeric runtime IDs inside the BuildStream sandbox; user-namespace mappings can reject them with `EINVAL`. After composition, reapply writable directory modes in the final OCI layer. Remove inherited `/run` service directories and let the numeric runtime user recreate them so ownership checks observe the actual user.
Expand Down Expand Up @@ -74,7 +74,7 @@ metadata:
- [ ] The graph contains exactly one FSDK private CUPS base.
- [ ] The staged CUPS source contains the DNS-SD and `USB_QUIRK_DIR` changes.
- [ ] The CUPS base still exposes `cups-libs` and `cups-license`.
- [ ] Both current Snap and Rock CUPS source versions accept the canonical patches while both packaging paths exist.
- [ ] The Snap and FSDK CUPS source versions both accept the canonical patches.
- [ ] Repository-built libraries install their `.pc` files in FSDK's multiarch pkg-config directory and are discoverable from a dependent element's build sandbox.
- [ ] The exported image runs with the numeric UID/GID, creates runtime directories, and reaches application readiness.
- [ ] TERM yields signal exit status `143`, not Podman's SIGKILL timeout status `137`; killing a required child makes the container exit nonzero.
Loading
Loading