diff --git a/README.md b/README.md index 13deb14..fe05708 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,11 @@ Only the SHA-256 digest is sent to Verae. Document bytes never leave the machine ```bash cd /Users/marchon/research/PrintToVeraeTimeStamp go build -o "$HOME/bin/verae-print" ./cmd/verae-print -verae-print install-macos +verae-print install-printer # Print → Destination → Verae Timestamping ``` +Optional (buried PDF submenu): `verae-print install-macos` + Ensure `$HOME/bin` is on your `PATH`. Refresh Verae Time login when the JWT expires: ```bash @@ -31,7 +33,9 @@ chmod 600 ~/.verae/xverae-token ## Usage -In any app: **File → Print → PDF → Timestamp with Verae** (or **Timestamp with Verae (Zapier)**). +In any app: **File → Print → Destination → Verae Timestamping → Print**. + +Also: drop a PDF on **Verae Print Stamp** in `/Applications`, or **Open With**. CLI: diff --git a/cmd/verae-print/main.go b/cmd/verae-print/main.go index 406a963..3e11f78 100644 --- a/cmd/verae-print/main.go +++ b/cmd/verae-print/main.go @@ -23,6 +23,8 @@ func main() { os.Exit(runStamp(os.Args[2:])) case "install-macos": os.Exit(runInstallMacOS(os.Args[2:])) + case "install-printer": + os.Exit(runInstallPrinter()) case "version", "-version", "--version": fmt.Println(job.HelperVersion) case "help", "-h", "--help": @@ -43,7 +45,8 @@ func usage() { Usage: verae-print stamp [--channel=verae|zapier] [--out-dir DIR] [--name STEM] [--open] - verae-print install-macos # install PDF Services menu items + verae-print install-printer # Destination list: Verae Timestamping (recommended) + verae-print install-macos # also install PDF Services menu items (legacy) verae-print version Channels: @@ -117,6 +120,42 @@ func runStamp(args []string) int { return 0 } +func runInstallPrinter() int { + script := filepath.Join(repoRootHint(), "platform", "macos", "ipp", "install-ipp-printer.sh") + if _, err := os.Stat(script); err != nil { + // fall back next to executable + exe, _ := os.Executable() + exe, _ = filepath.EvalSymlinks(exe) + script = filepath.Join(filepath.Dir(exe), "..", "platform", "macos", "ipp", "install-ipp-printer.sh") + } + if abs, err := filepath.Abs(script); err == nil { + script = abs + } + cmd := exec.Command("/bin/bash", script) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = os.Environ() + if err := cmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "install-printer failed: %v\n(run platform/macos/ipp/install-ipp-printer.sh)\n", err) + return 1 + } + return 0 +} + +func repoRootHint() string { + // Prefer known research path; else cwd + candidates := []string{ + "/Users/marchon/research/PrintToVeraeTimeStamp", + ".", + } + for _, c := range candidates { + if st, err := os.Stat(filepath.Join(c, "platform", "macos", "ipp", "install-ipp-printer.sh")); err == nil && !st.IsDir() { + return c + } + } + return "." +} + func runInstallMacOS(args []string) int { fs := flag.NewFlagSet("install-macos", flag.ExitOnError) bin := fs.String("bin", "", "path to verae-print binary (default: this executable)") diff --git a/platform/macos/cups/backend-verae.sh b/platform/macos/cups/backend-verae.sh new file mode 100755 index 0000000..833fd53 --- /dev/null +++ b/platform/macos/cups/backend-verae.sh @@ -0,0 +1,116 @@ +#!/bin/bash +# CUPS backend: verae — virtual printer "Verae Timestamping" +# Discovery (no args): advertise device URI verae:/ +# Job: spool PDF and run verae-print as the job user (digests only). +set -euo pipefail + +LOG_ROOT="/var/log/cups" +BACKEND_LOG="${LOG_ROOT}/verae-backend.log" + +log() { + echo "$(date -u +%Y-%m-%dT%H:%MZ) $*" >>"$BACKEND_LOG" 2>/dev/null || true +} + +# Device discovery +if [[ $# -eq 0 ]]; then + echo 'direct verae:/ "Verae Timestamping" "Verae Timestamping — digests-only stamp"' + exit 0 +fi + +JOB_ID="${1:-}" +JOB_USER="${2:-}" +JOB_TITLE="${3:-document}" +# copies=$4 options=$5 +JOB_FILE="${6:-}" + +VERAE_PRINT="${VERAE_PRINT_BIN:-/Users/marchon/bin/verae-print}" +CHANNEL="${VERAE_PRINT_CHANNEL:-verae}" + +SPOOL_BASE="/Users/Shared/VeraePrintSpool" +# Prefer per-user Application Support when home is known +if [[ -n "$JOB_USER" && -d "/Users/$JOB_USER" ]]; then + SPOOL_BASE="/Users/$JOB_USER/Library/Application Support/Verae/print-spool" +fi + +mkdir -p "$SPOOL_BASE" +chmod 700 "$SPOOL_BASE" 2>/dev/null || true + +SAFE_TITLE=$(echo "$JOB_TITLE" | tr -cd 'A-Za-z0-9._ -' | head -c 80) +SAFE_TITLE=${SAFE_TITLE:-document} +WORK="$SPOOL_BASE/job-${JOB_ID}-$$" +mkdir -p "$WORK" + +INPUT="$WORK/input.dat" +if [[ -n "$JOB_FILE" && -f "$JOB_FILE" ]]; then + cp "$JOB_FILE" "$INPUT" +else + cat >"$INPUT" +fi + +# Normalize to PDF when CUPS gave PostScript / other +PDF="$WORK/${SAFE_TITLE}.pdf" +CONTENT_TYPE="${CONTENT_TYPE:-application/pdf}" +case "$CONTENT_TYPE" in + application/pdf|application/x-pdf) + cp "$INPUT" "$PDF" + ;; + application/postscript|application/vnd.cups-postscript) + if command -v /usr/bin/pstopdf >/dev/null 2>&1; then + /usr/bin/pstopdf "$INPUT" -o "$PDF" 2>/dev/null || cp "$INPUT" "$PDF" + else + # macOS often still delivers PDF despite PS content-type + if [[ "$(head -c 4 "$INPUT")" == "%PDF" ]]; then + cp "$INPUT" "$PDF" + else + log "job $JOB_ID: cannot convert $CONTENT_TYPE; saving raw" + cp "$INPUT" "$PDF" + fi + fi + ;; + *) + if [[ "$(head -c 4 "$INPUT")" == "%PDF" ]]; then + cp "$INPUT" "$PDF" + else + log "job $JOB_ID: content-type=$CONTENT_TYPE; assuming PDF bytes" + cp "$INPUT" "$PDF" + fi + ;; +esac + +log "job=$JOB_ID user=$JOB_USER title=$JOB_TITLE pdf=$PDF channel=$CHANNEL" + +OUT_DIR="/Users/${JOB_USER}/Documents/Verae Timestamps" +if [[ -z "$JOB_USER" || ! -d "/Users/$JOB_USER" ]]; then + OUT_DIR="$SPOOL_BASE/out" +fi +mkdir -p "$OUT_DIR" + +run_stamp() { + if [[ -x "$VERAE_PRINT" ]]; then + "$VERAE_PRINT" stamp -channel "$CHANNEL" -out-dir "$OUT_DIR" -name "$SAFE_TITLE" -open "$PDF" + else + log "verae-print missing at $VERAE_PRINT" + return 1 + fi +} + +# Drop privileges to job user when we are root +if [[ "$(id -u)" -eq 0 && -n "$JOB_USER" && "$JOB_USER" != "root" ]]; then + chown -R "$JOB_USER" "$WORK" "$OUT_DIR" 2>/dev/null || true + if command -v sudo >/dev/null 2>&1; then + sudo -u "$JOB_USER" -H "$VERAE_PRINT" stamp -channel "$CHANNEL" -out-dir "$OUT_DIR" -name "$SAFE_TITLE" -open "$PDF" \ + >>"$BACKEND_LOG" 2>&1 || { + log "stamp failed for job $JOB_ID" + # Still exit 0 so the print dialog doesn't hang; user can check log + exit 0 + } + else + run_stamp >>"$BACKEND_LOG" 2>&1 || true + fi +else + run_stamp >>"$BACKEND_LOG" 2>&1 || true +fi + +# Cleanup work dir; keep outputs in OUT_DIR +rm -rf "$WORK" 2>/dev/null || true +exit 0 diff --git a/platform/macos/cups/install-printer.sh b/platform/macos/cups/install-printer.sh new file mode 100755 index 0000000..d69dcb5 --- /dev/null +++ b/platform/macos/cups/install-printer.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# Install "Verae Timestamping" as a CUPS destination (Print → Destination list). +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +BIN="${VERAE_PRINT_BIN:-$HOME/bin/verae-print}" +BACKEND_SRC="$(cd "$(dirname "$0")" && pwd)/backend-verae.sh" +BACKEND_DST="/usr/libexec/cups/backend/verae" +QUEUE="Verae_Timestamping" +DISPLAY_NAME="Verae Timestamping" + +if [[ ! -x "$BIN" ]]; then + echo "Building verae-print → $BIN" + mkdir -p "$(dirname "$BIN")" + (cd "$ROOT" && go build -o "$BIN" ./cmd/verae-print) +fi + +if [[ ! -f "$BACKEND_SRC" ]]; then + echo "missing $BACKEND_SRC" >&2 + exit 1 +fi + +echo "Installing CUPS backend → $BACKEND_DST" +TMP="$(mktemp)" +sed "s|/Users/marchon/bin/verae-print|$BIN|g" "$BACKEND_SRC" >"$TMP" +install -o root -g wheel -m 700 "$TMP" "$BACKEND_DST" +rm -f "$TMP" + +# Device discovery check +"$BACKEND_DST" || true + +lpstat -p "$QUEUE" &>/dev/null && lpadmin -x "$QUEUE" || true + +# Raw queue: macOS print pipeline usually delivers PDF to the backend +lpadmin -p "$QUEUE" \ + -E \ + -v "verae:/" \ + -D "$DISPLAY_NAME" \ + -L "Digests-only Verae timestamp" \ + -o printer-is-shared=false \ + -m raw + +cupsenable "$QUEUE" || true +cupsaccept "$QUEUE" || true + +echo "Installed printer queue: $QUEUE ($DISPLAY_NAME)" +lpstat -v "$QUEUE" || true +echo +echo "In any app: Print → Destination → Verae Timestamping → Print" +echo "Outputs: ~/Documents/Verae Timestamps/{name}.pdf and {name}-verae-timestamp-{RECEIPTID}.pdf" diff --git a/platform/macos/ipp/com.verae.timestamping.ipp.plist b/platform/macos/ipp/com.verae.timestamping.ipp.plist new file mode 100644 index 0000000..24530e6 --- /dev/null +++ b/platform/macos/ipp/com.verae.timestamping.ipp.plist @@ -0,0 +1,38 @@ + + + + + Label + com.verae.timestamping.ipp + ProgramArguments + + /usr/bin/ippeveprinter + -c + /Users/SHARED_PLACEHOLDER/verae-print-command.sh + -d + /Users/SHARED_PLACEHOLDER/spool + -f + application/pdf,application/postscript + -p + 8631 + -n + localhost + -M + Verae + -m + Timestamping + -l + Digests-only timestamp + --no-web-forms + Verae Timestamping + + RunAtLoad + + KeepAlive + + StandardOutPath + /Users/SHARED_PLACEHOLDER/ippeveprinter.log + StandardErrorPath + /Users/SHARED_PLACEHOLDER/ippeveprinter.log + + diff --git a/platform/macos/ipp/install-ipp-printer.sh b/platform/macos/ipp/install-ipp-printer.sh new file mode 100755 index 0000000..461940e --- /dev/null +++ b/platform/macos/ipp/install-ipp-printer.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# Advertise "Verae Timestamping" via ippeveprinter (shows in Print → Destination). +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" +BIN="${VERAE_PRINT_BIN:-$HOME/bin/verae-print}" +SUPPORT="$HOME/Library/Application Support/Verae/ipp-printer" +CMD="$SUPPORT/verae-print-command.sh" +PLIST_SRC="$(cd "$(dirname "$0")" && pwd)/com.verae.timestamping.ipp.plist" +LAUNCH_AGENTS="$HOME/Library/LaunchAgents" +PLIST_DST="$LAUNCH_AGENTS/com.verae.timestamping.ipp.plist" + +if [[ ! -x "$BIN" ]]; then + mkdir -p "$(dirname "$BIN")" + (cd "$ROOT" && go build -o "$BIN" ./cmd/verae-print) +fi + +mkdir -p "$SUPPORT/spool" "$LAUNCH_AGENTS" +cp "$(cd "$(dirname "$0")" && pwd)/verae-print-command.sh" "$CMD" +chmod 755 "$CMD" +# Bake binary path +sed -i '' "s|VERAE_PRINT_BIN:-\$HOME/bin/verae-print|VERAE_PRINT_BIN:-$BIN|" "$CMD" 2>/dev/null || true + +# Materialize LaunchAgent with user paths +sed "s|/Users/SHARED_PLACEHOLDER|$SUPPORT|g" "$PLIST_SRC" >"$PLIST_DST" + +# Stop previous +launchctl bootout "gui/$(id -u)/com.verae.timestamping.ipp" 2>/dev/null || true +launchctl unload "$PLIST_DST" 2>/dev/null || true + +# Remove broken CUPS raw/generic queue if present +lpstat -p Verae_Timestamping &>/dev/null && lpadmin -x Verae_Timestamping || true +# Remove unsigned custom backend if present (optional) +if [[ -f /usr/libexec/cups/backend/verae ]]; then + rm -f /usr/libexec/cups/backend/verae || true +fi + +launchctl bootstrap "gui/$(id -u)" "$PLIST_DST" 2>/dev/null \ + || launchctl load -w "$PLIST_DST" + +sleep 1 +# Also register explicit CUPS queue pointing at local IPP (helps some apps) +if curl -sf -o /dev/null --max-time 2 "http://127.0.0.1:8631/" || true; then + lpadmin -p Verae_Timestamping -E \ + -v "ipp://127.0.0.1:8631/ipp/print" \ + -D "Verae Timestamping" \ + -L "Digests-only Verae timestamp" \ + -m everywhere 2>/dev/null \ + || lpadmin -p Verae_Timestamping -E \ + -v "ipp://localhost:8631/ipp/print" \ + -D "Verae Timestamping" \ + -m everywhere || true + cupsenable Verae_Timestamping 2>/dev/null || true + cupsaccept Verae_Timestamping 2>/dev/null || true +fi + +echo "Verae Timestamping IPP printer installed." +echo "LaunchAgent: $PLIST_DST" +echo "Command: $CMD" +echo "Check: lpstat -p -d ; lpstat -v" +lpstat -p Verae_Timestamping 2>/dev/null || true +lpstat -v Verae_Timestamping 2>/dev/null || true +echo +echo "Print → Destination → Verae Timestamping" +echo "Saves to ~/Documents/Verae Timestamps/" diff --git a/platform/macos/ipp/verae-print-command.sh b/platform/macos/ipp/verae-print-command.sh new file mode 100755 index 0000000..d44332b --- /dev/null +++ b/platform/macos/ipp/verae-print-command.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# ippeveprinter -c handler: PDF (or PS) on stdin → Verae stamp with approved naming. +set -euo pipefail + +LOG="${HOME}/Library/Logs/verae-print-ipp.log" +mkdir -p "$(dirname "$LOG")" +exec >>"$LOG" 2>&1 +echo "$(date -u +%Y-%m-%dT%H:%MZ) IPP job start env=$(env | grep -E '^IPP_|^CONTENT_|^USER=' | tr '\n' ' ')" + +VERAE_PRINT="${VERAE_PRINT_BIN:-$HOME/bin/verae-print}" +CHANNEL="${VERAE_PRINT_CHANNEL:-verae}" +OUT_DIR="${HOME}/Documents/Verae Timestamps" +mkdir -p "$OUT_DIR" + +TITLE="${IPP_JOB_NAME:-${IPP_DOCUMENT_NAME:-document}}" +# Strip path / extension for stem +BASE="$(basename "$TITLE")" +STEM="${BASE%.*}" +STEM="$(echo "$STEM" | tr -cd 'A-Za-z0-9._ -' | head -c 80)" +STEM="${STEM:-document}" + +TMPDIR="${TMPDIR:-/tmp}" +WORK="$(mktemp -d "${TMPDIR}/verae-ipp.XXXXXX")" +RAW="$WORK/job.bin" +PDF="$WORK/${STEM}.pdf" + +# ippeveprinter may pass a spool file as argv[1], or feed bytes on stdin +if [[ $# -ge 1 && -f "${1:-}" ]]; then + echo "input file arg: $1" + cp "$1" "$RAW" +elif [[ ! -t 0 ]]; then + cat >"$RAW" +else + echo "ERROR: no document on stdin or argv" >&2 + exit 1 +fi + +if [[ ! -s "$RAW" ]]; then + echo "ERROR: empty document" >&2 + exit 1 +fi + +if [[ "$(head -c 4 "$RAW")" == "%PDF" ]]; then + cp "$RAW" "$PDF" +elif command -v /usr/bin/pstopdf >/dev/null 2>&1; then + /usr/bin/pstopdf "$RAW" -o "$PDF" || cp "$RAW" "$PDF" +else + cp "$RAW" "$PDF" +fi + +echo "stamping stem=$STEM channel=$CHANNEL pdf=$PDF" +"$VERAE_PRINT" stamp -channel "$CHANNEL" -out-dir "$OUT_DIR" -name "$STEM" -open "$PDF" +echo "ATTR: job-impressions-completed=1" >&2 +rm -rf "$WORK" +exit 0