<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet href="/rss.xsl" type="text/xsl"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><title>Self-Hosting on Hyteck</title><link href="https://hyteck.de/tags/self-hosting/"/><generator>Hugo -- gohugo.io</generator><language>en-us</language><id>https://hyteck.de/tags/self-hosting/</id><updated>2025-08-03T06:10:10+02:00</updated><link href="https://hyteck.de/tags/self-hosting/index.xml" rel="self" type="application/rss+xml"/><entry><title>Trying Twenty: How does an Open Source CRM work?</title><link href="https://hyteck.de/post/trying-twenty/" type="application/octet-stream"/><updated>2025-08-03T06:10:10+02:00</updated><id>https://hyteck.de/post/trying-twenty/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;p>I spend my day working with Salesforce, a very, very feature-rich CRM that you pay big money to use.
Salesforce is the opposite of OpenSource and the many features are expensive. Salesforce business model is based on this
and on the lock-in effect.
If your company invested in implementing Salesforce, they&amp;rsquo;ll likely pay a lot to keep it.&lt;/p>
&lt;p>So what does an alternative look like? Let&amp;rsquo;s have a look at &lt;a href="https://twenty.com">Twenty&lt;/a>, an OpenSource CRM that
recently reached the magic 1.0 version.&lt;/p>
&lt;h1 id="getting-started">Getting started&lt;/h1>
&lt;p>There are two options of getting started: Register at &lt;a href="https://app.twenty.com">app.twenty.com&lt;/a> and start right away on
the devs instance or self-host Twenty on your own server.
I did the ladder, so let&amp;rsquo;s discuss how that. The basic steps I took were&lt;/p>
&lt;ul>
&lt;li>point twenty.hyteck.de to a server&lt;/li>
&lt;li>Install traefik on the server (I cheated, traefik was already installed)&lt;/li>
&lt;li>Deploy &lt;a href="docker-compose.yml">this docker-compose.yml&lt;/a> with &lt;a href="env">this env file&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>Then visit the domain and set up the first user.&lt;/p>
&lt;h1 id="features">Features&lt;/h1>
&lt;p>Twenty offers an initial datamodel that you should be familiar from other CRMs. the standards objects are&lt;/p>
&lt;p>&lt;img src="person-model.png" alt="A screenshot of the person model in Twenty">&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Persons&lt;/strong> A individual person. You can attach notes, E-Mails, etc..&lt;/li>
&lt;li>&lt;strong>Companies&lt;/strong> The same for organizations. Organization websites must be unique&lt;/li>
&lt;li>&lt;strong>Opportunities&lt;/strong> The classic opportunity with customizable stages&lt;/li>
&lt;li>&lt;strong>Notes&lt;/strong> They can be attached to any of the objects above&lt;/li>
&lt;li>&lt;strong>Tasks&lt;/strong> Items to work on&lt;/li>
&lt;li>&lt;strong>Workflows&lt;/strong> Automations similar to Salesforce flows. E.g. you can create a task every time an Opportunity is
created.&lt;/li>
&lt;/ul>
&lt;p>The basic datamodel can be extended in the GUI. Here is how my &amp;ldquo;Company&amp;rdquo; model looks like&lt;/p>
&lt;p>&lt;img src="organization_dm.png" alt="A screenshot of twenty. It shows the company model being renamed to Organizations and deactivated fields such as Twitter links or number of employees.">&lt;/p>
&lt;p>You can add any of the following fields to an object.&lt;/p>
&lt;p>&lt;img src="fields.png" alt="A list of fields: Text, Number, True/False, Date and Time, Date, Select, Multi-Select, Rating, Currency, E-Mails, Links, Phones, Full Name, Address, Relation and the Advanced fields called Unique ID, JSON and Array">&lt;/p>
&lt;h3 id="workflows">Workflows&lt;/h3>
&lt;p>Workflows are Twenty&amp;rsquo;s way of allowing users to build automations. You can start a Workflow when a Record is created,
updated or deleted. In addition, they can be started manually, on a schedule and via Webhook (yeah!).&lt;/p>
&lt;p>&lt;img src="workflow1.png" alt="A workflow in twenty. After the Trigger &amp;ldquo;Organization&amp;rdquo; created there is a new task generated, a webhook send and a form used.">&lt;/p>
&lt;p>You can then add nodes that trigger actions. Available right now are&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Creating, updating or deleting a record&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Searching records&lt;/strong>&lt;/li>
&lt;li>&lt;strong>Sending E-Mails&lt;/strong> This is the only option to trigger e-mails so far&lt;/li>
&lt;li>&lt;strong>Code&lt;/strong> Serverless Javascript functions&lt;/li>
&lt;li>&lt;strong>Form&lt;/strong> The form will pop up on the user&amp;rsquo;s screen when the workflow is launched from a manual trigger. For other
types of triggers, it will be displayed in the Workflow run record page.&lt;/li>
&lt;li>&lt;strong>HTTP request&lt;/strong> Although possible via Code, this is a handy shortcut to trigger HTTP requests&lt;/li>
&lt;/ul>
&lt;p>What is currently completely missing are Foreach-loops
and &lt;a href="https://github.com/twentyhq/core-team-issues/issues/1265">conditions&lt;/a>. I can not say &amp;ldquo;If Opportunity stage is
updated to X do Y else, do Z&amp;rdquo;.
Without this, Workflows are really limited in their power.&lt;/p>
&lt;p>What already seems quite mature though is the code option. It allows to put in arbitrary code and output a result.&lt;/p>
&lt;p>&lt;img src="serverless_function.png" alt="Screenshot of a javascript function in Twenty that adds two numbers together">&lt;/p>
&lt;p>I did not try a lot, but I assume most basic Javascript works. I successfully built an http request that send data to a
server.&lt;/p>
&lt;p>If what you&amp;rsquo;re doing is straightforward enough to not use loops and conditions or if you are okay with doing all of them
in the Code node, you can do basically anything.&lt;/p>
&lt;h2 id="api">API&lt;/h2>
&lt;p>Twenty offers an extensive API that allows you to basically do everything. It&amp;rsquo;s well documented and easy to use.&lt;/p>
&lt;p>Here is an example of me, syncing Rescue Organizations from &lt;a href="https://notfellchen.org">notfellchen.org&lt;/a> to Twenty.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> requests
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> fellchensammlung.models &lt;span style="color:#f92672">import&lt;/span> RescueOrganization
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">sync_rescue_org_to_twenty&lt;/span>(rescue_org: RescueOrganization, base_url, token: str):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> rescue_org&lt;span style="color:#f92672">.&lt;/span>twenty_id:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> update &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">True&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> update &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">False&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> payload &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;eMails&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;primaryEmail&amp;#34;&lt;/span>: rescue_org&lt;span style="color:#f92672">.&lt;/span>email,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;additionalEmails&amp;#34;&lt;/span>: &lt;span style="color:#66d9ef">None&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;domainName&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;primaryLinkLabel&amp;#34;&lt;/span>: rescue_org&lt;span style="color:#f92672">.&lt;/span>website,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;primaryLinkUrl&amp;#34;&lt;/span>: rescue_org&lt;span style="color:#f92672">.&lt;/span>website,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;additionalLinks&amp;#34;&lt;/span>: []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;name&amp;#34;&lt;/span>: rescue_org&lt;span style="color:#f92672">.&lt;/span>name,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> rescue_org&lt;span style="color:#f92672">.&lt;/span>location:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> payload[&lt;span style="color:#e6db74">&amp;#34;address&amp;#34;&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;addressStreet1&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>rescue_org&lt;span style="color:#f92672">.&lt;/span>location&lt;span style="color:#f92672">.&lt;/span>street&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74"> &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>rescue_org&lt;span style="color:#f92672">.&lt;/span>location&lt;span style="color:#f92672">.&lt;/span>housenumber&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;addressCity&amp;#34;&lt;/span>: rescue_org&lt;span style="color:#f92672">.&lt;/span>location&lt;span style="color:#f92672">.&lt;/span>city,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;addressPostcode&amp;#34;&lt;/span>: rescue_org&lt;span style="color:#f92672">.&lt;/span>location&lt;span style="color:#f92672">.&lt;/span>postcode,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;addressCountry&amp;#34;&lt;/span>: rescue_org&lt;span style="color:#f92672">.&lt;/span>location&lt;span style="color:#f92672">.&lt;/span>countrycode,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;addressLat&amp;#34;&lt;/span>: rescue_org&lt;span style="color:#f92672">.&lt;/span>location&lt;span style="color:#f92672">.&lt;/span>latitude,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;addressLng&amp;#34;&lt;/span>: rescue_org&lt;span style="color:#f92672">.&lt;/span>location&lt;span style="color:#f92672">.&lt;/span>longitude,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> headers &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Content-Type&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;application/json&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;Authorization&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Bearer &lt;/span>&lt;span style="color:#e6db74">{&lt;/span>token&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> update:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>base_url&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">/rest/companies/&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>rescue_org&lt;span style="color:#f92672">.&lt;/span>twenty_id&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response &lt;span style="color:#f92672">=&lt;/span> requests&lt;span style="color:#f92672">.&lt;/span>patch(url, json&lt;span style="color:#f92672">=&lt;/span>payload, headers&lt;span style="color:#f92672">=&lt;/span>headers)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> response&lt;span style="color:#f92672">.&lt;/span>status_code &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">200&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> url &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">f&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">{&lt;/span>base_url&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">/rest/companies&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response &lt;span style="color:#f92672">=&lt;/span> requests&lt;span style="color:#f92672">.&lt;/span>post(url, json&lt;span style="color:#f92672">=&lt;/span>payload, headers&lt;span style="color:#f92672">=&lt;/span>headers)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">assert&lt;/span> response&lt;span style="color:#f92672">.&lt;/span>status_code &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">201&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rescue_org&lt;span style="color:#f92672">.&lt;/span>twenty_id &lt;span style="color:#f92672">=&lt;/span> response&lt;span style="color:#f92672">.&lt;/span>json()[&lt;span style="color:#e6db74">&amp;#34;data&amp;#34;&lt;/span>][&lt;span style="color:#e6db74">&amp;#34;createCompany&amp;#34;&lt;/span>][&lt;span style="color:#e6db74">&amp;#34;id&amp;#34;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rescue_org&lt;span style="color:#f92672">.&lt;/span>save()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="the-company-business-model-and-paid-features">The Company, Business Model and Paid Features&lt;/h1>
&lt;p>The company behind Twenty is called &amp;ldquo;Twenty.com PBC&amp;rdquo; and mostly seems to consist of former AirBnB employees in Paris.
The company is probably backed by Venture Capital.
The current business model is to charge for using the company&amp;rsquo;s instance of Twenty. It starts at 9$/user/month without
enterprise features. SSO and support will cost you 19$/user/month.&lt;/p>
&lt;p>Selfhosting is free but SSO is locked behind an enterprise badge with seemingly no way to pay for activating it.
I suspect that in the future more features will become &amp;ldquo;Enterprise only&amp;rdquo; even when self-hosting. All contributors must
agree
to &lt;a href="https://github.com/twentyhq/twenty/blob/main/.github/CLA.md">a Contributor License Agreement (CLA)&lt;/a>, therefore I
believe they could change the License in the future, including switching away from Open Source.&lt;/p>
&lt;h1 id="ai-usage">AI Usage&lt;/h1>
&lt;p>The repo contains a &lt;code>.cursor&lt;/code> directory and &lt;code>CLAUDE.md&lt;/code> so I assume the devs make (heavy?) use of LLM generated code.
The ethical and legal problems with this are for you to decide. I don&amp;rsquo;t know what effect this has on code quality, for
now I&amp;rsquo;d say things are sometimes buggy (failed upgrades) and UX could be better tested (looking at the e-mail
integration) - if this is due to AI slop I don&amp;rsquo;t know.&lt;/p>
&lt;h1 id="conclusion">Conclusion&lt;/h1>
&lt;p>Twenty is a really promising start of building a good CRM. The ease of customizing the datamodel,
using the API and a solid beginning to Flows allows users to get a lot of value from it already.
Flows need some more work to become as powerful as they should be and the E-Mail integration needs to get better.&lt;/p>
&lt;p>Stating the obvious: This is not something that could ever replace Salesforce. However, there are many organizations
that would benefit a lot from a CRM like Twenty, they simply don&amp;rsquo;t need, can&amp;rsquo;t handle or don&amp;rsquo;t want to pay for all the
features other CRMs like Salesforce offer.&lt;/p>
&lt;p>If Twenty continues to focus on small to medium companies and the right mix of standard features vs. custom development
options I see a path where it becomes a solid choice for these companies. On the other hand there are the usual problems
of VC-backed OSS development, and we shall see how it goes for them. Unless there is a strong userbase that credibly
threatens a hard fork, enshittification could start soon.&lt;/p>
&lt;h1 id="addendum-important-features">Addendum: Important Features&lt;/h1>
&lt;p>Here is a short list of features I missed and their place on the roadmap if they have one&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Compose &amp;amp; Send E-Mails&lt;/strong>
Planned &lt;a href="https://github.com/orgs/twentyhq/projects/1?pane=issue&amp;amp;itemId=106097937&amp;amp;issue=twentyhq%7Ccore-team-issues%7C811">Q4 2025&lt;/a>&lt;/li>
&lt;li>&lt;strong>Foreach loops in Workflows&lt;/strong> &lt;a href="https://github.com/orgs/twentyhq/projects/1/views/33?pane=issue&amp;amp;itemId=93150024&amp;amp;issue=twentyhq%7Ccore-team-issues%7C21">Q3 2025&lt;/a>&lt;/li>
&lt;li>&lt;strong>Conditions in Flows&lt;/strong> &lt;a href="https://github.com/orgs/twentyhq/projects/1/views/33?pane=issue&amp;amp;itemId=121287765&amp;amp;issue=twentyhq%7Ccore-team-issues%7C1265">Q4 2025&lt;/a>&lt;/li>
&lt;/ul></content></entry><entry><title>Disappearing messages with matrix</title><link href="https://hyteck.de/post/matrix-forget/" type="application/octet-stream"/><updated>2021-12-31T20:00:00+02:00</updated><id>https://hyteck.de/post/matrix-forget/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;h1 id="introduction">Introduction&lt;/h1>
&lt;p>I am a HUGE fan of matrix. It allows me to organize my chats in a sensible way, it works with multiple identities and completly anonymous if I want it to. &lt;a href="https://element.io/blog/spaces-blast-out-of-beta/">Spaces&lt;/a> made Matrix my favourite messenger by far. Yet, there is one feature I have been missing: Disappearing messages!&lt;/p>
&lt;p>Regarding the security and usability, only Signal is comparable to matrix. But: Signal offers the possibilty to define disappearing messages for groups and direct messages ranging from 30 seconds to 4 weeks. No Matrix client (to my knowledge) offers this functionality. Nevertheless, it is possible to configure matrix rooms to have the same feature. This needs a special server configuration and the sending of a special event in the room. This post tries to show both steps. If you do not administer a server you can probably skip to &lt;a href="#room-configuration">Room configuration&lt;/a>&lt;/p>
&lt;p>Be aware that this blogpost was written at the end of 2021 - Matrix develops fast and this could be subejct to changes.&lt;/p>
&lt;h1 id="instance-configuration">Instance configuration&lt;/h1>
&lt;p>To make disappearing messages possible you need to enable retention on your matrix instance.
&lt;a href="https://github.com/matrix-org/matrix-doc/blob/matthew/msc1763/proposals/1763-configurable-retention-periods.md">Retention&lt;/a> allows server and room admins to configure how long messages should be kept in the instances database before being purged from it. It is not part of the matrix specification, yet it is supported by synapse.&lt;/p>
&lt;p>A client SHOULD not display these messages anymore after the max_lifetime is exceeded. This was NOT true for element web an desktop while staying logged in. Nevertheless, a newly logged in client did not have access to the messages.&lt;/p>
&lt;p>To configure Synapse to make use of retention you will need to enable it in your &lt;code>homeserver.yaml&lt;/code>&lt;/p>
&lt;pre tabindex="0">&lt;code>retention:
enabled: true # enables the retention, is enough to enforce it once per day
purge_jobs: # configures a job that delete the events from the database after some tome
- longest_max_lifetime: 3d
interval: 1h
- shortest_max_lifetime: 3d
interval: 1d
&lt;/code>&lt;/pre>&lt;p>The example configuration creates two jobs that delete messages from the database. One only focuses on events that should be deleted after three days or less. These events will be deleted every hour. It is therefore possible for a message that was send in a room with a &lt;code>max_lifetime=7200000&lt;/code> (equals 2h) to be deleted one hour after the maximum lifetime.&lt;/p>
&lt;h2 id="ansible">Ansible&lt;/h2>
&lt;p>If you use the &lt;a href="https://github.com/spantaleev/matrix-docker-ansible-deploy">Ansible/Docker setup&lt;/a> to deploy your server you can add the following to &lt;code>inventory/host_vars/matrix.example.com/vars.yml&lt;/code>&lt;/p>
&lt;pre tabindex="0">&lt;code>matrix_synapse_configuration_extension_yaml: |
retention:
enabled: true
purge_jobs:
- longest_max_lifetime: 1d
interval: 2h
- shortest_max_lifetime: 1d
interval: 1d
&lt;/code>&lt;/pre>&lt;h1 id="room-configuration">Room configuration&lt;/h1>
&lt;p>If you are a user on a server that has retention enabled, you can enable disappearing messages yourself for each room. Sadly, this is still experimental - but managable! You have to craft a &lt;code>m.room.retention&lt;/code> event that defines the maximum lifetime of a message. You will need to access the rooms settings in order to do this.&lt;/p>
&lt;p>First you need to open the developer tools in the rooms settings.
&lt;img src="https://hyteck.de/uploads/matrix-retention/retention_dev.png" alt="Screenshot of the element room settings">&lt;/p>
&lt;p>Then click &amp;ldquo;Send custom event&amp;rdquo; to create your event
&lt;img src="https://hyteck.de/uploads/matrix-retention/retention_event_button.png" alt="Screenshot of element marking the button &amp;ldquo;Send custom event&amp;rdquo; in the developer tools">&lt;/p>
&lt;p>And fill the event with the appropriate &lt;code>max_liftime&lt;/code>. The time is an integer in milliseconds. X hours is therefore a value of &lt;code>X*3 600 000&lt;/code>. Make sure to click the red event button. The &lt;code>State Key&lt;/code> can be left empty nevertheless.
&lt;img src="https://hyteck.de/uploads/matrix-retention/retention_event.png" alt="Screenshot of the creation of creating a custom event. The field event type is filled with m.room.retention the event content is &amp;ldquo;max_lifetime&amp;rdquo;: 3600000 and the button event was clicked">&lt;/p>
&lt;p>Depending on your choosen lifetime the client should not show the messages anymore.&lt;/p>
&lt;h1 id="limitations">Limitations&lt;/h1>
&lt;p>The process of deleting messages can not be enforced. A malicious server or chat partner could ignore the request to delete the messages or they could have saved them elsewere. You should not rely on a deletion actually happening. Nevertheless I think this is a good step to take to improve your security in real life.&lt;/p>
&lt;h1 id="further-information">Further Information&lt;/h1>
&lt;ul>
&lt;li>Relevant part of the Synapse configuration file: &lt;a href="https://github.com/matrix-org/synapse/blob/v1.36.0/docs/sample_config.yaml#L451-L518">https://github.com/matrix-org/synapse/blob/v1.36.0/docs/sample_config.yaml#L451-L518&lt;/a>&lt;/li>
&lt;li>Synapse documenation on message retention policies &lt;a href="https://matrix-org.github.io/synapse/v1.41/message_retention_policies.html">https://matrix-org.github.io/synapse/v1.41/message_retention_policies.html&lt;/a>&lt;/li>
&lt;/ul>
&lt;h1 id="what-else">What else?&lt;/h1>
&lt;p>Thanks to &lt;a href="https://tastytea.de/">Tastytea&lt;/a> for helping me get this to work!&lt;/p>
&lt;h1 id="comments">Comments&lt;/h1>
&lt;p>If you have questions, corrections or want to leave something else, please feel free to use the comments!&lt;/p>
&lt;script type="text/javascript" src="https://latest.cactus.chat/cactus.js">&lt;/script>
&lt;link rel="stylesheet" href="https://latest.cactus.chat/style.css" type="text/css">
&lt;div id="comment-section">&lt;/div>
&lt;script>
initComments({
node: document.getElementById("comment-section"),
defaultHomeserverUrl: "https://matrix.cactus.chat:8448",
serverName: "cactus.chat",
siteName: "hyteck",
commentSectionId: "matrix-disappearing-messages"
})
&lt;/script></content></entry><entry><title>Cactus comments via Matrix</title><link href="https://hyteck.de/post/cactus-chat/" type="application/octet-stream"/><updated>2021-08-25T11:08:55+02:00</updated><id>https://hyteck.de/post/cactus-chat/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;h1 id="integration">Integration&lt;/h1>
&lt;p>Following the &lt;a href="https://cactus.chat/docs/getting-started/quick-start/">quickstart documentation&lt;/a> I tried to add cactus comments to this blog. I currently rely on infrastructure by cactus.chat as I do not host a private synapse server.&lt;/p>
&lt;p>I currently implemented this as a shortcode with hard-coded site title and a variable room name.&lt;/p>
&lt;h1 id="quickstart-with-hugo">Quickstart with HUGO&lt;/h1>
&lt;h2 id="register-your-site">Register your site&lt;/h2>
&lt;p>There is a registration system, that ensures that you are moderater in your comment section(s). I order to register your site you have to send a message to @cactusbot:cactus.chat . First try &lt;code>help&lt;/code> to ensure that the bot answers you, then register your site e.g. &lt;code>register hyteck&lt;/code>. The bot should inform you of success and add you to a moderation room.&lt;/p>
&lt;h2 id="embedd-comment-section-via-shortcode">Embedd comment section via Shortcode&lt;/h2>
&lt;p>HUGO, the static site generator I use, has an option to use &lt;em>shortcodes&lt;/em> that provide a nice interface to hide some HTML, CSS and JavaScript.&lt;/p>
&lt;p>The shortcode &lt;code>chat.html&lt;/code> must be added to &lt;code>layouts/shortcodes/&lt;/code> and looks like this&lt;/p>
&lt;pre tabindex="0">&lt;code>&amp;lt;script type=&amp;#34;text/javascript&amp;#34; src=&amp;#34;https://latest.cactus.chat/cactus.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;link rel=&amp;#34;stylesheet&amp;#34; href=&amp;#34;https://latest.cactus.chat/style.css&amp;#34; type=&amp;#34;text/css&amp;#34;&amp;gt;
&amp;lt;div id=&amp;#34;comment-section&amp;#34;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;script&amp;gt;
initComments({
node: document.getElementById(&amp;#34;comment-section&amp;#34;),
defaultHomeserverUrl: &amp;#34;https://matrix.cactus.chat:8448&amp;#34;,
serverName: &amp;#34;cactus.chat&amp;#34;,
siteName: &amp;#34;hyteck&amp;#34;,
commentSectionId: &amp;#34;{{ index .Params 0 }}&amp;#34;
})
&amp;lt;/script&amp;gt;
&lt;/code>&lt;/pre>&lt;p>If you want to use this, replace the site name with the one you registered in the previous step.&lt;/p>
&lt;p>You can then use it as simple as&lt;/p>
&lt;pre tabindex="0">&lt;code>{{&amp;lt; chat cactus-comments &amp;gt;}}
&lt;/code>&lt;/pre>&lt;p>where &lt;code>cactus-comments&lt;/code> is the name of the chatroom. You can decide if you want to create a new one for each post (change the name to somthing like the post title) or if you want to use only one (keep the same name).&lt;/p>
&lt;h1 id="organisation">Organisation&lt;/h1>
&lt;p>I try to make a comment with my main matrix account, so that I am automatically joined to the room (tbh. I don&amp;rsquo;t know if this is necessary). Then I organize the rooms by adding them to a private space.&lt;/p>
&lt;p>&lt;img src="https://hyteck.de/uploads/element_screenshot.PNG" alt="Element screenshot">&lt;/p>
&lt;h1 id="community">Community&lt;/h1>
&lt;p>CW: Homophobia, Slur&lt;/p>
&lt;p>The community in the official chat seemed helpful and nice. Nevertheless I want to mention, that the first time I tried the demo page a user (that does not seem to be associated with the develoipment or involved in the community) insulted me with a homophobic slur. The &amp;ldquo;reason&amp;rdquo; behind this were the prounouns I had in my name. I reported the comment and it was removed by a moderator. The moderator made it clear that this behaviour is agains the &lt;a href="https://cactus.chat/docs/community/coc/">Code of Conduct&lt;/a> and that the user is not active in the community. For me this is a very good indication of a functioning community, cheers!&lt;/p>
&lt;h1 id="comments">Comments&lt;/h1>
&lt;script type="text/javascript" src="https://latest.cactus.chat/cactus.js">&lt;/script>
&lt;link rel="stylesheet" href="https://latest.cactus.chat/style.css" type="text/css">
&lt;div id="comment-section">&lt;/div>
&lt;script>
initComments({
node: document.getElementById("comment-section"),
defaultHomeserverUrl: "https://matrix.cactus.chat:8448",
serverName: "cactus.chat",
siteName: "hyteck",
commentSectionId: "cactus-comments"
})
&lt;/script></content></entry><entry><title>Owncast &amp; Streaming a talk</title><link href="https://hyteck.de/post/owncast/" type="application/octet-stream"/><updated>2021-05-20T16:08:55+02:00</updated><id>https://hyteck.de/post/owncast/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;p>I recently installed an Owncast server and wanted to share my experience. Here it is:&lt;/p>
&lt;h1 id="what-is-owncast">What is owncast?&lt;/h1>
&lt;p>Owncast is a streaming server that you can selfhost, a &lt;em>Twicht in a box&lt;/em> as the developers call it.
You host owncast on your server (a small VM with good downlink is enough) and can stream your own own content like you would do on Twicht, YouTube etc&amp;hellip;&lt;/p>
&lt;p>It has a chat, a admin panel for customization and thats it! You don&amp;rsquo;t need more to e.g. stream while you are playing minecraft or want to share a talk.&lt;/p>
&lt;h1 id="getting-started">Getting started&lt;/h1>
&lt;p>Get the latest release on &lt;a href="https://github.com/owncast/owncast/releases">GitHub&lt;/a> by using&lt;/p>
&lt;pre tabindex="0">&lt;code>$ mkdir owncast
$ cd owncast
$ wget https://github.com/owncast/owncast/releases/download/v0.0.7/owncast-0.0.7-linux-64bit.zip
$ unzip owncast-0.0.7-linux-64bit.zip
$ rm owncast-0.0.7-linux-64bit.zip
&lt;/code>&lt;/pre>&lt;p>And move the webroot to your document root and make sure the permissions fit&lt;/p>
&lt;pre tabindex="0">&lt;code>$ cd ..
$ mv owncast /var/www/owncast
$ cd /var/www/
$ chown -R www-data:www-data owncast
&lt;/code>&lt;/pre>&lt;p>Now create a new NGINX site e.g. &lt;code>/etc/nginx/sites-enabled/owncast&lt;/code> with the following content&lt;/p>
&lt;pre tabindex="0">&lt;code>map $http_upgrade $connection_upgrade {
default upgrade;
&amp;#39;&amp;#39; close;
}
server {
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/stream.hyteck.de/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/stream.hyteck.de/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
server_name stream.hyteck.de;
# Set header
add_header X-Clacks-Overhead &amp;#34;GNU Terry Pratchett&amp;#34;;
add_header Permissions-Policy interest-cohort=(); #Anti FLoC
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://127.0.0.1:8080;
}
}
server {
listen 80;
listen [::]:80;
server_name stream.hyteck.de;
return 301 https://$server_name$request_uri;
}
&lt;/code>&lt;/pre>&lt;p>Make sure to adjust the server name and SSL certificats (I will not go into detail on how to obtain them, but &lt;a href="https://hyteck.de/about/">feel free to ask me!&lt;/a>).&lt;/p>
&lt;h1 id="start-server">Start server&lt;/h1>
&lt;p>Now start owncast to test&lt;/p>
&lt;pre tabindex="0">&lt;code>$ cd /var/www/owncast
$ ./owncast/owncast
&lt;/code>&lt;/pre>&lt;p>and visit &lt;a href="https://yourdomain.org">https://yourdomain.org&lt;/a>! If everything works you should see your site now. By visiting &lt;a href="https://yourdomain.org/admin">https://yourdomain.org/admin&lt;/a> you can configure your server. The default credentials are &lt;code>admin&lt;/code> and your stream key which is &lt;code>abc123&lt;/code>. Change this immediately!&lt;/p>
&lt;p>Before you configure, let&amp;rsquo;s make sure this runs whenever your server starts. Goback in the terminal and cancel with &lt;code>Ctrl+C&lt;/code>.&lt;/p>
&lt;h1 id="run-as-system-service">Run as system service&lt;/h1>
&lt;p>You want to install owncast as a system service. Therfore create &lt;code>/etc/systemd/system/owncast.service&lt;/code> with the following content:&lt;/p>
&lt;pre tabindex="0">&lt;code>[Unit]
Description=Owncast
After=network.target
[Service]
Type=simple
Restart=always
RestartSec=1
WorkingDirectory=/var/www/owncast
ExecStart=/var/www/owncast/owncast
[Install]
WantedBy=multi-user.target
&lt;/code>&lt;/pre>&lt;p>Update the daemon with &lt;code>systemctl daemon-reload&lt;/code> enable &lt;code>systemctl enable owncast&lt;/code> and start with &lt;code>systemctl start owncast&lt;/code>. Make sure everything is correct with &lt;code>systemctl status owncast&lt;/code>.&lt;/p>
&lt;h1 id="configuration">Configuration&lt;/h1>
&lt;p>You can now change back to yourdoiman.org/admin and configure, title, logo and more.&lt;/p>
&lt;h2 id="directory">Directory&lt;/h2>
&lt;p>If you start a stream and have directory enabled, Owncast will publish your activity, e.g. in the owncast RocketChat, on Twitter (by mentioning you if you gave Owncast your Twitter Handle) and in the Fediverse. Turn this of for testing!&lt;/p>
&lt;h1 id="streaming">Streaming&lt;/h1>
&lt;p>You can now use &lt;a href="https://obsproject.com/">OBS&lt;/a> or similar software to start streaming. Got to settings and configure your server.&lt;/p>
&lt;p>&lt;img src="https://hyteck.de/uploads/stream_config.png" alt="Configuration in the OBS streaming tab. The service is set to Custom.. and the server to rtmp://stream.hyteck.de/live. The stream key is hidden">&lt;/p>
&lt;h1 id="concept-for-talks">Concept for talks&lt;/h1>
&lt;p>To livestream we have a few important things to take care of:&lt;/p>
&lt;ul>
&lt;li>The speaker&lt;/li>
&lt;li>The presentation&lt;/li>
&lt;li>The streaming software&lt;/li>
&lt;li>The streaming server&lt;/li>
&lt;li>The chat&lt;/li>
&lt;/ul>
&lt;p>I suggest the following setup. Moderation and Speaker are in a BigBlueButton conference. The meet 15 minutes in advance, enough time to upload a presentation. Also in this room is a user called stream. This user does only listen and has OBS configured to record audio and the (fullscreen) window of the BBB room.&lt;/p>
&lt;p>Before the talk starts, it is a good opportunity to play some videos via the stream, or simply put on a picture, saing the talk will start soon.&lt;/p>
&lt;p>When the speaker an moderation are ready they tell the streamer and the scene is switched to the BBB room. The recording is started (if needed).&lt;/p>
&lt;p>A weakness of the setup is the streaner. If they loose their network connection, the stream stops.&lt;/p>
&lt;p>There is a neat &lt;a href="https://github.com/aau-zid/BigBlueButton-liveStreaming">project for live streaming directly from a BBB room&lt;/a>, but it is quite complicated to set up and a crash of the BBB server would make stop the stream. Also the start of the stream is not as beautiful.&lt;/p>
&lt;h1 id="performance">Performance&lt;/h1>
&lt;p>It can be hard to estimate the needed server capacities for a livestream that is why I want to share my experiences. All of this was done on a Strato Linux V40 with 16 GB of RAM, 8 vCPUs and 500 MBit/s.&lt;/p>
&lt;p>On the following screenshots you see the configuration and the monitoring data of a talk with 40 participants. We pplayed high-quality videos from 17:43 to 18:05. At 18:05 we changed to the presentation. You see the decrease in CPU load, as the compression of the videos was easier with a steady picture. At 19:05 we invited the participents to join us in the BBB room, some left the stream.&lt;/p>
&lt;p>The network load scales linear with the participants that are watching. You can simply calculate the needed downlink capacity with&lt;/p>
&lt;pre tabindex="0">&lt;code>Downlink capacity = Num. participants x Outbound Video Stream Rate
&lt;/code>&lt;/pre>&lt;p>Here this was&lt;/p>
&lt;pre tabindex="0">&lt;code>40 participants x 1200 kbps = 48 Mbps
&lt;/code>&lt;/pre>&lt;p>which matches the observed network traffic quite well.&lt;/p>
&lt;p>&lt;img src="https://hyteck.de/uploads/monitoring_vortrag.png" alt="Chart of the CPU load and the network traffic basic. The CPU load was jumpy around 25% from 17:45 to 18:05, after that it was steady around 17.5%. The Network Traffic went up until 18:10 and stayed at 50 Mbps until 19:00" title="Grafana Screenshot">&lt;/p>
&lt;p>The CPU load is determined by the compression the server has to do. The more different stream formats the server has to put out, the higher the load becomes. On the given Screenshot you see that only one downlink format was advertised, so the inbound stream had to be compresse only once. Matching imbound and outbound stream formats help reducing the server load.&lt;/p>
&lt;p>&lt;img src="https://hyteck.de/uploads/vortrag_eg.png" alt="A screenshot of the Owncast admin panel. The outbound stream details are: 1200 kbps, 24 fps and the Input is H.264@2500kbps at 30 fps" title="Screenshot of the owncast admin panel">&lt;/p>
&lt;p>If your bottleneck is the bandwidth you can try to add a second low streamrate, maybe ome clients will take that one.&lt;/p>
&lt;h1 id="final-notes">Final notes&lt;/h1>
&lt;p>Owncast is a great project. Be aware that it is still not in version 1.0 so expect some limitations. This is e.g. the unability to block a user from chat. If you have any questions let me know in the comments below.&lt;/p>
&lt;script type="text/javascript" src="https://latest.cactus.chat/cactus.js">&lt;/script>
&lt;link rel="stylesheet" href="https://latest.cactus.chat/style.css" type="text/css">
&lt;div id="comment-section">&lt;/div>
&lt;script>
initComments({
node: document.getElementById("comment-section"),
defaultHomeserverUrl: "https://matrix.cactus.chat:8448",
serverName: "cactus.chat",
siteName: "hyteck",
commentSectionId: "owncast"
})
&lt;/script></content></entry></feed>