Compare commits

..
23 changed files with 306 additions and 537 deletions

View file

@ -1,27 +1,21 @@
.. _libervia-app-config:
Libervia Apps
=============
=================================
Libervia App Configuration (YAML)
=================================
Libervia Apps are third party software, embedded and integrated in Libervia, usually as
containers (Podman, Docker).
The Libervia application uses a YAML configuration file to define various aspects of the
application's behavior, environment, and settings.
Each app is defined by a YAML configuration file, which describes how to run it and which
settings are exposed. App files are named ``libervia_app_<name>.yaml`` and are discovered
from the app manager plugin directories.
The file will be parsed by `PyYAML`_ which is a full-featured YAML implementation. Check
its documentation for details.
Below is the documentation of each field of the YAML file.
Below is the documentation explaining the structure and functionality of each field in the
YAML file.
-----------------
=================
Root-Level Fields
-----------------
- ``name``: Human readable name of the application. The file name (without the
``libervia_app_`` prefix) is used when not specified.
- ``single_instance``: When ``True`` (default), only one instance of the application can
be started at a time, and its data are kept between restarts. When ``False``, each start
creates a new temporary instance directory, removed when the backend is unloaded.
=================
- ``type``: Specifies the type of the application. See :ref:`libervia-app_type`.
@ -33,18 +27,16 @@ Root-Level Fields
- ``expose``: Specifies configurations exposed to frontends or administrators. See :ref:`libervia-app_expose`.
------------------
==================
Detailed Sections
------------------
===================
.. _libervia-app_type:
type
^^^^
Currently, the only supported type is ``docker-compose``. The container engine is chosen
with the ``engine`` plugin setting: ``auto`` (default, Podman is tried first), ``docker``
or ``podman``.
Currently, the only supported type is ``docker-compose``.
.. _libervia-app_prepare:
@ -54,7 +46,7 @@ prepare
The ``prepare`` section specifies things like git repositories to be cloned before running the application.
Example
~~~~~~~
-------
Cloning the repository at `https://example.org/some/repository.git` to prepare the app:
.. code-block:: yaml
@ -73,7 +65,7 @@ The YAML key is the name of the file to be created, and the content specified wi
populate that file.
Example
~~~~~~~
-------
Creating a file named `settings-override.py` with the content `USE_X_FORWARDED_HOST = True`:
.. code-block:: yaml
@ -94,7 +86,7 @@ creates a ``docker-compose.override.yml`` file that will be merged with the defa
<https://docs.docker.com/compose/reference/#specifying-multiple-compose-files>`_.
Example
~~~~~~~
-------
Overriding the `ports` for the `example_app` service to expose port `8080`:
.. code-block:: yaml
@ -110,31 +102,9 @@ Overriding the `ports` for the `example_app` service to expose port `8080`:
Exposing Data
^^^^^^^^^^^^^
The ``expose`` section specifies the data exposed to users, frontends or administrators.
The following keys are used by the Web frontend:
url_prefix
The internal URL where the app will be served.
web_label
Label which will be used in the Web frontend.
web_url_path
Public facing URL path which will be used in the Web frontend to access the app.
web_external
If True, the Web frontend will open the app in a separated page instead of embedding
it.
front_url
Whole URL to access the app, useful when it has its own domain or subdomain. The
``https`` scheme is added if not specified.
Ports and generated passwords are exposed in dicts, respectively at the ``ports`` and
``passwords`` keys. Each entry of ``ports`` maps a name to the compose ``service`` and
``private`` (container) port, it is resolved to the exposed ``host:port`` when the app is
running. Each entry of ``passwords`` maps a name to the list of keys leading to the value
to expose in the application data.
``url_prefix`` and ``front_url`` can also be specified as a list of keys leading to the
value in the application data.
The `expose` section specifies the configurations that are exposed to users or frontends.
See :ref:`libervia-app_param` for a reference of some exposed data. Ports and generated
passwords are exposed in dicts, respectively at the ``ports`` and ``passwords`` key.
.. _libervia-app_yaml-tags:
@ -146,49 +116,32 @@ The following YAML tags can be used in the Libervia App configuration file:
.. _libervia-app_conf:
!libervia_conf
^^^^^^^^^^^^^^
++++++++++++++
Get a value from the Libervia configuration. It takes a list of 1 to 4 items:
Get a value from Libervia configuration. A list is expected with either:
- ``[NAME]``: the ``NAME`` option of the ``[common]`` section
- ``[SECTION, NAME]``: the ``NAME`` option of the section named ``SECTION``
- ``[SECTION, NAME, DEFAULT]``: use ``DEFAULT`` if the option is not set
- ``[SECTION, NAME, DEFAULT, FILTER]``: apply a filter to the value (see below)
- `name` of a config parameter
- `section` and `name` of a config parameter
- `section`, `name`, and `default` value of a config parameter
- `section`, `name`, `default` value, and `filter` (either `first` or `not`)
The ``application.<name>`` section refers to the configuration section of the
``<name>`` application (e.g. ``application.peertube``).
Filter options:
Available filters:
- `first`: get the first item of the value
- `not`: get the opposite value (to be used with booleans)
- ``first``: get the first item of the value
- ``not``: get the opposite value (to be used with booleans)
Examples
~~~~~~~~
Getting a value from an application configuration section:
Example
-------
Getting a value from Libervia configuration:
.. code-block:: yaml
!libervia_conf [application.peertube, hostname]
Getting the SMTP server from the ``[email]`` section (an empty default is
used when the option is not set):
.. code-block:: yaml
!libervia_conf [email, server, ""]
Getting the first email of the ``[common]`` section admin list (using the
``first`` filter):
.. code-block:: yaml
!libervia_conf [common, email_admins_list, "", first]
!libervia_conf [section, name, default]
.. _libervia-app_generate_pwd:
!libervia_generate_pwd
^^^^^^^^^^^^^^^^^^^^^^
+++++++++++++++++++++
Generate a password and store it in persistent data. If the password has already been generated previously, it is reused.
@ -199,7 +152,7 @@ Arguments:
- `size` (int): Size of the password to generate. Default to 30
Example
~~~~~~~
-------
Generating a password named ``some_password`` with a size of 32 characters:
.. code-block:: yaml
@ -209,23 +162,35 @@ Generating a password named ``some_password`` with a size of 32 characters:
.. _libervia-app_param:
!libervia_param
^^^^^^^^^^^^^^^
++++++++++++++
Get a parameter specified when starting the application. The value can be either the name
of the parameter to get, or a list as `[name, default_value]`.
Available parameters:
url_prefix
The internal URL where the app will be served, it is given by the Web frontend.
front_url_path
The URL path which will be used in the Web frontend to access the app.
The internal URL where the app will be served.
web_label
Label which will be used in Web frontend.
web_url_path
Public facing URL path which will be used in the web frontend to access the app.
web_external
If True, the web frontend will open the app in a separated page instead of embedding
it.
front_url
Whole URL to access the app. It is specified when the app has its own domain or
subdomain.
Example
~~~~~~~
-------
Getting a parameter:
.. code-block:: yaml
!libervia_param [url_prefix, /some_app]
.. _PyYAML: https://pyyaml.org/

