commit 678c99c753a2a93ab3bcfc4920cd70edff6281c0
parent a6cd7011bf079f59231aa8a6950db70f6338d1a3
Author: Christian Grothoff <christian@grothoff.org>
Date: Mon, 25 May 2026 17:07:47 +0200
add paivana manual
Diffstat:
3 files changed, 523 insertions(+), 33 deletions(-)
diff --git a/conf.py b/conf.py
@@ -61,6 +61,7 @@ extensions = [
"httpdomain.httpdomain",
"myst_parser",
"sphinx_multitoc_numbering",
+ "sphinx_design",
]
imgmath_image_format = 'svg'
diff --git a/flake.nix b/flake.nix
@@ -33,6 +33,7 @@
python-pkgs.sphinx-book-theme
python-pkgs.myst-parser
python-pkgs.sphinxcontrib-httpdomain
+ python-pkgs.sphinx-design
]))
];
buildInputs = [
diff --git a/taler-paivana-manual.rst b/taler-paivana-manual.rst
@@ -79,75 +79,563 @@ Paivana service, you must have the base URL, username and password
of the selected merchant backend at hand.
+.. _Paivana-httpd:
+
Paivana-httpd
=============
This chapter documents the installation and operation of the Paivana
-reverse proxy.
-
+reverse proxy ``paivana-httpd``. The reverse proxy sits between the
+public Internet and an upstream Web service, intercepting requests
+that have not yet been paid for and presenting the client with a
+GNU Taler paywall. Once a payment has been confirmed by the
+configured GNU Taler merchant backend, ``paivana-httpd`` forwards
+subsequent requests of that client to the upstream service.
+
+The full list of command-line options is documented in
+:manpage:`paivana-httpd(1)`; the configuration file is
+documented in :manpage:`paivana.conf(5)`.
+
+
+Architecture overview
+---------------------
+
+``paivana-httpd`` does not implement any payment logic of its own.
+Instead, every Paivana deployment combines three components:
+
+1. **The upstream web service.** This is the existing HTTP service
+ whose content should be sold (a static website, a cgit service,
+ a REST API, …). It does not need to be modified to
+ work with Paivana.
+2. **A GNU Taler merchant backend** (``taler-merchant-httpd``). The
+ merchant backend manages templates, creates orders, talks to one
+ or more Taler exchanges, and ultimately reports back whether a
+ given order has been paid. See the
+ :ref:`Taler Merchant Backend Operator Manual
+ <taler-merchant-backend-operator-manual>` for full details.
+3. **``paivana-httpd`` itself.** This is the reverse proxy that
+ gates the upstream service. It reads a single
+ :ref:`paivana.conf <Paivana-Configuration>` configuration file
+ that points at both the merchant backend and the upstream
+ service.
+
+Typically a TLS-terminating reverse proxy (Nginx or Apache) is
+deployed in front of ``paivana-httpd`` to handle HTTPS and to route
+multiple virtual hosts; see :ref:`Paivana-ReverseProxy` below.
+
+In normal operation the request flow is:
+
+::
+
+ client ──▶ Nginx/Apache (TLS) ──▶ paivana-httpd ──▶ upstream
+ │
+ ▼
+ taler-merchant-httpd
+ │
+ ▼
+ Taler exchange
+
+
+Installation
+------------
Installing from source
-----------------------
-
-The following instructions will show how to install libgnunetutil and
-the core GNU Taler libraries from source.
+^^^^^^^^^^^^^^^^^^^^^^
-The package sources can be find in our
+The package sources can be found in our
`download directory <http://ftpmirror.gnu.org/taler/>`__.
-GNU Taler components version numbers follow the ``MAJOR.MINOR.MICRO`` format.
-The general rule for compatibility is that ``MAJOR`` and ``MINOR`` must match.
-Exceptions to this general rule are documented in the release notes. For
-example, paivana-httpd 1.6.0 should be compatible with Taler exchange 1.6.x as
-the MAJOR version matches. A MAJOR version of 0 indicates experimental
-development, and you are expected to always run all of the *latest* releases
-together (no compatibility guarantees).
+GNU Taler components follow the ``MAJOR.MINOR.MICRO`` version
+scheme. The general rule for compatibility is that ``MAJOR`` and
+``MINOR`` must match across components; exceptions are noted in the
+release notes. For example, ``paivana-httpd`` 1.6.x is expected to
+work with ``taler-merchant-httpd`` 1.6.x. A ``MAJOR`` version of 0
+indicates experimental development; in that case you should always
+run the *latest* releases of every component together.
-First, the following packages need to be installed before we can compile the
-backend:
+The following packages must be installed before compiling
+``paivana-httpd``:
-- Golang >= 1.19
+- GNUnet (``libgnunetutil``) matching the Taler release
+- GNU Taler exchange libraries (``libtalerexchange``,
+ ``libtalerutil``)
+- GNU Taler merchant client library (``libtalermerchant``)
+- GNU Taler HTTP daemon helpers (``libtalermhd``,
+ ``libtalertemplating``)
+- libmicrohttpd, libcurl, libjansson, libgcrypt, zlib
-- taler-merchant >= 1.6
+Build and install with:
+.. code-block:: shell-session
+
+ $ ./bootstrap
+ $ ./configure --prefix=$PREFIX
+ $ make
+ $ sudo make install
-Installing the directory binary packages on Debian
---------------------------------------------------
+Installing the binary packages on Debian
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. include:: frags/installing-debian.rst
-To install paivana-httpd can now simply run:
+To install ``paivana-httpd`` you can now simply run:
.. code-block:: shell-session
# apt install paivana-httpd
-Note that the package does not perform any configuration work except for
-setting up the various users and the systemd service scripts. You still must
-configure the HTTP request routing.
+The package does not perform any deployment-specific configuration
+work; it only sets up the ``paivana-httpd`` system user, the systemd
+service and socket units, and installs example configuration
+snippets for Nginx and Apache under ``/etc/nginx/sites-available/``
+and ``/etc/apache2/sites-available/``. You still must configure the
+HTTP request routing and the Paivana templates as described below.
-Installing the GNU Taler binary packages on Ubuntu
---------------------------------------------------
+Installing the binary packages on Ubuntu
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. include:: frags/installing-ubuntu.rst
-To install the Taler exchange, you can now simply run:
+To install ``paivana-httpd``, run:
.. code-block:: shell-session
# apt install paivana-httpd
-Note that the package does not perform any configuration work except for
-setting up the various users and the systemd service scripts. You still must
-configure the HTTP request routing.
+As on Debian, the package does not perform any deployment-specific
+configuration work.
-Configuration
--------------
+.. _Paivana-Configuration:
-FIXME!
+Configuring paivana-httpd
+-------------------------
+
+The main configuration file is ``/etc/paivana/paivana.conf``. Its
+syntax follows the standard GNUnet configuration file format and is
+documented in full in :manpage:`paivana.conf(5)`. Default values
+shipped with the package live under
+``/usr/share/paivana/config.d/``; values in ``paivana.conf``
+override those defaults.
+
+All Paivana-specific keys live in the ``[paivana]`` section. At a
+minimum, the file must specify three things:
+
+- where ``paivana-httpd`` should listen for incoming requests
+ (``SERVE``, ``UNIXPATH`` / ``PORT``);
+- where it should forward paid requests to
+ (``DESTINATION_BASE_URL``);
+- how it should reach the merchant backend
+ (``MERCHANT_BACKEND_URL`` and ``MERCHANT_ACCESS_TOKEN``).
+
+A typical configuration that listens on a UNIX domain socket
+managed by systemd and forwards to a local upstream server looks
+like this:
+
+.. code-block:: ini
+
+ [paivana]
+ # Listen on the socket provided by paivana-httpd.socket.
+ SERVE = unix
+ UNIXPATH = /run/paivana/httpd/paivana-http.sock
+ UNIXPATH_MODE = 660
+
+ # Public base URL of this paywall as seen by clients.
+ # Used when the Host/X-Forwarded-Host headers are unavailable.
+ BASE_URL = https://paywall.example.com/
+
+ # Upstream service that gets proxied after payment.
+ DESTINATION_BASE_URL = http://127.0.0.1:8080/
+
+ # Merchant backend used to create and verify orders.
+ MERCHANT_BACKEND_URL = http://localhost:9966/
+ MERCHANT_ACCESS_TOKEN = secret-token:CHANGE-ME
+
+ # Stable secret used to MAC the access cookie.
+ # If unset, a random value is generated at every startup,
+ # invalidating all previously issued cookies.
+ SECRET = please-change-this-to-a-long-random-value
+
+ # Resources that should never trigger the paywall, e.g.
+ # logos, stylesheets or favicons.
+ WHITELIST = ^/(favicon\.ico|assets/.*|robots\.txt)$
+
+The exhaustive list of supported keys (``SERVE``, ``PORT``,
+``BIND_TO``, ``UNIXPATH``, ``UNIXPATH_MODE``, ``BASE_URL``,
+``DESTINATION_BASE_URL``, ``MERCHANT_BACKEND_URL``,
+``MERCHANT_BACKEND_UNIX_PATH``, ``MERCHANT_ACCESS_TOKEN``,
+``SECRET``, ``WHITELIST``) is documented in
+:manpage:`paivana.conf(5)`.
+
+If you reach the merchant backend over a UNIX domain socket on the
+same host (recommended for a single-machine deployment), replace
+the ``MERCHANT_BACKEND_URL`` block with:
+
+.. code-block:: ini
+
+ MERCHANT_BACKEND_URL = http://localhost/
+ MERCHANT_BACKEND_UNIX_PATH = /run/taler-merchant/merchant.sock
+
+.. note::
+
+ ``MERCHANT_ACCESS_TOKEN`` and ``SECRET`` are sensitive values.
+ Make sure ``paivana.conf`` is only readable by the
+ ``paivana-httpd`` user. The Debian package installs the file
+ accordingly.
+
+When ``paivana-httpd`` runs behind a trusted reverse proxy
+(Nginx/Apache), pass ``-f`` / ``--respect-forwarded-headers`` in the
+systemd unit's ``ExecStart=`` so the real client address is taken
+from ``X-Forwarded-For``. See :manpage:`paivana-httpd(1)` for the
+remaining command-line flags (in particular ``-g`` to require only
+a single payment per site and ``-n`` to disable the paywall for
+debugging).
+
+
+Starting and stopping the service
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The Debian/Ubuntu package ships a socket-activated systemd unit.
+After editing ``/etc/paivana/paivana.conf`` enable and start it:
+
+.. code-block:: shell-session
+
+ # systemctl enable --now paivana-httpd.socket
+ # systemctl status paivana-httpd
+
+The socket listens on ``/run/paivana/httpd/paivana-http.sock`` with
+group ``www-data``, which lets a co-located Nginx or Apache talk to
+the daemon without granting it broader filesystem access. Logs are
+sent to the journal:
+
+.. code-block:: shell-session
+
+ # journalctl -u paivana-httpd -f
+
+
+.. _Paivana-Templates:
+
+Configuring Paivana templates
+-----------------------------
+
+``paivana-httpd`` does not store any per-site pricing or URL-matching rules
+itself. Instead, all rules are expressed as :ref:`merchant templates
+<template>` of type ``paivana`` in the merchant backend. When
+``paivana-httpd`` starts up it asks the merchant backend for every template
+configured for the instance identified by ``MERCHANT_BACKEND_URL`` and uses
+the ``website_regex`` field of each template to decide which template (and
+therefore which payment options) applies to an incoming request URL.
+
+The corresponding REST API is documented in detail in the
+:ref:`Merchant Backend HTTP API <merchant-api>`; see in particular
+the
+`POST /private/templates
+<https://docs.taler.net/core/api-merchant.html#post--private-templates>`__
+endpoint and the
+:ts:type:`TemplateContractPaivana` definition.
+
+Prerequisites
+^^^^^^^^^^^^^
+
+Before creating a template you need:
+
+- a running ``taler-merchant-httpd`` (see the
+ :ref:`Launching-the-backend` section of the merchant manual);
+- a merchant :ref:`instance <Instance-setup>` with at least one
+ configured :ref:`bank account <instance-bank-account>`;
+- the access token of that instance (used as
+ ``MERCHANT_ACCESS_TOKEN`` in ``paivana.conf``).
+
+In the examples below we assume the merchant backend is reachable
+at ``http://localhost:9966/``, the default instance is ``default``,
+its access token is ``secret-token:sandbox`` and the currency is
+``KUDOS``. Adjust the URLs, tokens and amounts to match your
+deployment. The
+`src/backend/test.sh
+<https://git.taler.net/paivana.git/tree/src/backend/test.sh>`__
+script that ships with Paivana sets up exactly this minimal
+configuration and is a good starting point for experimentation.
+
+Creating a single global template
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The simplest Paivana setup uses one template that matches every
+URL on the site and charges a fixed price. This is the
+configuration created by ``src/backend/test.sh``:
+
+.. code-block:: bash
+
+ $ curl -X POST http://localhost:9966/private/templates \
+ -H 'Authorization: Bearer secret-token:sandbox' \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "template_id": "paivana",
+ "template_description": "A Paivana template",
+ "template_contract": {
+ "template_type": "paivana",
+ "summary": "Access to example.com",
+ "website_regex": ".*",
+ "choices": [ { "amount": "KUDOS:1" } ]
+ }
+ }'
+
+The ``template_type`` must be ``"paivana"``: this allows
+``paivana-httpd`` to pick the template up at startup and
+also enables some required logic in the merchant backend. The
+``website_regex`` is a POSIX extended regular expression that is
+matched against the request URL; ``.*`` covers everything. Each
+entry in ``choices`` describes one way the client may pay and is an
+:ts:type:`OrderChoice` object (so the paywall can also support
+the use of subscription tokens, discount coupons, etc.).
+
+A successful create returns HTTP ``204 No Content``. After
+creating the template, (re)start ``paivana-httpd`` so that it
+re-reads the template list:
+
+.. code-block:: shell-session
+
+ # systemctl restart paivana-httpd
+
+Multiple templates with URL-specific pricing
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When a single site contains content with different prices, define one template
+per price bucket and use ``website_regex`` to scope each template to the
+matching URLs. When several templates match the same URL ``paivana-httpd``
+picks the first one if finds that matches. Be careful: if multiple templates
+match a URL, the result is non-deterministic!
+
+For example, a news site might charge 2 KUDOS for premium articles
+and 50 cents (``KUDOS:0.5``) for standard articles:
+
+.. code-block:: bash
+
+ $ curl -X POST http://localhost:9966/private/templates \
+ -H 'Authorization: Bearer secret-token:sandbox' \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "template_id": "premium",
+ "template_description": "Premium long-form articles",
+ "template_contract": {
+ "template_type": "paivana",
+ "summary": "Premium article on example.com",
+ "website_regex": "^/premium/.*",
+ "choices": [ { "amount": "KUDOS:2" } ]
+ }
+ }'
+
+ $ curl -X POST http://localhost:9966/private/templates \
+ -H 'Authorization: Bearer secret-token:sandbox' \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "template_id": "default",
+ "template_description": "Standard articles",
+ "template_contract": {
+ "template_type": "paivana",
+ "summary": "Standard article on example.com",
+ "website_regex": "^/standard/.*",
+ "choices": [ { "amount": "KUDOS:0.5" } ]
+ }
+ }'
+
+Offering multiple payment options
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``choices`` array lets a single template offer several mutually exclusive
+ways to pay. A common pattern is to accept either a cash payment or to sell a
+subscription; the wallet shows both options and the customer picks one. The
+third option, where the customer already has a subscription, will be used
+automatically by the wallet for subscribers and the customer will not even
+have to click to bypass the paywall as a subscriber. See the merchant manual
+for the details of :ref:`OrderChoice <template-choice>` objects.
+
+.. code-block:: bash
+
+ $ curl -X POST http://localhost:9966/private/templates \
+ -H 'Authorization: Bearer secret-token:sandbox' \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "template_id": "article",
+ "template_description": "Single article, paid or via subscription",
+ "template_contract": {
+ "template_type": "paivana",
+ "summary": "Article on example.com",
+ "website_regex": ".*",
+ "choices": [
+ { "amount": "KUDOS:1",
+ "description": "Pay per article" },
+ { "amount": "KUDOS:100",
+ "description": "Buy subscription",
+ "outputs": [ { "token": "monthly-subscription" } ] },
+ { "amount": "KUDOS:0",
+ "description": "Use my subscription",
+ "inputs": [ { "token": "monthly-subscription" } ],
+ "outputs": [ { "token": "monthly-subscription" } ] }
+ ]
+ }
+ }'
+
+Managing templates
+^^^^^^^^^^^^^^^^^^
+
+Templates can be listed, updated and deleted through the merchant
+backend's REST API or through the merchant backend SPA at
+``$MERCHANT_BACKEND_URL/``. See the merchant manual section on
+:ref:`templates <template>` for details, and the API reference for
+the relevant endpoints:
+
+- `GET /private/templates
+ <https://docs.taler.net/core/api-merchant.html#get--private-templates>`__ —
+ list all templates of the instance;
+- `PATCH /private/templates/$TEMPLATE_ID
+ <https://docs.taler.net/core/api-merchant.html#patch--private-templates-$TEMPLATE_ID>`__ —
+ update a template;
+- `DELETE /private/templates/$TEMPLATE_ID
+ <https://docs.taler.net/core/api-merchant.html#delete--private-templates-$TEMPLATE_ID>`__ —
+ remove a template.
+
+After any change, restart ``paivana-httpd`` so the new template
+list takes effect.
+
+
+.. _Paivana-ReverseProxy:
+
+Reverse proxy configuration
+---------------------------
+
+``paivana-httpd`` itself speaks plain HTTP on a UNIX socket (or a
+local TCP port). In production it is often run behind an Internet-facing
+reverse proxy that terminates TLS and forwards requests to the
+Paivana socket. This section gives minimal working examples for
+both Nginx and Apache. The same approach is used for the merchant
+backend; see the merchant manual's
+:ref:`reverse-proxy-configuration` section for additional
+discussion.
+
+The examples assume the public domain is ``example.com``,
+that ``paivana-httpd`` is socket-activated by the shipped
+``paivana-httpd.socket`` unit (so its listening socket lives at
+``/run/paivana/httpd/paivana-http.sock``) and that TLS termination
+happens at the reverse proxy.
+
+.. tab-set::
+
+ .. tab-item:: Nginx
+
+ Place the snippet below in
+ ``/etc/nginx/sites-available/example.com`` (the
+ Debian package installs a starter template under
+ ``/etc/nginx/sites-available/paivana``), then enable it via
+ ``ln -s ../sites-available/example.com
+ /etc/nginx/sites-enabled/`` and reload Nginx
+ (``systemctl reload nginx``).
+
+ .. code-block:: nginx
+
+ server {
+ listen 443 ssl http2;
+ listen [::]:443 ssl http2;
+ server_name example.com;
+
+ ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
+
+ location / {
+ proxy_pass http://unix:/run/paivana/httpd/paivana-http.sock;
+ proxy_redirect off;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $remote_addr;
+ proxy_set_header X-Forwarded-Host $host;
+ proxy_set_header X-Forwarded-Proto https;
+ }
+ }
+
+ server {
+ listen 80;
+ listen [::]:80;
+ server_name example.com;
+ return 301 https://$host$request_uri;
+ }
+
+ Make sure ``paivana-httpd`` is started with
+ ``--respect-forwarded-headers`` (see
+ :manpage:`paivana-httpd(1)`) so the ``X-Forwarded-For``
+ header set above is honoured.
+
+ .. tab-item:: Apache
+
+ Enable the required modules once:
+
+ .. code-block:: shell-session
+
+ # a2enmod proxy proxy_http headers ssl
+ # systemctl reload apache2
+
+ Then drop the following into
+ ``/etc/apache2/sites-available/example.com.conf``
+ (the Debian package installs a starter template at
+ ``/etc/apache2/sites-available/paivana.conf``), enable it
+ with ``a2ensite example.com`` and reload Apache.
+
+ .. code-block:: apacheconf
+
+ <VirtualHost *:80>
+ ServerName example.com
+ Redirect permanent / https://example.com/
+ </VirtualHost>
+
+ <VirtualHost *:443>
+ ServerName example.com
+
+ SSLEngine on
+ SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
+ SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
+
+ <Location "/">
+ ProxyPass "unix:/run/paivana/httpd/paivana-http.sock|http://example.com/"
+ ProxyPassReverse "unix:/run/paivana/httpd/paivana-http.sock|http://example.com/"
+ RequestHeader set X-Forwarded-Proto "https"
+ RequestHeader set X-Forwarded-Host "example.com"
+ </Location>
+ </VirtualHost>
+
+ As with Nginx, run ``paivana-httpd`` with
+ ``--respect-forwarded-headers`` so that the client IP is
+ taken from ``X-Forwarded-For``.
+
+If you operate both Paivana and the merchant backend on the same
+host, you typically expose them under two different hostnames (e.g.
+``example.com`` and ``backend.example.com``); the merchant
+backend must *never* be proxied through ``paivana-httpd``, only
+the upstream content service should be.
+
+
+Verifying the setup
+-------------------
+
+After completing the steps above, a quick smoke test is to request
+a paywalled URL with ``curl``:
+
+.. code-block:: shell-session
+
+ $ curl -i https://example.com/some-article
+
+An unpaid request should return ``HTTP/1.1 402 Payment Required`` together
+with a Taler-formatted paywall body containing the ``taler://pay/...`` URI of
+the freshly created order. Paying that order with any GNU Taler wallet (see
+the `Wallet documentation <https://docs.taler.net/wallet/>`__) and
+re-requesting the URL from the same client should then yield the upstream
+content unchanged. If the page is run in a browser, the client-side
+JavaScript should automatically trigger the required reload of the page after
+the wallet made the payment.
+
+For interactive debugging, ``paivana-httpd -n`` disables the
+paywall and turns the daemon into a transparent reverse proxy;
+this is useful to confirm that the network plumbing to the
+upstream service works before involving the merchant backend.
+See :manpage:`paivana-httpd(1)` for the other runtime flags.
Drupal integration