<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>draupnir &amp;mdash; julian</title>
    <link>https://wrily.foad.me.uk/tag:draupnir</link>
    <description>FOSS dev, self-hosting fan, Matrix, degoogling, small tech, indie tech, friendly tech for families and schools. Let&#39;s own our own identity &amp; data.</description>
    <pubDate>Thu, 30 Apr 2026 08:16:09 +0000</pubDate>
    <item>
      <title>Deploying Pantalaimon on my Matrix Test Rig</title>
      <link>https://wrily.foad.me.uk/deploying-pantalaimon-on-my-matrix-test-rig</link>
      <description>&lt;![CDATA[Last week I was setting up Draupnir on my matrix test rig, in order to become familiar with Draupnir deployment before I integrate it with PubHubs.&#xA;&#xA;Now I need to address end-to-end-encryption (E2EE). PubHubs exclusively uses encrypted matrix rooms, and Draupnir doesn&#39;t yet have E2EE functionality built-in. (Why is that? Moderation in public rooms is Draupnir&#39;s main use case, and for several reasons public matrix rooms are usually not encrypted. However PubHubs is different.)&#xA;&#xA;There is a generic solution for adding E2EE to a matrix bot, and it&#39;s called Pantalaimon, an &#34;E2EE aware proxy daemon for matrix clients.&#34; So this week I&#39;m setting up Pantalaimon.&#xA;!--more--&#xA;&#xA;The handy matrix-docker-ansible-deploy has no Pantalaimon role ... yet. I may be able to contribute one, but no promises. First I will make a stand-alone Pantalaimon setup role, in my quest to automate, with Ansible, as much as possible of the set-up.&#xA;&#xA;Simple Ansible Role for Pantalaimon&#xA;&#xA;I have made and published a simple Ansible role to run Pantalaimon. It consists of:&#xA;&#xA;default variables, with (minimal) documentation&#xA;main, build, install tasks&#xA;a pantalaimon.conf template&#xA;&#xA;I use the role in my playbook like this:&#xA;&#xA;  name: &#34;pantalaimon&#34;&#xA;    includerole: name=traxim.matrixansible.pantalaimon&#xA;    vars:&#xA;      pantalaimondockerimage: &#34;{{ mydockerregistryhost }}/pantalaimon:latest&#34;&#xA;      pantalaimondockernetwork: &#34;{{ matrixdockernetwork }}&#34;&#xA;      pantalaimonbasepath: /srv/PubHubs/pantalaimon&#xA;      pantalaimonloglevel: Debug&#xA;      pantalaimonhomeservername: example&#xA;      pantalaimonhomeserverurl: &#34;https://matrix.example.org&#34;&#xA;&#xA;My intention is to run Pantalaimon in a container, managed by Docker (alternatives like Podman should work similarly), on the target machine where the matrix server runs. Note that, in general, to minimise exposure of encrypted data, Pantalaimon should be run &#34;close to&#34; the bot or client that uses it. At least the unencrypted connection between the bot or client and Pantalaimon should be secured.&#xA;&#xA;There is no Pantalaimon docker image published by its creators, so my role builds it, and pushes it to my private registry. (You could delegate the building to a different machine such as localhost; that&#39;s commented out in tasks/main.yml in v1.1.0. Or, building on the target machine, you could avoid using a registry if you set mydockerregistryhost to localhost, I think; you might need to disable the &#39;push&#39; option in the build task as well.)&#xA;&#xA;Connecting Draupnir to Pantalaimon&#xA;&#xA;To connect Draupnir to Pantalaimon, I am going to show a playbook task that invokes the existing draupnir role. (Usually to set up a regular matrix system I run the &#39;matrix-docker-ansible-deploy&#39; playbook as a whole. Invoking a role individually from another playbook is working towards using it with PubHubs rather than a regular matrix system.)&#xA;&#xA;The key part here is to override the Draupnir config&#39;s homeserverUrl to point to Pantalaimon, while leaving the Draupnir config&#39;s rawHomeserverUrl pointing to the matrix server&#39;s public client-server API, as documented in Draupnir&#39;s example config.&#xA;&#xA;  name: &#34;draupnir&#34; &#xA;    includerole: name=playbooks-from/matrix-docker-ansible-deploy/roles/custom/matrix-bot-draupnir &#xA;    vars: &#xA;      matrixbotdraupniraccesstoken: &#34;{{ matrixdraupnirbotaccesstoken }}&#34;  # using value generated by login step earlier; alternatively read it from a vault-var&#xA;      matrixbotdraupnirmanagementroom: &#34;{{ matrixdraupnirmanagementroomid }}&#34;  # using value generated by room-creation step earlier; alternatively read it from an inventory var&#xA;      matrixbotdraupnirconfigurationextensionyaml: | &#xA;        homeserverUrl: &#34;http://pantalaimon:8009&#34; &#xA;        pantalaimon: &#xA;          use: true &#xA;          username: &#34;bot.draupnir-admin&#34; &#xA;          password: &#34;{{ matrixdraupnirbotpassword }}&#34;  # using an inventory vault-var&#xA;        protectAllJoinedRooms: true  # my preference&#xA;        commands: &#xA;          allowNoPrefix: true  # my preference&#xA;      matrixbotdraupnirsystemdrequiredserviceslist: &#xA;        docker.service &#xA;        matrix-synapse.service &#xA;        matrix-pantalaimon.service &#xA;&#xA;These vars would better be placed in the inventory than here. To use the role stand-alone like this we also need to set a few other base variables from matrix-docker-ansible-deploy, which we can do like this:&#xA;&#xA;  varsfiles:  # defined at the playbook level&#xA;    playbooks-from/matrix-docker-ansible-deploy/roles/galaxy/com.devture.ansible.role.playbookhelp/defaults/main.yml&#xA;    playbooks-from/matrix-docker-ansible-deploy/roles/galaxy/com.devture.ansible.role.systemddockerbase/defaults/main.yml&#xA;    playbooks-from/matrix-docker-ansible-deploy/roles/custom/matrix-base/defaults/main.yml&#xA;&#xA;  vars:  # defined at the task or block level&#xA;    matrixuseruid: 991  # to match our existing matrix installation&#xA;    matrixusergid: 991&#xA;    matrixhomeserverurl: &#34;https://matrix.example&#34;&#xA;&#xA;We have looked at a simple stand-alone Pantalaimon role. Now to integrate it in matrix-docker-ansible-deploy.&#xA;&#xA;M-D-A-D Playbook Role for Pantalaimon&#xA;&#xA;The first draft of a suitable role is published on TraxLab: roles/matrixpantalaimon.&#xA;&#xA;2023-10-19] I prepared [a merge request to the m-d-a-d playbook:&#xA;&#xA;  Pantalaimon role is activated by matrixpantalaimonenabled and connects to the homeserver by default. No further config is needed. Integration with Draupnir is included, activated by matrixbotdraupniruse_pantalaimon. No integration with Mjolnir or anything else. Alternatively Pantalaimon can be configured alone, and then whatever component is meant to talk to it, in or out of the playbook, may be configured separately/manually.&#xA;&#xA;... and Slavi has responded to me with a bunch of good review comments which I need to address.&#xA;&#xA;UPDATE 2023-12-21: Addressed those review comments.&#xA;&#xA;UPDATE 2024-03-29: This is now accepted and included in matrix-docker-ansible-deploy.&#xA;&#xA;---&#xA;&#xA;#matrix #awesomeFOSS #Draupnir #PubHubs&#xA;&#xA;!--more--&#xD;&#xA;----&#xD;&#xA;Follow/Feedback/Contact: RSS feed · Fedi follow this blog: @julian&amp;ZeroWidthSpace;@wrily.foad.me.uk · matrix me · Fedi follow me · email me · julian.foad.me.uk&#xD;&#xA;Donate: via Liberapay&#xD;&#xA;All posts &amp;copy; Julian Foad and licensed CC-BY-ND except quotes, translations, or where stated otherwise&#xD;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>Last week I was setting up Draupnir on my matrix test rig, in order to become familiar with Draupnir deployment before I <a href="https://wrily.foad.me.uk/pubhubs-moderation-tools">integrate it with PubHubs</a>.</p>

<p>Now I need to address end-to-end-encryption (E2EE). PubHubs exclusively uses encrypted matrix rooms, and Draupnir doesn&#39;t yet have E2EE functionality built-in. (Why is that? Moderation in public rooms is Draupnir&#39;s main use case, and for several reasons public matrix rooms are usually not encrypted. However PubHubs is different.)</p>

<p>There is a generic solution for adding E2EE to a matrix bot, and it&#39;s called <a href="https://github.com/matrix-org/pantalaimon">Pantalaimon</a>, an “E2EE aware proxy daemon for matrix clients.” So this week I&#39;m setting up Pantalaimon.
</p>

<p>The handy <a href="https://github.com/spantaleev/matrix-docker-ansible-deploy/">matrix-docker-ansible-deploy</a> has no Pantalaimon role ... yet. I may be able to contribute one, but no promises. First I will make a stand-alone Pantalaimon setup role, in my quest to automate, with Ansible, as much as possible of the set-up.</p>

<h2 id="simple-ansible-role-for-pantalaimon" id="simple-ansible-role-for-pantalaimon">Simple Ansible Role for Pantalaimon</h2>

<p>I have made and <a href="https://lab.trax.im/matrix/matrix-ansible/">published</a> a simple Ansible role to run Pantalaimon. It consists of:</p>
<ul><li><a href="https://lab.trax.im/matrix/matrix-ansible/-/blob/main/roles/pantalaimon/defaults/main.yml">default variables</a>, with (minimal) documentation</li>
<li><a href="https://lab.trax.im/matrix/matrix-ansible/-/blob/main/roles/pantalaimon/tasks/main.yml">main</a>, <a href="https://lab.trax.im/matrix/matrix-ansible/-/blob/main/roles/pantalaimon/tasks/build-pantalaimon.yml">build</a>, <a href="https://lab.trax.im/matrix/matrix-ansible/-/blob/main/roles/pantalaimon/tasks/install-pantalaimon.yml">install</a> tasks</li>
<li><a href="https://lab.trax.im/matrix/matrix-ansible/-/blob/main/roles/pantalaimon/templates/pantalaimon.conf.j2">a pantalaimon.conf template</a></li></ul>

<p>I use the role in my playbook like this:</p>

<pre><code class="language-yaml">  - name: &#34;pantalaimon&#34;
    include_role: name=trax_im.matrix_ansible.pantalaimon
    vars:
      pantalaimon_docker_image: &#34;{{ my_docker_registry_host }}/pantalaimon:latest&#34;
      pantalaimon_docker_network: &#34;{{ matrix_docker_network }}&#34;
      pantalaimon_base_path: /srv/PubHubs/pantalaimon
      pantalaimon_log_level: Debug
      pantalaimon_homeserver_name: example
      pantalaimon_homeserver_url: &#34;https://matrix.example.org&#34;
</code></pre>

<p>My intention is to run Pantalaimon in a container, managed by Docker (alternatives like Podman should work similarly), on the target machine where the matrix server runs. <em>Note that, in general, to minimise exposure of encrypted data, Pantalaimon should be run “close to” the bot or client that uses it. At least the unencrypted connection between the bot or client and Pantalaimon should be secured.</em></p>

<p>There is no Pantalaimon docker image published by its creators, so my role builds it, and pushes it to my private registry. (You could delegate the building to a different machine such as <code>localhost</code>; that&#39;s commented out in <code>tasks/main.yml</code> in v1.1.0. Or, building on the target machine, you could avoid using a registry if you set <code>my_docker_registry_host</code> to <code>localhost</code>, I think; you might need to disable the &#39;push&#39; option in the build task as well.)</p>

<h2 id="connecting-draupnir-to-pantalaimon" id="connecting-draupnir-to-pantalaimon">Connecting Draupnir to Pantalaimon</h2>

<p>To connect Draupnir to Pantalaimon, I am going to show a playbook task that invokes the existing draupnir role. (Usually to set up a regular matrix system I run the &#39;matrix-docker-ansible-deploy&#39; playbook as a whole. Invoking a role individually from another playbook is working towards using it with PubHubs rather than a regular matrix system.)</p>

<p>The key part here is to override the Draupnir config&#39;s <code>homeserverUrl</code> to point to Pantalaimon, while leaving the Draupnir config&#39;s <code>rawHomeserverUrl</code> pointing to the matrix server&#39;s public client-server API, as documented in <a href="https://github.com/the-draupnir-project/Draupnir/blob/main/config/default.yaml">Draupnir&#39;s example config</a>.</p>

<pre><code class="language-yaml">  - name: &#34;draupnir&#34; 
    include_role: name=playbooks-from/matrix-docker-ansible-deploy/roles/custom/matrix-bot-draupnir 
    vars: 
      matrix_bot_draupnir_access_token: &#34;{{ matrix_draupnir_bot_access_token }}&#34;  # using value generated by login step earlier; alternatively read it from a vault-var
      matrix_bot_draupnir_management_room: &#34;{{ matrix_draupnir_management_room_id }}&#34;  # using value generated by room-creation step earlier; alternatively read it from an inventory var
      matrix_bot_draupnir_configuration_extension_yaml: | 
        homeserverUrl: &#34;http://pantalaimon:8009&#34; 
        pantalaimon: 
          use: true 
          username: &#34;bot.draupnir-admin&#34; 
          password: &#34;{{ matrix_draupnir_bot_password }}&#34;  # using an inventory vault-var
        protectAllJoinedRooms: true  # my preference
        commands: 
          allowNoPrefix: true  # my preference
      matrix_bot_draupnir_systemd_required_services_list: 
        - docker.service 
        - matrix-synapse.service 
        - matrix-pantalaimon.service 
</code></pre>

<p>These vars would better be placed in the inventory than here. To use the role stand-alone like this we also need to set a few other base variables from matrix-docker-ansible-deploy, which we can do like this:</p>

<pre><code class="language-yaml">  vars_files:  # defined at the playbook level
    - playbooks-from/matrix-docker-ansible-deploy/roles/galaxy/com.devture.ansible.role.playbook_help/defaults/main.yml
    - playbooks-from/matrix-docker-ansible-deploy/roles/galaxy/com.devture.ansible.role.systemd_docker_base/defaults/main.yml
    - playbooks-from/matrix-docker-ansible-deploy/roles/custom/matrix-base/defaults/main.yml

  vars:  # defined at the task or block level
    matrix_user_uid: 991  # to match our existing matrix installation
    matrix_user_gid: 991
    matrix_homeserver_url: &#34;https://matrix.example&#34;
</code></pre>

<p>We have looked at a simple stand-alone Pantalaimon role. Now to integrate it in <a href="https://github.com/spantaleev/matrix-docker-ansible-deploy/">matrix-docker-ansible-deploy</a>.</p>

<h2 id="m-d-a-d-playbook-role-for-pantalaimon" id="m-d-a-d-playbook-role-for-pantalaimon">M-D-A-D Playbook Role for Pantalaimon</h2>

<p>The first draft of a suitable role is published on TraxLab: <a href="https://lab.trax.im/matrix/matrix-ansible/-/tree/main/roles/matrix_pantalaimon">roles/matrix_pantalaimon</a>.</p>

<p><em>[2023-10-19]</em> I prepared <a href="https://lab.trax.im/matrix/matrix-docker-ansible-deploy/-/merge_requests/1">a merge request</a> to the m-d-a-d playbook:</p>

<blockquote><p>Pantalaimon role is activated by <code>matrix_pantalaimon_enabled</code> and connects to the homeserver by default. No further config is needed. Integration with Draupnir is included, activated by <code>matrix_bot_draupnir_use_pantalaimon</code>. No integration with Mjolnir or anything else. Alternatively Pantalaimon can be configured alone, and then whatever component is meant to talk to it, in or out of the playbook, may be configured separately/manually.</p></blockquote>

<p>... and Slavi has responded to me with a bunch of good review comments which I need to address.</p>

<p><em>UPDATE 2023-12-21: Addressed those review comments.</em></p>

<p><em>UPDATE 2024-03-29: This is now <a href="https://matrix.org/blog/2024/03/29/this-week-in-matrix-2024-03-29/#matrix-docker-ansible-deploy-website">accepted</a> and <a href="https://github.com/spantaleev/matrix-docker-ansible-deploy/blob/master/docs/configuring-playbook-pantalaimon.md">included in matrix-docker-ansible-deploy</a>.</em></p>

<hr>

<p><a href="https://wrily.foad.me.uk/tag:matrix" class="hashtag"><span>#</span><span class="p-category">matrix</span></a> <a href="https://wrily.foad.me.uk/tag:awesomeFOSS" class="hashtag"><span>#</span><span class="p-category">awesomeFOSS</span></a> <a href="https://wrily.foad.me.uk/tag:Draupnir" class="hashtag"><span>#</span><span class="p-category">Draupnir</span></a> <a href="https://wrily.foad.me.uk/tag:PubHubs" class="hashtag"><span>#</span><span class="p-category">PubHubs</span></a></p>



<hr>

<p><em>Follow/Feedback/Contact:</em> <a href="https://wrily.foad.me.uk/feed/"><em>RSS feed</em></a> · <em>Fedi follow this blog: @julian​@wrily.foad.me.uk</em> · <a href="https://matrix.to/#/@julian:foad.me.uk" title="matrix Julian"><em>matrix me</em></a> · <a href="https://fed.foad.me.uk/%40julian%40fed.foad.me.uk" title="follow Julian"><em>Fedi follow me</em></a> · <a href="mailto:julian@foad.me.uk?subject=Wrily" title="email Julian"><em>email me</em></a> · <a href="https://julian.foad.me.uk/"><em>julian.foad.me.uk</em></a>
<em>Donate:</em> <a href="https://liberapay.com/julianfoad" title="Donate to Julian using Liberapay"><em>via Liberapay</em></a>
<em>All posts © Julian Foad and licensed <a href="https://creativecommons.org/licenses/by-nd/4.0/">CC-BY-ND</a> except quotes, translations, or where stated otherwise</em></p>
]]></content:encoded>
      <guid>https://wrily.foad.me.uk/deploying-pantalaimon-on-my-matrix-test-rig</guid>
      <pubDate>Thu, 28 Sep 2023 11:46:25 +0000</pubDate>
    </item>
    <item>
      <title>Deploying Draupnir on my Matrix Test Rig</title>
      <link>https://wrily.foad.me.uk/deploying-draupnir-on-my-matrix-test-rig</link>
      <description>&lt;![CDATA[This week I&#39;m setting up Draupnir on my matrix test rig, in order to become familiar with Draupnir deployment before I integrate it with PubHubs.&#xA;&#xA;Very glad to be able to use matrix-docker-ansible-deploy&#39;s Draupnir setup to automate the majority of the Draupnir deployment.&#xA;&#xA;I also want to automate, with Ansible, as much as possible of the set-up that is required before running that playbook. I aim to document here what I have done and open questions about it. The numbered steps here correspond to the manual instructions in that documentation linked above.&#xA;!--more--&#xA;&#xA;1. Register the bot account&#xA;&#xA;I register a matrix account for the bot, using a little Ansible role I wrote, calling it like this in my Ansible playbook (personal one, specific to this installation).&#xA;&#xA;  includerole: name=matrix-synapse-register-user&#xA;    vars:&#xA;      matrixsynapseuser:&#xA;        name:  &#34;{{ matrixdraupnirbotusername }}&#34;&#xA;        pw:    &#34;{{ matrixdraupnirbotpassword }}&#34;&#xA;        admin: &#34;{{ matrixdraupnirbotadmin }}&#34;&#xA;&#xA;defining those vars in my inventory file inventories/prod/hostvars/host/matrix-draupnir.yml:&#xA;&#xA;matrixdraupnirbotusername: &#34;bot.draupnir&#34;&#xA;matrixdraupnirbotpassword: !vault [... I use vault encoded values here ...]&#xA;matrixdraupnirbotadmin: false&#xA;&#xA;I published my matrix-synapse-register-user role. I thought I should, if there isn&#39;t already a more widely available alternative. It looks like this, in tasks/main.yml:&#xA;&#xA;---&#xA;name: &#34;register a matrix synapse user&#34;&#xA;  command:&#xA;    argv: &#34;{{ (cmdcheck if ansiblecheckmode else cmdreal) + args }}&#34;&#xA;  vars:&#xA;    cmdreal:&#xA;      /matrix/synapse/bin/register-user&#xA;    cmdcheck:&#xA;      echo&#xA;      &#34;Would run: /matrix/synapse/bin/register-user&#34;&#xA;    args:&#xA;      &#34;{{ matrixsynapseuser.name }}&#34;&#xA;      &#34;{{ matrixsynapseuser.pw }}&#34;&#xA;      &#34;{{ &#39;1&#39; if matrixsynapseuser.admin|default(false) else &#39;0&#39; }}&#34;&#xA;  checkmode: false&#xA;  register: result&#xA;  changedwhen: result.rc == 0&#xA;  failedwhen: result.rc != 0 and &#39;User ID already taken.&#39; not in result.stdout&#xA;  # if already registered, errors with (on stdout):&#xA;  #   Sending registration request...&#xA;  #   ERROR! Received 400 Bad Request&#xA;  #   User ID already taken.&#xA;&#xA;2. Get an access token&#xA;&#xA;Manually get an access token. OK I know how to do that. First question, though: as that&#39;s a speed bump in the road, and seems fragile, has anyone shown interest in making either Draupnir itself or the playbook do a login automatically? Can&#39;t see any issue filed about &#34;token&#34; or &#34;login&#34;.&#xA;&#xA;Basically these days I know I&#39;m going to have to repeat my steps on a different rig later, tear things down and build them up again, so I always look to automate the whole procedure. (Aware it&#39;s sometimes more efficient to go manually at first and automate only when worth the effort. And aware I&#39;m at risk of volunteering myself to contribute.)&#xA;&#xA;Well, I asked about this. The kind folks let me know their thoughts. There are several issues.&#xA;&#xA;When I said &#34;fragile&#34; I meant I wouldn&#39;t expect an access token to remain valid forever. At the time this was designed, access tokens were considered permanent until explicitly revoked (&#34;logged out&#34;). Putting an access token into a bot&#39;s config was taken for granted, and perhaps still is. However, nowadays the client authentication spec is more complex and tokens may need refreshing. That seems to me to suggest that&#39;s no longer good practice. It can still work if we take care that the token is not invalidated.&#xA;&#xA;In one sense using an access token is considered more secure than knowing an account&#39;s password. That&#39;s the sense in which many systems allow getting an API access token and giving it to an external service that will call the API. However, I think this argument applies to accessing other accounts, not for the bot&#39;s own account.&#xA;&#xA;Also, if Draupnir connects through Pantalaimon to get E2EE (as makes sense when it&#39;s being run by someone other than the server operator), then Pantalaimon needs the account password to create an E2EE device.&#xA;&#xA;Currently I&#39;m feeling the bot should know its password and we should automate its login. As a lesser step, I will do this in Ansible.&#xA;&#xA;  includerole: name=matrix-login-password&#xA;    vars:&#xA;      matrixlogin:&#xA;        hscsapi: &#34;{{ matrixdraupnirhscsapi }}&#34;&#xA;        user: &#34;{{ matrixdraupnirbotuserid }}&#34;&#xA;        password: &#34;{{ matrixdraupnirbotpassword }}&#34;&#xA;    # output: matrixloginresult&#xA;  setfact:&#xA;      matrixdraupnirbotaccesstoken: &#34;{{ matrixloginresult.accesstoken }}&#34;&#xA;&#xA;with additional inventory vars:&#xA;&#xA;matrixdraupnirhscsapi: &#34;https://matrix.example.net&#34;&#xA;matrixdraupnirbotuserid: &#34;@bot.draupnir:example.net&#34;&#xA;&#xA;My matrix-login-password role has this in its tasks/main.yml:&#xA;&#xA;name: &#34;log in to matrix&#34;&#xA;  uri:&#xA;    method: POST&#xA;    url: &#34;{{ matrixlogin.hscsapi }}/matrix/client/r0/login&#34;&#xA;    body:&#xA;      type: &#34;m.login.password&#34;&#xA;      identifier:&#xA;        type: &#34;m.id.user&#34;&#xA;        user: &#34;{{ matrixlogin.user }}&#34;&#xA;      password: &#34;{{ matrixlogin.password }}&#34;&#xA;    bodyformat: json&#xA;  register: result&#xA;  changedwhen: result.json.accesstoken&#xA;&#xA;setfact:&#xA;    matrixloginresult: &#34;{{ result.json }}&#34;&#xA;  when: not ansiblecheckmode&#xA;&#xA;matrixloginresult contains at least: accesstoken, deviceid, userid&#xA;# see e.g.: https://spec.matrix.org/v1.8/client-server-api/#login and older versions&#xA;&#xA;Update: now [published too.] (I hesitated because I thought I saw about a year ago someone had already published a set of ansible roles for matrix admin tasks like this. Maybe. Can&#39;t find it now.)&#xA;&#xA;3. Make sure the account is free from rate limiting&#xA;&#xA;Same for this &#39;overrideratelimit&#39; step of course. Would be nice to automate.&#xA;&#xA;One way to change this permission is through some other admin account. In that case, &#34;know an access token&#34; is an appropriate way to use that other admin account.&#xA;&#xA;In the case where the bot account itself is configured to be a (matrix) server admin account, then in Synapse&#39;s case at least it would already have sufficient permission to use Synapse&#39;s admin API to override its own rate limit.&#xA;&#xA;Now about admin APIs. Unfortunately matrix admin APIs are not standardised. Synapse has its admin API, Dendrite has another, and Conduit I gather doesn&#39;t have a REST admin API. On Dendrite, &#34;the username has to be specified in dendrite.yaml to disable rate limiting, and personally i hate when anything other than the sysadmins write to configs&#34; said boneswashere. The playbook, if instructed to install Dendrite, controls that config file, but Draupnir would (or should) not be able to do that by itself unlike with Synapse.&#xA;&#xA;So, there are lots of cases. Different homeservers, and the playbook being used to install Draupnir with or without also its homeserver, and choice of whether the playbook or Draupnir itself performs this configuration.&#xA;&#xA;Currently I&#39;m think I will automate it for the Synapse admin set-ratelimit API only, as that&#39;s the server we use in PubHubs.&#xA;&#xA;  name: &#34;disable rate limiting for Draupnir bot&#34;&#xA;    uri:&#xA;      method: POST&#xA;      url: &#34;{{ matrixdraupnirhscsapi }}/synapse/admin/v1/users/{{ matrixdraupnirbotuserid }}/overrideratelimit&#34;&#xA;      headers:&#xA;        # access token must be for a user with synapse admin access;&#xA;        # can be the bot&#39;s if it is an admin, else of another account.&#xA;        Authorization: &#34;Bearer {{ matrixdraupnirbotaccesstoken if matrixdraupnirbotadmin else matrixdraupnirsomesynapseadminaccountaccesstoken }}&#34;&#xA;      bodyformat: json&#xA;      body:&#xA;        messagespersecond: 0&#xA;        burstcount: 0&#xA;&#xA;4. Create a management room&#xA;&#xA;I&#39;m on a roll now. I&#39;ll just check the matrix spec for room creation, take a guess at which parameters make most sense for my case, and write it out in Ansible language.&#xA;&#xA;  name: &#34;create a management room for Draupnir bot&#34; &#xA;    uri: &#xA;      method: POST &#xA;      url: &#34;{{ matrixdraupnirhscsapi }}/matrix/client/v3/createRoom&#34; &#xA;      headers: &#xA;        Authorization: &#34;Bearer {{ matrixdraupnirbotaccesstoken }}&#34; &#xA;      body: &#xA;        name: &#34;{{ matrixdraupnirmanagementroomname }}&#34; &#xA;        creationcontent: &#xA;          m.federate: false &#xA;        visibility: private &#xA;        preset: trustedprivatechat &#xA;        invite: &#34;{{ matrixdraupniroperatoruserids }}&#34; &#xA;      bodyformat: json &#xA;    register: result &#xA;    when: matrixdraupnirmanagementroomid is undefined &#xA;    # output: result.json.roomid &#xA;  setfact: &#xA;      matrixdraupnirmanagementroomid: &#34;{{ result.json.roomid }}&#34; &#xA;    when: matrixdraupnirmanagementroomid is undefined &#xA;&#xA;with inventory vars:&#xA;&#xA;the management room (bot and its operators); create if roomid undefined&#xA;matrixdraupnirmanagementroomname: &#34;Draupnir management&#34;&#xA;matrixdraupnirmanagementroomid: &#39;!xxxxxxxxxxxx:example.net&#39;&#xA;&#xA;Starting Up&#xA;&#xA;After running my playbook with the above set-up, and pasting the resulting access token and room id into the corresponding inventory vars (TODO: join the two parts together in a better way than cut-n-paste), here we go with matrix-docker-ansible-deploy:&#xA;&#xA;ansible-playbook .../matrix-docker-ansible-deploy/setup.yml -l example.net --tags=setup-bot-draupnir,start -Dv&#xA;&#xA;In the logs, journalctl -n100 -fu matrix-bot-draupnir.service:&#xA;&#xA;Starting Matrix Draupnir bot...&#xA;Started Matrix Draupnir bot.&#xA;[INFO] [index] Starting bot...&#xA;[INFO] [index] Resolving management room...&#xA;[INFO] [index] Mjolnir is starting up. Use !mjolnir to query status.&#xA;[INFO] [ProtectedRoomsConfig] Resolving protected rooms...&#xA;[WARN] [ProtectedRoomsConfig] Couldn&#39;t find any explicitly protected rooms from Mjolnir&#39;s account data, assuming first start. MatrixError: Error during MatrixClient request GET /matrix/client/v3/user/%40bot.draupnir%3Aexample.net/accountdata/org.matrix.mjolnir.protectedrooms: 404 Not Found -- {&#34;errcode&#34;:&#34;MNOTFOUND&#34;,&#34;error&#34;:&#34;Account data not found&#34;}&#xA;[... three of these &#39;Account data not found&#39; errors ...]&#xA;[INFO] [Mjolnir@startup] Checking permissions...&#xA;[INFO] [Mjolnir@startup] Syncing lists...&#xA;[INFO] [Mjolnir@startup] Startup complete. Now monitoring rooms.&#xA;&#xA;And then I found and joined the management room. I used a Hydrogen web client.&#xA;&#xA;@bot.draupnir:example.net joined the room&#xA;bot.draupnir named the room &#34;Draupnir management&#34;&#xA;admin1 was invited to the room by bot.draupnir&#xA;&#xA;bot.draupnir:&#xA;Mjolnir is starting up. Use !mjolnir to query status.&#xA;Checking permissions...&#xA;All permissions look OK.&#xA;Syncing lists...&#xA;Done updating rooms - no errors&#xA;Startup complete. Now monitoring rooms.&#xA;&#xA;admin1 joined the room&#xA;&#xA;It&#39;s alive! Perhaps a little confused about its new name. Responds to both !mjolnir and !draupnir, either way replying:&#xA;&#xA;Old Commands:&#xA;!mjolnir        - Print status information&#xA;!mjolnir status - Print status information&#xA;[...]&#xA;&#xA;mjolnir commands:ban entity list [...reason] - Bans an entity from the policy list.Parameters:&#xA;entity - no description&#xA;list - no description&#xA;[...]&#xA;&#xA;Well, there we are. The bot&#39;s alive. Next it&#39;s time for me to learn its commands and put it to work.&#xA;&#xA;Update: All these Ansible roles are now published.&#xA;TODO: contribute some of this to matrix-docker-ansible-deploy?&#xA;TODO: re-implement these roles as Ansible modules instead, using a matrix python API such as synadm or mautrix-python?&#xA;&#xA;---&#xA;&#xA;#matrix #awesomeFOSS #Draupnir #PubHubs&#xA;&#xA;!--more--&#xD;&#xA;----&#xD;&#xA;Follow/Feedback/Contact: RSS feed · Fedi follow this blog: @julian&amp;ZeroWidthSpace;@wrily.foad.me.uk · matrix me · Fedi follow me · email me · julian.foad.me.uk&#xD;&#xA;Donate: via Liberapay&#xD;&#xA;All posts &amp;copy; Julian Foad and licensed CC-BY-ND except quotes, translations, or where stated otherwise&#xD;&#xA;]]&gt;</description>
      <content:encoded><![CDATA[<p>This week I&#39;m setting up Draupnir on my matrix test rig, in order to become familiar with Draupnir deployment before I <a href="https://wrily.foad.me.uk/pubhubs-moderation-tools">integrate it with PubHubs</a>.</p>

<p>Very glad to be able to use <a href="https://github.com/spantaleev/matrix-docker-ansible-deploy/blob/master/docs/configuring-playbook-bot-draupnir.md">matrix-docker-ansible-deploy&#39;s Draupnir setup</a> to automate the majority of the Draupnir deployment.</p>

<p>I also want to automate, with Ansible, as much as possible of the set-up that is required before running that playbook. I aim to document here what I have done and open questions about it. The numbered steps here correspond to the manual instructions in that documentation linked above.
</p>

<h2 id="1-register-the-bot-account" id="1-register-the-bot-account">1. Register the bot account</h2>

<p>I register a matrix account for the bot, using a little Ansible role I wrote, calling it like this in my Ansible playbook (personal one, specific to this installation).</p>

<pre><code class="language-yaml">  - include_role: name=matrix-synapse-register-user
    vars:
      matrix_synapse_user:
        name:  &#34;{{ matrix_draupnir_bot_username }}&#34;
        pw:    &#34;{{ matrix_draupnir_bot_password }}&#34;
        admin: &#34;{{ matrix_draupnir_bot_admin }}&#34;
</code></pre>

<p>defining those vars in my inventory file <code>inventories/prod/host_vars/&lt;host&gt;/matrix-draupnir.yml</code>:</p>

<pre><code class="language-yaml">matrix_draupnir_bot_username: &#34;bot.draupnir&#34;
matrix_draupnir_bot_password: !vault [... I use vault encoded values here ...]
matrix_draupnir_bot_admin: false
</code></pre>

<p>I <a href="https://lab.trax.im/matrix/matrix-ansible">published</a> my <code>matrix-synapse-register-user</code> role. I thought I should, if there isn&#39;t already a more widely available alternative. It looks like this, in <code>tasks/main.yml</code>:</p>

<pre><code class="language-yaml">---
- name: &#34;register a matrix synapse user&#34;
  command:
    argv: &#34;{{ (cmd_check if ansible_check_mode else cmd_real) + args }}&#34;
  vars:
    cmd_real:
      - /matrix/synapse/bin/register-user
    cmd_check:
      - echo
      - &#34;Would run: /matrix/synapse/bin/register-user&#34;
    args:
      - &#34;{{ matrix_synapse_user.name }}&#34;
      - &#34;{{ matrix_synapse_user.pw }}&#34;
      - &#34;{{ &#39;1&#39; if matrix_synapse_user.admin|default(false) else &#39;0&#39; }}&#34;
  check_mode: false
  register: result
  changed_when: result.rc == 0
  failed_when: result.rc != 0 and &#39;User ID already taken.&#39; not in result.stdout
  # if already registered, errors with (on stdout):
  # &gt; Sending registration request...
  # &gt; ERROR! Received 400 Bad Request
  # &gt; User ID already taken.
</code></pre>

<h2 id="2-get-an-access-token" id="2-get-an-access-token">2. Get an access token</h2>

<p>Manually get an access token. OK I know how to do that. First question, though: as that&#39;s a speed bump in the road, and seems fragile, has anyone shown interest in making either Draupnir itself or the playbook do a login automatically? Can&#39;t see any issue filed <a href="https://github.com/the-draupnir-project/Draupnir/issues?q=is%3Aissue+token">about “token”</a> <a href="https://github.com/the-draupnir-project/Draupnir/issues?q=is%3Aissue+login">or “login”</a>.</p>

<p>Basically these days I know I&#39;m going to have to repeat my steps on a different rig later, tear things down and build them up again, so I always look to automate the whole procedure. (Aware it&#39;s sometimes more efficient to go manually at first and automate only when worth the effort. And aware I&#39;m at risk of volunteering myself to contribute.)</p>

<p>Well, <a href="https://matrix.to/#/%23draupnir%3Amatrix.org/%247nnDZYBb0qidW-CZpQDmU0j98go7rNhqLqjS2QbxktU?via=foad.me.uk&amp;via=matrix.org&amp;via=maclemon.at&amp;via=catvibers.me">I asked</a> about this. The kind folks let me know their thoughts. There are several issues.</p>

<p>When I said “fragile” I meant I wouldn&#39;t expect an access token to remain valid forever. At the time this was designed, access tokens were considered permanent until explicitly revoked (“logged out”). Putting an access token into a bot&#39;s config was taken for granted, and perhaps still is. However, nowadays <a href="https://spec.matrix.org/v1.8/client-server-api/#client-authentication">the client authentication spec</a> is more complex and tokens may need refreshing. That seems to me to suggest that&#39;s no longer good practice. It can still work if we take care that the token is not invalidated.</p>

<p>In one sense using an access token is considered more secure than knowing an account&#39;s password. That&#39;s the sense in which many systems allow getting an API access token and giving it to an external service that will call the API. However, I think this argument applies to accessing other accounts, not for the bot&#39;s own account.</p>

<p>Also, if Draupnir connects through Pantalaimon to get E2EE (as makes sense when it&#39;s being run by someone other than the server operator), then Pantalaimon needs the account password to create an E2EE device.</p>

<p>Currently I&#39;m feeling the bot should know its password and we should automate its login. As a lesser step, I will do this in Ansible.</p>

<pre><code class="language-yaml">  - include_role: name=matrix-login-password
    vars:
      matrix_login:
        hs_cs_api: &#34;{{ matrix_draupnir_hs_cs_api }}&#34;
        user: &#34;{{ matrix_draupnir_bot_user_id }}&#34;
        password: &#34;{{ matrix_draupnir_bot_password }}&#34;
    # output: matrix_login_result
  - set_fact:
      matrix_draupnir_bot_access_token: &#34;{{ matrix_login_result.access_token }}&#34;
</code></pre>

<p>with additional inventory vars:</p>

<pre><code class="language-yaml">matrix_draupnir_hs_cs_api: &#34;https://matrix.example.net&#34;
matrix_draupnir_bot_user_id: &#34;@bot.draupnir:example.net&#34;
</code></pre>

<p>My <code>matrix-login-password</code> role has this in its <code>tasks/main.yml</code>:</p>

<pre><code class="language-yaml">- name: &#34;log in to matrix&#34;
  uri:
    method: POST
    url: &#34;{{ matrix_login.hs_cs_api }}/_matrix/client/r0/login&#34;
    body:
      type: &#34;m.login.password&#34;
      identifier:
        type: &#34;m.id.user&#34;
        user: &#34;{{ matrix_login.user }}&#34;
      password: &#34;{{ matrix_login.password }}&#34;
    body_format: json
  register: _result
  changed_when: _result.json.access_token

- set_fact:
    matrix_login_result: &#34;{{ _result.json }}&#34;
  when: not ansible_check_mode

# matrix_login_result contains at least: access_token, device_id, user_id
# see e.g.: https://spec.matrix.org/v1.8/client-server-api/#login and older versions
</code></pre>

<p>[Update: now <a href="https://lab.trax.im/matrix/matrix-ansible">published</a> too.] (I hesitated because I thought I saw about a year ago someone had already published a set of ansible roles for matrix admin tasks like this. Maybe. Can&#39;t find it now.)</p>

<h2 id="3-make-sure-the-account-is-free-from-rate-limiting" id="3-make-sure-the-account-is-free-from-rate-limiting">3. Make sure the account is free from rate limiting</h2>

<p>Same for this &#39;override_ratelimit&#39; step of course. Would be nice to automate.</p>

<p>One way to change this permission is through some other admin account. In that case, “know an access token” is an appropriate way to use that other admin account.</p>

<p>In the case where the bot account itself is configured to be a (matrix) server admin account, then in Synapse&#39;s case at least it would already have sufficient permission to use Synapse&#39;s admin API to override its own rate limit.</p>

<p>Now about admin APIs. Unfortunately matrix admin APIs are not standardised. Synapse has <a href="https://matrix-org.github.io/synapse/latest/usage/administration/admin_api/">its admin API</a>, Dendrite has <a href="https://matrix-org.github.io/dendrite/administration/adminapi">another</a>, and Conduit I gather doesn&#39;t have a REST admin API. On Dendrite, “the username has to be specified in dendrite.yaml to disable rate limiting, and personally i hate when anything other than the sysadmins write to configs” said <code>bones_was_here</code>. The playbook, if instructed to install Dendrite, controls that config file, but Draupnir would (or should) not be able to do that by itself unlike with Synapse.</p>

<p>So, there are lots of cases. Different homeservers, and the playbook being used to install Draupnir with or without also its homeserver, and choice of whether the playbook or Draupnir itself performs this configuration.</p>

<p>Currently I&#39;m think I will automate it for <a href="https://matrix-org.github.io/synapse/latest/admin_api/user_admin_api.html#set-ratelimit">the Synapse admin set-ratelimit API</a> only, as that&#39;s the server we use in PubHubs.</p>

<pre><code class="language-yaml">  - name: &#34;disable rate limiting for Draupnir bot&#34;
    uri:
      method: POST
      url: &#34;{{ matrix_draupnir_hs_cs_api }}/_synapse/admin/v1/users/{{ matrix_draupnir_bot_user_id }}/override_ratelimit&#34;
      headers:
        # access token must be for a user with synapse admin access;
        # can be the bot&#39;s if it is an admin, else of another account.
        Authorization: &#34;Bearer {{ matrix_draupnir_bot_access_token if matrix_draupnir_bot_admin else matrix_draupnir_some_synapse_admin_account_access_token }}&#34;
      body_format: json
      body:
        messages_per_second: 0
        burst_count: 0
</code></pre>

<h2 id="4-create-a-management-room" id="4-create-a-management-room">4. Create a management room</h2>

<p>I&#39;m on a roll now. I&#39;ll just check the <a href="https://spec.matrix.org/v1.8/client-server-api/#creation">matrix spec for room creation</a>, take a guess at which parameters make most sense for my case, and write it out in Ansible language.</p>

<pre><code class="language-yaml">  - name: &#34;create a management room for Draupnir bot&#34; 
    uri: 
      method: POST 
      url: &#34;{{ matrix_draupnir_hs_cs_api }}/_matrix/client/v3/createRoom&#34; 
      headers: 
        Authorization: &#34;Bearer {{ matrix_draupnir_bot_access_token }}&#34; 
      body: 
        name: &#34;{{ matrix_draupnir_management_room_name }}&#34; 
        creation_content: 
          m.federate: false 
        visibility: private 
        preset: trusted_private_chat 
        invite: &#34;{{ matrix_draupnir_operator_user_ids }}&#34; 
      body_format: json 
    register: _result 
    when: matrix_draupnir_management_room_id is undefined 
    # output: _result.json.room_id 
  - set_fact: 
      matrix_draupnir_management_room_id: &#34;{{ _result.json.room_id }}&#34; 
    when: matrix_draupnir_management_room_id is undefined 
</code></pre>

<p>with inventory vars:</p>

<pre><code class="language-yaml"># the management room (bot and its operators); create if room_id undefined
matrix_draupnir_management_room_name: &#34;Draupnir management&#34;
matrix_draupnir_management_room_id: &#39;!xxxxxxxxxxxx:example.net&#39;
</code></pre>

<h2 id="starting-up" id="starting-up">Starting Up</h2>

<p>After running my playbook with the above set-up, and pasting the resulting access token and room id into the corresponding inventory vars (<em>TODO: join the two parts together in a better way than cut-n-paste</em>), here we go with matrix-docker-ansible-deploy:</p>

<pre><code>ansible-playbook .../matrix-docker-ansible-deploy/setup.yml -l example.net --tags=setup-bot-draupnir,start -Dv
</code></pre>

<p>In the logs, <code>journalctl -n100 -fu matrix-bot-draupnir.service</code>:</p>

<pre><code>Starting Matrix Draupnir bot...
Started Matrix Draupnir bot.
[INFO] [index] Starting bot...
[INFO] [index] Resolving management room...
[INFO] [index] Mjolnir is starting up. Use !mjolnir to query status.
[INFO] [ProtectedRoomsConfig] Resolving protected rooms...
[WARN] [ProtectedRoomsConfig] Couldn&#39;t find any explicitly protected rooms from Mjolnir&#39;s account data, assuming first start. MatrixError: Error during MatrixClient request GET /_matrix/client/v3/user/%40bot.draupnir%3Aexample.net/account_data/org.matrix.mjolnir.protected_rooms: 404 Not Found -- {&#34;errcode&#34;:&#34;M_NOT_FOUND&#34;,&#34;error&#34;:&#34;Account data not found&#34;}
[... three of these &#39;Account data not found&#39; errors ...]
[INFO] [Mjolnir@startup] Checking permissions...
[INFO] [Mjolnir@startup] Syncing lists...
[INFO] [Mjolnir@startup] Startup complete. Now monitoring rooms.
</code></pre>

<p>And then I found and joined the management room. I used a Hydrogen web client.</p>

<pre><code class="language-markdown">_@bot.draupnir:example.net joined the room_
_bot.draupnir named the room &#34;Draupnir management&#34;_
_admin1 was invited to the room by bot.draupnir_

**bot.draupnir:**
Mjolnir is starting up. Use !mjolnir to query status.
Checking permissions...
All permissions look OK.
Syncing lists...
Done updating rooms - no errors
Startup complete. Now monitoring rooms.

_admin1 joined the room_
</code></pre>

<p>It&#39;s alive! Perhaps a little confused about its new name. Responds to both <code>!mjolnir</code> and <code>!draupnir</code>, either way replying:</p>

<pre><code class="language-markdown">Old Commands:
`!mjolnir        - Print status information`
`!mjolnir status - Print status information`
[...]

mjolnir commands:`ban &lt;entity&gt; &lt;list&gt; [...reason]` - Bans an entity from the policy list.Parameters:
entity - no description
list - no description
[...]
</code></pre>

<p>Well, there we are. The bot&#39;s alive. Next it&#39;s time for me to learn its commands and put it to work.</p>

<p><em>Update: All these Ansible roles are now <a href="https://lab.trax.im/matrix/matrix-ansible">published</a>.</em>
<em>TODO: contribute some of this to matrix-docker-ansible-deploy?</em>
<em>TODO: re-implement these roles as Ansible modules instead, using a matrix python API such as <a href="https://synadm.readthedocs.io/en/latest/synadm.module.html">synadm</a> or <a href="https://docs.mau.fi/python/">mautrix-python</a>?</em></p>

<hr>

<p><a href="https://wrily.foad.me.uk/tag:matrix" class="hashtag"><span>#</span><span class="p-category">matrix</span></a> <a href="https://wrily.foad.me.uk/tag:awesomeFOSS" class="hashtag"><span>#</span><span class="p-category">awesomeFOSS</span></a> <a href="https://wrily.foad.me.uk/tag:Draupnir" class="hashtag"><span>#</span><span class="p-category">Draupnir</span></a> <a href="https://wrily.foad.me.uk/tag:PubHubs" class="hashtag"><span>#</span><span class="p-category">PubHubs</span></a></p>



<hr>

<p><em>Follow/Feedback/Contact:</em> <a href="https://wrily.foad.me.uk/feed/"><em>RSS feed</em></a> · <em>Fedi follow this blog: @julian​@wrily.foad.me.uk</em> · <a href="https://matrix.to/#/@julian:foad.me.uk" title="matrix Julian"><em>matrix me</em></a> · <a href="https://fed.foad.me.uk/%40julian%40fed.foad.me.uk" title="follow Julian"><em>Fedi follow me</em></a> · <a href="mailto:julian@foad.me.uk?subject=Wrily" title="email Julian"><em>email me</em></a> · <a href="https://julian.foad.me.uk/"><em>julian.foad.me.uk</em></a>
<em>Donate:</em> <a href="https://liberapay.com/julianfoad" title="Donate to Julian using Liberapay"><em>via Liberapay</em></a>
<em>All posts © Julian Foad and licensed <a href="https://creativecommons.org/licenses/by-nd/4.0/">CC-BY-ND</a> except quotes, translations, or where stated otherwise</em></p>
]]></content:encoded>
      <guid>https://wrily.foad.me.uk/deploying-draupnir-on-my-matrix-test-rig</guid>
      <pubDate>Wed, 20 Sep 2023 17:00:00 +0000</pubDate>
    </item>
  </channel>
</rss>