Keep the network printer off the driver helper port and install folder.

Use 8632 when 631 is taken, install standalone Windows and macOS builds beside the driver instead of over it, open the LAN firewall ports, and give silent NSIS dialogs a default plus an install log.

Co-authored-by: George Lambert <marchon@gmail.com>
This commit is contained in:
Cursor Agent 2026-10-04 19:51:03 +00:00
commit 2f7f7aa873
No known key found for this signature in database
17 changed files with 748 additions and 126 deletions

View file

@ -26,7 +26,7 @@ verae-print setup
verae-print stamp document.pdf verae-print stamp document.pdf
``` ```
**Network printer (optional, separate from the driver):** an IPP Everywhere queue named **Verae Timestamp** for other devices on the LAN. Install the standalone package, or leave the driver installer’s option off (Windows checkbox, macOS Customize, Debian debconf). Both use one service and one tray / menu-bar control. See [docs/network-printer.md](docs/network-printer.md). **Network printer (optional, separate from the driver):** an IPP Everywhere queue named **Verae Timestamp** for other devices on the LAN. Install the standalone package, or leave the driver installer’s option off (Windows checkbox, macOS Customize, Debian debconf). Both use one service and one tray / menu-bar control. The standalone Windows folder is `C:\Program Files\Verae\NetPrint` (the driver stays in `PrintStamp`). The LAN fallback port is **8632**, so it does not collide with the driver helper on `127.0.0.1:8631`. See [docs/network-printer.md](docs/network-printer.md).
| OS | Standalone | With the driver (off unless you opt in) | | OS | Standalone | With the driver (off unless you opt in) |
|----|------------|------------------------------------------| |----|------------|------------------------------------------|

View file