View file

@ -1,8 +1,8 @@
.. _components:
==========
Components
==========
===================
Libervia Components
===================
Libervia can act as an XMPP server component, which can be seen as a generic plugin for
XMPP servers.
@ -38,20 +38,23 @@ OpenFire
Prosody
https://prosody.im/doc/components
.. Tigase documentation is down (2026-11-03, the whole docs.tigase.net site is 404),
uncomment once it's back:
.. Tigase
.. https://docs.tigase.net/en/latest/Tigase_Administration/Components/_Components.html#external-component-configuration
Tigase
https://docs.tigase.net/en/latest/Tigase_Administration/Components/_Components.html#external-component-configuration
Simplified Component Configuration
==================================
Most user will want to use this simplified configuration. To set up a component, you
simply need to create a configuration section named after the entry point, with dashes
replaced by underscores (e.g. the ``email-gateway`` entry point uses the
``[component.email_gateway]`` section), where you'll set the ``jid`` and ``secret``
options. Everything will be automatically configured on backend startup, and the
component will start automatically.
simply need to create a configuration section with ``[component.<entry_point>]`` where
you'll set the ``jid`` and ``secret`` options. Everything will be automatically configured
on backend startup, and the component will start automatically.
settings
--------
In ``[component.<entry_point>]``:
.. autopydantic_settings:: libervia.backend.tools.config.ComponentSettings
example
-------
@ -66,10 +69,6 @@ find that it's ``pubsub``, then you simply put in your configuration (usually
jid = "pubsub.example.org"
secret = "your_shared_secret"
To run the component in an isolated backend instance (e.g. as a dedicated systemd
service), see :ref:`installation_system`. To work with such an instance from the CLI,
use the ``-i/--isolation`` option (see :ref:`li_isolation`).
Manual Component Configuration
==============================
@ -109,14 +108,6 @@ wants to connect it immediately, she can use::
$ li profile connect -cp ap-gateway
Common Component Settings
=========================
Besides component specific options (see the component sections below), every
component supports these common options in its configuration section:
.. autopydantic_settings:: libervia.backend.tools.config.ComponentSettings
Available Components
====================
@ -148,7 +139,7 @@ entities (JIDs). Permissions can be set through Ad-Hoc Commands.
Configuration
~~~~~~~~~~~~~
All options are to be set in ``[component.file_sharing]`` section.
All options are to be set in ``[components.file_sharing]`` section.
``http_upload_port``
port to use for HTTP File Upload
@ -225,13 +216,7 @@ documentation to find how this must be done.
Configuration
~~~~~~~~~~~~~
All options are to be set in ``[component.ap_gateway]`` section:
.. autopydantic_settings:: libervia.backend.plugins.plugin_comp_ap_gateway.settings.APGatewaySettings
By default, the HTTP server uses HTTPS, thus a TLS certificate is required, either in the
``tls`` option or in the global ``[tls]`` section. If an HTTPS proxy already handles TLS,
use ``http_connection_type = "http"`` instead.
All options are to be set in ``[components.ap_gateway]`` section.
.. _ap-actor-from-xmpp:
@ -760,7 +745,7 @@ any kind of extra informations) while AP only indicates if one will attend or no
event.
.. _Mobilizon: https://mobilizon.org/
.. _Mobilizon: https://joinmobilizon.org/
Using the Component (for developers)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -827,8 +812,6 @@ All options are to be set in the ``[component.conferences]`` section.
.. _Galène project: https://galene.org/
.. _email-gateway:
Email Gateway
-------------

View file

