PrintToVeraeTimeStamp/docs/network-printer.md
Cursor Agent 5ed87da68f
Some checks failed
installers / netprint (push) Has been cancelled
installers / macos-pkg (push) Has been cancelled
installers / publish (push) Has been cancelled
Keep the macOS receipts SMB share across reboots.
Save the installer choice as shareReceiptsSMB, recreate the share when it is missing, and show the live smb:// address on the receipts page.

Co-authored-by: George Lambert <marchon@gmail.com>
2026-10-05 16:34:51 +00:00

108 lines
20 KiB
Markdown

# Verae network timestamp printer
Optional IPP Everywhere printer. Phones and other computers on the LAN print to it. The default name is **Verae Timestamp** plus this computer's name, for example **Verae Timestamp (Georges-MacBook-Pro)**. Settings can change it. A saved name that is still the old shared **Verae Timestamp** is upgraded on the next start. If that name is already registered on the network, mDNS renames it to **Name (2)** and the new name is saved. This machine hashes the document, registers the SHA-256 with Verae the same way the desktop drivers do, and keeps the receipt. Document bytes are not uploaded.
The printer is off until you install the standalone package or turn on the driver installer option. Both paths install one service and one tray / menu-bar control. They do not share an install folder: the standalone package never overwrites the driver binary. If both are installed, the service runs whichever binary is the same version or newer, and removing one leaves the other in place.
| | Windows | macOS | Debian / Ubuntu |
|---|---|---|---|
| Standalone installer | `VeraeNetPrint-windows-*-setup.exe` | `VeraeNetPrint-macos-arm64-0.3.4.pkg` | `verae-netprint_*_amd64.deb` |
| Driver installer option | Unchecked checkbox on the options page | Customize → “Network timestamp printer” (unselected) | debconf “Enable the Verae network timestamp printer?” (default no), or `apt install verae-netprint` |
| Service | `VeraeNetPrint` | LaunchDaemon `com.verae.netprint` | `verae-netprint.service` |
| Tray / menu bar | `verae-netprint-tray.exe` (notification area) | LaunchAgent `com.verae.netprint.tray` (`/Applications/Verae NetPrint.app`, Aqua session) | `verae-print tray` (StatusNotifier / AppIndicator) |
| Standalone command | `%ProgramFiles%\Verae\NetPrint\bin\verae-print.exe` | `/Library/Application Support/Verae/NetPrint/bin/verae-print` | `/usr/lib/verae-netprint/verae-print` |
| Driver command | `%ProgramFiles%\Verae\PrintStamp\bin\verae-print.exe` | `/Library/Application Support/Verae/PrintStamp/bin/verae-print` | `/usr/lib/verae-print/verae-print` |
| CLI on `PATH` | the driver binary when the driver is installed | `/usr/local/bin/verae-print` points at the driver when it is installed, otherwise at NetPrint | `/usr/bin/verae-print` points at the driver when it is installed, otherwise at NetPrint |
| Config | `%ProgramData%\Verae\PrintStamp\netprint.json` | `/Library/Application Support/Verae/PrintStamp/netprint.json` | `/etc/verae/netprint.json` |
| Receipts | `%ProgramData%\Verae\NetPrint` | `/Users/Shared/Verae Receipts` | `/var/lib/verae-netprint` |
The desktop driver's loopback helper stays on `127.0.0.1:8631`. The network printer does not use that port.
## Try it
1. Install using either path above. The Windows setup starts the tray for the logged-in user at the end of install (not elevated), and again at the next login. macOS and Linux start the menu bar at the next login.
2. Open **Settings** from the icon and paste the same `xVerae-` signing key the drivers use (or sign in first with `verae-print setup` on that machine). The service stores the key; the tray does not edit config files.
3. On another device, add the printer. Its default name includes the computer, for example **Verae Timestamp (Georges-MacBook-Pro)**. It is advertised with DNS-SD (`_ipp._tcp`, plus the AirPrint `_universal` and IPP Everywhere `_print` subtypes) and AirPrint text records. On macOS, **Advertise on the network** (on by default) calls `DNSServiceRegister` with regtype `_ipp._tcp,_universal,_print` and `kDNSServiceFlagsNoAutoRename`. mDNSResponder publishes the subtype PTRs aimed at `<name>._ipp._tcp.local`, which is what `dns-sd -B _ipp._tcp,_universal` and `dns-sd -B _ipp._tcp,_print` accept. A second registration advertises `_verae._tcp`. The host argument is empty so mDNSResponder uses this Mac's hostname and addresses. The UDP 5353 socket stays closed while that registration is up, so the printer is not listed twice. If the name is already in use, the callback reports the conflict and both services are registered again as **Name (2)**. That name is saved and becomes the IPP printer name. If `DNSServiceRegister` fails for another reason, the socket advertises the full set instead. On Windows 10 and later `DnsServiceRegister` publishes only `<name>._ipp._tcp.local`, including its SRV, TXT, and address records. It cannot publish a subtype PTR that points at that name, so the service also opens a shared UDP 5353 socket (`SO_REUSEADDR`, joined to `224.0.0.251` and `ff02::fb`) that answers only `_universal._sub._ipp._tcp` and `_print._sub._ipp._tcp`. Those PTRs point at `<name>._ipp._tcp.local`. The UDP socket does not answer the base `_ipp._tcp` PTR and does not send SRV, TXT, or address records, so the two stacks do not advertise two copies of the printer. If `DnsServiceRegister` fails, the UDP socket advertises the full set instead. `dns-sd -B _ipp._tcp` and `dns-sd -B _ipp._tcp,_universal` each list the printer once. On macOS, cupsd keeps `127.0.0.1:631` and `[::1]:631`. This printer binds 631 only on the Mac's non-loopback addresses, so printing from another device uses port 631 and localhost CUPS is unchanged. mDNS advertises 631. If none of those LAN binds succeed, it listens on 8632 on every interface and advertises 8632. Status JSON `ippBind` is `lan` or `all`, and `port` is the port that was bound. On Windows and Linux the listener is IPv4 plus `[::]` with `IPV6_V6ONLY`, trying 631 and falling back to 8632 when 631 is taken. It does not use 8631. A connect to the host's link-local IPv6 address completes; our own A/AAAA records omit fe80 and 169.254. Get-Printer-Attributes includes `media-col-default` for US Letter and A4 and `document-format-supported` lists the same formats as the TXT `pdl` record: PDF, Apple Raster, PWG Raster, and JPEG.
4. Print a PDF. Open **Receipts** from the tray, or browse to `http://<lan-address>:<port>/receipts` (the AirPrint admin URL). The page lists recent jobs, newest first, with the stamped PDF and the original. Each job also has a verify link on `https://explorer.verae.ai/time`.
The icon shows running or stopped, the printer name, a LAN address and port, how many jobs arrived today, whether discovery is advertising, and the receipts URL. It can start, stop, and restart the service, turn launch-at-login on or off for the service and for the icon, turn **Email stamped PDFs** on or off, open recent receipts, and open settings (printer name, forward-to printer, signing key, mail, and the receipts PIN). On Windows it can also allow Public networks. When the active network is Public and that option is off, the menu says other devices cannot reach the printer.
## Email
Settings and the tray can mail each completed job. The message subject is `Verae timestamp: <job title>`. The body contains the receipt ID, the SHA-256, and the `.verae.json` receipt summary. The certificate PDF and the original document are attached. The default recipient is `marchon@gmail.com`, and the default server is `smtp.gmail.com` port 587 with STARTTLS. A Gmail app password works: the username is the Gmail address and the password is the 16-character app password. Mail stays off until the toggle is on and a password is saved. A failure is written to the log and to `emailError` in the status JSON, then retried in the background. It does not fail the print.
The SMTP password and the optional receipts PIN are not stored in `netprint.json`. On Windows they are sealed with DPAPI for the service account. On macOS and Linux they are in `netprint-secrets.bin` next to the config, mode `0600`. Neither value is written to the log.
## Receipts on the phone
`http://<host>:<port>/receipts` is served on the same port as IPP, so the existing Private and Domain firewall rules cover it. Public networks stay blocked unless that option is on. An optional PIN (or `?access=` token) can be required on the LAN. The loopback control page does not ask for the PIN. The menu bar **Open Receipts…** item opens the live `receiptsUrl` from status (for example `http://192.168.1.108:631/receipts` or `http://<hostname>.local:631/receipts`), not a fixed address. **Open Settings…** opens the control page. The menu also shows how to print from an iPhone: Share → Print → Select Printer → the advertised printer name. The status JSON fields are `receiptsUrl` and `receiptsDir`. On macOS `receiptsDir` is `/Users/Shared/Verae Receipts`. That folder holds the job files and is readable by the console user. The macOS installer Customize pane has **Share receipts folder over the network (SMB)**, off unless you check it. When checked, postinstall shares that folder as **Verae Receipts** (`sharing -a` with `-s 001 -g 000`: SMB only, guest off) and enables `com.apple.smbd` so File Sharing stays on across reboots. The choice is saved as `shareReceiptsSMB` in `netprint.json`. Settings and the menu bar can turn it on or off later. If the choice is on and the share is missing, the daemon recreates it at startup, and the menu-bar app asks it to again at login. Status JSON includes `shareReceiptsSMB` and `smbUrl` (for example `smb://Georges-MacBook-Pro.local/Verae%20Receipts`). The receipts page shows that address. The first connection from iPhone Files needs a username and password from this Mac. Guest access is off. The Windows installers have the same checkbox, off unless you check it or pass `/SMB=1`, and create a read-only share named `Verae Receipts`. The program stays in `/Library/Application Support/Verae/NetPrint/bin` and is not in that folder. An older install that stored jobs next to the program moves those job files on the next start. The page links to the iPhone shortcut and shows a QR code for `http://<host>:<port>/ios-shortcut`.
## Stamp and return
`POST http://<host>:<port>/api/stamp` accepts a PDF or JPEG, either as the raw body (set `X-Filename` or `Content-Disposition` for the title) or as multipart with a `file` field. It runs the same pipeline as Print-Job, including the same `stampWithClient` call the desktop driver uses (`job.Run` against the Verae client, with the document title as the file name). The job is stored, it shows on the receipts page, and it is emailed when email is on. The response is the stamped certificate PDF. Headers `X-Verae-Receipt-Id`, `X-Verae-SHA256`, and `X-Verae-Receipt-JSON` identify the receipt. `?format=zip` returns `original.pdf`, `certificate.pdf`, and `receipt.json`. A receipt id beginning with `mock-cert` is refused, so a test double is not returned outside test builds. The same Private and Domain rule and the same PIN apply. Send the PIN as `?access=`, the `X-Verae-Access` header, or `Authorization: Bearer`. A failure does not return a PDF.
The server also advertises `_verae._tcp` (PTR, SRV, and TXT `path=/api/stamp`) beside the IPP printer. That record is a different service name, so it does not add a second `_ipp._tcp` advertisement. While Windows DNS-SD owns the printer, the shared UDP socket still answers only the AirPrint subtype PTRs for `_ipp._tcp` and does not add IPP SRV, TXT, or address records. The `_verae._tcp` SRV and TXT go out on that socket because DNS-SD does not register that name. Address records stay with the printer advertisement.
The Share sheet steps are in `docs/ios-shortcut/README.md`. A signed `.shortcut` file is not in this tree: signing needs the Shortcuts app on a Mac or iPhone. The design for a native share extension in the Verae Timestamping iOS app (`com.flightlookup.verae-timestamping`) is `docs/ios-shortcut/share-extension.md`.
Forward-to is optional: a local queue name, or an `ipp://` URI. A failed forward does not fail the timestamp.
## Firewall
The Windows installers add inbound rules named **Verae NetPrint IPP** (TCP 631), **Verae NetPrint IPP Fallback** (TCP 8632), and **Verae NetPrint mDNS** (UDP 5353) on the private and domain profiles. Each rule sets `remoteip=any`, which includes IPv4, global IPv6, and link-local `fe80::/10`. A checkbox, off by default, also allows Public networks. A silent install can pass `/PUBLIC=1`. The tray has the same switch. The rules are removed when the last NetPrint product is uninstalled. Port 8631 is not opened; that is the driver helper on loopback.
An optional checkbox (silent `/QUEUE=1`) adds a local Windows printer named **Verae Timestamp** aimed at `http://127.0.0.1:<bound-port>/ipp/print`. It is removed when the last NetPrint product is uninstalled. The driver printer **Verae TimeStamp PDF** is a different queue.
On Debian and Ubuntu, `postinst` opens the same three ports with `ufw` when ufw is installed and already active. The rules are removed when the last package is removed.
On macOS, the Application Firewall is per application (System Settings → Network → Firewall). Install runs `/usr/libexec/ApplicationFirewall/socketfilterfw --add` and `--unblockapp` on the service binary when that tool is present. If other devices still cannot connect, allow `verae-print` in that pane. The first time the service advertises or accepts a LAN connection, macOS asks once for Local Network permission for `verae-print`. Allow it. The menu-bar app may ask as well; allow that too.
## macOS package
The Apple Silicon installer is `VeraeNetPrint-macos-arm64-0.3.4.pkg`. GitHub Actions builds it on a `macos-14` runner with `pkgbuild` and `productbuild`, then runs `pkgutil --expand`, `pkgutil --check-signature`, and `installer -pkginfo`. It installs:
- `/Library/Application Support/Verae/NetPrint/bin/verae-print` (the arm64 daemon binary)
- `/Applications/Verae NetPrint.app` (the menu-bar app: `Contents/Info.plist` with bundle id `ai.verae.netprint`, `LSUIElement`, and `NSLocalNetworkUsageDescription`; `Contents/MacOS/verae-print`, `Contents/MacOS/verae-menubar`, and `Contents/MacOS/verae-airdrop`)
- LaunchDaemon `com.verae.netprint` (root, the print server, the binary under Application Support)
- LaunchAgent `com.verae.netprint.tray` in `/Library/LaunchAgents`, limited to the Aqua session, running `/Applications/Verae NetPrint.app/Contents/MacOS/verae-print tray` as the logged-in user
Receipts and job files go to `/Users/Shared/Verae Receipts`, not next to those binaries. The package from CI is not signed and not notarized. macOS blocks a normal double-click until the package is signed with a Developer ID Installer certificate and notarized. To install this unsigned package, right-click it in Finder and choose Open, then Open again. Or, in Terminal:
```bash
sudo installer -pkg VeraeNetPrint-macos-arm64-0.3.4.pkg -target /
```
`postinstall` checks that `verae-print`, `verae-airdrop`, and `verae-menubar` are in the app, then runs `verae-print netprint install --owner standalone`, which bootstraps the daemon into `system` and the menu-bar agent into the console user's `gui` domain. The agent is not the root daemon. AirDrop runs there.
`verae-print tray` loads AppKit and stays in `NSApplication run` on the main thread. If that run returns without Quit, or the status item cannot be created, it logs the reason to `~/Library/Logs/Verae/netprint-tray.log` and execs `verae-menubar` in the same app (a small AppKit status item with status, Start, Stop, Open Receipts…, Open Settings…, Share receipts folder over SMB, and AirDrop again). A failure exits 1. It does not exit 0 when the menu bar cannot stay up. `verae-print tray --smoke` prints `smoke ok` or `no window server` and does not enter the run loop. Customize on the installer shows **Share receipts folder over the network (SMB)**, unselected unless you check it. That choice is saved as `shareReceiptsSMB`. The first connection from iPhone Files needs a username and password from this Mac.
## AirDrop on the Mac
Settings and the menu bar have **AirDrop stamped PDF after each job**. It is off until you turn it on. When a job completes, the daemon records it and tells the menu-bar app on `GET http://127.0.0.1:8630/api/v1/events` (an event stream on the loopback control API). The agent downloads the certificate and the receipt JSON through that API and opens the AirDrop share sheet with both files, using `NSSharingService(named: .sendViaAirDrop)`. A notification shows the receipt ID and an **AirDrop again** action. The same action is on the menu. The root daemon never opens the sheet.
`platform/macos/airdrop/main.swift` is that share sheet as `verae-airdrop`. The macOS package build compiles it with `swiftc` and installs it inside `Verae NetPrint.app`, next to the tray executable. The menu-bar app launches that helper. macOS asks for notification permission the first time the agent posts **AirDrop again**. `UNUserNotificationCenter` is used only when the process is that app bundle (`bundleIdentifier` set and the bundle path ends in `.app`). A bare binary skips the call instead of crashing, because `currentNotificationCenter` throws when `mainBundle` is a directory.
The control API listens only on `127.0.0.1:8630`. The receipts page is on the IPP port, not on the control port, and it stays on Private and Domain networks with the rest of the printer.
A silent Windows install (`/S`) does not wait on a dialog. Upgrades stop `VeraeNetPrint`, wait until it is stopped, and close `verae-netprint-tray.exe` before replacing those files. If a file is still locked, setup logs the error to `%ProgramData%\Verae\logs\netprint-installer.log` and exits with a failure instead of leaving the old binary in place. The service is started again after the new files are in place. `verae-print netprint install` starts the tray once, through Explorer (via a one-shot scheduled task when an interactive user is available). A second copy exits immediately on the `Local\VeraeNetPrintTray` mutex. The tray is not a Task Scheduler job and not a child of setup, so `Start-Process -Wait` returns and the icon stays up. Tray startup, mutex, and message-loop errors are appended to `netprint.log`. An unknown `netprint` subcommand does not start a second server. The service appends startup, mDNS, and job lines to `%ProgramData%\Verae\logs\netprint.log` and flushes each line, including when the process has no console. Job numbers continue from `%ProgramData%\Verae\NetPrint\next-job` and from existing `job-N` folders, so a restart does not write a new document into an old folder. `http://127.0.0.1:8630/api/v1/status` includes `mdnsAdvertising`, `mdnsMode`, `mdnsDetail`, `mdnsError`, `receiptsUrl`, `receiptsDir`, `ippBind`, `emailEnabled`, `emailTo`, `emailError`, `smtpHost`, `smtpUser`, `smtpPasswordSet`, `receiptsPinSet`, `shareReceiptsSMB`, and `smbUrl`. It does not include the SMTP password or the receipts PIN.
macOS upgrades run `launchctl bootout` on `com.verae.netprint` before the package copies the binary. Debian upgrades run `systemctl stop verae-netprint` from `preinst` when the unit is running that package's binary.
## Troubleshooting
- The icon says stopped. Start it from the menu. On Linux, `systemctl status verae-netprint`. On Windows, `sc query VeraeNetPrint`. On macOS, `sudo launchctl print system/com.verae.netprint`.
- Other devices do not see it. Open `http://127.0.0.1:8630/api/v1/status` and read `mdnsMode` / `mdnsError`, and `%ProgramData%\Verae\logs\netprint.log` on Windows. `mdnsMode` `dns-sd` means Windows DNS-SD accepted the printer. Confirm the host has a LAN address and the network is Private, or turn on Allow Public networks. On a Mac, CUPS keeps localhost port 631. This printer uses 631 on the LAN addresses (`ippBind` `lan`) or 8632 when that bind does not succeed (`ippBind` `all`, `port` 8632). It does not fall back to 8631. Receipt files are in `receiptsDir`.
- Windows setup seemed to hang with `/S`. Every installer dialog has a silent default. Read `%ProgramData%\Verae\logs\netprint-installer.log`.
- Jobs fail with an authentication error. Open Settings and save a current `xVerae-` key. The service reads the machine token (`/etc/verae/xverae-token`, the macOS Application Support file, or `%ProgramData%\Verae\PrintStamp\xverae-token`) and the usual per-user token.
- Raster jobs (AirPrint `image/urf`, Linux `image/pwg-raster`) and `image/jpeg` are converted to PDF on this machine before they are hashed. PDF is accepted as-is.
Commands:
```bash
verae-print netprint # foreground, for debugging
verae-print netprint install --owner driver --allow-public=false
verae-print netprint install --owner standalone --allow-public=true --local-queue
verae-print netprint launch-tray # Windows: tray as the logged-in user
verae-print netprint uninstall --owner driver
verae-print tray
```