@ -2,7 +2,7 @@
Optional IPP Everywhere printer. Phones and other computers on the LAN print to **Verae Timestamp**. 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. Optional IPP Everywhere printer. Phones and other computers on the LAN print to **Verae Timestamp**. 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 binary command, one config file, one service, and one tray / menu-bar control. 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 | | | Windows | macOS | Debian / Ubuntu |
|---|---|---|---| |---|---|---|---|
@ -10,17 +10,19 @@ The printer is off until you install the standalone package or turn on the drive
| 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` | | 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` | | Service | `VeraeNetPrint` | LaunchDaemon `com.verae.netprint` | `verae-netprint.service` |
| Tray / menu bar | `verae-netprint-tray.exe` (notification area) | `verae-print tray` (menu bar) | `verae-print tray` (StatusNotifier / AppIndicator) | | Tray / menu bar | `verae-netprint-tray.exe` (notification area) | `verae-print tray` (menu bar) | `verae-print tray` (StatusNotifier / AppIndicator) |
| Command | `%ProgramFiles%\Verae\PrintStamp\bin\verae-print.exe` | `/Library/Application Support/Verae/PrintStamp/bin/verae-print` | `/usr/bin/verae-print` | | 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` | | Config | `%ProgramData%\Verae\PrintStamp\netprint.json` | `/Library/Application Support/Verae/PrintStamp/netprint.json` | `/etc/verae/netprint.json` |
| Receipts | `%ProgramData%\Verae\NetPrint` | `/Library/Application Support/Verae/NetPrint` | `/var/lib/verae-netprint` | | Receipts | `%ProgramData%\Verae\NetPrint` | `/Library/Application Support/Verae/NetPrint` | `/var/lib/verae-netprint` |
Installing the standalone package when the driver option is already installed (or the other way around) keeps the same service name and config. Removing one product leaves the service in place while the other is still installed. The desktop driver's loopback helper stays on `127.0.0.1:8631`. The network printer does not use that port.
## Try it ## Try it
1. Install using either path above. The tray or menu-bar icon starts at login only after the print server is installed. 1. Install using either path above. The tray or menu-bar icon starts at login only after the print server is installed.
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. 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 **Verae Timestamp**. It is advertised with DNS-SD (`_ipp._tcp`, plus `_universal` and `_print`) and AirPrint text records. The IPP port is 631, or 8631 when 631 is already taken by CUPS. 3. On another device, add the printer **Verae Timestamp**. It is advertised with DNS-SD (`_ipp._tcp`, plus `_universal` and `_print`) and AirPrint text records. The IPP port is 631, or 8632 when 631 is already taken by CUPS. mDNS and the tray show the port that was actually bound.
4. Print a PDF. Open **Receipts** from the tray, or browse to the printer’s admin URL (`http://<lan-address>:<port>/`). Each job has a receipt PDF and a verify link on `https://explorer.verae.ai/time`. 4. Print a PDF. Open **Receipts** from the tray, or browse to the printer’s admin URL (`http://<lan-address>:<port>/`). Each job has a receipt PDF and a verify link on `https://explorer.verae.ai/time`.
The icon shows running or stopped, the printer name, a LAN address and port, and how many jobs arrived today. It can start, stop, and restart the service, turn launch-at-login on or off for the service and for the icon, open recent receipts, and open settings (printer name, forward-to printer, signing key). The icon shows running or stopped, the printer name, a LAN address and port, and how many jobs arrived today. It can start, stop, and restart the service, turn launch-at-login on or off for the service and for the icon, open recent receipts, and open settings (printer name, forward-to printer, signing key).
@ -29,12 +31,21 @@ Forward-to is optional: a local queue name, or an `ipp://` URI. A failed forward
## Firewall ## Firewall
Allow TCP **631** (and **8631** if CUPS already owns 631) from your LAN. Allow UDP **5353** so other devices can discover the printer. The control API listens only on `127.0.0.1:8630`. 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. They are removed when the last NetPrint product is uninstalled. Port 8631 is not opened; that is the driver helper on loopback.
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 control API listens only on `127.0.0.1:8630`.
A silent Windows install (`/S`) does not wait on a dialog. If the service does not start, the message is written to `%ProgramData%\Verae\logs\netprint-installer.log`.
## Troubleshooting ## 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`. - 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. Confirm the service is running, mDNS is not blocked, and the host has a LAN address. CUPS on the same machine is fine: this printer falls back to port 8631 and still advertises that port. - Other devices do not see it. Confirm the service is running, mDNS (UDP 5353) is not blocked, and the host has a LAN address. CUPS on the same machine is fine: this printer falls back to port 8632 and still advertises that port. It does not fall back to 8631.
- 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. - 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`) are converted to PDF on this machine before they are hashed. PDF is accepted as-is. - Raster jobs (AirPrint `image/urf`, Linux `image/pwg-raster`) are converted to PDF on this machine before they are hashed. PDF is accepted as-is.

View file

@ -12,6 +12,17 @@ import (
"strings" "strings"
) )
const (
// DefaultFallbackPort is the LAN IPP port when 631 is already taken.
// It is not 8631: that port belongs to the desktop driver's loopback helper.
DefaultFallbackPort = 8632
// DriverHelperPort is reserved for the desktop driver's loopback IPP helper
// (127.0.0.1:8631). The network printer never binds it.
DriverHelperPort = 8631
// DefaultControlPort is the loopback control API.
DefaultControlPort = 8630
)
// Config is the network printer. The Verae signing key is not stored here; // Config is the network printer. The Verae signing key is not stored here;
// the process loads it the same way the drivers do (internal/auth). // the process loads it the same way the drivers do (internal/auth).
type Config struct { type Config struct {
@ -20,6 +31,7 @@ type Config struct {
// Port is the IPP/HTTP port to try first. 631 is the IPP default. // Port is the IPP/HTTP port to try first. 631 is the IPP default.
Port int `json:"port,omitempty"` Port int `json:"port,omitempty"`
// FallbackPort is used when Port is already taken (CUPS on macOS and Debian). // FallbackPort is used when Port is already taken (CUPS on macOS and Debian).
// The default is 8632. 8631 is reserved for the driver helper and is never bound.
FallbackPort int `json:"fallbackPort,omitempty"` FallbackPort int `json:"fallbackPort,omitempty"`
// Bind is the listen address without a port. Empty listens on all interfaces. // Bind is the listen address without a port. Empty listens on all interfaces.
Bind string `json:"bind,omitempty"` Bind string `json:"bind,omitempty"`
@ -50,8 +62,8 @@ func Defaults() Config {
return Config{ return Config{
Name: "Verae Timestamp", Name: "Verae Timestamp",
Port: 631, Port: 631,
FallbackPort: 8631, FallbackPort: DefaultFallbackPort,
ControlPort: 8630, ControlPort: DefaultControlPort,
DataDir: defaultDataDir(), DataDir: defaultDataDir(),
KeepPDF: &keep, KeepPDF: &keep,
Advertise: &adv, Advertise: &adv,
@ -191,7 +203,7 @@ func (c *Config) normalize() error {
return fmt.Errorf("port must be 1-65535") return fmt.Errorf("port must be 1-65535")
} }
if c.ControlPort <= 0 { if c.ControlPort <= 0 {
c.ControlPort = 8630 c.ControlPort = DefaultControlPort
} }
if c.ControlPort > 65535 { if c.ControlPort > 65535 {
return fmt.Errorf("controlPort must be 1-65535") return fmt.Errorf("controlPort must be 1-65535")
@ -220,8 +232,8 @@ func ExampleJSON(dataDir string) []byte {
c := Config{ c := Config{
Name: "Verae Timestamp", Name: "Verae Timestamp",
Port: 631, Port: 631,
FallbackPort: 8631, FallbackPort: DefaultFallbackPort,
ControlPort: 8630, ControlPort: DefaultControlPort,
DataDir: dataDir, DataDir: dataDir,
KeepPDF: &keep, KeepPDF: &keep,
ForwardPrinter: "", ForwardPrinter: "",

View file

@ -190,12 +190,30 @@ func (s *Server) Shutdown(ctx context.Context) error {
return err return err
} }
// ippListenPorts is the IPP port list for cfg. Port 8631 is the desktop
// driver's loopback helper and is never used, even when a saved config asks for it.
func ippListenPorts(cfg Config) []int {
primary := cfg.Port
fallback := cfg.FallbackPort
if primary == DriverHelperPort {
primary = DefaultFallbackPort
}
if fallback == DriverHelperPort {
fallback = DefaultFallbackPort
}
if primary <= 0 {
primary = 631
}
out := []int{primary}
if fallback > 0 && fallback != primary {
out = append(out, fallback)
}
return out
}
func listenIPP(cfg Config) (net.Listener, int, error) { func listenIPP(cfg Config) (net.Listener, int, error) {
bind := cfg.Bind bind := cfg.Bind
try := []int{cfg.Port} try := ippListenPorts(cfg)
if cfg.FallbackPort > 0 && cfg.FallbackPort != cfg.Port {
try = append(try, cfg.FallbackPort)
}
var last error var last error
for _, port := range try { for _, port := range try {
ln, err := net.Listen("tcp", net.JoinHostPort(bind, fmt.Sprint(port))) ln, err := net.Listen("tcp", net.JoinHostPort(bind, fmt.Sprint(port)))

View file

@ -4,6 +4,7 @@ package netprint
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"os/user" "os/user"
@ -110,7 +111,10 @@ func launchctl(args ...string) error {
func mustExe() string { func mustExe() string {
p, err := Executable() p, err := Executable()
if err != nil { if err != nil {
return "/Library/Application Support/Verae/PrintStamp/bin/verae-print" if _, statErr := os.Stat("/Library/Application Support/Verae/PrintStamp/bin/verae-print"); statErr == nil {
return "/Library/Application Support/Verae/PrintStamp/bin/verae-print"
}
return "/Library/Application Support/Verae/NetPrint/bin/verae-print"
} }
return p return p
} }
@ -149,24 +153,22 @@ func Install(owner, tokenFile string) error {
return err return err
} }
} }
chosen := selectServiceBinary(exe, currentServiceExe())
if err := os.MkdirAll("/Library/LaunchDaemons", 0o755); err != nil { if err := os.MkdirAll("/Library/LaunchDaemons", 0o755); err != nil {
return err return err
} }
if _, err := os.Stat(daemonPlist); os.IsNotExist(err) { if err := os.WriteFile(daemonPlist, []byte(plistDaemon(chosen, cfgPath)), 0o644); err != nil {
if err := os.WriteFile(daemonPlist, []byte(plistDaemon(exe, cfgPath)), 0o644); err != nil { return err
return err
}
} }
if err := os.MkdirAll("/Library/LaunchAgents", 0o755); err != nil { if err := os.MkdirAll("/Library/LaunchAgents", 0o755); err != nil {
return err return err
} }
if err := os.WriteFile(agentPlist, []byte(plistAgent(exe)), 0o644); err != nil { if err := os.WriteFile(agentPlist, []byte(plistAgent(chosen)), 0o644); err != nil {
return err return err
} }
_ = os.MkdirAll("/usr/local/bin", 0o755) linkCLI(exe)
_ = os.Remove("/usr/local/bin/verae-print")
_ = os.Symlink(exe, "/usr/local/bin/verae-print")
copyToken(tokenFile) copyToken(tokenFile)
allowAppFirewall(chosen)
_ = exec.Command("launchctl", "bootout", "system/"+launchdLabel).Run() _ = exec.Command("launchctl", "bootout", "system/"+launchdLabel).Run()
if err := launchctl("bootstrap", "system", daemonPlist); err != nil { if err := launchctl("bootstrap", "system", daemonPlist); err != nil {
return err return err
@ -183,6 +185,13 @@ func Uninstall(owner string) error {
return err return err
} }
if !o.empty() { if !o.empty() {
if other := peerBinary(owner); other != "" {
cfgPath := SystemConfigPath()
_ = os.WriteFile(daemonPlist, []byte(plistDaemon(other, cfgPath)), 0o644)
_ = os.WriteFile(agentPlist, []byte(plistAgent(other)), 0o644)
_ = exec.Command("launchctl", "bootout", "system/"+launchdLabel).Run()
_ = launchctl("bootstrap", "system", daemonPlist)
}
return nil return nil
} }
_ = exec.Command("launchctl", "bootout", "system/"+launchdLabel).Run() _ = exec.Command("launchctl", "bootout", "system/"+launchdLabel).Run()
@ -194,5 +203,60 @@ func Uninstall(owner string) error {
return nil return nil
} }
func currentServiceExe() string {
b, err := os.ReadFile(daemonPlist)
if err != nil {
return ""
}
return parseDarwinProgram(string(b))
}
func peerBinary(owner string) string {
p := "/Library/Application Support/Verae/NetPrint/bin/verae-print"
if owner == "standalone" {
p = "/Library/Application Support/Verae/PrintStamp/bin/verae-print"
}
if _, err := os.Stat(p); err == nil {
return p
}
return ""
}
// linkCLI keeps /usr/local/bin/verae-print on the driver binary when that
// install is present. A real file at the link path is left alone.
func linkCLI(exe string) {
const link = "/usr/local/bin/verae-print"
const driver = "/Library/Application Support/Verae/PrintStamp/bin/verae-print"
target := exe
if _, err := os.Stat(driver); err == nil {
target = driver
}
_ = os.MkdirAll("/usr/local/bin", 0o755)
st, err := os.Lstat(link)
if err == nil && st.Mode()&os.ModeSymlink == 0 {
return
}
if err == nil {
_ = os.Remove(link)
}
_ = os.Symlink(target, link)
}
// allowAppFirewall asks the macOS application firewall to accept the service
// binary. System Settings → Network → Firewall still applies if this tool
// is unavailable or the user has blocked the app.
func allowAppFirewall(exe string) {
const fw = "/usr/libexec/ApplicationFirewall/socketfilterfw"
if _, err := os.Stat(fw); err != nil {
return
}
if out, err := exec.Command(fw, "--add", exe).CombinedOutput(); err != nil {
log.Printf("netprint: socketfilterfw --add: %v (%s)", err, strings.TrimSpace(string(out)))
}
if out, err := exec.Command(fw, "--unblockapp", exe).CombinedOutput(); err != nil {
log.Printf("netprint: socketfilterfw --unblockapp: %v (%s)", err, strings.TrimSpace(string(out)))
}
}
// silence unused in case strconv is needed by future uid parsing // silence unused in case strconv is needed by future uid parsing
var _ = strconv.Itoa var _ = strconv.Itoa

View file

@ -4,6 +4,7 @@ package netprint
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -180,13 +181,12 @@ func Install(owner, tokenFile string) error {
_ = os.Chown("/etc/verae", uid, uid) _ = os.Chown("/etc/verae", uid, uid)
_ = os.Chown(cfgPath, uid, uid) _ = os.Chown(cfgPath, uid, uid)
} }
if _, err := os.Stat(unitPath); err != nil { chosen := selectServiceBinary(exe, currentServiceExe())
if err := os.MkdirAll(filepath.Dir(unitPath), 0o755); err != nil { if err := os.MkdirAll(filepath.Dir(unitPath), 0o755); err != nil {
return err return err
} }
if err := os.WriteFile(unitPath, []byte(unitText(exe, cfgPath)), 0o644); err != nil { if err := os.WriteFile(unitPath, []byte(unitText(chosen, cfgPath)), 0o644); err != nil {
return err return err
}
} }
if err := os.MkdirAll(filepath.Dir(ctlPath), 0o755); err != nil { if err := os.MkdirAll(filepath.Dir(ctlPath), 0o755); err != nil {
return err return err
@ -200,11 +200,12 @@ func Install(owner, tokenFile string) error {
if err := os.MkdirAll(filepath.Dir(trayDesktop), 0o755); err != nil { if err := os.MkdirAll(filepath.Dir(trayDesktop), 0o755); err != nil {
return err return err
} }
if err := os.WriteFile(trayDesktop, []byte(trayDesktopText(exe, true)), 0o644); err != nil { if err := os.WriteFile(trayDesktop, []byte(trayDesktopText(chosen, true)), 0o644); err != nil {
return err return err
} }
linkBin(exe) linkBin(exe)
copyToken(tokenFile) copyToken(tokenFile)
ensureFirewall()
_ = exec.Command("systemctl", "daemon-reload").Run() _ = exec.Command("systemctl", "daemon-reload").Run()
if err := systemctlNow("enable", "--now", linuxUnit); err != nil { if err := systemctlNow("enable", "--now", linuxUnit); err != nil {
return err return err
@ -220,8 +221,16 @@ func Uninstall(owner string) error {
return err return err
} }
if !o.empty() { if !o.empty() {
if other := peerBinary(owner); other != "" {
cfgPath := SystemConfigPath()
_ = os.WriteFile(unitPath, []byte(unitText(other, cfgPath)), 0o644)
_ = os.WriteFile(trayDesktop, []byte(trayDesktopText(other, true)), 0o644)
_ = exec.Command("systemctl", "daemon-reload").Run()
_ = systemctlNow("restart", linuxUnit)
}
return nil return nil
} }
removeFirewall()
_ = systemctlNow("disable", "--now", linuxUnit) _ = systemctlNow("disable", "--now", linuxUnit)
_ = os.Remove(unitPath) _ = os.Remove(unitPath)
_ = os.Remove(trayDesktop) _ = os.Remove(trayDesktop)
@ -230,13 +239,74 @@ func Uninstall(owner string) error {
return nil return nil
} }
func currentServiceExe() string {
b, err := os.ReadFile(unitPath)
if err != nil {
return ""
}
return parseExecStart(string(b))
}
func peerBinary(owner string) string {
p := "/usr/lib/verae-netprint/verae-print"
if owner == "standalone" {
p = "/usr/lib/verae-print/verae-print"
}
if _, err := os.Stat(p); err == nil {
return p
}
return ""
}
// linkBin points /usr/bin/verae-print at this binary only when the driver
// package is not installed. An installed driver keeps the CLI. A real
// (non-symlink) file is left alone.
func linkBin(exe string) { func linkBin(exe string) {
const link = "/usr/bin/verae-print" const link = "/usr/bin/verae-print"
if exe == link { const driver = "/usr/lib/verae-print/verae-print"
target := exe
if _, err := os.Stat(driver); err == nil {
target = driver
}
if target == link {
return return
} }
_ = os.Remove(link) st, err := os.Lstat(link)
_ = os.Symlink(exe, link) if err == nil && st.Mode()&os.ModeSymlink == 0 {
return
}
if err == nil {
_ = os.Remove(link)
}
_ = os.Symlink(target, link)
}
func ensureFirewall() {
if _, err := exec.LookPath("ufw"); err != nil {
return
}
out, err := exec.Command("ufw", "status").CombinedOutput()
if err != nil || !strings.Contains(string(out), "Status: active") {
return
}
status := string(out)
for _, rule := range []string{"631/tcp", "8632/tcp", "5353/udp"} {
if strings.Contains(status, rule) {
continue
}
if err := exec.Command("ufw", "--force", "allow", rule, "comment", "Verae NetPrint").Run(); err != nil {
log.Printf("netprint: ufw allow %s: %v", rule, err)
}
}
}
func removeFirewall() {
if _, err := exec.LookPath("ufw"); err != nil {
return
}
for _, rule := range []string{"631/tcp", "8632/tcp", "5353/udp"} {
_ = exec.Command("ufw", "--force", "delete", "allow", rule).Run()
}
} }
func ensureServiceUser() error { func ensureServiceUser() error {

View file

@ -4,6 +4,7 @@ package netprint
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -42,11 +43,8 @@ func (osControl) TrayAtLogin() (bool, error) {
return err == nil && strings.Contains(string(out), "VeraeNetPrintTray"), nil return err == nil && strings.Contains(string(out), "VeraeNetPrintTray"), nil
} }
func (osControl) SetTrayAtLogin(on bool) error { func (osControl) SetTrayAtLogin(on bool) error {
if !on { exe, _ := Executable()
return exec.Command("reg.exe", "delete", runKey, "/v", "VeraeNetPrintTray", "/f").Run() return setTrayFor(exe, on)
}
tray := trayExe()
return exec.Command("reg.exe", "add", runKey, "/v", "VeraeNetPrintTray", "/t", "REG_SZ", "/d", tray, "/f").Run()
} }
func sc(args ...string) error { func sc(args ...string) error {
@ -63,16 +61,85 @@ func delayed(args ...string) error {
return cmd.Start() return cmd.Start()
} }
func trayExe() string { func trayNextTo(serviceExe string) string {
exe, err := Executable() serviceExe = strings.Trim(serviceExe, `"`)
if err != nil { if serviceExe == "" {
return `verae-print.exe tray` return `verae-print.exe tray`
} }
sibling := filepath.Join(filepath.Dir(exe), "verae-netprint-tray.exe") sibling := filepath.Join(filepath.Dir(serviceExe), "verae-netprint-tray.exe")
if _, err := os.Stat(sibling); err == nil { if _, err := os.Stat(sibling); err == nil {
return `"` + sibling + `"` return `"` + sibling + `"`
} }
return `"` + exe + `" tray` return `"` + serviceExe + `" tray`
}
func setTrayFor(serviceExe string, on bool) error {
if !on {
return exec.Command("reg.exe", "delete", runKey, "/v", "VeraeNetPrintTray", "/f").Run()
}
return exec.Command("reg.exe", "add", runKey, "/v", "VeraeNetPrintTray", "/t", "REG_SZ", "/d", trayNextTo(serviceExe), "/f").Run()
}
func serviceBinPath(exe string) string {
return fmt.Sprintf(`"%s" netprint`, exe)
}
func currentServiceExe() string {
out, err := exec.Command("sc.exe", "qc", winService).CombinedOutput()
if err != nil {
return ""
}
return parseWindowsBinPath(string(out))
}
func peerBinary(owner string) string {
rel := filepath.Join("Verae", "NetPrint", "bin", "verae-print.exe")
if owner == "standalone" {
rel = filepath.Join("Verae", "PrintStamp", "bin", "verae-print.exe")
}
var roots []string
if p := os.Getenv("ProgramW6432"); p != "" {
roots = append(roots, p)
}
if p := os.Getenv("ProgramFiles"); p != "" {
roots = append(roots, p)
}
roots = append(roots, `C:\Program Files`)
seen := map[string]bool{}
for _, root := range roots {
if seen[root] {
continue
}
seen[root] = true
p := filepath.Join(root, rel)
if _, err := os.Stat(p); err == nil {
return p
}
}
return ""
}
func ensureFirewall() {
removeFirewall()
rules := []struct{ name, proto, port string }{
{"Verae NetPrint IPP", "TCP", "631"},
{"Verae NetPrint IPP Fallback", "TCP", "8632"},
{"Verae NetPrint mDNS", "UDP", "5353"},
}
for _, r := range rules {
out, err := exec.Command("netsh", "advfirewall", "firewall", "add", "rule",
"name="+r.name, "dir=in", "action=allow", "protocol="+r.proto,
"localport="+r.port, "profile=private,domain").CombinedOutput()
if err != nil {
log.Printf("netprint: firewall %s: %v (%s)", r.name, err, strings.TrimSpace(string(out)))
}
}
}
func removeFirewall() {
for _, name := range []string{"Verae NetPrint IPP", "Verae NetPrint IPP Fallback", "Verae NetPrint mDNS"} {
_ = exec.Command("netsh", "advfirewall", "firewall", "delete", "rule", "name="+name).Run()
}
} }
func setOwnerReg(name string, on bool) { func setOwnerReg(name string, on bool) {
@ -112,18 +179,27 @@ func Install(owner, tokenFile string) error {
return err return err
} }
} }
bin := fmt.Sprintf(`"%s" netprint`, exe) current := currentServiceExe()
_ = exec.Command("sc.exe", "stop", winService).Run() chosen := selectServiceBinary(exe, current)
_ = exec.Command("sc.exe", "delete", winService).Run() bin := serviceBinPath(chosen)
if err := sc("create", winService, "binPath=", bin, "start=", "auto", "DisplayName=", "Verae Network Printer"); err != nil { if current == "" {
return err if err := sc("create", winService, "binPath=", bin, "start=", "auto", "DisplayName=", "Verae Network Printer"); err != nil {
return err
}
} else if chosen != current {
_ = exec.Command("sc.exe", "stop", winService).Run()
if err := sc("config", winService, "binPath=", bin); err != nil {
return err
}
} }
_ = exec.Command("sc.exe", "description", winService, "Verae network timestamp printer").Run() _ = exec.Command("sc.exe", "description", winService, "Verae network timestamp printer").Run()
_ = exec.Command("sc.exe", "config", winService, "start=", "auto").Run()
copyToken(tokenFile) copyToken(tokenFile)
ensureFirewall()
if err := sc("start", winService); err != nil { if err := sc("start", winService); err != nil {
return err return err
} }
return osControl{}.SetTrayAtLogin(true) return setTrayFor(chosen, true)
} }
// Uninstall removes the service only when neither installer still owns it. // Uninstall removes the service only when neither installer still owns it.
@ -138,10 +214,17 @@ func Uninstall(owner string) error {
setOwnerReg("Standalone", false) setOwnerReg("Standalone", false)
} }
if !o.empty() || ownerReg("Driver") || ownerReg("Standalone") { if !o.empty() || ownerReg("Driver") || ownerReg("Standalone") {
if other := peerBinary(owner); other != "" {
_ = exec.Command("sc.exe", "stop", winService).Run()
_ = sc("config", winService, "binPath=", serviceBinPath(other))
_ = sc("start", winService)
_ = setTrayFor(other, true)
}
return nil return nil
} }
_ = exec.Command("sc.exe", "stop", winService).Run() _ = exec.Command("sc.exe", "stop", winService).Run()
_ = exec.Command("sc.exe", "delete", winService).Run() _ = exec.Command("sc.exe", "delete", winService).Run()
_ = osControl{}.SetTrayAtLogin(false) _ = setTrayFor("", false)
removeFirewall()
return nil return nil
} }