@ -60,9 +60,6 @@ rst_prolog = """
# ones.
extensions = ["sphinx.ext.autodoc", "docstring", "sphinxcontrib.autodoc_pydantic"]
# do not show pydantic field constraints (custom validators are not meaningful here)
autodoc_pydantic_field_show_constraints = False
# Add any paths that contain templates here, relative to this directory.
templates_path = [".templates"]
@ -85,8 +82,7 @@ language = "en"
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
# the TUI is temporarily on hold and broken, its documentation is excluded
exclude_patterns = [".build", "Thumbs.db", ".DS_Store", "libervia-tui"]
exclude_patterns = [".build", "Thumbs.db", ".DS_Store"]
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = None
@ -124,7 +120,7 @@ html_static_path = [".static"]
# -- Options for HTMLHelp output ---------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = "LiberviaBackenddoc"
htmlhelp_basename = "SalutToidoc"
# -- Options for LaTeX output ------------------------------------------------
@ -148,13 +144,7 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(
master_doc,
"LiberviaBackend.tex",
"Libervia Backend Documentation",
"Libervia team",
"manual",
),
(master_doc, "SalutToi.tex", "Libervia Documentation", "Libervia team", "manual"),
]
@ -173,10 +163,10 @@ man_pages = [(master_doc, "saluttoi", "Libervia Documentation", [author], 1)]
texinfo_documents = [
(
master_doc,
"LiberviaBackend",
"Libervia Backend Documentation",
"SalutToi",
"Libervia Documentation",
author,
"LiberviaBackend",
"SalutToi",
"One line description of project.",
"Miscellaneous",
),

View file

@ -20,7 +20,7 @@ A backup is made of data *types*, included or excluded with ``-i/--include`` and
- ``private``: private storage: PEP-like client data, OMEMO crypto keys, PGP trust state
- ``roster``: contact lists (from the roster cache in the database, kept up to date on
every connection)
- ``pep``: client PEP nodes and items (nick, avatar metadata, …)
- ``pep``: client PEP nodes and items (nick, avatar metadata, ...)
- ``gpg``: GPG home per profile (public and secret keys)
- ``files``: shared files (the blobs of ``~/.sat/files/``)
- ``db``: full copy of the database (not portable, SQLite only)

View file

@ -4,23 +4,18 @@
How-To: Email Gateway
=====================
This document explains how to configure and use the Libervia Email Gateway component.
This document explains how to configure and use the Libervia Email Gateway component. **Note that the Libervia backend must be installed first** (see :ref:`installation`).
The Email Gateway component allows users to send and receive emails through XMPP clients
and connect mailing lists to XMPP forums. It connects to IMAP/SMTP servers using
credentials provided by users during registration.
For the supported features and other details, refer to the :ref:`email-gateway` section
of the components documentation.
The Email Gateway component allows users to send and receive emails through XMPP clients and convert mailing list to XMPP forums. It connects to IMAP/SMTP servers using credentials provided by users during registration.
Prerequisites
-------------
Before configuring the Email Gateway, ensure you have:
1. Libervia backend installed and working (see :ref:`installation`)
2. An XMPP server configured with component support (such as Prosody, Ejabberd,
Openfire, Tigase, MongooseIM, etc).
1. Libervia backend installed and running (see :ref:`installation`)
2. An XMPP server configured with component support (such as Prosody, Ejabberd, Openfire,
Tigase, MongooseIM, etc).
Configuration Steps
-------------------
@ -35,7 +30,7 @@ Configuration Steps
-- Add to /etc/prosody/prosody.cfg.lua
Component "email.example.org"
component_secret = "secret"
component_secret = "your_unique_secret"
*For Ejabberd:*
@ -51,26 +46,30 @@ Configuration Steps
module: ejabberd_service
hosts:
"email.example.org":
password: "secret"
password: "your_unique_secret"
Of course replace ``email.example.org`` with your actual domain and ``secret`` with a
secure random string (you may use ``openssl rand -hex 16`` to generate one).
**Important:** Replace `email.example.org` with your actual domain and `your_unique_secret` with a secure random string (you may use `openssl rand -hex 16` to generate).
2. **Configure the Email Gateway Component in Libervia**
2. **Create Libervia Profile for Email Gateway**
Add the following section to your Libervia configuration file (usually
``libervia.toml``), using the same JID and shared secret as the ones configured on
your XMPP server:
Use the Libervia CLI to create the gateway profile:
.. code-block:: toml
.. code-block:: bash
[component.email_gateway]
jid = "email.example.org"
secret = "secret"
$ li profile create email-gateway \
-j email.example.org \
-p "" \
-x "your_unique_secret" \
-C email-gateway \
-A
The component profile is then created automatically on backend startup, and the
component starts automatically. See :ref:`components` for details about the
components configuration.
This creates a profile named `email-gateway` with:
- JID: `email.example.org`
- No profile password (`-p ""`), to avoid password prompts during Libervia backend startup.
- XMPP server shared secret (`-x "your_unique_secret"`)
- Component entry point (`-C email-gateway`)
- Auto-connect enabled (`-A`)
3. **User Registration**
@ -82,7 +81,7 @@ Configuration Steps
.. code-block:: bash
$ li gateway register -c -p your_user_profile email.example.org
$ li gateway register -cp your_user_profile email.example.org
**Registration Fields**
@ -118,16 +117,55 @@ Configuration Steps
SMTP Password
Password for SMTP authentication
.. note::
**Important Notes**
- Ensure your IMAP/SMTP servers are accessible from the Libervia backend
- The IMAP server must support the IDLE extension for real-time notifications
- Ensure your IMAP/SMTP servers are accessible from the Libervia backend
- The IMAP server must support the IDLE extension for real-time notifications
Configuration
-------------
Besides the component configuration described above, no other configuration is needed:
the gateway uses user-specific data provided during registration.
The Email Gateway uses user-specific registration data - **no additional configuration is needed** in Libervia's main config file. All settings are stored per-user during registration.
Supported Features
------------------
- **Normal Email Conversion**: Standard email messages are converted to XMPP chat
messages. Each email appears as a regular message in the user's XMPP client.
- **Mailing List Handling**: Mailing list emails are converted to PubSub blog nodes
(microblogging) using XEP-0277:
* Each mailing list becomes a separate pubsub node
* Each email in the list becomes a blog item
* Replies to specific topics appear as comments to the corresponding item
* Title tags within square brackets (e.g., `[tag]`) are converted to blog categories
* Only the recipient JID associated with the mailing list can access the pubsub node by
default.
- **Extended Addressing Support**: Properly handles email addressing metadata (To, CC,
BCC) via XEP‑0033, converting it into XMPP addressing information for correct delivery.
This allows multi‑party discussions without relying on group chat (XEP‑0045 MUC), which
can be tricky to implement in email‑based communication.
- **Header Conversion**: Converts important email headers to XMPP metadata, such as:
* Keywords → XMPP "Keywords" header (XEP-0131)
* Importance → XMPP "Urgency" header (XEP-0131)
- **Message Cleaning**: Automatically detects noisy elements in mailing list emails
(quoted replies, signatures) and tags them with a CSS class named "noise" so XMPP
clients can selectively hide or visually distinguish these parts of the message.
- **Attachment Management**: Allows users to retrieve and delete email attachments via
PubSub File Sharing (XEP-0498).
.. note::
Attachments cannot yet be sent with messages (this
functionality is in development).
- **End-to-End Encryption**: Supports Gateway Relayed Encryption (not yet an official XEP)
for encrypted email messages. Requires XMPP clients that implement this specification.
Troubleshooting
---------------
@ -140,33 +178,37 @@ Troubleshooting
- Solution
* - XMPP component connection failed
- Verify the shared secret matches on **both** Libervia and the XMPP server.
The component JID must *exactly* match the JID configured on the XMPP server
(e.g. "email.example.org" in Prosody must match the JID you set in the Libervia
configuration).
The component JID must *exactly* match the JID configured on the XMPP server (e.g.
“email.example.org” in Prosody must match the JID you set when creating the
Libervia profile).
* - User registration fails
- * Check the IMAP credentials and make sure the IMAP server is reachable from the
- * Check the IMAP/SMTP credentials and make sure the servers are reachable from the
Libervia host.
* Verify that the IMAP server supports the **IDLE** extension (required for
real-time notifications).
* If you use self-signed certificates, add them to the system trust store.
real‑time notifications).
* If you use self‑signed certificates, add them to the system trust store.
* - Email not received
- * Confirm the user's email credentials (you can re-register to check).
- * Confirm the user’s email credentials (you can re-register to check).
* Check Libervia Backend logs to ensure that the IMAP connection succeeded.
* Verify that messages are arriving in the user's INBOX.
* - Email not sent
- * Check the SMTP credentials and make sure the SMTP server is reachable from the
Libervia host.
* Check the Libervia backend logs for the sending error.
* Verify that messages are arriving in the user’s INBOX.
* - “DBus not launched” error
- Follow :ref:`installation_dbus` instructions for headless servers – the
Libervia backend relies on D‑Bus to run.
* - Attachments not appearing
- The Email Gateway can *retrieve* attachments, but does not yet deliver them with
outgoing messages (feature work in progress).
* - Encrypted emails not working
- "Gateway Relayed Encryption" is a work-in-progress extension (not yet an official
XEP). Use a client that implements this specification.
- “Gateway Relayed Encryption” is a work‑in‑progress extension (not yet an official XEP).
Use a client that implements this specification.
* - Mailing lists not converted
- Verify that the incoming e-mail contains a **valid "List-ID"** header. The value
- Verify that the incoming e‑mail contains a **valid “List‑ID”** header. The value
must match the format expected for PubSub node creation.
.. note::
The Email Gateway is under active development. Some features may have limitations.
See Also
--------
- For Libervia components overview: :ref:`components`

View file

@ -11,7 +11,7 @@ Libervia-specific sections, so a backup remains readable by future Libervia vers
A backup is made of data *types*, selected with ``-i/--include`` and ``-e/--exclude``
(repeatable): ``params``, ``private``, ``roster``, ``pep``, ``gpg``, ``files`` and ``db``
(full copy of the database, included by default on export). A backup is complete by
default (passwords, OMEMO keys, GPG secret keys…), so the archive should be kept
default (passwords, OMEMO keys, GPG secret keys...), so the archive should be kept
private: it is written with mode 0600, and ``--encrypt`` can be used for untrusted
storage.

View file

@ -38,7 +38,7 @@ The common arguments for profile are:
history of your shell. If the profile password is needed and not specified, it will be
prompted (with echo disabled). If you are on a shared machine or if anybody can access
your shell history at some point, you should either connect first your profile with an
other frontend (Libervia TUI for instance), or avoid the ``--pwd`` option and use the
other frontend (Primitivus for instance), or avoid the ``--pwd`` option and use the
prompt instead.
.. _pubsub_common:

View file

@ -1,6 +1,6 @@
===========================
==========================
gateway: Gateway management
===========================
==========================
``gateway`` command allows you to list, register, and unregister from gateways (usually used to access other networks like ActivityPub or Email) on an XMPP server.

View file

@ -37,29 +37,37 @@ After the commands come the options. For instance if you want to send a message,
get the available options with ``--help`` as explained above::
$ li message send --help
usage: li message send [-h] [-p PROFILE] [--pwd PASSWORD] [-c]
usage: li message send [-h] [-p PROFILE] [--pwd PASSWORD] [-c] [-l LANG] [-s]
[-n] [-S SUBJECT] [-L SUBJECT_LANG]
[-t {chat,error,groupchat,headline,normal,auto}]
[-e ALGORITHM] [--encrypt-noreplace] […]
[-e ALGORITHM] [--encrypt-noreplace] [-x | -r]
jid
positional arguments:
jid the destination jid
options:
optional arguments:
-h, --help show this help message and exit
-p, --profile PROFILE
-p PROFILE, --profile PROFILE
Use PROFILE profile key (default: @DEFAULT@)
--pwd PASSWORD Password used to connect profile, if necessary
-c, --connect Connect the profile before doing anything else
-t, --type {chat,error,groupchat,headline,normal,auto}
-l LANG, --lang LANG language of the message
-s, --separate separate xmpp messages: send one message per line
instead of one message alone.
-n, --new-line add a new line at the beginning of the input (usefull
for ascii art ;))
-S SUBJECT, --subject SUBJECT
subject of the message
-L SUBJECT_LANG, --subject_lang SUBJECT_LANG
language of subject
-t {chat,error,groupchat,headline,normal,auto}, --type {chat,error,groupchat,headline,normal,auto}
type of the message
-e, --encrypt ALGORITHM
-e ALGORITHM, --encrypt ALGORITHM
encrypt message using given algorithm
--encrypt-noreplace don't replace encryption algorithm if another one is
--encrypt-noreplace don't replace encryption algorithm if an other one is
already used
[…]
The output above has been shortened: use ``--help`` to see the full list of options.
-x, --xhtml XHTML body
If you want to send a message to, say, ``pierre@example.net``, and encrypt it with OMEMO,
just do the following::
@ -69,25 +77,6 @@ just do the following::
(note that with OMEMO, you need to have previously validated fingerprint of your contact
for this to work).
.. _li_isolation:
Isolation
---------
Several Libervia instances can run side by side, each with its own data, this is called
isolation (see :ref:`configuration_isolation` for details). The instance to work with is
selected with the ``-i/--isolation NAME`` option, which must be used before the command:
.. code-block:: console
$ li -i pubsub profile list
.. note::
If the ``LIBERVIA_ISOLATION`` environment variable is already set, this option is not
needed, unless you want to work with another instance (the option takes priority over
the environment variable).
The different commands are explained in dedicated sections.
.. toctree::

View file

@ -1,26 +1,26 @@
.. _libervia-tui-documentation:
.. _primitivus-documentation:
============
Libervia TUI
============
==========
Primitivus
==========
``Libervia TUI`` is the TUI (Terminal User Interface) frontend of Libervia
``Primitivus`` is the TUI (Terminal User Interface) frontend of Libervia
Overview
========
``Libervia TUI`` is a text based frontend. It is specially adapted for systems without
``Primitivus`` is a text based frontend. It is specially adapted for systems without
graphical environments (e.g. servers), for low bandwidth remote shells (e.g. ssh) or for
people who like straightforward interfaces without distracting images or animations.
For the moment, Libervia TUI implements one 2 one chat, group chat (also called *MUC* for
For the moment, Primitivus implements one 2 one chat, group chat (also called *MUC* for
Multi-Users Chat), and some related features (end 2 end encryption, bookmarks, gateways
interaction, file sending, etc.).
Usage
=====
Libervia TUI is modal (vi-like), one can switch from one mode to another
Primitivus is modal (vi-like), one can switch from one mode to another
in the same way as in vi/vim:
- From any mode, press ``[Esc]`` to switch to normal mode.
@ -28,7 +28,7 @@ in the same way as in vi/vim:
- From normal mode, press ``i`` to switch to insert mode – the one you
use to write messages to your contacts.
Libervia TUI can be handled either with the mouse, in a very intuitive way, or with the
Primitivus can be handled either with the mouse, in a very intuitive way, or with the
keyboard. Below, the keyboard shortcuts are explained.
Keyboard handling
@ -46,8 +46,8 @@ Main keys
``[Tab]`` and/or ``CTRL + up/down arrows``
change focus
``CTRL + X``
quit Libervia TUI
``CTRL + X``
quit Primitivus
``CTRL + F, 1`` or ``CTRL + F, M``
focus on the menu
``CTRL + F, 2`` or ``CTRL + F, B``
@ -128,12 +128,12 @@ The following commands must be typed in command mode (type ``[Esc]`` to switch t
mode, then the ``:`` at the beginning of the command will switch to command mode).
``:quit``
quit Libervia TUI
quit Primitivus
``:messages``
display log messages (see also the logging configuration)
..
FIXME: :presence and :status are currently disabled in Libervia TUI
FIXME: :presence and :status are currently disabled in Primitivus
``:presence [status]``
set your presence status. Invoked without argument, a pop-up will allow you to choose your presence status; otherwise, you can use the following arguments:
- ``online``

View file

@ -113,25 +113,24 @@ The `official Libervia website`_ is made with Libervia web framework.
.. _Jinja2: http://jinja.pocoo.org/
.. _official Libervia website: https://salut-a-toi.org
Libervia TUI
------------
Libervia TUI (Primitivus)
-------------------------
Libervia TUI is the Terminal User Interface. In other words, it works in console and is
intended for people at ease with it. Its text only interface has several advantages: you
get rid of many distracting things (like images), it works without graphical environment
installed (which is often the case on servers) and it works on distant shell (like `ssh`_)
while staying gentle with your bandwidth.
Libervia TUI (alias ``Primitivus``) is the Terminal User Interface. In other words, it
works in console and is intended for people at ease with it. Its text only interface has
several advantages: you get rid of many distracting things (like images), it works without
graphical environment installed (which is often the case on servers) and it works on
distant shell (like `ssh`_) while staying gentle with your bandwidth.
Libervia TUI is shipped with the backend, so it should be always available once Libervia
is installed (but some distributions may provide it separately).
Primitivus is shipped with the backend, so it should be always available once Libervia is
installed (but some distributions may provide it separately).
.. The TUI is temporarily on hold and broken, its documentation is not
.. included in the build for the moment.
You can check :doc:`Libervia TUI documentation <libervia-tui/index>` for more details.
.. _ssh: https://en.wikipedia.org/wiki/Secure_Shell
Libervia CLI
------------
Libervia CLI (jp)
-----------------
Libervia CLI (alias ``li`` or ``jp``) is the Command Line Interface. It's a
powerful tool which allows to do nearly everything you can do with other frontends.

View file

@ -145,7 +145,7 @@ class MiscAccount(object):
host.bridge.add_method(
"credentials_xmpp_connect",
".plugin",
in_sign="ssb",
in_sign="ss",
out_sign="b",
method=self.credentials_xmpp_connect,
async_=True,
@ -293,11 +293,11 @@ class MiscAccount(object):
# there is no email when an existing XMPP account is used
body = f"New account created on {domain}: {profile} [{email or '<no email>'}]"
d_admin = sat_email.send_email(
self.get_email_conf(),
self.host.memory.config,
admins_emails,
subject,
body,
email_settings=self.get_email_conf(),
)
admins_emails_txt = ", ".join(["<" + addr + ">" for addr in admins_emails])
@ -326,13 +326,7 @@ class MiscAccount(object):
# XXX: this will not fail when the email address doesn't exist
# FIXME: check email reception to validate email given by the user
# FIXME: delete the profile if the email could not been sent?
d_user = sat_email.send_email(
self.host.memory.config,
[email],
subject,
body,
email_settings=self.get_email_conf(),
)
d_user = sat_email.send_email(self.get_email_conf, [email], subject, body)
d_user.addCallbacks(
lambda __: log.debug(
"Account creation confirmation sent to <{}>".format(email)
@ -695,13 +689,12 @@ class MiscAccount(object):
d.addCallbacks(deleted, errback)
return d
def credentials_xmpp_connect(self, jid_s, password, send_email=True):
def credentials_xmpp_connect(self, jid_s, password):
"""Create and connect a new SàT profile using the given XMPP credentials.
Re-use given JID and XMPP password for the profile name and profile password.
@param jid_s (unicode): JID
@param password (unicode): XMPP password
@param send_email (bool): send the administrator notification email
@return Deferred(bool)
@raise exceptions.PasswordError, exceptions.ConflictError
"""
@ -723,8 +716,7 @@ class MiscAccount(object):
)
def connected(result):
if send_email:
self.send_emails(None, profile=jid_s)
self.send_emails(None, profile=jid_s)
return result
def remove_profile(

View file

@ -34,7 +34,6 @@ def send_email(
subject: str = "",
body: str = "",
from_email: str | None = None,
email_settings: tools_config.EmailSettings | None = None,
) -> defer.Deferred:
"""Send an email using Libervia configuration
@ -44,14 +43,12 @@ def send_email(
@param subject: subject of the message
@param body: body of the message
@param from_email: address of the sender
@param email_settings: ``EmailSettings`` to use, default to ``config.email``
@return:Same as smtp.sendmail.
"""
if isinstance(to_emails, str):
to_emails = to_emails.split()
settings = email_settings or config.email
email_host = settings.server
email_from = from_email or settings.from_
email_host = config.email.server
email_from = from_email or config.email.from_
# we suppose that email domain and XMPP domain are identical
domain = config.common.xmpp_domain
@ -63,12 +60,12 @@ def send_email(
if email_from is None:
email_from = "no_reply@" + domain
email_sender_domain = settings.sender_domain or domain
email_port = settings.port
email_username = settings.username
email_password = settings.password
email_auth = settings.auth
email_starttls = settings.starttls
email_sender_domain = config.email.sender_domain or domain
email_port = config.email.port
email_username = config.email.username
email_password = config.email.password
email_auth = config.email.auth
email_starttls = config.email.starttls
msg = MIMEText(body, "plain", "UTF-8")
msg["Subject"] = subject

View file

@ -349,12 +349,7 @@ class QuickApp:
log.warning(_("Can't retrieve encryption plugins: {msg}").format(msg=failure_))
def on_bridge_connected(self):
d = self.bridge.ready_get()
d.addErrback(self._ready_get_eb)
d.addCallback(lambda __: self.on_backend_ready())
def _ready_get_eb(self, failure_):
log.error("Can't check backend readiness: {msg}".format(msg=failure_))
self.bridge.ready_get(self.on_backend_ready)
def _bridge_cb(self):
self.register_signal("connected")

View file

@ -1,118 +0,0 @@
#!/usr/bin/env python3
# Libervia: an XMPP client
# Copyright (C) 2009-2026 Jérôme Poisson (goffi@goffi.org)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Adapte the legacy callback/errback bridge API to bridges"""
import asyncio
import inspect
from typing import Any, Callable
from twisted.internet import defer
from twisted.python.failure import Failure
from libervia.backend.core.log import getLogger
log = getLogger(__name__)
class QuickBridgeAdapter:
"""Adapt legacy callback/errback to bridges
@param bridge: bridge instance
"""
def __init__(self, bridge: Any) -> None:
self._bridge = bridge
def __getattr__(self, name: str) -> Any:
attr = getattr(self._bridge, name)
if not callable(attr):
return attr
def wrapper(*args: Any, **kwargs: Any) -> Any:
callback = kwargs.pop("callback", None)
errback = kwargs.pop("errback", None)
result = attr(*args, **kwargs)
if inspect.iscoroutine(result):
result = asyncio.ensure_future(result)
if isinstance(result, asyncio.Future):
# async bridges (e.g. AIOBridge) return asyncio futures
result = defer.Deferred.fromFuture(result)
if (callback is None and errback is None) or not hasattr(
result, "addCallback"
):
return result
if callback is not None:
result.addCallback(callback)
if errback is not None:
result.addErrback(errback)
return result
return wrapper
def register_signal(self, name: str, handler: Callable, iface: str = "core") -> None:
"""Register a signal handler, adapting sync handlers to async bridges
@param name: signal name
@param handler: signal handler, sync or async
@param iface: bridge interface ("core" or "plugin")
"""
if not inspect.iscoroutinefunction(handler):
async def async_handler(*args: Any,
_handler: Callable = handler) -> None:
_handler(*args)
handler = async_handler
try:
self._bridge.register_signal(name, handler, iface)
except AttributeError:
log.warning(f"Signal {name} is not available on the bridge")
def bridge_connect(self, callback: Callable | None = None,
errback: Callable | None = None) -> None:
"""Connect to the bridge
@param callback: called when the bridge is connected
@param errback: called with a twisted Failure on connection error
"""
async def _connect() -> None:
try:
await self._bridge.bridge_connect()
except Exception as e:
if errback is not None:
errback(Failure(e))
else:
if callback is not None:
callback()
try:
asyncio.get_running_loop()
except RuntimeError:
# the event loop is not running yet (frontends usually call connect_bridge()
# before starting their loop)
try:
loop = asyncio.get_event_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.call_soon(asyncio.ensure_future, _connect())
else:
asyncio.ensure_future(_connect())

View file

@ -233,16 +233,9 @@ class QuickProfileManager(object):
def refill_profiles(self):
"""Rebuild the list of profiles"""
self.host.bridge.profiles_list_get(
callback=self._profiles_loaded,
errback=self._profiles_list_eb,
)
def _profiles_loaded(self, profiles):
self.set_profiles(sorted(profiles))
def _profiles_list_eb(self, failure_):
log.error("Can't get profiles list: {msg}".format(msg=failure_))
profiles = self.host.bridge.profiles_list_get()
profiles.sort()
self.set_profiles(profiles)
## Method which must be implemented by frontends ##

View file

@ -28,10 +28,9 @@ from libervia.backend.core import log as logging
log = logging.getLogger(__name__)
from libervia.backend.tools import config as tools_config
import urwid
from urwid import is_wide_char
from urwid.util import is_wide_char
from urwid_satext import sat_widgets
from libervia.frontends.quick_frontend.quick_app import QuickApp
from libervia.frontends.quick_frontend import quick_bridge
from libervia.frontends.quick_frontend import quick_utils
from libervia.frontends.quick_frontend import quick_chat
from libervia.tui.profile_manager import ProfileManager
@ -46,7 +45,6 @@ from libervia.tui.keys import action_key_map as a_key
from libervia.frontends.tools.misc import InputHistory
from libervia.backend.tools.common import dynamic_import
from libervia.frontends.tools import jid
import asyncio
import signal
import sys
@ -69,19 +67,10 @@ class TUISettings(tools_config.FrontendSettings, key="tui"):
## bridge handling
# we get bridge name from conf and initialise the right class accordingly
main_config = tools_config.parse_main_conf(ignore_cache=True)
bridge_name = next(iter(main_config.bridges)).type
bridge_name = next(iter(main_config.common.bridges)).type
if "dbus" not in bridge_name:
print("only D-Bus bridge is currently supported")
sys.exit(3)
# install the asyncio twisted reactor
try:
asyncio.get_event_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
from twisted.internet import asyncioreactor
asyncioreactor.install()
class EditBar(sat_widgets.ModalEdit):
@ -349,7 +338,9 @@ class LiberviaTUIApp(QuickApp, InputHistory):
AVATARS_HANDLER = False
def __init__(self):
bridge_module = dynamic_import.import_bridge(bridge_name)
bridge_module = dynamic_import.import_bridge(
bridge_name, "libervia.frontends.bridge"
)
if bridge_module is None:
log.error("Can't import {} bridge".format(bridge_name))
sys.exit(3)
@ -357,18 +348,16 @@ class LiberviaTUIApp(QuickApp, InputHistory):
log.debug("Loading {} bridge".format(bridge_name))
QuickApp.__init__(
self,
# AIOBridge is the only bridge compatible with the asyncio loop
bridge_factory=lambda: quick_bridge.QuickBridgeAdapter(
bridge_module.AIOBridge()
),
bridge_factory=bridge_module.bridge,
xmlui=xmlui,
check_options=quick_utils.check_options,
connect_bridge=False,
)
self.tui_settings = main_config.get_settings(TUISettings)
## main loop setup ##
# the new D-Bus bridge is twisted based (txdbus)
event_loop = urwid.TwistedEventLoop
event_loop = (
urwid.GLibEventLoop if "dbus" in bridge_name else urwid.TwistedEventLoop
)
self.loop = urwid.MainLoop(
urwid.SolidFill(),
C.PALETTE,
@ -384,10 +373,6 @@ class LiberviaTUIApp(QuickApp, InputHistory):
def on_bridge_connected(self):
##misc setup##
self._version = None
self.bridge.version_get(
callback=self._version_get_cb, errback=lambda __: None
)
self._visible_widgets = set()
self.notif_bar = sat_widgets.NotificationBar()
urwid.connect_signal(self.notif_bar, "change", self.on_notification)
@ -415,9 +400,6 @@ class LiberviaTUIApp(QuickApp, InputHistory):
self.loop.widget = self.main_widget = ProfileManager(self)
self.post_init()
def _version_get_cb(self, version):
self._version = version
@property
def visible_widgets(self):
return self._visible_widgets
@ -597,7 +579,7 @@ class LiberviaTUIApp(QuickApp, InputHistory):
self.__saved_overlay = None
elif (
input_ == a_key["DEBUG"] and ".dev0" in (self._version or "")
input_ == a_key["DEBUG"] and ".dev0" in self.bridge.version_get()
): # Debug only for dev versions
self.debug()
elif input_ == a_key["CONTACTS_HIDE"]: # user wants to (un)hide the contact lists
@ -632,9 +614,6 @@ class LiberviaTUIApp(QuickApp, InputHistory):
def add_menus(self, menu, type_filter, menu_data=None):
"""Add cached menus to instance
The menus are retrieved asynchronously from the bridge.
@param menu: sat_widgets.Menu instance
@param type_filter: menu type like is sat.core.sat_main.import_menu
@param menu_data: data to send with these menus
@ -644,24 +623,16 @@ class LiberviaTUIApp(QuickApp, InputHistory):
def add_menu_cb(callback_id):
self.action_launch(callback_id, menu_data, profile=self.current_profile)
def menus_get_cb(menus):
for id_, type_, path, path_i18n, extra in menus: # TODO: manage extra
if type_ != type_filter:
continue
if len(path) != 2:
raise NotImplementedError(
"Menu with a path != 2 are not implemented yet"
)
menu.add_menu(
path_i18n[0], path_i18n[1], lambda dummy, id_=id_: add_menu_cb(id_)
)
def menus_get_eb(failure):
log.error("Can't get menus: {}".format(failure))
self.bridge.menus_get(
"", C.NO_SECURITY_LIMIT, callback=menus_get_cb, errback=menus_get_eb
)
for id_, type_, path, path_i18n, extra in self.bridge.menus_get(
"", C.NO_SECURITY_LIMIT
): # TODO: manage extra
if type_ != type_filter:
continue
if len(path) != 2:
raise NotImplementedError("Menu with a path != 2 are not implemented yet")
menu.add_menu(
path_i18n[0], path_i18n[1], lambda dummy, id_=id_: add_menu_cb(id_)
)
def _build_menu_roller(self):
menu = sat_widgets.Menu(self.loop)
@ -1045,25 +1016,19 @@ class LiberviaTUIApp(QuickApp, InputHistory):
def on_join_room_request(self, menu):
"""User wants to join a MUC room"""
def show_join_dialog(default_service):
pop_up_widget = sat_widgets.InputDialog(
_("Entering a MUC room"),
_("Please enter MUC's JID"),
default_txt=default_service,
ok_cb=self.on_join_room,
)
pop_up_widget.set_callback(
"cancel", lambda dummy: self.remove_pop_up(pop_up_widget)
)
self.show_pop_up(pop_up_widget)
self.bridge.muc_get_default_service(
callback=show_join_dialog, errback=lambda __: show_join_dialog("")
pop_up_widget = sat_widgets.InputDialog(
_("Entering a MUC room"),
_("Please enter MUC's JID"),
default_txt=self.bridge.muc_get_default_service(),
ok_cb=self.on_join_room,
)
pop_up_widget.set_callback(
"cancel", lambda dummy: self.remove_pop_up(pop_up_widget)
)
self.show_pop_up(pop_up_widget)
def on_about_request(self, menu):
self.alert(_("About"), C.APP_NAME + " v" + (self._version or ""))
self.alert(_("About"), C.APP_NAME + " v" + self.bridge.version_get())
# MISC CALLBACKS#

View file

@ -174,13 +174,10 @@ class Card(TarotCard):
return CardDisplayer(self)
class Table(urwid.Widget):
class Table(urwid.FlowWidget):
"""Represent the cards currently on the table"""
_sizing = frozenset([urwid.Sizing.FLOW])
def __init__(self):
super().__init__()
self.top = self.left = self.bottom = self.right = None
def put_card(self, location, card):
@ -316,7 +313,7 @@ class TarotGame(QuickTarotGame, urwid.WidgetWrap):
if not winners and not loosers:
title = _("Draw game")
else:
title = _("You win \\o/") if self.player_nick in winners else _("You loose :(")
title = _("You win \o/") if self.player_nick in winners else _("You loose :(")
form = xmlui.create(
self.parent.host,
xml_data,

View file

@ -17,10 +17,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
try:
import dbus
except ImportError:
dbus = None
import dbus
class Notify(object):

View file

@ -37,11 +37,11 @@ class ProfileManager(QuickProfileManager, urwid.WidgetWrap):
self.pass_wid = sat_widgets.Password(_("Password:"), align="center")
style = ["no_first_select"]
profiles = host.bridge.profiles_list_get()
profiles.sort()
self.list_profile = sat_widgets.List(
[], style=style, align="center", on_change=self.on_profile_change
profiles, style=style, align="center", on_change=self.on_profile_change
)
# the profiles list is retrieved asynchronously
self.refill_profiles()
# new & delete buttons
buttons = [

View file

@ -54,31 +54,24 @@ class Progress(urwid.WidgetWrap, quick_widgets.QuickWidget):
def progress_cb(self, loop, data):
progress_id, message, profile = data
data = self.host.bridge.progress_get(progress_id, profile)
pbar = self.progress_dict[(progress_id, profile)]["progress"]
if data:
if self.progress_dict[(progress_id, profile)]["state"] == "init":
# first answer, we must construct the bar
self.progress_dict[(progress_id, profile)]["state"] = "progress"
pbar.done = float(data["size"])
def progress_get_cb(data):
entry = self.progress_dict.get((progress_id, profile))
if entry is None:
# the bar has been removed in the meantime
return
pbar = entry["progress"]
if data:
if entry["state"] == "init":
# first answer, we must construct the bar
entry["state"] = "progress"
pbar.done = float(data["size"])
pbar.set_completion(float(data["position"]))
pbar.set_completion(float(data["position"]))
self.update_not_bar()
else:
if self.progress_dict[(progress_id, profile)]["state"] == "progress":
self.progress_dict[(progress_id, profile)]["state"] = "done"
pbar.set_completion(pbar.done)
self.update_not_bar()
else:
if entry["state"] == "progress":
entry["state"] = "done"
pbar.set_completion(pbar.done)
self.update_not_bar()
return
return
loop.set_alarm_in(0.2, self.progress_cb, (progress_id, message, profile))
self.host.bridge.progress_get(progress_id, profile, callback=progress_get_cb)
loop.set_alarm_in(0.2, self.progress_cb, (progress_id, message, profile))
def _remove_bar(self, progress_id, profile):
wid = self.progress_dict[(progress_id, profile)]["full"]

View file

@ -45,44 +45,34 @@ class StatusBar(urwid.Columns):
urwid.connect_signal(self.status, "click", self.on_status_click)
def on_presence_click(self, sender=None):
def show_presence_popup(connected):
if not connected:
return
options = [
commonConst.PRESENCE[presence] for presence in commonConst.PRESENCE
]
list_widget = sat_widgets.GenericList(
options=options,
option_type=sat_widgets.ClickableText,
on_click=self.on_change,
)
decorated = sat_widgets.LabelLine(
list_widget, sat_widgets.SurroundedText(_("Set your presence"))
)
self.host.show_pop_up(decorated)
# FIXME: manage multi-profiles
self.host.bridge.is_connected(
self.host.current_profile, callback=show_presence_popup
if not self.host.bridge.is_connected(
self.host.current_profile
): # FIXME: manage multi-profiles
return
options = [commonConst.PRESENCE[presence] for presence in commonConst.PRESENCE]
list_widget = sat_widgets.GenericList(
options=options,
option_type=sat_widgets.ClickableText,
on_click=self.on_change,
)
decorated = sat_widgets.LabelLine(
list_widget, sat_widgets.SurroundedText(_("Set your presence"))
)
self.host.show_pop_up(decorated)
def on_status_click(self, sender=None):
def show_status_popup(connected):
if not connected:
return
pop_up_widget = sat_widgets.InputDialog(
_("Set your status"),
_("New status"),
default_txt=self.status.get_text(),
cancel_cb=lambda _: self.host.remove_pop_up(),
ok_cb=self.on_change,
)
self.host.show_pop_up(pop_up_widget)
# FIXME: manage multi-profiles
self.host.bridge.is_connected(
self.host.current_profile, callback=show_status_popup
if not self.host.bridge.is_connected(
self.host.current_profile
): # FIXME: manage multi-profiles
return
pop_up_widget = sat_widgets.InputDialog(
_("Set your status"),
_("New status"),
default_txt=self.status.get_text(),
cancel_cb=lambda _: self.host.remove_pop_up(),
ok_cb=self.on_change,
)
self.host.show_pop_up(pop_up_widget)
def on_change(self, sender=None, user_data=None):
new_value = user_data.get_text()

View file

@ -57,7 +57,7 @@ dependencies = [
"twisted[tls] >= 22.10",
"twomemo >= 1.0.0, < 2",
"txdbus",
"urwid >= 4.2.5, < 5",
"urwid >= 1.2.0, < 3",
"urwid-satext",
"xmlschema < 4.3.1",
"tx-xmpp>=0.15",