# qubes_client_examples.md This document shows minimal caller-qube examples for invoking `nsigner --listen qrexec` through Qubes qrexec. For complete protocol details (framing, JSON-RPC, error handling), see `documents/CLIENT_IMPLEMENTATION.md`. --- ## 1) Shell example (`qrexec-client-vm` + framed JSON) This sends one `get_public_key` request and decodes one framed response. ```bash #!/bin/sh set -eu TARGET_QUBE="nsigner-vault" SERVICE="qubes.NsignerRpc" REQ='{"id":"1","method":"get_public_key","params":[]}' python3 - "$TARGET_QUBE" "$SERVICE" "$REQ" <<'PY' import json import struct import subprocess import sys target, service, req_json = sys.argv[1], sys.argv[2], sys.argv[3] frame = struct.pack(">I", len(req_json.encode("utf-8"))) + req_json.encode("utf-8") p = subprocess.Popen( ["qrexec-client-vm", target, service], stdin=subprocess.PIPE, stdout=subprocess.PIPE, ) out, _ = p.communicate(frame) if p.returncode != 0: raise SystemExit(f"qrexec-client-vm failed: {p.returncode}") if len(out) < 4: raise SystemExit("short response (missing frame header)") n = struct.unpack(">I", out[:4])[0] payload = out[4:4+n] if len(payload) != n: raise SystemExit("short response payload") print(json.dumps(json.loads(payload.decode("utf-8")), indent=2)) PY ``` --- ## 2) Python example (explicit frame helpers over qrexec stdio) ```python #!/usr/bin/env python3 import json import struct import subprocess def frame_encode(obj: dict) -> bytes: payload = json.dumps(obj, separators=(",", ":")).encode("utf-8") return struct.pack(">I", len(payload)) + payload def frame_decode(buf: bytes) -> dict: if len(buf) < 4: raise ValueError("missing frame header") n = struct.unpack(">I", buf[:4])[0] payload = buf[4:4 + n] if len(payload) != n: raise ValueError("short frame payload") return json.loads(payload.decode("utf-8")) def call_nsigner_qrexec(target_qube: str, request: dict) -> dict: proc = subprocess.Popen( ["qrexec-client-vm", target_qube, "qubes.NsignerRpc"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) out, err = proc.communicate(frame_encode(request)) if proc.returncode != 0: raise RuntimeError(f"qrexec failed ({proc.returncode}): {err.decode('utf-8', 'replace')}") return frame_decode(out) if __name__ == "__main__": req = {"id": "1", "method": "get_public_key", "params": []} resp = call_nsigner_qrexec("nsigner-vault", req) print(json.dumps(resp, indent=2)) ```