View file

@ -0,0 +1,161 @@
package netprint
import (
"os/exec"
"strconv"
"strings"
)
// compareVersions compares dotted numeric versions.
// It returns -1 when a is older than b, 0 when they are equal, and 1 when a is newer.
func compareVersions(a, b string) int {
ap := splitVer(a)
bp := splitVer(b)
n := len(ap)
if len(bp) > n {
n = len(bp)
}
for i := 0; i < n; i++ {
av, bv := 0, 0
if i < len(ap) {
av = ap[i]
}
if i < len(bp) {
bv = bp[i]
}
if av < bv {
return -1
}
if av > bv {
return 1
}
}
return 0
}
func splitVer(s string) []int {
s = strings.TrimSpace(s)
s = strings.TrimPrefix(s, "v")
if i := strings.IndexAny(s, " \t\r\n"); i >= 0 {
s = s[:i]
}
if s == "" {
return nil
}
parts := strings.Split(s, ".")
out := make([]int, 0, len(parts))
for _, p := range parts {
n, err := strconv.Atoi(p)
if err != nil {
digits := strings.TrimLeftFunc(p, func(r rune) bool { return r < '0' || r > '9' })
end := 0
for end < len(digits) && digits[end] >= '0' && digits[end] <= '9' {
end++
}
if end == 0 {
break
}
n, _ = strconv.Atoi(digits[:end])
}
out = append(out, n)
}
return out
}
// versionOf runs `exe version` and returns the first line.
func versionOf(exe string) string {
exe = strings.TrimSpace(exe)
if exe == "" {
return ""
}
out, err := exec.Command(exe, "version").Output()
if err != nil {
return ""
}
line := strings.TrimSpace(string(out))
if i := strings.IndexAny(line, "\r\n"); i >= 0 {
line = strings.TrimSpace(line[:i])
}
return line
}
// selectServiceBinary chooses which installed binary the shared service should run.
// ours is used when current is missing or ours is the same version or newer.
// A newer current binary is kept so an older installer cannot downgrade the service
// or replace the other product's files.
func selectServiceBinary(ours, current string) string {
return selectServiceBinaryVersions(ours, current, versionOf(ours), versionOf(current))
}
func selectServiceBinaryVersions(ours, current, oursVer, curVer string) string {
ours = strings.TrimSpace(ours)
current = strings.TrimSpace(current)
if current == "" {
return ours
}
if ours == "" {
return current
}
if compareVersions(oursVer, curVer) < 0 {
return current
}
return ours
}
func parseWindowsBinPath(qc string) string {
for _, line := range strings.Split(qc, "\n") {
if !strings.Contains(strings.ToUpper(line), "BINARY_PATH_NAME") {
continue
}
i := strings.Index(line, ":")
if i < 0 {
continue
}
return firstExecutable(strings.TrimSpace(line[i+1:]))
}
return ""
}
func parseExecStart(unit string) string {
for _, line := range strings.Split(unit, "\n") {
line = strings.TrimSpace(line)
if !strings.HasPrefix(line, "ExecStart=") {
continue
}
return firstExecutable(strings.TrimSpace(strings.TrimPrefix(line, "ExecStart=")))
}
return ""
}
func parseDarwinProgram(plist string) string {
i := strings.Index(plist, "<key>ProgramArguments</key>")
if i < 0 {
return ""
}
rest := plist[i:]
s := strings.Index(rest, "<string>")
e := strings.Index(rest, "</string>")
if s < 0 || e < 0 || e <= s+len("<string>") {
return ""
}
return strings.TrimSpace(rest[s+len("<string>") : e])
}
func firstExecutable(rest string) string {
rest = strings.TrimSpace(rest)
if rest == "" {
return ""
}
if strings.HasPrefix(rest, `"`) {
rest = rest[1:]
if j := strings.Index(rest, `"`); j >= 0 {
return rest[:j]
}
return rest
}
fields := strings.Fields(rest)
if len(fields) == 0 {
return ""
}
return fields[0]
}

View file

@ -0,0 +1,66 @@
package netprint
import (
"strings"
"testing"
)
func TestFallbackPortAvoidsDriverHelper(t *testing.T) {
d := Defaults()
if d.FallbackPort != DefaultFallbackPort || d.FallbackPort == DriverHelperPort {
t.Fatalf("fallback port %d, want %d and not the driver helper %d", d.FallbackPort, DefaultFallbackPort, DriverHelperPort)
}
if d.Port == DriverHelperPort {
t.Fatal("primary port collides with the driver helper")
}
sample := string(ExampleJSON(d.DataDir))
if !strings.Contains(sample, "8632") || strings.Contains(sample, "8631") {
t.Fatalf("example config should advertise 8632 and not 8631:\n%s", sample)
}
ports := ippListenPorts(Config{Port: 631, FallbackPort: DriverHelperPort})
for _, p := range ports {
if p == DriverHelperPort {
t.Fatalf("listen ports include driver helper: %v", ports)
}
}
if len(ports) != 2 || ports[0] != 631 || ports[1] != DefaultFallbackPort {
t.Fatalf("631 with a saved 8631 fallback should try 631 then %d, got %v", DefaultFallbackPort, ports)
}
only := ippListenPorts(Config{Port: DriverHelperPort, FallbackPort: DriverHelperPort})
if len(only) != 1 || only[0] != DefaultFallbackPort {
t.Fatalf("both ports set to the helper should bind %d, got %v", DefaultFallbackPort, only)
}
}
func TestSelectServiceBinary(t *testing.T) {
if compareVersions("0.3.4", "0.3.3") <= 0 || compareVersions("0.3.4", "0.3.4") != 0 || compareVersions("0.3.4", "0.4") >= 0 {
t.Fatal("compareVersions")
}
if got := selectServiceBinaryVersions(`/new/verae-print.exe`, `/old/verae-print.exe`, "0.3.4", "0.3.3"); got != `/new/verae-print.exe` {
t.Fatal(got)
}
if got := selectServiceBinaryVersions(`/new/verae-print.exe`, `/old/verae-print.exe`, "0.3.3", "0.3.4"); got != `/old/verae-print.exe` {
t.Fatal(got)
}
if got := selectServiceBinaryVersions(`/new/verae-print.exe`, `/old/verae-print.exe`, "0.3.4", "0.3.4"); got != `/new/verae-print.exe` {
t.Fatal(got)
}
if got := selectServiceBinaryVersions(`/new/verae-print.exe`, "", "0.3.4", ""); got != `/new/verae-print.exe` {
t.Fatal(got)
}
}
func TestParseServiceExecutable(t *testing.T) {
qc := "BINARY_PATH_NAME : \"C:\\Program Files\\Verae\\NetPrint\\bin\\verae-print.exe\" netprint\r\n"
if got := parseWindowsBinPath(qc); got != `C:\Program Files\Verae\NetPrint\bin\verae-print.exe` {
t.Fatal(got)
}
unit := "ExecStart=/usr/lib/verae-netprint/verae-print netprint --config /etc/verae/netprint.json\n"
if got := parseExecStart(unit); got != "/usr/lib/verae-netprint/verae-print" {
t.Fatal(got)
}
plist := "<key>ProgramArguments</key><array><string>/Library/Application Support/Verae/NetPrint/bin/verae-print</string><string>netprint</string></array>"
if got := parseDarwinProgram(plist); got != "/Library/Application Support/Verae/NetPrint/bin/verae-print" {
t.Fatal(got)
}
}

View file

@ -40,8 +40,8 @@ build_arch() {
"$stage/usr/share/man/man1" \ "$stage/usr/share/man/man1" \
"$stage/usr/share/applications" "$stage/usr/share/applications"
# The command is /usr/bin/verae-print, a symlink created in postinst, so the # The command is /usr/bin/verae-print, a symlink created in postinst.
# standalone verae-netprint package can share that path without dpkg conflicts. # The standalone package uses /usr/lib/verae-netprint and does not replace this file.
install -m 755 "$bin" "$stage/usr/lib/verae-print/verae-print" install -m 755 "$bin" "$stage/usr/lib/verae-print/verae-print"
if [[ -f "$ROOT/platform/linux/cups/backend-verae.sh" ]]; then if [[ -f "$ROOT/platform/linux/cups/backend-verae.sh" ]]; then
install -m 700 "$ROOT/platform/linux/cups/backend-verae.sh" "$stage/usr/lib/cups/backend/verae" install -m 700 "$ROOT/platform/linux/cups/backend-verae.sh" "$stage/usr/lib/cups/backend/verae"
@ -127,8 +127,10 @@ Description: Digests-only Verae timestamping from Print / CLI
The same package can enable the Verae network timestamp printer The same package can enable the Verae network timestamp printer
(IPP Everywhere on the LAN). That choice is off unless you accept (IPP Everywhere on the LAN). That choice is off unless you accept
the debconf prompt, or you install the verae-netprint package. the debconf prompt, or you install the verae-netprint package.
Both use /usr/bin/verae-print, /etc/verae/netprint.json, and Both use /etc/verae/netprint.json and systemd unit verae-netprint.service.
systemd unit verae-netprint.service. The driver binary stays in /usr/lib/verae-print. The standalone package
uses /usr/lib/verae-netprint and does not replace this binary.
/usr/bin/verae-print points at the driver while this package is installed.
EOF EOF
cat >"$stage/DEBIAN/templates" <<'EOF' cat >"$stage/DEBIAN/templates" <<'EOF'
@ -173,6 +175,11 @@ if [ -e /usr/share/debconf/confmodule ]; then
enable=${RET:-false} enable=${RET:-false}
fi fi
if [ "$enable" = "true" ]; then if [ "$enable" = "true" ]; then
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q "Status: active"; then
ufw status | grep -q "631/tcp" || ufw allow 631/tcp comment "Verae NetPrint" || true
ufw status | grep -q "8632/tcp" || ufw allow 8632/tcp comment "Verae NetPrint" || true
ufw status | grep -q "5353/udp" || ufw allow 5353/udp comment "Verae NetPrint" || true
fi
/usr/lib/verae-print/verae-print netprint install --owner driver || true /usr/lib/verae-print/verae-print netprint install --owner driver || true
fi fi
echo "verae-print installed. Next: verae-print setup" echo "verae-print installed. Next: verae-print setup"
@ -201,10 +208,17 @@ EOF
set -e set -e
if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then
if [ -x /usr/lib/verae-netprint/verae-print ]; then if [ -x /usr/lib/verae-netprint/verae-print ]; then
ln -sfn /usr/lib/verae-netprint/verae-print /usr/bin/verae-print if [ -L /usr/bin/verae-print ] || [ ! -e /usr/bin/verae-print ]; then
else ln -sfn /usr/lib/verae-netprint/verae-print /usr/bin/verae-print
fi
elif [ -L /usr/bin/verae-print ]; then
rm -f /usr/bin/verae-print rm -f /usr/bin/verae-print
fi fi
if [ ! -x /usr/lib/verae-netprint/verae-print ] && command -v ufw >/dev/null 2>&1; then
ufw --force delete allow 631/tcp >/dev/null 2>&1 || true
ufw --force delete allow 8632/tcp >/dev/null 2>&1 || true
ufw --force delete allow 5353/udp >/dev/null 2>&1 || true
fi
fi fi
exit 0 exit 0
EOF EOF

View file

@ -1,7 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Build the standalone verae-netprint Debian package. # Build the standalone verae-netprint Debian package.
# It shares /usr/bin/verae-print (a symlink), /etc/verae/netprint.json, and # The binary lives in /usr/lib/verae-netprint and does not replace
# systemd unit verae-netprint.service with the driver package's opt-in. # /usr/lib/verae-print. /usr/bin/verae-print stays on the driver when that
# package is installed. Both share /etc/verae/netprint.json and
# systemd unit verae-netprint.service.
set -euo pipefail set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
@ -59,16 +61,28 @@ Description: Verae network timestamp printer (IPP Everywhere)
on this computer. Installs systemd unit verae-netprint, a tray icon at on this computer. Installs systemd unit verae-netprint, a tray icon at
login, and the loopback control API on 127.0.0.1:8630. login, and the loopback control API on 127.0.0.1:8630.
. .
Shares /usr/bin/verae-print and the service name with the optional Uses the same service name as the optional component of the verae-print
component of the verae-print driver package, so installing both upgrades driver package, so installing both keeps one server. The driver keeps
in place instead of running two servers. /usr/bin/verae-print when it is installed. This package does not replace
the driver binary.
EOF EOF
cat >"$stage/DEBIAN/postinst" <<'EOF' cat >"$stage/DEBIAN/postinst" <<'EOF'
#!/bin/sh #!/bin/sh
set -e set -e
if [ "$1" = "configure" ]; then if [ "$1" = "configure" ]; then
ln -sfn /usr/lib/verae-netprint/verae-print /usr/bin/verae-print # Leave the CLI on the driver when that package is installed. Do not
# replace a real (non-symlink) /usr/bin/verae-print.
if [ ! -x /usr/lib/verae-print/verae-print ]; then
if [ -L /usr/bin/verae-print ] || [ ! -e /usr/bin/verae-print ]; then
ln -sfn /usr/lib/verae-netprint/verae-print /usr/bin/verae-print
fi
fi
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q "Status: active"; then
ufw status | grep -q "631/tcp" || ufw allow 631/tcp comment "Verae NetPrint" || true
ufw status | grep -q "8632/tcp" || ufw allow 8632/tcp comment "Verae NetPrint" || true
ufw status | grep -q "5353/udp" || ufw allow 5353/udp comment "Verae NetPrint" || true
fi
/usr/lib/verae-netprint/verae-print netprint install --owner standalone /usr/lib/verae-netprint/verae-print netprint install --owner standalone
fi fi
exit 0 exit 0
@ -91,9 +105,14 @@ set -e
if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then
if [ -x /usr/lib/verae-print/verae-print ]; then if [ -x /usr/lib/verae-print/verae-print ]; then
ln -sfn /usr/lib/verae-print/verae-print /usr/bin/verae-print ln -sfn /usr/lib/verae-print/verae-print /usr/bin/verae-print
else elif [ -L /usr/bin/verae-print ]; then
rm -f /usr/bin/verae-print rm -f /usr/bin/verae-print
fi fi
if [ ! -x /usr/lib/verae-print/verae-print ] && command -v ufw >/dev/null 2>&1; then
ufw --force delete allow 631/tcp >/dev/null 2>&1 || true
ufw --force delete allow 8632/tcp >/dev/null 2>&1 || true
ufw --force delete allow 5353/udp >/dev/null 2>&1 || true
fi
fi fi
exit 0 exit 0
EOF EOF

View file

@ -4,7 +4,7 @@
<key>Label</key><string>com.verae.netprint</string> <key>Label</key><string>com.verae.netprint</string>
<key>ProgramArguments</key> <key>ProgramArguments</key>
<array> <array>
<string>/Library/Application Support/Verae/PrintStamp/bin/verae-print</string> <string>/Library/Application Support/Verae/NetPrint/bin/verae-print</string>
<string>netprint</string> <string>netprint</string>
<string>--config</string> <string>--config</string>
<string>/Library/Application Support/Verae/PrintStamp/netprint.json</string> <string>/Library/Application Support/Verae/PrintStamp/netprint.json</string>

View file

@ -4,7 +4,7 @@
<key>Label</key><string>com.verae.netprint.tray</string> <key>Label</key><string>com.verae.netprint.tray</string>
<key>ProgramArguments</key> <key>ProgramArguments</key>
<array> <array>
<string>/Library/Application Support/Verae/PrintStamp/bin/verae-print</string> <string>/Library/Application Support/Verae/NetPrint/bin/verae-print</string>
<string>tray</string> <string>tray</string>
</array> </array>
<key>RunAtLoad</key><true/> <key>RunAtLoad</key><true/>

View file

@ -19,15 +19,15 @@ fi
STAGE="$OUT/payload" STAGE="$OUT/payload"
SCRIPTS="$OUT/scripts" SCRIPTS="$OUT/scripts"
rm -rf "$STAGE" "$SCRIPTS" rm -rf "$STAGE" "$SCRIPTS"
mkdir -p "$STAGE/Library/Application Support/Verae/PrintStamp/bin" "$SCRIPTS" mkdir -p "$STAGE/Library/Application Support/Verae/NetPrint/bin" "$SCRIPTS"
cp -f "$BIN" "$STAGE/Library/Application Support/Verae/PrintStamp/bin/verae-print" cp -f "$BIN" "$STAGE/Library/Application Support/Verae/NetPrint/bin/verae-print"
chmod 755 "$STAGE/Library/Application Support/Verae/PrintStamp/bin/verae-print" chmod 755 "$STAGE/Library/Application Support/Verae/NetPrint/bin/verae-print"
cp -f "$ROOT/platform/macos/netprint/com.verae.netprint.plist" "$STAGE/Library/Application Support/Verae/PrintStamp/" cp -f "$ROOT/platform/macos/netprint/com.verae.netprint.plist" "$STAGE/Library/Application Support/Verae/NetPrint/"
cp -f "$ROOT/platform/macos/netprint/com.verae.netprint.tray.plist" "$STAGE/Library/Application Support/Verae/PrintStamp/" cp -f "$ROOT/platform/macos/netprint/com.verae.netprint.tray.plist" "$STAGE/Library/Application Support/Verae/NetPrint/"
cat >"$SCRIPTS/postinstall" <<'EOF' cat >"$SCRIPTS/postinstall" <<'EOF'
#!/bin/bash #!/bin/bash
export VERAE_NETPRINT_OWNER=standalone export VERAE_NETPRINT_OWNER=standalone
exec "/Library/Application Support/Verae/PrintStamp/bin/verae-print" netprint install --owner standalone exec "/Library/Application Support/Verae/NetPrint/bin/verae-print" netprint install --owner standalone
EOF EOF
chmod 755 "$SCRIPTS/postinstall" chmod 755 "$SCRIPTS/postinstall"

View file

@ -1,7 +1,9 @@
; Verae Network Printer — standalone Windows installer (NSIS). ; Verae Network Printer — standalone Windows installer (NSIS).
; Installs the same binaries, service name (VeraeNetPrint), and config as the ; Installs into its own folder (C:\Program Files\Verae\NetPrint) so it does not
; optional checkbox in the driver installer. Installing one when the other is ; overwrite the driver's C:\Program Files\Verae\PrintStamp\bin\verae-print.exe.
; present upgrades in place. ; The service name (VeraeNetPrint) and %ProgramData% config are shared. Installing
; one product when the other is present points the service at the same or newer
; binary and leaves the other product's files in place.
!ifndef VERSION !ifndef VERSION
!define VERSION "0.3.4" !define VERSION "0.3.4"
!endif !endif
@ -20,7 +22,7 @@
Name "${PRODUCT_NAME} ${VERSION}" Name "${PRODUCT_NAME} ${VERSION}"
OutFile "${OUTDIR}\VeraeNetPrint-windows-${ARCH}-${VERSION}-setup.exe" OutFile "${OUTDIR}\VeraeNetPrint-windows-${ARCH}-${VERSION}-setup.exe"
InstallDir "$PROGRAMFILES64\Verae\PrintStamp" InstallDir "$PROGRAMFILES64\Verae\NetPrint"
RequestExecutionLevel admin RequestExecutionLevel admin
Unicode true Unicode true
SetCompressor /SOLID lzma SetCompressor /SOLID lzma
@ -40,15 +42,69 @@ ShowInstDetails show
!insertmacro MUI_UNPAGE_INSTFILES !insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English" !insertmacro MUI_LANGUAGE "English"
Var LogFile
Var LogDir
Function .onInit Function .onInit
${IfNot} ${RunningX64} ${IfNot} ${RunningX64}
MessageBox MB_ICONSTOP "This build targets 64-bit Windows." MessageBox MB_ICONSTOP "This build targets 64-bit Windows." /SD IDOK
Abort Abort
${EndIf} ${EndIf}
SetRegView 64 SetRegView 64
SetShellVarContext all SetShellVarContext all
Call SetupLogPath
FunctionEnd FunctionEnd
Function un.onInit
SetRegView 64
SetShellVarContext all
Call un.SetupLogPath
FunctionEnd
Function SetupLogPath
ReadEnvStr $LogDir "ProgramData"
${If} $LogDir == ""
StrCpy $LogDir "C:\ProgramData"
${EndIf}
StrCpy $LogDir "$LogDir\Verae\logs"
StrCpy $LogFile "$LogDir\netprint-installer.log"
FunctionEnd
Function un.SetupLogPath
ReadEnvStr $LogDir "ProgramData"
${If} $LogDir == ""
StrCpy $LogDir "C:\ProgramData"
${EndIf}
StrCpy $LogDir "$LogDir\Verae\logs"
StrCpy $LogFile "$LogDir\netprint-installer.log"
FunctionEnd
; Inbound TCP 631 (IPP), TCP 8632 (fallback; 8631 is the driver helper), UDP 5353 (mDNS).
; Private and domain profiles only. A macro so the uninstaller can run the same commands.
!macro NETPRINT_FIREWALL_ADD
nsExec::Exec 'netsh advfirewall firewall delete rule name="Verae NetPrint IPP"'
Pop $0
nsExec::Exec 'netsh advfirewall firewall add rule name="Verae NetPrint IPP" dir=in action=allow protocol=TCP localport=631 profile=private,domain'
Pop $0
nsExec::Exec 'netsh advfirewall firewall delete rule name="Verae NetPrint IPP Fallback"'
Pop $0
nsExec::Exec 'netsh advfirewall firewall add rule name="Verae NetPrint IPP Fallback" dir=in action=allow protocol=TCP localport=8632 profile=private,domain'
Pop $0
nsExec::Exec 'netsh advfirewall firewall delete rule name="Verae NetPrint mDNS"'
Pop $0
nsExec::Exec 'netsh advfirewall firewall add rule name="Verae NetPrint mDNS" dir=in action=allow protocol=UDP localport=5353 profile=private,domain'
Pop $0
!macroend
!macro NETPRINT_FIREWALL_REMOVE
nsExec::Exec 'netsh advfirewall firewall delete rule name="Verae NetPrint IPP"'
Pop $0
nsExec::Exec 'netsh advfirewall firewall delete rule name="Verae NetPrint IPP Fallback"'
Pop $0
nsExec::Exec 'netsh advfirewall firewall delete rule name="Verae NetPrint mDNS"'
Pop $0
!macroend
Section "Verae Network Printer" SecMain Section "Verae Network Printer" SecMain
SectionIn RO SectionIn RO
SetOutPath "$INSTDIR\bin" SetOutPath "$INSTDIR\bin"
@ -63,16 +119,35 @@ Section "Verae Network Printer" SecMain
WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "Verae" WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "Verae"
CreateDirectory "$SMPROGRAMS\Verae" CreateDirectory "$SMPROGRAMS\Verae"
CreateShortCut "$SMPROGRAMS\Verae\Verae Network Printer.lnk" "$INSTDIR\bin\verae-netprint-tray.exe" CreateShortCut "$SMPROGRAMS\Verae\Verae Network Printer.lnk" "$INSTDIR\bin\verae-netprint-tray.exe"
nsExec::Exec '"$INSTDIR\bin\verae-print.exe" netprint install --owner standalone' !insertmacro NETPRINT_FIREWALL_ADD
CreateDirectory "$LogDir"
nsExec::ExecToStack '"$INSTDIR\bin\verae-print.exe" netprint install --owner standalone'
Pop $0 Pop $0
Pop $1
ClearErrors
FileOpen $2 "$LogFile" a
${IfNot} ${Errors}
FileSeek $2 0 END
FileWrite $2 "netprint install exit $0$\r$\n$1$\r$\n"
FileClose $2
${EndIf}
${If} $0 != 0 ${If} $0 != 0
MessageBox MB_ICONEXCLAMATION "The service did not start (exit $0). You can start it from the tray icon after signing in." MessageBox MB_ICONEXCLAMATION "The service did not start (exit $0).$\r$\nDetails were written to $LogFile$\r$\nYou can start it from the tray icon after signing in." /SD IDOK
${EndIf} ${EndIf}
SectionEnd SectionEnd
Section "Uninstall" Section "Uninstall"
nsExec::Exec '"$INSTDIR\bin\verae-print.exe" netprint uninstall --owner standalone' nsExec::ExecToStack '"$INSTDIR\bin\verae-print.exe" netprint uninstall --owner standalone'
Pop $0 Pop $0
Pop $1
CreateDirectory "$LogDir"
ClearErrors
FileOpen $2 "$LogFile" a
${IfNot} ${Errors}
FileSeek $2 0 END
FileWrite $2 "netprint uninstall exit $0$\r$\n$1$\r$\n"
FileClose $2
${EndIf}
ClearErrors ClearErrors
ReadRegDWORD $R1 HKLM "Software\Verae\NetPrint" "Driver" ReadRegDWORD $R1 HKLM "Software\Verae\NetPrint" "Driver"
${If} ${Errors} ${If} ${Errors}
@ -82,17 +157,15 @@ Section "Uninstall"
${If} $R1 != 1 ${If} $R1 != 1
nsExec::Exec 'taskkill /F /IM verae-netprint-tray.exe' nsExec::Exec 'taskkill /F /IM verae-netprint-tray.exe'
Pop $0 Pop $0
Delete "$INSTDIR\bin\verae-print.exe" !insertmacro NETPRINT_FIREWALL_REMOVE
Delete "$INSTDIR\bin\verae-netprint-tray.exe"
RMDir "$INSTDIR\bin"
Delete "$INSTDIR\LICENSE.txt"
Delete "$INSTDIR\Uninstall-NetPrint.exe"
RMDir "$INSTDIR"
Delete "$SMPROGRAMS\Verae\Verae Network Printer.lnk"
RMDir "$SMPROGRAMS\Verae"
${Else}
Delete "$INSTDIR\Uninstall-NetPrint.exe"
Delete "$SMPROGRAMS\Verae\Verae Network Printer.lnk"
${EndIf} ${EndIf}
Delete "$INSTDIR\bin\verae-print.exe"
Delete "$INSTDIR\bin\verae-netprint-tray.exe"
RMDir "$INSTDIR\bin"
Delete "$INSTDIR\LICENSE.txt"
Delete "$INSTDIR\Uninstall-NetPrint.exe"
RMDir "$INSTDIR"
Delete "$SMPROGRAMS\Verae\Verae Network Printer.lnk"
RMDir "$SMPROGRAMS\Verae"
DeleteRegKey HKLM "${UNINST_KEY}" DeleteRegKey HKLM "${UNINST_KEY}"
SectionEnd SectionEnd

View file

@ -143,7 +143,7 @@ FunctionEnd
Function .onInit Function .onInit
${IfNot} ${RunningX64} ${IfNot} ${RunningX64}
MessageBox MB_ICONSTOP "This build targets 64-bit Windows." MessageBox MB_ICONSTOP "This build targets 64-bit Windows." /SD IDOK
Abort Abort
${EndIf} ${EndIf}
SetRegView 64 SetRegView 64
@ -252,7 +252,7 @@ Function KeyPageLeave
StrCpy $KeyChoice "paste" StrCpy $KeyChoice "paste"
${Else} ${Else}
StrCpy $KeyChoice "skip" StrCpy $KeyChoice "skip"
MessageBox MB_YESNO|MB_ICONEXCLAMATION "Continue without a Verae key?$\r$\n$\r$\nThe printer will be installed, but documents cannot be timestamped until you add a key. On your first print you will be asked for one (or use Start > Verae > Setup signing key)." IDYES +2 MessageBox MB_YESNO|MB_ICONEXCLAMATION "Continue without a Verae key?$\r$\n$\r$\nThe printer will be installed, but documents cannot be timestamped until you add a key. On your first print you will be asked for one (or use Start > Verae > Setup signing key)." /SD IDYES IDYES +2
Abort Abort
${EndIf} ${EndIf}
StrCpy $TokenReady 0 StrCpy $TokenReady 0
@ -301,7 +301,7 @@ Function LoginPageLeave
${NSD_GetText} $hTotp $3 ${NSD_GetText} $hTotp $3
${If} $1 == "" ${If} $1 == ""
${OrIf} $2 == "" ${OrIf} $2 == ""
MessageBox MB_ICONEXCLAMATION "Please enter your email and password." MessageBox MB_ICONEXCLAMATION "Please enter your email and password." /SD IDOK
Abort Abort
${EndIf} ${EndIf}
!insertmacro REQ_OPEN "$PLUGINSDIR\req.txt" !insertmacro REQ_OPEN "$PLUGINSDIR\req.txt"
@ -331,18 +331,18 @@ Function HandleAccountResult
${EndIf} ${EndIf}
${If} $4 == "ok" ${If} $4 == "ok"
StrCpy $TokenReady 1 StrCpy $TokenReady 1
MessageBox MB_ICONINFORMATION "A new Verae key was created for this PC.$\r$\nIt will be saved for $TargetUser." MessageBox MB_ICONINFORMATION "A new Verae key was created for this PC.$\r$\nIt will be saved for $TargetUser." /SD IDOK
${ElseIf} $4 == "code_sent" ${ElseIf} $4 == "code_sent"
; handled by caller ; handled by caller
${ElseIf} $4 == "totp_required" ${ElseIf} $4 == "totp_required"
MessageBox MB_ICONINFORMATION "$5" MessageBox MB_ICONINFORMATION "$5" /SD IDOK
${NSD_SetFocus} $hTotp ${NSD_SetFocus} $hTotp
Abort Abort
${Else} ${Else}
${If} $4 == "" ${If} $4 == ""
StrCpy $5 "No response from the helper (exit $0)." StrCpy $5 "No response from the helper (exit $0)."
${EndIf} ${EndIf}
MessageBox MB_ICONEXCLAMATION "Verae said: $5" MessageBox MB_ICONEXCLAMATION "Verae said: $5" /SD IDOK
Abort Abort
${EndIf} ${EndIf}
FunctionEnd FunctionEnd
@ -393,7 +393,7 @@ Function CreatePageLeave
${NSD_GetText} $hPass $3 ${NSD_GetText} $hPass $3
${NSD_GetText} $hConfirm $6 ${NSD_GetText} $hConfirm $6
${If} $3 != $6 ${If} $3 != $6
MessageBox MB_ICONEXCLAMATION "The passwords do not match." MessageBox MB_ICONEXCLAMATION "The passwords do not match." /SD IDOK
Abort Abort
${EndIf} ${EndIf}
!insertmacro REQ_OPEN "$PLUGINSDIR\req.txt" !insertmacro REQ_OPEN "$PLUGINSDIR\req.txt"
@ -449,7 +449,7 @@ Function ResendCode
Pop $0 Pop $0
ReadINIStr $5 "$PLUGINSDIR\acct.ini" account detail ReadINIStr $5 "$PLUGINSDIR\acct.ini" account detail
Delete "$PLUGINSDIR\acct.ini" Delete "$PLUGINSDIR\acct.ini"
MessageBox MB_ICONINFORMATION "$5" MessageBox MB_ICONINFORMATION "$5" /SD IDOK
FunctionEnd FunctionEnd
Function VerifyPageLeave Function VerifyPageLeave
@ -490,7 +490,7 @@ FunctionEnd
Function PastePageLeave Function PastePageLeave
${NSD_GetText} $hKey $1 ${NSD_GetText} $hKey $1
${If} $1 == "" ${If} $1 == ""
MessageBox MB_ICONEXCLAMATION "Please paste your key (or go Back and choose Skip)." MessageBox MB_ICONEXCLAMATION "Please paste your key (or go Back and choose Skip)." /SD IDOK
Abort Abort
${EndIf} ${EndIf}
FileOpen $9 "$PLUGINSDIR\newtoken.txt" w FileOpen $9 "$PLUGINSDIR\newtoken.txt" w
@ -513,14 +513,14 @@ Function PastePageLeave
StrCpy $TokenReady 1 StrCpy $TokenReady 1
${ElseIf} $4 == "invalid" ${ElseIf} $4 == "invalid"
Delete "$PLUGINSDIR\newtoken.txt" Delete "$PLUGINSDIR\newtoken.txt"
MessageBox MB_ICONEXCLAMATION "Verae rejected this key ($5). Please check it and paste again." MessageBox MB_ICONEXCLAMATION "Verae rejected this key ($5). Please check it and paste again." /SD IDOK
Abort Abort
${ElseIf} $4 == "badformat" ${ElseIf} $4 == "badformat"
Delete "$PLUGINSDIR\newtoken.txt" Delete "$PLUGINSDIR\newtoken.txt"
MessageBox MB_ICONEXCLAMATION "That does not look like a Verae key: $5" MessageBox MB_ICONEXCLAMATION "That does not look like a Verae key: $5" /SD IDOK
Abort Abort
${Else} ${Else}
MessageBox MB_YESNO|MB_ICONQUESTION "Could not reach Verae to check the key ($5).$\r$\nSave it anyway?" IDYES +3 MessageBox MB_YESNO|MB_ICONQUESTION "Could not reach Verae to check the key ($5).$\r$\nSave it anyway?" /SD IDYES IDYES +3
Delete "$PLUGINSDIR\newtoken.txt" Delete "$PLUGINSDIR\newtoken.txt"
Abort Abort
StrCpy $TokenReady 1 StrCpy $TokenReady 1
@ -716,13 +716,35 @@ Section "Verae Print Stamp + printer" SecMain
SetOutPath "$INSTDIR\bin" SetOutPath "$INSTDIR\bin"
File "${SRCDIR}\verae-netprint-tray.exe" File "${SRCDIR}\verae-netprint-tray.exe"
DetailPrint "Installing the Verae network printer (service + tray)" DetailPrint "Installing the Verae network printer (service + tray)"
nsExec::Exec '"$INSTDIR\bin\verae-print.exe" netprint install --owner driver' ; TCP 631, TCP 8632 (not the helper's 8631), UDP 5353. Private and domain only.
nsExec::Exec 'netsh advfirewall firewall delete rule name="Verae NetPrint IPP"'
Pop $0 Pop $0
nsExec::Exec 'netsh advfirewall firewall add rule name="Verae NetPrint IPP" dir=in action=allow protocol=TCP localport=631 profile=private,domain'
Pop $0
nsExec::Exec 'netsh advfirewall firewall delete rule name="Verae NetPrint IPP Fallback"'
Pop $0
nsExec::Exec 'netsh advfirewall firewall add rule name="Verae NetPrint IPP Fallback" dir=in action=allow protocol=TCP localport=8632 profile=private,domain'
Pop $0
nsExec::Exec 'netsh advfirewall firewall delete rule name="Verae NetPrint mDNS"'
Pop $0
nsExec::Exec 'netsh advfirewall firewall add rule name="Verae NetPrint mDNS" dir=in action=allow protocol=UDP localport=5353 profile=private,domain'
Pop $0
CreateDirectory "$LogDir"
nsExec::ExecToStack '"$INSTDIR\bin\verae-print.exe" netprint install --owner driver'
Pop $0
Pop $1
ClearErrors
FileOpen $2 "$LogDir\netprint-installer.log" a
${IfNot} ${Errors}
FileSeek $2 0 END
FileWrite $2 "driver netprint install exit $0$\r$\n$1$\r$\n"
FileClose $2
${EndIf}
${If} $0 == 0 ${If} $0 == 0
!insertmacro STEP_RESULT "Network printer" "OK - Verae Timestamp on the LAN, tray at login" !insertmacro STEP_RESULT "Network printer" "OK - Verae Timestamp on the LAN, tray at login"
${Else} ${Else}
${LOG} "ERROR network printer install exit $0" ${LOG} "ERROR network printer install exit $0. See $LogDir\netprint-installer.log"
!insertmacro STEP_RESULT "Network printer" "FAIL (exit $0)" !insertmacro STEP_RESULT "Network printer" "FAIL (exit $0) - see netprint-installer.log"
${EndIf} ${EndIf}
${Else} ${Else}
!insertmacro STEP_RESULT "Network printer" "SKIPPED" !insertmacro STEP_RESULT "Network printer" "SKIPPED"
@ -746,8 +768,8 @@ Section "Uninstall"
Pop $0 Pop $0
nsExec::Exec 'taskkill /F /IM verae-printhelper.exe' nsExec::Exec 'taskkill /F /IM verae-printhelper.exe'
Pop $0 Pop $0
; The standalone network-printer setup shares these binaries. Leave them ; Standalone NetPrint lives in its own folder. Always remove this driver's files.
; when that product is still installed. ; Drop the shared firewall rules only when the standalone product is gone.
ClearErrors ClearErrors
ReadRegDWORD $R1 HKLM "Software\Verae\NetPrint" "Standalone" ReadRegDWORD $R1 HKLM "Software\Verae\NetPrint" "Standalone"
${If} ${Errors} ${If} ${Errors}
@ -757,22 +779,31 @@ Section "Uninstall"
${If} $R1 != 1 ${If} $R1 != 1
nsExec::Exec 'taskkill /F /IM verae-netprint-tray.exe' nsExec::Exec 'taskkill /F /IM verae-netprint-tray.exe'
Pop $0 Pop $0
Delete "$INSTDIR\bin\verae-print.exe" nsExec::Exec 'netsh advfirewall firewall delete rule name="Verae NetPrint IPP"'
Delete "$INSTDIR\bin\verae-printhelper.exe" Pop $0
Delete "$INSTDIR\bin\verae-netprint-tray.exe" nsExec::Exec 'netsh advfirewall firewall delete rule name="Verae NetPrint IPP Fallback"'
RMDir "$INSTDIR\bin" Pop $0
Delete "$INSTDIR\scripts\*.ps1" nsExec::Exec 'netsh advfirewall firewall delete rule name="Verae NetPrint mDNS"'
RMDir "$INSTDIR\scripts" Pop $0
Delete "$INSTDIR\README-WINDOWS.txt"
Delete "$INSTDIR\LICENSE.txt"
Delete "$INSTDIR\Uninstall.exe"
RMDir "$INSTDIR"
Delete "$SMPROGRAMS\Verae\*.lnk"
RMDir "$SMPROGRAMS\Verae"
DeleteRegKey HKLM "Software\Verae\PrintStamp"
${Else}
Delete "$INSTDIR\Uninstall.exe"
${EndIf} ${EndIf}
Delete "$INSTDIR\bin\verae-print.exe"
Delete "$INSTDIR\bin\verae-printhelper.exe"
Delete "$INSTDIR\bin\verae-netprint-tray.exe"
RMDir "$INSTDIR\bin"
Delete "$INSTDIR\scripts\*.ps1"
RMDir "$INSTDIR\scripts"
Delete "$INSTDIR\README-WINDOWS.txt"
Delete "$INSTDIR\LICENSE.txt"
Delete "$INSTDIR\Uninstall.exe"
RMDir "$INSTDIR"
Delete "$SMPROGRAMS\Verae\Verae - Setup signing key.lnk"
Delete "$SMPROGRAMS\Verae\Verae - Start print helper.lnk"
Delete "$SMPROGRAMS\Verae\Verae - Logs folder.lnk"
Delete "$SMPROGRAMS\Verae\Verae - Collect diagnostics.lnk"
Delete "$SMPROGRAMS\Verae\Uninstall Verae Print Stamp.lnk"
Delete "$SMPROGRAMS\Verae\Uninstall Verae Print Stamp (debug).lnk"
RMDir "$SMPROGRAMS\Verae"
DeleteRegKey HKLM "Software\Verae\PrintStamp"
DeleteRegKey HKLM "${UNINST_KEY}" DeleteRegKey HKLM "${UNINST_KEY}"
; logs and the user's .verae\xverae-token are kept on purpose ; logs and the user's .verae\xverae-token are kept on purpose
SectionEnd SectionEnd

View file

@ -193,7 +193,7 @@
<div class="row"> <div class="row">
<div> <div>
<h2>Network printer</h2> <h2>Network printer</h2>
<p>Optional LAN queue “Verae Timestamp” (IPP Everywhere) plus a tray or menu-bar control. Standalone installers, or the off-by-default option inside the driver installers. One service either way. Version 0.3.4.</p> <p>Optional LAN queue “Verae Timestamp” (IPP Everywhere) plus a tray or menu-bar control. Standalone installers, or the off-by-default option inside the driver installers. One service either way. The Windows standalone setup uses <code>C:\Program Files\Verae\NetPrint</code> and does not replace the driver. If port 631 is taken, the printer uses 8632 (the driver helper keeps 8631). Version 0.3.4.</p>
</div> </div>
<div class="actions"> <div class="actions">
<a class="btn btn-primary" href="/downloads/verae-netprint_amd64.deb">Debian amd64</a> <a class="btn btn-primary" href="/downloads/verae-netprint_amd64.deb">Debian amd64</a>