<?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>English on Hyteck</title><link href="https://hyteck.de/categories/english/"/><generator>Hugo -- gohugo.io</generator><language>en-us</language><id>https://hyteck.de/categories/english/</id><updated>2026-04-12T06:10:10+02:00</updated><link href="https://hyteck.de/categories/english/index.xml" rel="self" type="application/rss+xml"/><entry><title>Setting up an access point with OpenWRT</title><link href="https://hyteck.de/post/openwrt-access-points/" type="application/octet-stream"/><updated>2026-04-12T06:10:10+02:00</updated><id>https://hyteck.de/post/openwrt-access-points/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;p>My parents complained about the WiFi in their house. They had a router in the basement, an access point in the first
floor and a mesh-repeater in the ground floor. However, the WiFi performance in the ground floor was abysmal. So they
asked me to do something.&lt;/p>
&lt;h2 id="devices">Devices&lt;/h2>
&lt;p>I had two old devices around, a FritzBox 4040 and a TP-Link TL-WR1043ND v2. The latter was already set up
with &lt;a href="https://openwrt.org/">OpenWRT&lt;/a>. The Fritzbox however was bricked by past me. After reading
the &lt;a href="https://openwrt.org/docs/guide-user/troubleshooting/generic.debrick">Debricking Guide&lt;/a> I nearly lost hope, but then
I
found &lt;a href="https://openwrt.org/toh/avm/avm_fritz_box_4040#flashing_stock_firmware">AVM provides a recovery tool (Windows only)&lt;/a>.
Without any sarcasm: Fritzboxes are a pleasure to work with.&lt;/p>
&lt;p>So after finding a usable windows laptop I first flashed the stock firmware and then
used &lt;a href="https://fritz-tools.readthedocs.io/en/latest/flashing/ubuntu_1804.html">fritz-tools&lt;/a> to flash OpenWRT. Fritz-Tools
works, even if the page only mentions Gluon.&lt;/p>
&lt;h2 id="planning">Planning&lt;/h2>
&lt;p>My parents for some reason decided to rent a router from the ISP. While this is obviously not ideal I decided to just
work with it. So WAN connectivity and DHCP will be handled by the ISP&amp;rsquo;s router in the basement. I&amp;rsquo;ll only add two dumb
access points, connected to the ISP&amp;rsquo;s router by LAN.&lt;/p>
&lt;h2 id="configuring-internet-access">Configuring internet access&lt;/h2>
&lt;p>I mostly followed &lt;a href="https://www.youtube.com/watch?v=kMgs2XFClaM">OneMarkFifty&amp;rsquo;s great video&lt;/a> on how to do this.
The &lt;a href="https://openwrt.org/docs/guide-user/network/wifi/wifiextenders/bridgedap">OpenWRT docs article&lt;/a> is not tht easy to
follow and assumes using a static IP for the access points. However, I wanted the AP&amp;rsquo;s to get their IP via DHCP.&lt;/p>
&lt;p>The first step is to connect the AP the local LAN and your computer. Make sure that both cables are connected to LAN, do
not use the WAN jack. You can then access the AP&amp;rsquo;s configuration page at 192.168.1.1
Got to Network -&amp;gt; Interfaces and remove all interfaces except LAN, you don&amp;rsquo;t need them.&lt;/p>
&lt;p>For LAN you set the following settings&lt;/p>
&lt;p>&lt;img src="interface_lan_1.png" alt="OpenWRT settings for LAN. Protocol is set to DHCP client, Device is br-lan">&lt;/p>
&lt;p>Also set&lt;/p>
&lt;ul>
&lt;li>&lt;em>DHCP Server -&amp;gt; General Setup -&amp;gt; Ignore Interface&lt;/em> to &lt;strong>True&lt;/strong>: This stops the AP from assigning IP addresses for IPv4&lt;/li>
&lt;li>&lt;em>DHCP Server -&amp;gt; IPv6 Settings -&amp;gt; RA Service&lt;/em> to &lt;strong>Disabled&lt;/strong>&lt;/li>
&lt;li>&lt;em>DHCP Server -&amp;gt; IPv6 Settings -&amp;gt; DHCPv6-Service&lt;/em> to &lt;strong>Disabled&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>In combination this now tells the AP to get a IP Address from DHCP and completely deactivates assigning IP addresses to
clients. Instead, clients will get an IP from the main router.&lt;/p>
&lt;p>&lt;strong>Important&lt;/strong> When applying this setting, your connection to the IP will be lost as it gets a new IP. You then have 90
seconds to visit the configuration page again, otherwise the configuration will be reset. This is an important security
feature, so don&amp;rsquo;t turn this off! The best way to re-access the AP is to look up the newly assigned IP in the main router
and then input that in the browser.&lt;/p>
&lt;p>You should now be able to browse the internet via this AP alone.&lt;/p>
&lt;h2 id="configuring-wifi">Configuring WiFi&lt;/h2>
&lt;p>Go to Network -&amp;gt; Wireless and click on &amp;ldquo;Edit&amp;rdquo; for the station you want to set up. Put in the ESSID (WiFi name), the
Network (lan) and set Wireless Security. If you have multiple APs, go to WLAN roaming and set &lt;code>802.11r Fast Transition&lt;/code>
to True and add a Mobility Domain.&lt;/p>
&lt;p>Then apply the settings. It can take up to a minute for the Access point to be enabled on slower hardware.&lt;/p>
&lt;h2 id="setting-up-additional-aps">Setting up additional APs&lt;/h2>
&lt;p>You should be able to do the same procedure for every AP you want to add. Use the same ESSID, Wireless Security and
mobility domain for each AP.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>My parents can now finally watch TV in their kitchen, powered by two previous unused devices. I&amp;rsquo;m really impressed how
well it works once set up. I appreciate a lot that OpenWRT enables me to connect two devices of different manufacturers
without issues. If you give it a try, let me know how it goes!&lt;/p></content></entry><entry><title>How to manually check hundreds of animal shelters - every 14 days</title><link href="https://hyteck.de/post/checking-shelters/" type="application/octet-stream"/><updated>2025-11-08T12:05:10+02:00</updated><id>https://hyteck.de/post/checking-shelters/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;p>I run a website called &lt;a href="https://notfellchen.org">Notfellchen&lt;/a> that list animals that are waiting for adoption. It&amp;rsquo;s
currently restricted to fancy rats in Germany and that for good reason: Running this website involves &lt;strong>checking every
shelter every two weeks manually&lt;/strong>. You need to visit the website, check if there are new animals, contact the shelter
and add them to notfellchen if they allow it. This takes time. A lot.&lt;/p>
&lt;p>This blog post will outline some of the things I did in order to streamline this and make it possible to &lt;strong>check every
german shelter in 2.5 hours&lt;/strong>.&lt;/p>
&lt;h2 id="general-process">General process&lt;/h2>
&lt;p>When you establish a process. want others to help you or if you want to find inefficiencies, it&amp;rsquo;s a good idea to
formalize it. So here is a rough BPMN diagram of the whole process.&lt;/p>
&lt;div class="embedded-html">
&lt;!--[if IE]>&lt;meta http-equiv="X-UA-Compatible" content="IE=5,IE=9" >&lt;![endif]-->
&lt;!DOCTYPE html>
&lt;html>
&lt;head>
&lt;title>Rescue save process&lt;/title>
&lt;meta charset="utf-8"/>
&lt;/head>
&lt;body>&lt;div class="mxgraph" style="max-width:100%;border:1px solid transparent;" data-mxgraph="{&amp;quot;highlight&amp;quot;:&amp;quot;#0000ff&amp;quot;,&amp;quot;nav&amp;quot;:true,&amp;quot;resize&amp;quot;:true,&amp;quot;dark-mode&amp;quot;:&amp;quot;auto&amp;quot;,&amp;quot;toolbar&amp;quot;:&amp;quot;zoom layers tags lightbox&amp;quot;,&amp;quot;edit&amp;quot;:&amp;quot;_blank&amp;quot;,&amp;quot;xml&amp;quot;:&amp;quot;&amp;lt;mxfile host=\&amp;quot;app.diagrams.net\&amp;quot; agent=\&amp;quot;Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0\&amp;quot; version=\&amp;quot;28.2.7\&amp;quot;&amp;gt;\n &amp;lt;diagram name=\&amp;quot;Page-1\&amp;quot; id=\&amp;quot;ZPuKLs6SsL8PyHUOEfSV\&amp;quot;&amp;gt;\n &amp;lt;mxGraphModel dx=\&amp;quot;1099\&amp;quot; dy=\&amp;quot;1436\&amp;quot; grid=\&amp;quot;1\&amp;quot; gridSize=\&amp;quot;10\&amp;quot; guides=\&amp;quot;1\&amp;quot; tooltips=\&amp;quot;1\&amp;quot; connect=\&amp;quot;1\&amp;quot; arrows=\&amp;quot;1\&amp;quot; fold=\&amp;quot;1\&amp;quot; page=\&amp;quot;1\&amp;quot; pageScale=\&amp;quot;1\&amp;quot; pageWidth=\&amp;quot;850\&amp;quot; pageHeight=\&amp;quot;1100\&amp;quot; math=\&amp;quot;0\&amp;quot; shadow=\&amp;quot;0\&amp;quot;&amp;gt;\n &amp;lt;root&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;0\&amp;quot; /&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;1\&amp;quot; parent=\&amp;quot;0\&amp;quot; /&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-1\&amp;quot; value=\&amp;quot;\&amp;quot; style=\&amp;quot;points=[[0.145,0.145,0],[0.5,0,0],[0.855,0.145,0],[1,0.5,0],[0.855,0.855,0],[0.5,1,0],[0.145,0.855,0],[0,0.5,0]];shape=mxgraph.bpmn.event;html=1;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;align=center;perimeter=ellipsePerimeter;outlineConnect=0;aspect=fixed;outline=standard;symbol=general;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;50\&amp;quot; y=\&amp;quot;135\&amp;quot; width=\&amp;quot;50\&amp;quot; height=\&amp;quot;50\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-2\&amp;quot; value=\&amp;quot;\&amp;quot; style=\&amp;quot;points=[[0.145,0.145,0],[0.5,0,0],[0.855,0.145,0],[1,0.5,0],[0.855,0.855,0],[0.5,1,0],[0.145,0.855,0],[0,0.5,0]];shape=mxgraph.bpmn.event;html=1;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;align=center;perimeter=ellipsePerimeter;outlineConnect=0;aspect=fixed;outline=end;symbol=terminate2;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;1780\&amp;quot; y=\&amp;quot;85\&amp;quot; width=\&amp;quot;50\&amp;quot; height=\&amp;quot;50\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-3\&amp;quot; value=\&amp;quot;Open animal shelter website\&amp;quot; style=\&amp;quot;points=[[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];shape=mxgraph.bpmn.task2;whiteSpace=wrap;rectStyle=rounded;size=10;html=1;container=1;expand=0;collapsible=0;taskMarker=abstract;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;130\&amp;quot; y=\&amp;quot;120\&amp;quot; width=\&amp;quot;120\&amp;quot; height=\&amp;quot;80\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-4\&amp;quot; value=\&amp;quot;\&amp;quot; style=\&amp;quot;points=[[0.145,0.145,0],[0.5,0,0],[0.855,0.145,0],[1,0.5,0],[0.855,0.855,0],[0.5,1,0],[0.145,0.855,0],[0,0.5,0]];shape=mxgraph.bpmn.event;html=1;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;align=center;perimeter=ellipsePerimeter;outlineConnect=0;aspect=fixed;outline=end;symbol=terminate2;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;620\&amp;quot; y=\&amp;quot;20\&amp;quot; width=\&amp;quot;50\&amp;quot; height=\&amp;quot;50\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-5\&amp;quot; value=\&amp;quot;Mark organization as checked\&amp;quot; style=\&amp;quot;points=[[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];shape=mxgraph.bpmn.task2;whiteSpace=wrap;rectStyle=rounded;size=10;html=1;container=1;expand=0;collapsible=0;taskMarker=abstract;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;450\&amp;quot; y=\&amp;quot;5\&amp;quot; width=\&amp;quot;120\&amp;quot; height=\&amp;quot;80\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-6\&amp;quot; value=\&amp;quot;Notfellchen-relevant&amp;amp;lt;br&amp;amp;gt;animals there?\&amp;quot; style=\&amp;quot;points=[[0.25,0.25,0],[0.5,0,0],[0.75,0.25,0],[1,0.5,0],[0.75,0.75,0],[0.5,1,0],[0.25,0.75,0],[0,0.5,0]];shape=mxgraph.bpmn.gateway2;html=1;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;align=center;perimeter=rhombusPerimeter;outlineConnect=0;outline=none;symbol=none;gwType=exclusive;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;290\&amp;quot; y=\&amp;quot;135\&amp;quot; width=\&amp;quot;50\&amp;quot; height=\&amp;quot;50\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-7\&amp;quot; value=\&amp;quot;Animals&amp;amp;lt;br&amp;amp;gt;&amp;amp;lt;div&amp;amp;gt;already&amp;amp;lt;/div&amp;amp;gt;&amp;amp;lt;div&amp;amp;gt;listed on&amp;amp;lt;br&amp;amp;gt;Notfellchen?&amp;amp;lt;/div&amp;amp;gt;\&amp;quot; style=\&amp;quot;points=[[0.25,0.25,0],[0.5,0,0],[0.75,0.25,0],[1,0.5,0],[0.75,0.75,0],[0.5,1,0],[0.25,0.75,0],[0,0.5,0]];shape=mxgraph.bpmn.gateway2;html=1;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;align=center;perimeter=rhombusPerimeter;outlineConnect=0;outline=none;symbol=none;gwType=exclusive;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;400\&amp;quot; y=\&amp;quot;135\&amp;quot; width=\&amp;quot;50\&amp;quot; height=\&amp;quot;50\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-8\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;curved=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-7\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-5\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-9\&amp;quot; value=\&amp;quot;Yes\&amp;quot; style=\&amp;quot;edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];\&amp;quot; parent=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-8\&amp;quot; vertex=\&amp;quot;1\&amp;quot; connectable=\&amp;quot;0\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;-0.3565\&amp;quot; y=\&amp;quot;-3\&amp;quot; relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot;&amp;gt;\n &amp;lt;mxPoint x=\&amp;quot;-3\&amp;quot; y=\&amp;quot;22\&amp;quot; as=\&amp;quot;offset\&amp;quot; /&amp;gt;\n &amp;lt;/mxGeometry&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-10\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;curved=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-6\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-5\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-11\&amp;quot; value=\&amp;quot;No\&amp;quot; style=\&amp;quot;edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];\&amp;quot; parent=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-10\&amp;quot; vertex=\&amp;quot;1\&amp;quot; connectable=\&amp;quot;0\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;-0.84\&amp;quot; relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot;&amp;gt;\n &amp;lt;mxPoint as=\&amp;quot;offset\&amp;quot; /&amp;gt;\n &amp;lt;/mxGeometry&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-12\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-5\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-4\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-13\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-3\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-6\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-14\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-6\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-7\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-15\&amp;quot; value=\&amp;quot;Yes\&amp;quot; style=\&amp;quot;edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];\&amp;quot; parent=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-14\&amp;quot; vertex=\&amp;quot;1\&amp;quot; connectable=\&amp;quot;0\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;-0.3333\&amp;quot; y=\&amp;quot;1\&amp;quot; relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot;&amp;gt;\n &amp;lt;mxPoint as=\&amp;quot;offset\&amp;quot; /&amp;gt;\n &amp;lt;/mxGeometry&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-16\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;curved=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-18\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-25\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-17\&amp;quot; value=\&amp;quot;No\&amp;quot; style=\&amp;quot;edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];\&amp;quot; parent=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-16\&amp;quot; vertex=\&amp;quot;1\&amp;quot; connectable=\&amp;quot;0\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;-0.088\&amp;quot; y=\&amp;quot;3\&amp;quot; relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot;&amp;gt;\n &amp;lt;mxPoint as=\&amp;quot;offset\&amp;quot; /&amp;gt;\n &amp;lt;/mxGeometry&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-18\&amp;quot; value=\&amp;quot;Organization&amp;amp;lt;br&amp;amp;gt;allows&amp;amp;lt;br&amp;amp;gt;listing?\&amp;quot; style=\&amp;quot;points=[[0.25,0.25,0],[0.5,0,0],[0.75,0.25,0],[1,0.5,0],[0.75,0.75,0],[0.5,1,0],[0.25,0.75,0],[0,0.5,0]];shape=mxgraph.bpmn.gateway2;html=1;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;align=center;perimeter=rhombusPerimeter;outlineConnect=0;outline=none;symbol=none;gwType=exclusive;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;510\&amp;quot; y=\&amp;quot;135\&amp;quot; width=\&amp;quot;50\&amp;quot; height=\&amp;quot;50\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-19\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-7\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-18\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-20\&amp;quot; value=\&amp;quot;No\&amp;quot; style=\&amp;quot;edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];\&amp;quot; parent=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-19\&amp;quot; vertex=\&amp;quot;1\&amp;quot; connectable=\&amp;quot;0\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;-0.2\&amp;quot; y=\&amp;quot;2\&amp;quot; relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot;&amp;gt;\n &amp;lt;mxPoint as=\&amp;quot;offset\&amp;quot; /&amp;gt;\n &amp;lt;/mxGeometry&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-21\&amp;quot; value=\&amp;quot;Add animals to Notfellchen\&amp;quot; style=\&amp;quot;points=[[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];shape=mxgraph.bpmn.task2;whiteSpace=wrap;rectStyle=rounded;size=10;html=1;container=1;expand=0;collapsible=0;taskMarker=abstract;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;1610\&amp;quot; y=\&amp;quot;70\&amp;quot; width=\&amp;quot;120\&amp;quot; height=\&amp;quot;80\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-22\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;curved=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-18\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-21\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot;&amp;gt;\n &amp;lt;Array as=\&amp;quot;points\&amp;quot;&amp;gt;\n &amp;lt;mxPoint x=\&amp;quot;610\&amp;quot; y=\&amp;quot;160\&amp;quot; /&amp;gt;\n &amp;lt;mxPoint x=\&amp;quot;610\&amp;quot; y=\&amp;quot;110\&amp;quot; /&amp;gt;\n &amp;lt;/Array&amp;gt;\n &amp;lt;/mxGeometry&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-23\&amp;quot; value=\&amp;quot;Yes\&amp;quot; style=\&amp;quot;edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];\&amp;quot; parent=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-22\&amp;quot; vertex=\&amp;quot;1\&amp;quot; connectable=\&amp;quot;0\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;-0.4\&amp;quot; y=\&amp;quot;1\&amp;quot; relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot;&amp;gt;\n &amp;lt;mxPoint x=\&amp;quot;-230\&amp;quot; y=\&amp;quot;26\&amp;quot; as=\&amp;quot;offset\&amp;quot; /&amp;gt;\n &amp;lt;/mxGeometry&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-24\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-1\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-3\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-25\&amp;quot; value=\&amp;quot;Mark organization as &amp;amp;quot;Ongoing conversation&amp;amp;quot;\&amp;quot; style=\&amp;quot;points=[[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];shape=mxgraph.bpmn.task2;whiteSpace=wrap;rectStyle=rounded;size=10;html=1;container=1;expand=0;collapsible=0;taskMarker=abstract;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;550\&amp;quot; y=\&amp;quot;265\&amp;quot; width=\&amp;quot;120\&amp;quot; height=\&amp;quot;80\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-26\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-21\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-2\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-27\&amp;quot; value=\&amp;quot;Send E-Mail asking for listing permission\&amp;quot; style=\&amp;quot;points=[[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];shape=mxgraph.bpmn.task2;whiteSpace=wrap;rectStyle=rounded;size=10;html=1;container=1;expand=0;collapsible=0;taskMarker=abstract;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;710\&amp;quot; y=\&amp;quot;265\&amp;quot; width=\&amp;quot;120\&amp;quot; height=\&amp;quot;80\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-28\&amp;quot; value=\&amp;quot;\&amp;quot; style=\&amp;quot;points=[[0.145,0.145,0],[0.5,0,0],[0.855,0.145,0],[1,0.5,0],[0.855,0.855,0],[0.5,1,0],[0.145,0.855,0],[0,0.5,0]];shape=mxgraph.bpmn.event;html=1;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;align=center;perimeter=ellipsePerimeter;outlineConnect=0;aspect=fixed;outline=standard;symbol=message;\&amp;quot; parent=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-27\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;80\&amp;quot; y=\&amp;quot;60\&amp;quot; width=\&amp;quot;50\&amp;quot; height=\&amp;quot;50\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-29\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-25\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-27\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-30\&amp;quot; value=\&amp;quot;Answer received\&amp;quot; style=\&amp;quot;points=[[0.145,0.145,0],[0.5,0,0],[0.855,0.145,0],[1,0.5,0],[0.855,0.855,0],[0.5,1,0],[0.145,0.855,0],[0,0.5,0]];shape=mxgraph.bpmn.event;html=1;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;align=center;perimeter=ellipsePerimeter;outlineConnect=0;aspect=fixed;outline=catching;symbol=message;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;880\&amp;quot; y=\&amp;quot;370\&amp;quot; width=\&amp;quot;50\&amp;quot; height=\&amp;quot;50\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-31\&amp;quot; value=\&amp;quot;min. 1 day\&amp;quot; style=\&amp;quot;points=[[0.145,0.145,0],[0.5,0,0],[0.855,0.145,0],[1,0.5,0],[0.855,0.855,0],[0.5,1,0],[0.145,0.855,0],[0,0.5,0]];shape=mxgraph.bpmn.event;html=1;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;align=center;perimeter=ellipsePerimeter;outlineConnect=0;aspect=fixed;outline=standard;symbol=timer;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;880\&amp;quot; y=\&amp;quot;280\&amp;quot; width=\&amp;quot;50\&amp;quot; height=\&amp;quot;50\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-32\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-27\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-31\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-33\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-34\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-36\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-34\&amp;quot; value=\&amp;quot;Call\&amp;quot; style=\&amp;quot;points=[[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];shape=mxgraph.bpmn.task2;whiteSpace=wrap;rectStyle=rounded;size=10;html=1;container=1;expand=0;collapsible=0;taskMarker=abstract;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;970\&amp;quot; y=\&amp;quot;265\&amp;quot; width=\&amp;quot;120\&amp;quot; height=\&amp;quot;80\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-35\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-31\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-34\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-36\&amp;quot; value=\&amp;quot;phone was&amp;amp;lt;br&amp;amp;gt;answered?\&amp;quot; style=\&amp;quot;points=[[0.25,0.25,0],[0.5,0,0],[0.75,0.25,0],[1,0.5,0],[0.75,0.75,0],[0.5,1,0],[0.25,0.75,0],[0,0.5,0]];shape=mxgraph.bpmn.gateway2;html=1;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;align=center;perimeter=rhombusPerimeter;outlineConnect=0;outline=none;symbol=none;gwType=exclusive;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;1120\&amp;quot; y=\&amp;quot;280\&amp;quot; width=\&amp;quot;50\&amp;quot; height=\&amp;quot;50\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-37\&amp;quot; value=\&amp;quot;any\&amp;quot; style=\&amp;quot;points=[[0.145,0.145,0],[0.5,0,0],[0.855,0.145,0],[1,0.5,0],[0.855,0.855,0],[0.5,1,0],[0.145,0.855,0],[0,0.5,0]];shape=mxgraph.bpmn.event;html=1;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;align=center;perimeter=ellipsePerimeter;outlineConnect=0;aspect=fixed;outline=standard;symbol=timer;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;1005\&amp;quot; y=\&amp;quot;185\&amp;quot; width=\&amp;quot;50\&amp;quot; height=\&amp;quot;50\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-38\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;curved=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-36\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-37\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-39\&amp;quot; value=\&amp;quot;nein\&amp;quot; style=\&amp;quot;edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];\&amp;quot; parent=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-38\&amp;quot; vertex=\&amp;quot;1\&amp;quot; connectable=\&amp;quot;0\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;-0.713\&amp;quot; y=\&amp;quot;1\&amp;quot; relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot;&amp;gt;\n &amp;lt;mxPoint as=\&amp;quot;offset\&amp;quot; /&amp;gt;\n &amp;lt;/mxGeometry&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-40\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;curved=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-37\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-34\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot;&amp;gt;\n &amp;lt;Array as=\&amp;quot;points\&amp;quot;&amp;gt;\n &amp;lt;mxPoint x=\&amp;quot;950\&amp;quot; y=\&amp;quot;210\&amp;quot; /&amp;gt;\n &amp;lt;mxPoint x=\&amp;quot;950\&amp;quot; y=\&amp;quot;305\&amp;quot; /&amp;gt;\n &amp;lt;/Array&amp;gt;\n &amp;lt;/mxGeometry&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-41\&amp;quot; value=\&amp;quot;Answered\&amp;quot; style=\&amp;quot;points=[[0.25,0.25,0],[0.5,0,0],[0.75,0.25,0],[1,0.5,0],[0.75,0.75,0],[0.5,1,0],[0.25,0.75,0],[0,0.5,0]];shape=mxgraph.bpmn.gateway2;html=1;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;align=center;perimeter=rhombusPerimeter;outlineConnect=0;outline=none;symbol=none;gwType=exclusive;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;1320\&amp;quot; y=\&amp;quot;280\&amp;quot; width=\&amp;quot;50\&amp;quot; height=\&amp;quot;50\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-42\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;exitDx=0;exitDy=0;exitPerimeter=0;entryDx=0;entryDy=0;entryPerimeter=0;curved=0;entryX=1;entryY=0.5;exitX=0.5;exitY=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-41\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-44\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot;&amp;gt;\n &amp;lt;mxPoint x=\&amp;quot;1190\&amp;quot; y=\&amp;quot;200\&amp;quot; as=\&amp;quot;targetPoint\&amp;quot; /&amp;gt;\n &amp;lt;/mxGeometry&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-43\&amp;quot; value=\&amp;quot;unclear\&amp;quot; style=\&amp;quot;edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];\&amp;quot; parent=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-42\&amp;quot; vertex=\&amp;quot;1\&amp;quot; connectable=\&amp;quot;0\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;-0.7988\&amp;quot; relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot;&amp;gt;\n &amp;lt;mxPoint as=\&amp;quot;offset\&amp;quot; /&amp;gt;\n &amp;lt;/mxGeometry&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-44\&amp;quot; value=\&amp;quot;Log call in internal comment including date\&amp;quot; style=\&amp;quot;points=[[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];shape=mxgraph.bpmn.task2;whiteSpace=wrap;rectStyle=rounded;size=10;html=1;container=1;expand=0;collapsible=0;taskMarker=abstract;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;1170\&amp;quot; y=\&amp;quot;170\&amp;quot; width=\&amp;quot;120\&amp;quot; height=\&amp;quot;80\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-45\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-44\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-37\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-46\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-36\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-41\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-47\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;curved=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-41\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-49\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-48\&amp;quot; value=\&amp;quot;Usage&amp;amp;lt;br&amp;amp;gt;allowed\&amp;quot; style=\&amp;quot;edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];\&amp;quot; parent=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-47\&amp;quot; vertex=\&amp;quot;1\&amp;quot; connectable=\&amp;quot;0\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;-0.2077\&amp;quot; relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot;&amp;gt;\n &amp;lt;mxPoint as=\&amp;quot;offset\&amp;quot; /&amp;gt;\n &amp;lt;/mxGeometry&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-49\&amp;quot; value=\&amp;quot;Log status and add internal comment including date and medium\&amp;quot; style=\&amp;quot;points=[[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];shape=mxgraph.bpmn.task2;whiteSpace=wrap;rectStyle=rounded;size=10;html=1;container=1;expand=0;collapsible=0;taskMarker=abstract;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;1430\&amp;quot; y=\&amp;quot;170\&amp;quot; width=\&amp;quot;120\&amp;quot; height=\&amp;quot;80\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-50\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;curved=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-49\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-21\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-51\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-52\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-55\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-52\&amp;quot; value=\&amp;quot;Log denial status and in internal comment including the reason of denial\&amp;quot; style=\&amp;quot;points=[[0.25,0,0],[0.5,0,0],[0.75,0,0],[1,0.25,0],[1,0.5,0],[1,0.75,0],[0.75,1,0],[0.5,1,0],[0.25,1,0],[0,0.75,0],[0,0.5,0],[0,0.25,0]];shape=mxgraph.bpmn.task2;whiteSpace=wrap;rectStyle=rounded;size=10;html=1;container=1;expand=0;collapsible=0;taskMarker=abstract;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;1440\&amp;quot; y=\&amp;quot;360\&amp;quot; width=\&amp;quot;120\&amp;quot; height=\&amp;quot;80\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-53\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;curved=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-41\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-52\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-54\&amp;quot; value=\&amp;quot;Usage&amp;amp;lt;br&amp;amp;gt;denied\&amp;quot; style=\&amp;quot;edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];\&amp;quot; parent=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-53\&amp;quot; vertex=\&amp;quot;1\&amp;quot; connectable=\&amp;quot;0\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;-0.2282\&amp;quot; relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot;&amp;gt;\n &amp;lt;mxPoint as=\&amp;quot;offset\&amp;quot; /&amp;gt;\n &amp;lt;/mxGeometry&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-55\&amp;quot; value=\&amp;quot;\&amp;quot; style=\&amp;quot;points=[[0.145,0.145,0],[0.5,0,0],[0.855,0.145,0],[1,0.5,0],[0.855,0.855,0],[0.5,1,0],[0.145,0.855,0],[0,0.5,0]];shape=mxgraph.bpmn.event;html=1;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;align=center;perimeter=ellipsePerimeter;outlineConnect=0;aspect=fixed;outline=end;symbol=terminate2;\&amp;quot; parent=\&amp;quot;1\&amp;quot; vertex=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry x=\&amp;quot;1600\&amp;quot; y=\&amp;quot;375\&amp;quot; width=\&amp;quot;50\&amp;quot; height=\&amp;quot;50\&amp;quot; as=\&amp;quot;geometry\&amp;quot; /&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;mxCell id=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-56\&amp;quot; style=\&amp;quot;edgeStyle=orthogonalEdgeStyle;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;curved=0;\&amp;quot; parent=\&amp;quot;1\&amp;quot; source=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-30\&amp;quot; target=\&amp;quot;kHcOzL8m4RCkJ4hDhTre-41\&amp;quot; edge=\&amp;quot;1\&amp;quot;&amp;gt;\n &amp;lt;mxGeometry relative=\&amp;quot;1\&amp;quot; as=\&amp;quot;geometry\&amp;quot;&amp;gt;\n &amp;lt;Array as=\&amp;quot;points\&amp;quot;&amp;gt;\n &amp;lt;mxPoint x=\&amp;quot;1280\&amp;quot; y=\&amp;quot;395\&amp;quot; /&amp;gt;\n &amp;lt;mxPoint x=\&amp;quot;1280\&amp;quot; y=\&amp;quot;305\&amp;quot; /&amp;gt;\n &amp;lt;/Array&amp;gt;\n &amp;lt;/mxGeometry&amp;gt;\n &amp;lt;/mxCell&amp;gt;\n &amp;lt;/root&amp;gt;\n &amp;lt;/mxGraphModel&amp;gt;\n &amp;lt;/diagram&amp;gt;\n&amp;lt;/mxfile&amp;gt;\n&amp;quot;}">&lt;/div>
&lt;script type="text/javascript" src="https://hyteck.de/js/viewer-static.min.js">&lt;/script>
&lt;/body>
&lt;/html>
&lt;/div>
&lt;style>
.geAdaptiveAsset {
width: unset;
}
&lt;/style>
&lt;h2 id="list-of-animal-shelters">List of animal shelters&lt;/h2>
&lt;p>Focusing on the first step: We want to check the website of an animal shelter - but where do we get a list of animal
shelters from? Luckily there is an easy answer: &lt;a href="https://openstreetmap.org">OpenStreetMap&lt;/a> and I wrote a
whole &lt;a href="https://hyteck.de/post/improve-osm-by-using-it/">other blog post on how I imported and improved this data&lt;/a>.&lt;/p>
&lt;h2 id="species-specific-link">Species-specific link&lt;/h2>
&lt;p>Importing this data provides us (most of the time) with a link to the shelter&amp;rsquo;s website. However, rats are usually not
listed on the home page but on a subsite.
In order to save time, I introduced the concept of a species-specific link per organization and species.&lt;/p>
&lt;p>So for the Tierheim Entenhausen this might look like this&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Species&lt;/th>
&lt;th>Species specific link&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Cat&lt;/td>
&lt;td>&lt;a href="https://tierheim-entenhausen.de/adoption/cats">https://tierheim-entenhausen.de/adoption/cats&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Rats&lt;/td>
&lt;td>&lt;a href="https://tierheim-entenhausen.de/adoption/small-mammals">https://tierheim-entenhausen.de/adoption/small-mammals&lt;/a>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>As animal shelter pages look very different from each other, clicking this link provides an enormous time benefit
compared to clicking through a homepage manually.&lt;/p>
&lt;h1 id="org-check-page">Org check page&lt;/h1>
&lt;p>I set up a special page to make it most efficient to check shelters. It&amp;rsquo;s structured in four parts:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Stats&lt;/strong>: The stats show how many animal shelters are checked in the last two weeks and how many to go.&lt;/li>
&lt;li>&lt;strong>Not checked for the longest period&lt;/strong>: Shows the animal shelters to check next, it&amp;rsquo;s therefore sorted by the date
they were last checked&lt;/li>
&lt;li>&lt;strong>In active communication&lt;/strong>: A overview of the organizations where there is communication (or an attempt thereof).
This can take multiple das or even weeks so the internal comment field is very useful to keep track.&lt;/li>
&lt;li>&lt;strong>Last checked&lt;/strong> It sometimes happens that I accidentally set a organization to &amp;ldquo;Checked&amp;rdquo; by accident. I added this
section to make it easier to revert that.&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="screenshot-checking-site.png" alt="A screenshot of the Notfellchen-Website showing the mentiond sections. Each rescue organization is listed with their website, species specific urls and shows the internal comment. There are three color coded buttons: &amp;ldquo;Organization checked&amp;rdquo;, &amp;ldquo;Active Communication&amp;rdquo; and &amp;ldquo;Exclude from check&amp;rdquo;">&lt;/p>
&lt;h2 id="shortcuts">Shortcuts&lt;/h2>
&lt;p>To make it even faster to work through the organizations I added some shortcuts for the most common functionality and
documented the browser own shortcut to close a tab.&lt;/p>
&lt;ul>
&lt;li>&lt;code>O&lt;/code>: Open website of the first organization&lt;/li>
&lt;li>&lt;code>CTRL+W&lt;/code>: Close tab (Firefox, Chrome)&lt;/li>
&lt;li>&lt;code>C&lt;/code>: Mark first organization as checked&lt;/li>
&lt;/ul>
&lt;h2 id="results">Results&lt;/h2>
&lt;p>After implementing all this, how long does it take now to check all organizations? Here are the numbers&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Measurement&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Time to check one organization (avg.)&lt;/td>
&lt;td>12.1s&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Organization checked per minute&lt;/td>
&lt;td>4.96 org/min&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Time to check all (eligible) german animal shelters (429)&lt;/td>
&lt;td>1 h 16 min&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>This excludes the time, it takes to add animals or contact rescue organizations. One of these actions must be taken
whenever an eligible animal is found on a website. Here you can see how this interrupts the process:&lt;/p>
&lt;p>&lt;img src="progress.png" alt="A diagramm showing time on the x axis and number of shelters checked on the y axis. In the period from 11am to 12:15pm there are over 200 shelters checked at a relativly constanct rate. The checking is interrupted two times by adding animals and three times by contacting">&lt;/p>
&lt;p>And here is the breakdown of time per activity. A big caveat here is, that I did not follow up on previous conversations
here, therefore the contacting number is likely an underestimation.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Activity&lt;/th>
&lt;th>Time spent&lt;/th>
&lt;th>Percentage&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Checking&lt;/td>
&lt;td>54 min 44s&lt;/td>
&lt;td>72.3%&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Adding&lt;/td>
&lt;td>11 min 15s&lt;/td>
&lt;td>14.9%&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Contacting&lt;/td>
&lt;td>9min 41s&lt;/td>
&lt;td>12.8%&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>To me, this looks like a pretty good result. I can&amp;rsquo;t say which optimizations brought how much improvement, but I&amp;rsquo;d argue
they all play a role in reaching the 12s per rescue organizations that is checked.&lt;/p>
&lt;p>In order to check all german animal shelters, one needs to put in about 2 and a half hours every two weeks. That seems
reasonable to me. Further improvements of the likely do not lie in the organization check page but the contact process
and adoption notice form.&lt;/p>
&lt;p>For now, I&amp;rsquo;m happy with the results.&lt;/p>
&lt;h1 id="addendum-common-annoyances">Addendum: Common annoyances&lt;/h1>
&lt;p>When doing this over the last few months I encountered some recurring issues that not only were annoying but also take
up a majority of the time. Here are some that stood out&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Broken SSL encryption&lt;/strong> So many animal shelters do not have a functioning SSL certificate. It takes time to work
around the warnings.&lt;/li>
&lt;li>&lt;strong>No results not indicated&lt;/strong> More often than not, animal shelters do not have rats. However, when you visit a page
like &lt;a href="https://tierschutzliga.de/tierheime/tierparadies-oberdinger-moos/tiervermittlung/#?THM=TOM&amp;amp;Tierart=Kleintiere">this&lt;/a>
it&amp;rsquo;s hard to know if there is a technical issue or if there are no animals for your search.&lt;/li>
&lt;li>&lt;strong>No static links&lt;/strong> Sites where you have to click through a menu to get to the right page, but you can not link
directly to it.&lt;/li>
&lt;li>&lt;strong>No website&lt;/strong> Seriously, there are some animal shelters that only use Instagram or Facebook to tell people about the
animals they have. This is not only a privacy nightmare, it&amp;rsquo;s also incredibly hard to find out which information is
up-to-date. Furthermore, there exists no data structure, so posts about animals often miss crucial information like
the sex.&lt;/li>
&lt;/ul>
&lt;p>While I obviously have some grievances here, I know the organizations never have enough resources, and they&amp;rsquo;d
usually love to have a nicer website. Just keep that in mind too.&lt;/p></content></entry><entry><title>Postmortem - how to completely screw up an update</title><link href="https://hyteck.de/post/postmortem-gpa/" type="application/octet-stream"/><updated>2025-10-19T12:05:10+02:00</updated><id>https://hyteck.de/post/postmortem-gpa/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;p>The fediverse instance &lt;a href="https://gay-pirate-assassins.de">gay-pirate-assassins.de&lt;/a> was down for a couple of days. This
postmortem will outline what went wrong and what I did to prevent things from going that wrong in the future.&lt;/p>
&lt;h1 id="timeline">Timeline&lt;/h1>
&lt;ul>
&lt;li>2025-10-05 17:26: &lt;a href="https://gay-pirate-assassins.de/@moanos/statuses/01K6TFQ1HVPAR6AYN08XYQ7XFV">Update announcement&lt;/a>&lt;/li>
&lt;li>2025-10-05 ~17:45: Update started&lt;/li>
&lt;li>2025-10-05 ~18:00: Services restart&lt;/li>
&lt;li>2025-10-05 ~18:00: GoToSocial doesn&amp;rsquo;t come up&lt;/li>
&lt;li>2025-10-12 ~10:00: Issue is found&lt;/li>
&lt;li>2025-10-12 10:30: Issue is fixed&lt;/li>
&lt;li>2025-10-12 10:31: GoToSocial is started, migrations start&lt;/li>
&lt;li>2025-10-12 15:38: Migrations finished successfully&lt;/li>
&lt;li>2025-10-12 15:38: Service available again&lt;/li>
&lt;li>2025-10-12 18:36:&lt;a href="https://gay-pirate-assassins.de/@moanos/statuses/01K7CMGF7S2TE39792CMADGEPJ">Announcement sent&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>All times are given in CEST.&lt;/p>
&lt;h2 id="the-beginning-an-update-goes-wrong">The beginning: An update goes wrong&lt;/h2>
&lt;p>I run a small fediverse server with a few users called. &lt;a href="https://gay-pirate-assassins.de/">gay-pirate-assassins&lt;/a> which is powered by &lt;a href="https://gotosocial.org/">GoToSocial&lt;/a>.
The (amazing) GoToSocial devs released &lt;code>v0.20.0-rc1&lt;/code> and &lt;code>v0.20.0-rc2&lt;/code>. As the new features seemed pretty cool, I&amp;rsquo;m
inpatient and the second release candidate seemed stable,
I decided to update to &lt;code>v0.20.0-rc2&lt;/code>. So I stared a backup (via borgmatic), waited for it to finish and confirmed it ran
successfully.
Then I changed the version number in the &lt;a href="https://github.com/mother-of-all-self-hosting/mash-playbook">mash&lt;/a>-ansible
playbook I use. Then I pulled the newest version of the playbook and it&amp;rsquo;s roles because I wanted to update all services
that run on the server. I checked
the &lt;a href="https://github.com/mother-of-all-self-hosting/mash-playbook/blob/main/CHANGELOG.md">Changelog&lt;/a>,
didn&amp;rsquo;t see anything and then started the update. It went through and GoToSocial started up just fine.&lt;/p>
&lt;p>But the instance start page showed me 0 users, 0 posts and 0 federated instances. &lt;strong>Something has gone horribly wrong!&lt;/strong>&lt;/p>
&lt;h2 id="migrations">Migrations&lt;/h2>
&lt;p>It was pretty clear to me, that the migrations went wrong.
The &lt;a href="https://codeberg.org/superseriousbusiness/gotosocial/releases/tag/v0.20.0-rc1">GoToSocial Migration notes&lt;/a>
specifically mentioned long-running migrations that could take several hours. I assumed that somehow, during the running
database migration, the service must have restarted and left the DB in a broken state. This issue happened to me before.&lt;/p>
&lt;p>Well, that&amp;rsquo;s what backups are for, so let&amp;rsquo;s pull it.&lt;/p>
&lt;h2 id="backups">Backups&lt;/h2>
&lt;p>Backups for this server are done two ways:&lt;/p>
&lt;ul>
&lt;li>via postgres-backup: Backups of the database are written to disk&lt;/li>
&lt;li>via &lt;a href="https://torsion.org/borgmatic/">borgmatic&lt;/a>: Backups via borg are written to backup nodes, one of them at my home&lt;/li>
&lt;/ul>
&lt;p>They run every night automatically, monitored by &lt;a href="https://healthchecks.io/">Healthchecks&lt;/a>. I triggered a manual run
before the update so that is the one I mounted using &lt;a href="https://vorta.borgbase.com/">Vorta&lt;/a>.&lt;/p>
&lt;p>And then the realization.&lt;/p>
&lt;pre tabindex="0">&lt;code>
mash-postgres:5432 $ ls -lh
total 2.1M
-r-------- 1 moanos root 418K Oct 05 04:03 gitea
-r-------- 1 moanos root 123K Oct 05 04:03 healthchecks
-r-------- 1 moanos root 217K Oct 05 04:03 ilmo
-r-------- 1 moanos root 370K Oct 05 04:03 notfellchen
-r-------- 1 moanos root 67K Oct 05 04:03 oxitraffic
-r-------- 1 moanos root 931 Oct 05 04:03 prometheus_postgres_exporter
-r-------- 1 moanos root 142K Oct 05 04:03 semaphore
-r-------- 1 moanos root 110K Oct 05 04:03 vaultwarden
-r-------- 1 moanos root 669K Oct 05 04:03 woodpecker_ci_server
&lt;/code>&lt;/pre>&lt;p>Fuck. The database gay-pirate-assassins is not there. Why?&lt;/p>
&lt;p>To explain that I have to tell you how it &lt;em>should&lt;/em> work: Services deployed by the mash-playbook are automatically wired
to the database and reverse proxy by a complex set of Ansible variables. This is great, because adding a service can
therefore be as easy as adding&lt;/p>
&lt;pre tabindex="0">&lt;code>healthchecks_enabled: true
healthchecks_hostname: health.hyteck.de
&lt;/code>&lt;/pre>&lt;p>to the &lt;code>vars.yml&lt;/code> file.&lt;/p>
&lt;p>This will then configure the postgres database automatically, based on the &lt;code>group_vars&lt;/code>. They look like this&lt;/p>
&lt;pre tabindex="0">&lt;code>mash_playbook_postgres_managed_databases_auto_itemized:
- |-
{{
({
&amp;#39;name&amp;#39;: healthchecks_database_name,
&amp;#39;username&amp;#39;: healthchecks_database_username,
&amp;#39;password&amp;#39;: healthchecks_database_password,
} if healthchecks_enabled and healthchecks_database_hostname == postgres_connection_hostname and healthchecks_database_type == &amp;#39;postgres&amp;#39; else omit)
}}
&lt;/code>&lt;/pre>&lt;p>Note that a healthchecks database is only added to the managed databases if &lt;code>healthchecks_enabled&lt;/code> is &lt;code>True&lt;/code>.&lt;/p>
&lt;p>This is really useful for backups because the borgmatic configuration also pulls the list
&lt;code>mash_playbook_postgres_managed_databases_auto_itemized&lt;/code>. Therefore, you do not need to specify which databases to back
up, it just backs up all managed databases.&lt;/p>
&lt;p>However, the database for gay-pirate assassins was not managed. In the playbook it&amp;rsquo;s only possible to configure a
service once. You can not manage multiple GoToSocial instances in the same &lt;code>vars.yml&lt;/code>. In the past, I had two instances
of GoToSocial running on the server. I therefore
followed &lt;a href="https://github.com/mother-of-all-self-hosting/mash-playbook/blob/main/docs/running-multiple-instances.md">the how-to of &amp;ldquo;Running multiple instances of the same service on the same host&amp;rdquo;&lt;/a>.&lt;/p>
&lt;p>Basically this means that an additional &lt;code>vars.yml&lt;/code> must be created that is treated as a completely different server.
Databases must be created manually as they are not managed.&lt;/p>
&lt;p>With that knowledge you can understand that when I say that the database for gay-pirate-assassins was not managed,
this means it was not included in the list of databases to be backed up. The backup service thought it ran successfully,
because it backed up everything it knew of.&lt;/p>
&lt;p>So this left me with a three-month-old backup. Unacceptable.&lt;/p>
&lt;h2 id="investigating">Investigating&lt;/h2>
&lt;p>So the existing database needed to be rescued. I SSHed into the server and checked the database. It looked completely
normal.
I asked the devs if they could me provide me with the migrations as they already did in the past. However, they pointed
out that the migrations are too difficult for that approach. They suggested to delete the oldest migration to force a
re-run of the migrations.&lt;/p>
&lt;p>Here is where I was confused, because this was the &lt;code>bun_migrations&lt;/code> table:&lt;/p>
&lt;pre tabindex="0">&lt;code>gay-pirate-assassins=# SELECT * FROM bun_migrations ORDER BY id DESC LIMIT 5;
id | name | group_id | migrated_at
-----+----------------+----------+-------------------------------
193 | 20250324173534 | 20 | 2025-04-23 20:00:33.955776+00
192 | 20250321131230 | 20 | 2025-04-23 19:58:06.873134+00
191 | 20250318093828 | 20 | 2025-04-23 19:57:50.540568+00
190 | 20250314120945 | 20 | 2025-04-23 19:57:30.677481+00
&lt;/code>&lt;/pre>&lt;p>The last migration ran in April, when I updated to &lt;code>v0.19.1&lt;/code>. Strange.&lt;/p>
&lt;p>At this point I went on vacation and paused investigations, not only because the vacation was great, but also because I
bamboozeld by this state.&lt;/p>
&lt;hr>
&lt;p>After my vacation I came back, and did some backups of the database.&lt;/p>
&lt;pre tabindex="0">&lt;code>$ docker run -e PGPASSWORD=&amp;#34;XXXX&amp;#34; -it --rm --network mash-postgres postgres pg_dump -U gay-pirate-assassins -h mash-postgres gay-pirate-assassins &amp;gt; manual-backup/gay-pirate-assassins-2025-10-13.sql
&lt;/code>&lt;/pre>&lt;p>Then I deleted the last migration, as I was advised&lt;/p>
&lt;pre tabindex="0">&lt;code>DELETE FROM bun_migration WHERE id=193;
&lt;/code>&lt;/pre>&lt;p>and restarted the server. While watching the server come up it hit me in the face:&lt;/p>
&lt;pre tabindex="0">&lt;code>Oct 12 08:31:29 s3 mash-gpa-gotosocial[2251925]: timestamp=&amp;#34;12/10/2025 08:31:29.905&amp;#34; func=bundb.sqliteConn level=INFO msg=&amp;#34;connected to SQLITE database with address file:/opt/gotosocial/sqlite.db?_pragma=busy_timeout%281800000%29&amp;amp;_pragma=journal_mode%&amp;gt;
Oct 12 13:38:46 s3 mash-gpa-gotosocial[2304549]: timestamp=&amp;#34;12/10/2025 13:38:46.588&amp;#34; func=router.(*Router).Start.func1 level=INFO msg=&amp;#34;listening on 0.0.0.0:8080&amp;#34;
&lt;/code>&lt;/pre>&lt;p>The server is &lt;strong>starting from a completely different database&lt;/strong>! That explains why&lt;/p>
&lt;ul>
&lt;li>the last migration was never done&lt;/li>
&lt;li>the server showed me 0 users, 0 posts and 0 federated instances even though the postgres database had plenty of those&lt;/li>
&lt;/ul>
&lt;p>All of a sudden a SQlite database was configured. This happened because
of &lt;a href="https://github.com/mother-of-all-self-hosting/ansible-role-gotosocial/commit/df34af385f9765bda8f160f6985a47cb7204fe96">this commit&lt;/a>
which introduced SQlite support and set it as default. This was not mentioned in
the &lt;a href="https://github.com/mother-of-all-self-hosting/mash-playbook/blob/main/CHANGELOG.md">Changelog&lt;/a>.&lt;/p>
&lt;p>So what happened is, that the config changed and then the server was restarted and an empty DB was initialized. The
postgres DB never started to migrate.&lt;/p>
&lt;h2 id="fixing">Fixing&lt;/h2>
&lt;p>To fix it, I did the following&lt;/p>
&lt;ol>
&lt;li>Configure the playbook to use postgres for GoToSocial:&lt;/li>
&lt;/ol>
&lt;pre tabindex="0">&lt;code># vars.yml
gotosocial_database_type: postgres
&lt;/code>&lt;/pre>&lt;ol start="2">
&lt;li>Run the playbook to configure GoToSocial (but not starting the service)&lt;/li>
&lt;/ol>
&lt;pre tabindex="0">&lt;code>just run-tags install-gotosocial
&lt;/code>&lt;/pre>&lt;ol start="3">
&lt;li>Check the configuration is correct&lt;/li>
&lt;li>Start the service&lt;/li>
&lt;/ol>
&lt;p>The migrations took several hours but after that, everything looked stable again. I don&amp;rsquo;t think there are any lasting
consequences. However, the server was unavailable for several days.&lt;/p>
&lt;h2 id="learnings">Learnings&lt;/h2>
&lt;p>I believe the main issue here was not the change in the config that went unnoticed by me. While I&amp;rsquo;d ideally notice stuff
like this, the server is a hobby, and I&amp;rsquo;ll continue to not check every config option that changed.&lt;/p>
&lt;p>The larger issue was the backup. Having a backup would have made this easy to solve. And there are other, less lucky
problems where I&amp;rsquo;d be completely lost without a backup. So to make sure this doesn&amp;rsquo;t happen again, I did/will do the
following:&lt;/p>
&lt;h3 id="1-mainstream-the-config">1. Mainstream the config&lt;/h3>
&lt;p>As explained, I used a specific non-mainstream setup in the ansible playbook because, in the past, I ran two instances
of GoToSocial on the server. After shutting down one of them, I never moved gay-pirate-assassins to be part of the main
config. This means important parts of the configuration had to be done manually, which I botched.&lt;/p>
&lt;p>So in the past week I cleaned up and gay-pirate-assassins is now part of the main &lt;code>vars.yml&lt;/code> and will benefit from all
relevant automations.&lt;/p>
&lt;h3 id="2-checking-backups">2. Checking backups&lt;/h3>
&lt;p>I was confident in my backups because&lt;/p>
&lt;ul>
&lt;li>they run every night very consistently. If they fail e.g. because of a network outage I reliably get a warning.&lt;/li>
&lt;li>I verified successfully run of the backup job prior to upgrading&lt;/li>
&lt;/ul>
&lt;p>The main problem was me assuming that a successful run of the backup command, meant a successful backup. Everyone will
tell you that a backup that is not tested is not to be trusted. And they are right. However, doing frequent
test-restores
exceeds my time and server capacity. So what I&amp;rsquo;ll do instead is the following:&lt;/p>
&lt;ul>
&lt;li>mount the backup before an upgrade&lt;/li>
&lt;li>&lt;code>tail&lt;/code> the backup file as created by postgres-backup and ensure the data is from the same day&lt;/li>
&lt;li>check media folders for the last changed image&lt;/li>
&lt;/ul>
&lt;p>This is not a 100% guarantee, but I&amp;rsquo;d argue it&amp;rsquo;s a pretty good compromise for now. As the frequency of mounting backups
increases and therefore becomes faster, I&amp;rsquo;ll re-evaluate to do a test-restore at least semi-regulary.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>I fucked up, but I was lucky that my error was recoverable and no data was lost. Next time this will hopefully be not due
to luck, but better planning!&lt;/p>
&lt;p>Any questions? Let me know!&lt;/p></content></entry><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>Thoughts on HTML mails</title><link href="https://hyteck.de/post/about-html-mails/" type="application/octet-stream"/><updated>2025-07-12T12:05:10+02:00</updated><id>https://hyteck.de/post/about-html-mails/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;p>Lately I worked on notification e-mails for &lt;a href="https://notfellchen.org">notfellchen.org&lt;/a>. Initially I just sent text
notifications without links to the site. Terrible idea! An E-Mail notification I send always has Call-to-Action or at
minimum a link to more information.&lt;/p>
&lt;p>I left the system like this for half a year because it kinda worked for me (didn&amp;rsquo;t suck enough for me to care), and I was the main receiver of these notifications.
However, as the platform is developed further and more users join I need to think about more user-centric notifications.&lt;/p>
&lt;p>So what do I imagine is important to a user?
*&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Information benefit&lt;/strong>: An e-mail has the purpose to inform a user. This information should be immediately visible &amp;amp; understandable.&lt;/li>
&lt;li>&lt;strong>Actionables&lt;/strong>: Users should be able to act on the information received. This is the bright red button &amp;ldquo;DO SOMETHING NOW!&amp;rdquo; you see so often.&lt;/li>
&lt;li>&lt;strong>Unsubscribing&lt;/strong>: Informing e-mails stop is not only a legal requirement and morally the right thing to do but it also gives users agency and - I hope - increases the User Experience&lt;/li>
&lt;/ul>
&lt;p>With these I naturally came to the next question: Plaintext or HTML?&lt;/p>
&lt;p>Some people would say &lt;a href="https://useplaintext.email/">Plaintext is inherently better&lt;/a> than HTML e-mails. Many of these reasons resonate with me including:&lt;/p>
&lt;ul>
&lt;li>Privacy invasion and tracking&lt;/li>
&lt;li>HTML emails are less accessible&lt;/li>
&lt;li>Some clients can&amp;rsquo;t display HTML emails at all&lt;/li>
&lt;li>Mail client vulnerabilities&lt;/li>
&lt;/ul>
&lt;p>These are all valid points and are a reason I generally enjoy plaintext e-mails when I receive them.
But this is not about me but users. And there are some real benefits of HTML e-mails:&lt;/p>
&lt;ul>
&lt;li>Visually appealing: This is subjective but generally most users seem to agree on that&lt;/li>
&lt;li>User guidance: Rich text provides a real benefit when searching for the relevant information&lt;/li>
&lt;/ul>
&lt;p>Be honest: Do you read automated e-mails you receive completely? Or do you just skim for important information?&lt;/p>
&lt;p>And here HTML-mails shine: &lt;strong>Information can easily be highlighted&lt;/strong> and big button can lead the user to do the right action.
Some might argue that you can also a highlight a link in plaintext but that nearly always will worsen accessibility for screen-reader user.&lt;/p>
&lt;h1 id="the-result">The result&lt;/h1>
&lt;p>In the end, I decided that providing plaintext-only e-mails was not enough. I set up html mails, mostly using
&lt;a href="https://docs.djangoproject.com/en/5.2/topics/email/#send-mail">djangos send_mail&lt;/a> function where I can pass the html message and attattching it correctly is done for me.&lt;/p>
&lt;p>&lt;img src="mail_screenshot.png" alt="A screenshot of an e-mail in thunderbird. The e-mail is structured in header, body and footer. The header says &amp;ldquo;Notfellchen.org&amp;rdquo;, the body shows a message that a new user was registered and a bright green button to show the user. The footer offers a link to unsubscribe">&lt;/p>
&lt;p>For anyone that is interested, here is how most my notifications are sent&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:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">send_notification_email&lt;/span>(notification_pk):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> notification &lt;span style="color:#f92672">=&lt;/span> Notification&lt;span style="color:#f92672">.&lt;/span>objects&lt;span style="color:#f92672">.&lt;/span>get(pk&lt;span style="color:#f92672">=&lt;/span>notification_pk)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> subject &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>notification&lt;span style="color:#f92672">.&lt;/span>title&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> context &lt;span style="color:#f92672">=&lt;/span> {&lt;span style="color:#e6db74">&amp;#34;notification&amp;#34;&lt;/span>: notification, }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> notification&lt;span style="color:#f92672">.&lt;/span>notification_type &lt;span style="color:#f92672">==&lt;/span> NotificationTypeChoices&lt;span style="color:#f92672">.&lt;/span>NEW_REPORT_COMMENT &lt;span style="color:#f92672">or&lt;/span> notification&lt;span style="color:#f92672">.&lt;/span>notification_type &lt;span style="color:#f92672">==&lt;/span> NotificationTypeChoices&lt;span style="color:#f92672">.&lt;/span>NEW_REPORT_AN:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> html_message &lt;span style="color:#f92672">=&lt;/span> render_to_string(&lt;span style="color:#e6db74">&amp;#39;fellchensammlung/mail/notifications/report.html&amp;#39;&lt;/span>, context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plain_message &lt;span style="color:#f92672">=&lt;/span> render_to_string(&lt;span style="color:#e6db74">&amp;#39;fellchensammlung/mail/notifications/report.txt&amp;#39;&lt;/span>, context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [&lt;span style="color:#f92672">...&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">elif&lt;/span> notification&lt;span style="color:#f92672">.&lt;/span>notification_type &lt;span style="color:#f92672">==&lt;/span> NotificationTypeChoices&lt;span style="color:#f92672">.&lt;/span>NEW_COMMENT:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> html_message &lt;span style="color:#f92672">=&lt;/span> render_to_string(&lt;span style="color:#e6db74">&amp;#39;fellchensammlung/mail/notifications/new-comment.html&amp;#39;&lt;/span>, context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plain_message &lt;span style="color:#f92672">=&lt;/span> render_to_string(&lt;span style="color:#e6db74">&amp;#39;fellchensammlung/mail/notifications/new-comment.txt&amp;#39;&lt;/span>, context)
&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> &lt;span style="color:#66d9ef">raise&lt;/span> &lt;span style="color:#a6e22e">NotImplementedError&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;Unknown notification type&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:#66d9ef">if&lt;/span> &lt;span style="color:#e6db74">&amp;#34;plain_message&amp;#34;&lt;/span> &lt;span style="color:#f92672">not&lt;/span> &lt;span style="color:#f92672">in&lt;/span> locals():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> plain_message &lt;span style="color:#f92672">=&lt;/span> strip_tags(html_message)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mail&lt;span style="color:#f92672">.&lt;/span>send_mail(subject, plain_message, settings&lt;span style="color:#f92672">.&lt;/span>DEFAULT_FROM_EMAIL,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> [notification&lt;span style="color:#f92672">.&lt;/span>user_to_notify&lt;span style="color:#f92672">.&lt;/span>email],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> html_message&lt;span style="color:#f92672">=&lt;/span>html_message)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Yes this could be made more efficient - for now it works. I made the notification framework too complicated initially, so I&amp;rsquo;m still tyring out what works and what doesn&amp;rsquo;t.&lt;/p>
&lt;p>Here is the html template&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-html" data-lang="html">&lt;span style="display:flex;">&lt;span>{% extends &amp;#34;fellchensammlung/mail/base.html&amp;#34; %}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{% load i18n %}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{% block title %}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {% translate &amp;#39;Neuer User&amp;#39; %}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{% endblock %}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{% block content %}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#f92672">p&lt;/span>&amp;gt;Moin,&amp;lt;/&lt;span style="color:#f92672">p&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#f92672">p&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> es wurde ein neuer Useraccount erstellt.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/&lt;span style="color:#f92672">p&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#f92672">p&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Details findest du hier
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/&lt;span style="color:#f92672">p&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#f92672">p&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#f92672">a&lt;/span> &lt;span style="color:#a6e22e">href&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;{{ notification.user_related.get_full_url }}&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">class&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;cta-button&amp;#34;&lt;/span>&amp;gt;{% translate &amp;#39;User anzeigen&amp;#39; %}&amp;lt;/&lt;span style="color:#f92672">a&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/&lt;span style="color:#f92672">p&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{% endblock %}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and here the plaintext&lt;/p>
&lt;pre tabindex="0">&lt;code>{% extends &amp;#34;fellchensammlung/mail/base.txt&amp;#34; %}
{% load i18n %}
{% block content %}{% blocktranslate %}Moin,
es wurde ein neuer Useraccount erstellt.
User anzeigen: {{ new_user_url }}
{% endblocktranslate %}{% endblock %}
&lt;/code>&lt;/pre>&lt;p>Works pretty well for now. People that prefer plaintext will get these and most users will have skimmable html e-mail where the
styling will help them recognize where it&amp;rsquo;s from and what to do. Accessibility-wise this seems like the best option.&lt;/p>
&lt;p>And while adding a new notification will force me to create&lt;/p>
&lt;ul>
&lt;li>a new notification type,&lt;/li>
&lt;li>two new e-mail templates and&lt;/li>
&lt;li>a proper rendering on the website&lt;/li>
&lt;/ul>
&lt;p>this seems okay. Notifications are useful, but I don&amp;rsquo;t want to shove them everywhere. I&amp;rsquo;m not running facebook or linkedin after all.&lt;/p>
&lt;p>So for now I&amp;rsquo;m pretty happy with the new shiny e-mails and will roll out the changes soon (if I don&amp;rsquo;t find any more wired bugs).&lt;/p>
&lt;p>PS: I wrote this post after reading &lt;a href="https://blog.avas.space/blog-website-eval/">blog &amp;amp; website in the age of containerized socials&lt;/a> by ava.
Maybe this &amp;ldquo;Thoughts on&amp;rdquo; format will stay and I will post these in addition to more structured deep dives.&lt;/p>
&lt;h1 id="update">Update&lt;/h1>
&lt;p>I did a rework of the notification function and it&amp;rsquo;s now much cleaner now. However, it&amp;rsquo;s less readable so this blogpost will stay as-is.
If you want to check out the new code have a look &lt;a href="https://codeberg.org/moanos/notfellchen/src/commit/a4b8486bd489dacf8867b49d04f70f091556dc9d/src/fellchensammlung/mail.py">on Codeberg&lt;/a>.&lt;/p></content></entry><entry><title>Improve OpenStreetMap data by using it</title><link href="https://hyteck.de/post/improve-osm-by-using-it/" type="application/octet-stream"/><updated>2025-06-28T14:05:10+02:00</updated><id>https://hyteck.de/post/improve-osm-by-using-it/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>In the last month I improved the mapping of about 100 german animal shelters - not only out of the goodness of my heart, but because it helped me.&lt;/p>
&lt;p>Let me explain why: I develop &lt;a href="https://notfellchen.org/">notfellchen.org&lt;/a>, where users can search animals in animal shelters, specifically rats, they might want to adopt.
The idea is to have a central website that allows you to search for rats in your area.&lt;/p>
&lt;p>This is necessary because only a small percentage of animal shelters has rats. As a user, just checking your next
shelter doesn&amp;rsquo;t work. Some users will stop after checking the second or third one and just buy from a pet shop (which is a very, very bad idea).&lt;/p>
&lt;p>Now a central platform for is nice for users but has one problem: How do I, as operator of notfellchen, know where rats are?&lt;/p>
&lt;p>I need to &lt;strong>manually check every animal shelter in the country&lt;/strong> and if they have rats, ask them for permission to use
images of the rats on my site.
So wait I need to have is a list of animal shelters in germany and have their website, e-mail and phone number.&lt;/p>
&lt;p>The source for all of this: You guessed it - OpenStreetMap 🥳&lt;/p>
&lt;h1 id="getting-the-data">Getting the data&lt;/h1>
&lt;p>Downloading all german animal shelters is surprisingly easy: You use &lt;a href="https://overpass-turbo.eu/">Overpass Turbo&lt;/a> and get a &lt;code>.geojson&lt;/code> to download.&lt;/p>
&lt;p>here is the query I used:&lt;/p>
&lt;pre tabindex="0">&lt;code>[out:json][timeout:25];
// fetch area “Germany” to search in
{{geocodeArea:Germany}}-&amp;gt;.searchArea;
// Check search area for all objects with animal shelter tag
nwr[&amp;#34;amenity&amp;#34;=&amp;#34;animal_shelter&amp;#34;](area.searchArea);
// print results
out geom;
&lt;/code>&lt;/pre>&lt;p>Now upload it to notfellchen.org and I&amp;rsquo;ll be fine right?&lt;/p>
&lt;h1 id="data-issues">Data Issues&lt;/h1>
&lt;p>Yeah well, this only &lt;em>mostly&lt;/em> works. There were two main problems:&lt;/p>
&lt;p>&lt;strong>Missing contact data&lt;/strong> is annoying because I quickly want to check the website of animal shelters.&lt;/p>
&lt;p>More annoying were what I&amp;rsquo;d call &lt;strong>mapping errors&lt;/strong>.
Most commonly an animal shelter had multiple nodes/ways tagged as &lt;code>amenity:animal_shelter&lt;/code>.
The highlight was the &amp;ldquo;Tierheim München&amp;rdquo; where about 10 buildings were tagged as &lt;code>amenity:animal_shelter&lt;/code> and the contact
data was sitting on the building with name &amp;ldquo;Katzenhaus&amp;rdquo; (&amp;ldquo;cat house&amp;rdquo;).&lt;/p>
&lt;p>Now the &amp;ldquo;Tierheim München&amp;rdquo; appeared in my list 10 times but 9 of them had no contact data at all.&lt;/p>
&lt;h1 id="correcting-it">Correcting it&lt;/h1>
&lt;p>I could have corrected this only in the notfellchen database. It would have been faster and I could even automate parts of it.
But I didn&amp;rsquo;t.&lt;/p>
&lt;p>For each issue I found, I opened OpenStreetMap and added websites, phone numbers or even re-mapped the area.
For &amp;ldquo;Tierheim München&amp;rdquo; I even &lt;a href="https://community.openstreetmap.org/t/mapping-of-multiple-related-buildings-animal-shelters/131801">opened a thread in the forum&lt;/a>
to discuss a proper tagging.&lt;/p>
&lt;p>That makes sense for me because I get one important thing:&lt;/p>
&lt;h1 id="what-i-get-out-of-it-updates">What I get out of it: Updates&lt;/h1>
&lt;p>What if a new shelter was added later or a shelter changed? I already profit a lot from the time people spend adding information, so why stop?&lt;/p>
&lt;p>My database stores the OSM ID, so I can regularly query the data again to get updates.
But that only works if I take an &amp;ldquo;upstream&amp;rdquo; approach: Fix the data in OSM, then load it into notfellchen.
Otherwise, any change in my database will be overwritten by &amp;ldquo;old&amp;rdquo; OSM data.&lt;/p>
&lt;h1 id="result">Result&lt;/h1>
&lt;p>In the last month, I made 86 changes to OSM adding the following information&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Type of information&lt;/th>
&lt;th>Number of times added&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Website&lt;/td>
&lt;td>66&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Phone Numbers&lt;/td>
&lt;td>65&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Operator&lt;/td>
&lt;td>63&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>E-Mail&lt;/td>
&lt;td>49&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Fax&lt;/td>
&lt;td>9&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Yes I sometimes even added fax numbers. It was easy enough to add and maybe there is someone might use it.&lt;/p>
&lt;h1 id="looking-forward">Looking forward&lt;/h1>
&lt;p>I&amp;rsquo;m of course not done. Only half of the rescues known to OSM in germany are currently checked, so I&amp;rsquo;ll continue that work.&lt;/p>
&lt;p>After that I&amp;rsquo;ll start adding the shelters that are just in my database.
Currently, 33 animal shelters are known to notfellchen that are not known to OSM. This number will likely grow, maybe double.&lt;/p>
&lt;p>A lot to do. And luckily, this work both benefits me and everyone using OSM. Happy mapping!&lt;/p></content></entry><entry><title>I did something naughty: Circumventing Authorized-Fetch as implemented by GoToSocial</title><link href="https://hyteck.de/post/public-posts-with-authorized-fetch/" type="application/octet-stream"/><updated>2024-12-11T06:10:10+02:00</updated><id>https://hyteck.de/post/public-posts-with-authorized-fetch/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;p>Yes the title is correct, but I had nothing malicious in mind!&lt;/p>
&lt;h2 id="what-this-is-about">What this is about&lt;/h2>
&lt;p>For &lt;a href="https://social.queereszentrumtuebingen.de/@qzt">@qzt@queereszentrumtuebingen.de&lt;/a> we include the public feed &lt;a href="https://queereszentrumtuebingen.de/">in a sidbar on the homepage&lt;/a>. Initially this was done using the standard API to fetch statuses &lt;code>/api/v1/accounts/{account_id}/statuses&lt;/code> and worked like a charm. The problem started when &lt;a href="https://gotosocial.org/">GoToSocial&lt;/a> (the fediverse server we use, similar to mastodon) implemented authorized fetch. This is a a good thing! Authorized fetch means, that every call to a endpoint needs to be authorized by an &lt;code>access_token&lt;/code>. You get an access token from a fedi account. It&amp;rsquo;s what fediverse clients like Tusky or Phanpy do on your behalf to get the posts that make up you timeline.&lt;/p>
&lt;p>Authorized fetch has major advantages as&lt;/p>
&lt;ul>
&lt;li>data scraping can only be done by other fediaccounts&lt;/li>
&lt;li>blocking can not be circumvented by using the public API&lt;/li>
&lt;/ul>
&lt;p>and much more. Sadly it also broke our website integration.&lt;/p>
&lt;h2 id="possible-solutions">Possible Solutions&lt;/h2>
&lt;p>So what now? I initially wanted to turn of authorized fetch for &lt;a href="https://social.queereszentrumtuebingen.de/@qzt">@qzt@queereszentrumtuebingen.de&lt;/a> by messing with the GoToSocial code and turning it off for the whole server. This would have been possible as this is the only user on the server. The GoToSocial devs helped me manage to find where to do that. But it&amp;rsquo;s not ideal and would make me build a custom docker image fore each update.&lt;/p>
&lt;p>Next idea: The whole point of authorized fetch is, that only fedi-accounts (and apps they authorized) can access the API. So lets do that! Set up a new account, add app and authorize it &lt;a href="https://docs.gotosocial.org/en/latest/api/authentication/">as described in the GoToSocial documentation&lt;/a>. I used #Bruno for that, that was much more comfortable than using curl for me.
With that authorization code you can now get an access token for your app. Put that in the Javascript that loads posts and we are good right? Sadly no. It would totally work. But it would also allow anyone to read and post on behalf of the account. That calls for malicious actors using this for scraping or spamming.&lt;/p>
&lt;p>So instead, we need a proxy that stores the access token securely and restricts the actions.&lt;/p>
&lt;h2 id="the-proxy">The proxy&lt;/h2>
&lt;p>Such a proxie must&lt;/p>
&lt;ul>
&lt;li>offer the endpoint that provides the same data as the FediverseAPI&lt;/li>
&lt;li>authorize itself to the FediverseAPI via &lt;code>access_token&lt;/code>&lt;/li>
&lt;li>restrict to read access of consenting accounts&lt;/li>
&lt;/ul>
&lt;p>The last point is really important, as we don&amp;rsquo;t want to allow others to use this endpoint to scrape data unauthorized.&lt;/p>
&lt;p>I wrote a short FastAPI server that offers this. It only implements one method&lt;/p>
&lt;pre tabindex="0">&lt;code>@app.get(&amp;#34;/api/v1/accounts/{account_id}/statuses&amp;#34;)
async def fetch_data(account_id):
if account_id not in ALLOWED_ACCOUNTS:
raise HTTPException(status_code=401, detail=&amp;#34;You can only use this proxy to access configured accounts&amp;#34;)
headers = {&amp;#34;Authorization&amp;#34;: f&amp;#34;Bearer {ACCESS_TOKEN}&amp;#34;}
response = requests.get(f&amp;#34;{EXTERNAL_API_BASE_URL}/api/v1/accounts/{account_id}/statuses&amp;#34;, headers=headers)
return response.json()
&lt;/code>&lt;/pre>&lt;p>Basically this is the whole API code, I only trimmed a few checks and error handling.&lt;/p>
&lt;h2 id="deployment">Deployment&lt;/h2>
&lt;p>To deploy, I put it in a docker container and started it via docker-compose. Reverse proxing is handled by Traefik, I won&amp;rsquo;t go into detail here.&lt;/p>
&lt;pre tabindex="0">&lt;code>services:
fediproxy.example.org:
image: docker.io/moanos/fediproxy
container_name: &amp;#34;fediproxy.example.org&amp;#34;
restart: unless-stopped
environment:
EXTERNAL_API_BASE_URL: ${EXTERNAL_API_BASE_URL}
ACCESS_TOKEN: ${ACCESS_TOKEN}
ALLOWED_ACCOUNTS: ${ALLOWED_ACCOUNTS}
labels:
- &amp;#34;traefik.enable=true&amp;#34;
- &amp;#34;traefik.docker.network=traefik&amp;#34;
- &amp;#34;traefik.http.routers.fediproxy.rule=Host(`fediproxy.example.org`)&amp;#34;
- &amp;#34;traefik.http.routers.fediproxy.service=fediproxy-service&amp;#34;
- &amp;#34;traefik.http.routers.fediproxy.entrypoints=web-secure&amp;#34;
- &amp;#34;traefik.http.routers.fediproxy.tls=true&amp;#34;
- &amp;#34;traefik.http.routers.fediproxy.tls.certResolver=default&amp;#34;
- &amp;#34;traefik.http.services.fediproxy-service.loadbalancer.server.port=8000&amp;#34;
networks:
- traefik
networks:
traefik:
name: &amp;#34;traefik&amp;#34;
external: true
&lt;/code>&lt;/pre>&lt;p>I added a short &lt;code>.env&lt;/code> to configure:&lt;/p>
&lt;pre tabindex="0">&lt;code>ACCESS_TOKEN=VERYSECRETTOKENTHATISDEFINETLYREAL
EXTERNAL_API_BASE_URL=https://gay-pirate-assassins.de
ALLOWED_ACCOUNTS=ZGGZF4G8NNOTREAL81Z8G7RTC
&lt;/code>&lt;/pre>&lt;h2 id="results">Results&lt;/h2>
&lt;p>Now I can again use something like &lt;a href="https://wordpress.org/plugins/include-mastodon-feed/#installation">the wordpress plugin Include Mastodon Feed&lt;/a> just by pointing to the proxy: &lt;code>[include-mastodon-feed instance=&amp;quot;fediproxy.example.org.de&amp;quot; account=&amp;quot;ZGGZF4G8NNOTREAL81Z8G7RTC&amp;quot;]&lt;/code>&lt;/p>
&lt;p>Hope you enjoyed the read. Source code for the proxy can be found here: &lt;a href="https://git.hyteck.de/moanos/FediProxy">https://git.hyteck.de/moanos/FediProxy&lt;/a>
If you want to play around a bit you can use &lt;a href="https://git.hyteck.de/moanos/include-fedi">https://git.hyteck.de/moanos/include-fedi&lt;/a>&lt;/p>
&lt;p>Sloth logo of GTS by &lt;a href="https://abramek.art/">Anna Abramek&lt;/a>, &lt;a href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons BY-SA license&lt;/a>.&lt;/p></content></entry><entry><title>Rules, Terms and Privacy for GoToSocial: gay-pirate-assassins.de</title><link href="https://hyteck.de/gay-pirate-assassins/" type="application/octet-stream"/><updated>2022-11-20T04:56:10+02:00</updated><id>https://hyteck.de/gay-pirate-assassins/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;h1 id="rules">Rules&lt;/h1>
&lt;p>&lt;em>&amp;quot;Be excellent to each other&amp;quot;&lt;/em> is easier said than done, and means
different things to different people.&lt;/p>
&lt;p>The following rules are a (non-exhaustive) list of behaviours that may
lead to deletion of toots, silencing or suspension of accounts, at the
descretion of the instance administrators, as described in our
&lt;a href="#terms-of-service">Terms&lt;/a>&lt;/p>
&lt;p>Please report behaviour that bothers you. We will keep your report
confidential.&lt;/p>
&lt;ul>
&lt;li>We do not tolerate discriminatory behaviour and content promoting or
advocating the oppression of members of marginalised groups. These
groups may be characterised by any of the following (though this
list is naturally incomplete):
&lt;ul>
&lt;li>ethnicity&lt;/li>
&lt;li>gender identity or expression&lt;/li>
&lt;li>sexual identity or expression&lt;/li>
&lt;li>physical characteristics or age&lt;/li>
&lt;li>disability or illness&lt;/li>
&lt;li>nationality, residency, citizen status&lt;/li>
&lt;li>wealth or education&lt;/li>
&lt;li>religious affiliation, agnosticism or atheism&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>We do not tolerate threatening behaviour, stalking, and
&lt;a href="https://en.wikipedia.org/wiki/Doxxing">doxxing&lt;/a>&lt;/li>
&lt;li>We do not tolerate harassment, including brigading, dogpiling, or
any other form of contact with a user who has stated that they do
not wish to be contacted.&lt;/li>
&lt;li>We do not tolerate mobbing, including name-calling, intentional
misgendering or deadnaming.&lt;/li>
&lt;li>We do not tolerate violent nationalist propaganda, Nazi symbolism or
promoting the ideology of National Socialism.&lt;/li>
&lt;li>We do not tolerate conspiracy narratives or other reactionary myths
supporting or leading to the above-mentioned (and/or similar)
behavior.&lt;/li>
&lt;li>Actions intended to damage this instance or its performance may lead
to immediate account suspension.&lt;/li>
&lt;li>Content that is illegal in Germany will be deleted and may lead to
immediate account suspension.&lt;/li>
&lt;/ul>
&lt;h2 id="best-practices">Best practices&lt;/h2>
&lt;p>The list below is a collection of behaviour that we expect to see from
our users. If you see a user go against these best practices in a way
that bothers you, please file a report and we will talk to them. While
these best practices are designed to be guidelines for a good communal
instance, &lt;strong>repeated malicious unwillingness&lt;/strong> to follow the best
practices will be considered just like breaking a rule.&lt;/p>
&lt;ul>
&lt;li>In general, use the tools provided to foster a considerate and
accessible atmosphere. This includes the liberal use of content
warnings (especially on potentially disturbing or controversial
topics), and alt-text captioning of media files.&lt;/li>
&lt;li>When possible, provide credit for creative works in your posts that
are not your own.&lt;/li>
&lt;li>Uninvited comments about another user's personal choices, lifestyle
or family are strongly discouraged and may be considered harassment.
Inappropriate sexual attention, comments about appearance and
implication of physical contact will not be tolerated toward any
non-consenting user.&lt;/li>
&lt;li>If you post sexual content or gore, use content warnings.&lt;/li>
&lt;li>If you post advertisements, use a content warning. Advertisements
should not be excessive or automated.&lt;/li>
&lt;li>Bots may only interact with a user when they're invited by that
user to do so.&lt;/li>
&lt;li>Automated posts and high-frequency posts should be &lt;strong>unlisted&lt;/strong>
(rendering visible to everybody, but not appearing on the local
timeline) to keep the local timeline of our instance a place of
community dialogue and human interaction. This extends to bots, feed
posters, Twitter &amp;quot;retweets&amp;quot; and Twitter crossposts with broken
mentions (&amp;quot;&amp;hellip;@twitter.com&amp;quot;). Crossposter accounts that stop being
active participants in our community may be removed at the
discretion of the moderators.&lt;/li>
&lt;li>In discussions, please remain civil, do not insult the people
you're talking to. Note that irony, sarcasm, or similar modes of
language don't translate well to written language and tend to
escalate discussions or misunderstandings.&lt;/li>
&lt;/ul>
&lt;p>&lt;em>(These best practices were inspired by the Terms of bsd.network. Thank
you!)&lt;/em>&lt;/p>
&lt;h2 id="resources">Resources&lt;/h2>
&lt;p>I am maintaining this instance on my spare time, hardware and nerves.
Don't push either of those.&lt;/p>
&lt;h1 id="terms-of-service">Terms of Service&lt;/h1>
&lt;p>adapted from chaos.social which was in turn adepted by the bsd.network ToS&lt;/p>
&lt;p>It is our intention that you use this service for personal enjoyment and
respectful, friendly interaction. To that end, we hope to foster a
welcoming and inclusive environment.&lt;/p>
&lt;p>The server is privately owned and open to users voluntarily, not a
public space. Users wishing to join this community are expected to act
without malice and in good faith. Doing otherwise may lead to removal
from the service, independent of whether a user violates any rules
outlined below.&lt;/p>
&lt;p>The administrators and moderators of this instance are&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://chaos.social/@moanos">moanos&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>The server hosting the instance is located in Germany.&lt;/p>
&lt;p>The following statements apply regardless of privacy level and instance
of the users involved. In rare cases, public or private offline conduct
or conduct on a separate instance may constitute grounds for removal
from the service.&lt;/p>
&lt;hr>
&lt;h3 id="policies-and-rules">Policies and Rules&lt;/h3>
&lt;p>Our instance is subject to a set of rules governing user behaviour. The
rules are defined above.&lt;/p>
&lt;p>These rules are designed to maintain a friendly and open atmosphere, and
to prevent harassment and discrimination. As such, they are a set of
guidelines, but by necessity incomplete. Users violating the spirit of
these rules will be treated no differently than users violating a
specific rule.&lt;/p>
&lt;p>Please note that our rules contain a section on best practices, and
users who repeatedly and despite warnings disregard these best practices
may be seen to be in violation of our rules.&lt;/p>
&lt;p>The moderators may remove accounts who spam the instance, or are
suspected of camping just to reserve an account name. Violation of the
policies and rules may also lead to account removal at the discretion of
the moderators.&lt;/p>
&lt;h3 id="data-access">Data Access&lt;/h3>
&lt;p>Content on this instance must not be used for the purposes of machine
learning or other &amp;quot;research&amp;quot; purposes without the explicit consent of
the users involved.&lt;/p>
&lt;p>Content on this instance beyond this page must not be archived or
indexed wholesale by automated means by any user or service. Active
users may export their following lists and posts through the export
provided on their settings page, or the API.&lt;/p>
&lt;hr>
&lt;h1 id="privacy">Privacy Policy&lt;/h1>
&lt;h3 id="collect">Information collection&lt;/h3>
&lt;ul>
&lt;li>&lt;em>Mandatory account information&lt;/em>: Username (always public), e-mail
address, and password.&lt;/li>
&lt;li>&lt;em>Optional account information&lt;/em>: Display name, biography, profile
information fields, profile picture, and header image. Display name,
biography, profile picture and header image will always be public.&lt;/li>
&lt;li>&lt;em>Statuses and interactions&lt;/em>: We retain all your posts including
attachments, and other interactions (such as favourites, follows and
reblogs). In addition to the content and people involved, we also
store the timestamps for all of the listed data entries. If these
interactions impact another server (eg. following, boosting, or
messaging a user on a different server), this other server will
receive all required information. Public, unlisted, and pinned posts
are available publicly. Follower-only posts are available to your
followers, and direct messages are available to you and all people
mentioned in the message. Please note that since we cannot control
other servers, this means that we cannot guarantee the privacy
status of your messages as soon as they leave our server.&lt;/li>
&lt;li>&lt;em>Cookies&lt;/em>: We use cookies to keep you logged in and save your
preferences for future visits.&lt;/li>
&lt;li>&lt;em>Other metadata&lt;/em>: We do log and store your IP address.
We retain the name of your browser application to allow you
to review your currently logged in sessions for security reasons.&lt;/li>
&lt;/ul>
&lt;h3 id="use">Information usage&lt;/h3>
&lt;p>Any of the information we collect from you may be used in the following
ways:&lt;/p>
&lt;ul>
&lt;li>To provide the core functionality of GoToSocial. You can only interact
with other people's content and post your own content when you are
logged in. For example, you may follow other people to view their
combined posts in your own personalized home timeline.&lt;/li>
&lt;li>To aid moderation of the community &amp;ndash; when a status or account is
reported, we will look into the matter as part of our moderation
tasks.&lt;/li>
&lt;li>The email address you provide may be used to send you information,
notifications about other people interacting with your content or
sending you messages, and to respond to inquiries, and/or other
requests or questions.&lt;/li>
&lt;li>To aid debugging and reliable providing service.&lt;/li>
&lt;/ul>
&lt;h3 id="protect">Information protection&lt;/h3>
&lt;p>We implement a variety of security measures to maintain the safety of
your personal information when you enter, submit, or access your
personal information. Among other things, your browser session, as well
as the traffic between your applications and the API, are secured with
HTTPS, and your password is hashed using a strong one-way algorithm. You
may enable two-factor authentication to further secure access to your
account.&lt;/p>
&lt;h3 id="data-retention">Information deletion and retention&lt;/h3>
&lt;p>You can request and download an archive of your content, including your
posts, media attachments, profile picture, and header image.&lt;/p>
&lt;p>You may irreversibly delete your account at any time.&lt;/p>
&lt;p>If we judge you to be breaking our instance rules, we may irreversibly
delete your account at any time.&lt;/p>
&lt;h3 id="disclose">Information disclosure&lt;/h3>
&lt;p>Information is not disclosed unless you explicitly permit it. The only
exception is the provider of our server, who is a trusted and
unavoidable third party.&lt;/p>
&lt;p>Contacting or permitting contact from a user from a different instance
implies your consent that the required data is shared with the server in
question.&lt;/p>
&lt;p>Authorization of a third-party application grants information access
depending on the scope of permissions you approve. The application may
access your public profile information, your following list, your
followers, your lists, all your posts, and your favourites. Applications
can never access your e-mail address or password.&lt;/p>
&lt;hr>
&lt;h1 id="attribution-">Attribution &lt;a href="#attribution">¶&lt;/a>&lt;/h1>
&lt;p>This text was adapted from &lt;a href="https://chaos.social/about">https://chaos.social/about&lt;/a> and is free to be
adapted and remixed under the terms of the &lt;a href="https://creativecommons.org/licenses/by/4.0/">CC-BY (Attribution 4.0
International)&lt;/a>.&lt;/p></content></entry><entry><title>Vim shortcuts to execute current line</title><link href="https://hyteck.de/post/easy_execute_in_vim/" type="application/octet-stream"/><updated>2022-05-25T15:15:55+02:00</updated><id>https://hyteck.de/post/easy_execute_in_vim/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;p>I recently had to write a lot of SQL code and thought it would be very neat to have some vim shortcuts to execute the current line or the current command.
I want to share this with everyone as the second command needed some try-and-error on my part.&lt;/p>
&lt;p>Adding the following to &lt;code>~/.vimrc&lt;/code>&lt;/p>
&lt;pre tabindex="0">&lt;code>map &amp;lt;F2&amp;gt; :.w !psql&amp;lt;CR&amp;gt;
map &amp;lt;F3&amp;gt; :.,/;/w !psql&amp;lt;CR&amp;gt;
map &amp;lt;F4&amp;gt; :w !psql&amp;lt;CR&amp;gt;
&lt;/code>&lt;/pre>&lt;p>will enable you to execute the current line in psql with &lt;code>F2&lt;/code>. &lt;code>F3&lt;/code> executes the current line and the next lines until a&lt;code>;&lt;/code>.
&lt;code>F4&lt;/code> executes the whole file.&lt;/p>
&lt;p>This can easily adapted to your needs. If you have any questions or improvemnts feel free to use the chat 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: "vim-shortcuts"
})
&lt;/script></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><entry><title>Transparency</title><link href="https://hyteck.de/transparency/" type="application/octet-stream"/><updated>2021-02-08T15:00:10+02:00</updated><id>https://hyteck.de/transparency/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;h1 id="deutsch">Deutsch&lt;/h1>
&lt;p>Wir haben die bisherigen Kosten für Server-Hardware in der Tabelle unten angegeben. Das beinhaltet nicht die Kosten für die Domain und unseren Monitoring-Server.&lt;/p>
&lt;p>An alle die bisher gespendet habe: Ganz ganz vielen Dank euch!&lt;/p>
&lt;h1 id="english">English&lt;/h1>
&lt;p>We summarized the cost of hardware in the table below. This does not include costs of the domain and our monitoring server.&lt;/p>
&lt;p>To everyone that contributed: Thank you so so much!&lt;/p>
&lt;h1 id="überblickoverview">Überblick/Overview&lt;/h1>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Kosten&lt;/th>
&lt;th>Spenden&lt;/th>
&lt;th>Saldo&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>-321.63 €&lt;/td>
&lt;td>235.5 €&lt;/td>
&lt;td>- 86.13 €&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h1 id="kostenaufstellung--exact-cost">Kostenaufstellung / Exact cost&lt;/h1>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Date&lt;/th>
&lt;th>Cost (€)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>27.04.2021&lt;/td>
&lt;td>-25&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>1.04.2021&lt;/td>
&lt;td>-25&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>26.02.2021&lt;/td>
&lt;td>-25&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>22.01.2021&lt;/td>
&lt;td>-25&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>22.12.2020&lt;/td>
&lt;td>-24.78&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>25.11.2020&lt;/td>
&lt;td>-24.37&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>22.10.2020&lt;/td>
&lt;td>-24.37&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>22.09.2020&lt;/td>
&lt;td>-24.37&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>22.08.2020&lt;/td>
&lt;td>-24.37&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>22.07.2020&lt;/td>
&lt;td>-24.37&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>22.06.2020&lt;/td>
&lt;td>-25&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>22.05.2020&lt;/td>
&lt;td>-25&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>22.04.2020&lt;/td>
&lt;td>-25&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Total&lt;/strong>&lt;/td>
&lt;td>&lt;strong>-321.63&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Die wechselnden monatlichen Kosten sind durch die temporär gesenkte Mehrwertsteuer bedingt.&lt;/p>
&lt;p>The altering monthly costs are explained by a decrease in sales tax in Germany.&lt;/p>
&lt;h1 id="spenden--donations">Spenden / Donations&lt;/h1>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Date&lt;/th>
&lt;th>Donation (€)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>03.05.2021&lt;/td>
&lt;td>7.50&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>06.04.2021&lt;/td>
&lt;td>7.50&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>02.03.2021&lt;/td>
&lt;td>7.50&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>01.03.2021&lt;/td>
&lt;td>100&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>12.02.2021&lt;/td>
&lt;td>33&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>12.02.2021&lt;/td>
&lt;td>20&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>09.02.2021&lt;/td>
&lt;td>50&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>09.02.2021&lt;/td>
&lt;td>10&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Total&lt;/strong>&lt;/td>
&lt;td>&lt;strong>235.5&lt;/strong>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h1 id="letzte-aktualisierung--last-update">Letzte Aktualisierung / Last update&lt;/h1>
&lt;p>21.05.2021&lt;/p>
&lt;h1 id="kontak--contact">Kontak / Contact&lt;/h1>
&lt;p>Bei Fragen meldet euch gerne bei: &lt;a href="mailto:bbb@hyteck.de">bbb@hyteck.de&lt;/a>&lt;/p>
&lt;p>If you have any questions regarding this ask us via: &lt;a href="mailto:bbb@hyteck.de">bbb@hyteck.de&lt;/a>&lt;/p></content></entry><entry><title>Set up and secure an MQTT broker on Ubuntu</title><link href="https://hyteck.de/post/mqtt-telegraf-influx/" type="application/octet-stream"/><updated>2021-01-01T18:18:10+02:00</updated><id>https://hyteck.de/post/mqtt-telegraf-influx/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;p>I had some IoT devices that I wanted to integrate in my monitoring. For this I set up a MQTT broker as the MQTT protocol is a simple solution to send data from IoT devices to a server. This tutorial is focusing on setting up the server, but I also introduce a Python based MQTT client to test our installation.&lt;/p>
&lt;p>On your server, first install mosquitto, our MQTT server/broker.&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo apt-get install mosquitto
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Allow standard mqtt port in firewall (if you have ufw installed)&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo ufw allow &lt;span style="color:#ae81ff">1883&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now on the client side connect to the server and publish some fake sensor values.
First install the mqtt client&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo pip install phao-mqtt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and then use the following python code on your client side to send fake values to your server. You only need to change &lt;code>mqtt.example.com&lt;/code> to your servers IP/domain.&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> time
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> paho.mqtt.client &lt;span style="color:#66d9ef">as&lt;/span> mqtt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> numpy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> numpy &lt;span style="color:#66d9ef">as&lt;/span> np
&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">calc_temp&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> temp &lt;span style="color:#f92672">=&lt;/span> np&lt;span style="color:#f92672">.&lt;/span>sin(time&lt;span style="color:#f92672">.&lt;/span>time()&lt;span style="color:#f92672">%&lt;/span>(&lt;span style="color:#ae81ff">3600&lt;/span>)&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">2&lt;/span>&lt;span style="color:#f92672">*&lt;/span>np&lt;span style="color:#f92672">.&lt;/span>pi)&lt;span style="color:#f92672">*&lt;/span>&lt;span style="color:#ae81ff">5&lt;/span>&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#ae81ff">20&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> temp
&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">on_connect&lt;/span>(client, userdata, flags, rc):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Connected with result code &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> str(rc))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client &lt;span style="color:#f92672">=&lt;/span> mqtt&lt;span style="color:#f92672">.&lt;/span>Client()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#client.username_pw_set(username=&amp;#34;username&amp;#34;,password=&amp;#34;my_super_secret_pw&amp;#34;)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client&lt;span style="color:#f92672">.&lt;/span>on_connect &lt;span style="color:#f92672">=&lt;/span> on_connect
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client&lt;span style="color:#f92672">.&lt;/span>connect(&lt;span style="color:#e6db74">&amp;#34;mqtt.example.com&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">1883&lt;/span>, &lt;span style="color:#ae81ff">60&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client&lt;span style="color:#f92672">.&lt;/span>loop_start()
&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">while&lt;/span> &lt;span style="color:#66d9ef">True&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> time&lt;span style="color:#f92672">.&lt;/span>sleep(&lt;span style="color:#ae81ff">2&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client&lt;span style="color:#f92672">.&lt;/span>publish(&lt;span style="color:#e6db74">&amp;#34;test/temperature&amp;#34;&lt;/span>, calc_temp())
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can check if the broker accepts the values by subscribing to the topic:&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:#75715e">#!/usr/bin/env python&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> paho.mqtt.client &lt;span style="color:#66d9ef">as&lt;/span> mqtt
&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">on_connect&lt;/span>(client, userdata, flags, rc):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(&lt;span style="color:#e6db74">&amp;#34;Connected with result code &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> str(rc))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> client&lt;span style="color:#f92672">.&lt;/span>subscribe(&lt;span style="color:#e6db74">&amp;#34;test/#&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:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">on_message&lt;/span>(client, userdata, msg):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(msg&lt;span style="color:#f92672">.&lt;/span>topic &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#e6db74">&amp;#34; &amp;#34;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> str(msg&lt;span style="color:#f92672">.&lt;/span>payload))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client &lt;span style="color:#f92672">=&lt;/span> mqtt&lt;span style="color:#f92672">.&lt;/span>Client()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#client.username_pw_set(username=&amp;#34;username&amp;#34;,password=&amp;#34;my_super_secret_pw&amp;#34;)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client&lt;span style="color:#f92672">.&lt;/span>on_connect &lt;span style="color:#f92672">=&lt;/span> on_connect
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client&lt;span style="color:#f92672">.&lt;/span>on_message &lt;span style="color:#f92672">=&lt;/span> on_message
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client&lt;span style="color:#f92672">.&lt;/span>connect(&lt;span style="color:#e6db74">&amp;#34;mqtt.example.com&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">1883&lt;/span>, &lt;span style="color:#ae81ff">60&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>client&lt;span style="color:#f92672">.&lt;/span>loop_forever()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now secure your broker by creating a user with a password&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo mosquitto_passwd -c /etc/mosquitto/passwd &amp;lt;username&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and configure mosquitto to use it in &lt;code>/etc/mosquitto/conf.d/default.conf&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code>allow_anonymous false
password_file /etc/mosquitto/passwd
&lt;/code>&lt;/pre>&lt;p>Now restart mosquitto to enable the protection&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo systemctl restart mosquitto
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Test the installation by uncommenting &lt;code>client.username_pw_set(username=&amp;quot;username&amp;quot;,password=&amp;quot;my_super_secret_pw&amp;quot;)&lt;/code> and filling in your credentials.
The result code &lt;code>0&lt;/code> indicates a valid connection. &lt;code>5&lt;/code> indicates a authentication error.&lt;/p>
&lt;p>I hope this helps setting up a MQTT broker. Hopefully I will have the time to write how to connect such a broker to Grafana via Telegraf and Influx DB.&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: "monitoring"
})
&lt;/script></content></entry><entry><title>Translation for video conferences</title><link href="https://hyteck.de/post/bbb_translation/" type="application/octet-stream"/><updated>2020-07-23T07:30:00+02:00</updated><id>https://hyteck.de/post/bbb_translation/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;p>Although physical meetings are not possible, many groups and organizations are dependent on multilingual meetings.
In videoconferencing, concepts such as a whispered translation are not intuitive. Therefore a friend and I have developed a concept for translation in videoconferencing.
The concept was planned for a conference with 80 participants who will discuss and make decisions over two days either in large groups or in sub-groups.
The videoconferencing software that was used was BigBlueButton (BBB).&lt;/p>
&lt;h1 id="translation-via-subtitles">Translation via subtitles&lt;/h1>
&lt;p>BBB offers the possibility to write subtitles simultaneously to video conferencing.
This feature is especially aimed at hearing impaired people.
For translation, one person would translate and type in one language at a time.
Due to the limited typing speed, this procedure is associated with large losses of content.
However, it is a great option for hearing-impaired people and can be combined with a simultaneous oral translation.&lt;/p>
&lt;h1 id="simultaneous-translation">Simultaneous translation&lt;/h1>
&lt;p>The concept of simultaneous translation is based on the fact that there is a seperate BBB-room for each language. These are communicated to the participants in advance.&lt;/p>
&lt;p>The room whose language most of the participants speak will be the main room. People who do not speak the language of the main room will still join it, as moderation tasks and the presentation will be only in this room.&lt;/p>
&lt;p>All statements in the main room will be translated by overseers in the other rooms. The translators should be present in the main room with the microphone switched off. If a person from a secondary room wants to speak they indicate this in the chat of the main room, - preferably with &amp;ldquo;* (language)&amp;rdquo;. They will then be allowed to speak by the moderator and speak in their respective room. The translator will unmute their microphone in the main room and translates to the primar language.&lt;/p>
&lt;h2 id="breakout-rooms">Breakout Rooms&lt;/h2>
&lt;p>Breakput rooms are often formed to debate in smaller groups.
To ensure a reasonable amount of translation effort, all participants and translators of one language are assigned to one breakout room (in the main room).
If the Breakout Rooms become too full, Breakout Rooms can also be formed in the secondary rooms, but translators are then required for each room.&lt;/p>
&lt;h1 id="tips-and-tricks">Tips and tricks&lt;/h1>
&lt;ul>
&lt;li>Use two translators per language. One person cannot do this even at a moderate speed of discussion. It also allows for better reproduction of conversations by alternating the translators.&lt;/li>
&lt;li>Ask participants to set their names according to the &amp;ldquo;Name [pronouns] (language)&amp;rdquo; scheme. This makes it easier for moderation and translators.&lt;/li>
&lt;li>Participants do not need to join the audio in the main room, but they can, so that their contributions can be heard in the original. However, they should mute the browser window of the main room.&lt;/li>
&lt;/ul>
&lt;h1 id="conclusion">Conclusion&lt;/h1>
&lt;p>Whispered translation is possible in BBB; in order to use it, the possibility must be announced in good time by the moderator and enough translators must be found. The concept of oral translation has already proven itself at a large meeting.&lt;/p>
&lt;p>If you are interested in implementing the concept you are welcome to contact me at &lt;a href="mailto:fluesteruebersetzung@hyteck.de">fluesteruebersetzung@hyteck.de&lt;/a> or 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: "bbb-translation"
})
&lt;/script></content></entry><entry><title>Services</title><link href="https://hyteck.de/services/" type="application/octet-stream"/><updated>2019-11-14T09:56:10+02:00</updated><id>https://hyteck.de/services/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;h1 id="services">Services&lt;/h1>
&lt;p>This is a non-extensive list of services I offer. All services are hosted in Germany and come with monitoring of service
uptime.&lt;/p>
&lt;h2 id="library-management">Library management&lt;/h2>
&lt;p>The library management software &lt;a href="https://github.com/moan0s/ILMO2">ILMO&lt;/a> is devoloped by me. It is a perfect tool for the
management of small to middle-sized libraries. It offers user management, reminders on loans and an easy borrow procedure.
The software is open source so you can host it yourself. If you do not want the hassle of self-hosting I offer managed
hosting.&lt;/p>
&lt;h2 id="livestreams">Livestreams&lt;/h2>
&lt;p>I offer livestreams for online talks. During the global COVID pandemic, a lot of lectures, seminars and talks had to be
held online. I want to offer you a way to still reach and interact with people while maintaining a high level of data
protection. This is what &lt;a href="https://owncast.online/">Owncast&lt;/a> in combination with other solutions offers: Just reach out
and we can discuss a concept that will work for you!&lt;/p>
&lt;h2 id="list-of-all-services">List of all services&lt;/h2>
&lt;p>I host some services that are publicly available, some that are only for friends and some that are private.
Get in touch if you want me to host a service like this exclusively for yourself or your organization.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Service&lt;/th>
&lt;th>Description&lt;/th>
&lt;th>Access&lt;/th>
&lt;th>Status&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;a href="https://nextcloud.hyteck.de">Nextcloud&lt;/a>&lt;/td>
&lt;td>Cloud storage with collaboration suite&lt;/td>
&lt;td>Upon request&lt;/td>
&lt;td>Live&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://matrix.hyteck.de">Matrix&lt;/a>&lt;/td>
&lt;td>Encrypted chat for teams&lt;/td>
&lt;td>Upon request/Einladung&lt;/td>
&lt;td>Live&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://gay-pirate-assassins.de">GoToSocial&lt;/a>&lt;/td>
&lt;td>Social Media server in the Fediverse (Mastodon-compatible)&lt;/td>
&lt;td>Public&lt;/td>
&lt;td>Live&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://noise.hyteck.de">Funkwhale&lt;/a>&lt;/td>
&lt;td>Music sharing &amp;amp; streaming&lt;/td>
&lt;td>Upon request&lt;/td>
&lt;td>Live&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://stream.hyteck.de">Owncast&lt;/a>&lt;/td>
&lt;td>Livestreams&lt;/td>
&lt;td>Upon request&lt;/td>
&lt;td>Live&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://notfellchen.org">Notfellchen&lt;/a>&lt;/td>
&lt;td>Find animals and give them a loving home. Not available as commercial hosting - happy to do do this non-profit.&lt;/td>
&lt;td>Public&lt;/td>
&lt;td>Live&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://code.hyteck.de">Forgjo&lt;/a> &amp;amp; Forgjo actions&lt;/td>
&lt;td>Git hosting and automations&lt;/td>
&lt;td>Invitiation-only&lt;/td>
&lt;td>Live&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Grafana&lt;/td>
&lt;td>Display telemetry data&lt;/td>
&lt;td>Private&lt;/td>
&lt;td>Live&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Prometheus&lt;/td>
&lt;td>Monitoring system &amp;amp; time series database&lt;/td>
&lt;td>Private&lt;/td>
&lt;td>Live&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Influx DB&lt;/td>
&lt;td>Time series database&lt;/td>
&lt;td>Private&lt;/td>
&lt;td>Live&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>ILMO&lt;/td>
&lt;td>Library management tool&lt;/td>
&lt;td>Test instance offered upon request&lt;/td>
&lt;td>Testing&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>BigBlueButton&lt;/td>
&lt;td>Videoconferencing software&lt;/td>
&lt;td>Public&lt;/td>
&lt;td>Discontinued April 21. Use &lt;a href="https://senfcall.de">Senfcall&lt;/a>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h1 id="usage-policy-german">Usage policy (german)&lt;/h1>
&lt;p>Unless a contract specifies otherwise the services are provided free of charge and as-is, without warranty of any kind,
expressed or implied. The usage of the services can be restricted or blocked permanently immediately and without warning.&lt;/p>
&lt;p>This is especially true for anything that goes against the following rules.
The rules are a (non-exhaustive) list of behaviours that may
lead to deletion of content or suspension of accounts. In some cases, public or private offline conduct
or conduct in using other services may constitute grounds for removal from the service.&lt;/p>
&lt;ul>
&lt;li>We do not tolerate discriminatory behaviour and content promoting or
advocating the oppression of members of marginalised groups. These
groups may be characterised by any of the following (though this
list is naturally incomplete):
&lt;ul>
&lt;li>ethnicity&lt;/li>
&lt;li>gender identity or expression&lt;/li>
&lt;li>sexual identity or expression&lt;/li>
&lt;li>physical characteristics or age&lt;/li>
&lt;li>disability or illness&lt;/li>
&lt;li>nationality, residency, citizen status&lt;/li>
&lt;li>wealth or education&lt;/li>
&lt;li>religious affiliation, agnosticism or atheism&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>We do not tolerate threatening behaviour, stalking, and
&lt;a href="https://en.wikipedia.org/wiki/Doxxing">doxxing&lt;/a>&lt;/li>
&lt;li>We do not tolerate harassment, including brigading, dogpiling, or
any other form of contact with a user who has stated that they do
not wish to be contacted.&lt;/li>
&lt;li>We do not tolerate mobbing, including name-calling, intentional
misgendering or deadnaming.&lt;/li>
&lt;li>We do not tolerate violent nationalist propaganda, Nazi symbolism or
promoting the ideology of National Socialism.&lt;/li>
&lt;li>We do not tolerate conspiracy narratives or other reactionary myths
supporting or leading to the above-mentioned (and/or similar)
behavior.&lt;/li>
&lt;li>Actions intended to damage a service or its performance may lead
to immediate suspension.&lt;/li>
&lt;li>Content that is illegal in Germany will be deleted and may lead to
immediate account suspension.&lt;/li>
&lt;/ul>
&lt;p>You can report content that goes against these rules even if you are not the affected person. The report will be kept
confidential. &lt;a href="https://hyteck.de/about/#contact">Contact&lt;/a>&lt;/p>
&lt;h2 id="availability">Availability&lt;/h2>
&lt;p>There is &lt;strong>no&lt;/strong> guarantee of availability, unless specified in a separate contract. There is no guarantee of data backups.
Suspension of a service will usually be announced but this is not guaranteed. Join &lt;a href="https://matrix.to/#/#announcements:hyteck.de">Announcements&lt;/a> for this purpose.&lt;/p>
&lt;h1 id="contact">Contact&lt;/h1>
&lt;p>See &lt;a href="https://hyteck.de/about/#contact">here&lt;/a>&lt;/p></content></entry><entry><title>Introduction to git</title><link href="https://hyteck.de/post/introduction_to_git/" type="application/octet-stream"/><updated>2019-10-22T19:00:00+02:00</updated><id>https://hyteck.de/post/introduction_to_git/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;p>As you man now I work in a research lab. Nearly everyone in our lab needs to do at least some programming. There are trained software engineers next to people how write their first line of code here.
As we produce a lot of code that is only scripts not many people used git or any other version control system.
For all the good reasons (some are mentioned in the guide) we everybody to learn git in a way that is tradeoff between good version control and fast development.
As most of us work on separate projects there is not so much need to collaborate which is reflected ind this guide as I completely excluded branching and merge conflicts.
Yet I hope this will maybe help some learners of git or some people that need to introduce git to others.&lt;/p>
&lt;p>I will not be updating this guide, for the newest version please visit &lt;a href="https://github.com/GerJuli/introduction-to-git">https://github.com/GerJuli/introduction-to-git&lt;/a>&lt;/p>
&lt;p>Now enjoy the guide!&lt;/p>
&lt;h1 id="installation">Installation&lt;/h1>
&lt;ul>
&lt;li>On Windows: Download git here: &lt;a href="https://git-scm.com/download/win">https://git-scm.com/download/win&lt;/a> and install it&lt;/li>
&lt;li>On Linux: execute &amp;lsquo;sudo apt install git&amp;rsquo; in the terminal&lt;/li>
&lt;/ul>
&lt;h1 id="configuration">Configuration&lt;/h1>
&lt;h2 id="basics">Basics&lt;/h2>
&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git config --global user.name &lt;span style="color:#e6db74">&amp;#34;John Doe&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ git config --global user.email johndoe@example.com
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Run these commands without &amp;ndash;global to apply changes only for local repository.&lt;/p>
&lt;h2 id="advanced">Advanced&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>Change from default editor by executing &lt;code>$ git config --global core.editor vim&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Create aliases by:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;code>git config --global alias.unstage 'reset HEAD --'&lt;/code>:
Introduces new command unstage&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;code>git config --global alias.hist 'log --pretty=format:&amp;quot;%h - %an, %ar : %s&amp;quot; --graph'&lt;/code>:
Is an pretty alternative to git log.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Check the configuration by &lt;code>$ git config --list&lt;/code>&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h1 id="getting-started">Getting started&lt;/h1>
&lt;h2 id="create-an-directory">Create an directory&lt;/h2>
&lt;p>Select an directory of your choice e.g. ~/Desktop/git-training/,
open the terminal there and type&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git init
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Congratulations you have your first git repository!&lt;/p>
&lt;h2 id="committing-files">Committing files&lt;/h2>
&lt;p>Create some text files e.g. &lt;code>hallo.txt&lt;/code> or &lt;code>hello_world.py&lt;/code>.
To start version-controlling them add the file to git by using the command&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git add filename
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can also add multiple files. E.g. you want to add all text files the do&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git add *.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now we can commit these changes by typing&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git commit
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will open your preferred text editor
where you can type in a commit message.&lt;/p>
&lt;h3 id="commit-messages">Commit messages&lt;/h3>
&lt;p>&lt;em>Commit messages are important&lt;/em>&lt;/p>
&lt;p>Why are the so important? Because it is much easier to track bugs with it,
they describe the process of development, make it easier for others to work
with your code (e.g. code review) and will help you in two years to understand what you did.
They extend your labbook in its function.&lt;/p>
&lt;h4 id="how-to-write-commit-messages">How to write commit messages&lt;/h4>
&lt;ul>
&lt;li>Use present tense and use imperative&lt;/li>
&lt;li>Tell why you did changes.&lt;/li>
&lt;li>Limit yourself to 50 characters in the first line.&lt;/li>
&lt;li>Wrap the body at 72 characters&lt;/li>
&lt;/ul>
&lt;h4 id="good-commit-messages">Good commit messages&lt;/h4>
&lt;p>For short commits that need only few explanation it is convenient to use&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git commit -m &lt;span style="color:#e6db74">&amp;#34;Fix typo in introduction to user guide&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>For long commit message you use&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git commit
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which opens a text editor and lets you describe your changes in detail. Do not forget the line
between header and body of the commit message.&lt;/p>
&lt;pre tabindex="0">&lt;code>Summarize changes in around 50 characters or less
More detailed explanatory text, if necessary. Wrap it to about 72
characters or so. In some contexts, the first line is treated as the
subject of the commit and the rest of the text as the body. The
blank line separating the summary from the body is critical (unless
you omit the body entirely); various tools like `log`, `shortlog`
and `rebase` can get confused if you run the two together.
Explain the problem that this commit is solving. Focus on why you
are making this change as opposed to how (the code explains that).
Are there side effects or other unintuitive consequences of this
change? Here&amp;#39;s the place to explain them.
Further paragraphs come after blank lines.
- Bullet points are okay, too
- Typically a hyphen or asterisk is used for the bullet, preceded
by a single space, with blank lines in between, but conventions
vary here
&lt;/code>&lt;/pre>&lt;h4 id="hall-of-shame">Hall of shame&lt;/h4>
&lt;p>Here you see some examples of bad commit messages.&lt;/p>
&lt;ul>
&lt;li>
&lt;p>“bug fix”, &amp;ldquo;more work”, “minor changes”:
Bad because it contains no useful information&lt;/p>
&lt;/li>
&lt;li>
&lt;p>“Change X constant to be 10”:
Bad as it does not tell why. What was changed is easy to find out by
git diff.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>“super long commit message goes here, something like 100 words and lots of characters woohoo!”:
Bad because it is unreadable.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="storing-changes-on-remote-server">Storing changes on remote server&lt;/h2>
&lt;p>To store changes on a remote server e.g. Github you need to push your repository there.
If you are doing this for the first time you first need to define where to push your repository to.
Use&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git remote add origin https://github.com/GerJuli/introduction-to-git.git
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The URL can be
replaced with any URL that points to an existing repository. This can even be a USB drive!
Now push by using &lt;code>git push -u origin master&lt;/code>. With this command you created an upstream to your
remote &amp;lsquo;origin&amp;rsquo; where you branch &amp;lsquo;master&amp;rsquo; will be pushed.
As this is set up you can use &lt;code>git push&lt;/code> from now on.&lt;/p>
&lt;h1 id="cloning-existing-repositories">Cloning existing repositories&lt;/h1>
&lt;p>Downloading existing repositories is called &amp;lsquo;cloning&amp;rsquo;. You can do this by using&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git clone https://github.com/GerJuli/introduction-to-git.git
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That is it. Now you can start working on it.&lt;/p>
&lt;h1 id="workflow">Workflow&lt;/h1>
&lt;h2 id="check-status">Check status&lt;/h2>
&lt;p>With&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git status
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>you can check the status of your current directory.
This will look like this:&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>On branch master
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Your branch is up-to-date with &lt;span style="color:#e6db74">&amp;#39;origin/master&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>nothing to commit, working directory clean
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="add-file">Add file&lt;/h2>
&lt;p>If you now add a file to the repository e.g. README then the status changes.&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ echo &lt;span style="color:#e6db74">&amp;#39;My Project&amp;#39;&lt;/span> &amp;gt; README
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ git status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>On branch master
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Your branch is up-to-date with &lt;span style="color:#e6db74">&amp;#39;origin/master&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Untracked files:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">(&lt;/span>use &lt;span style="color:#e6db74">&amp;#34;git add &amp;lt;file&amp;gt;...&amp;#34;&lt;/span> to include in what will be committed&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> README
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> nothing added to commit but untracked files present &lt;span style="color:#f92672">(&lt;/span>use &lt;span style="color:#e6db74">&amp;#34;git add&amp;#34;&lt;/span> to track&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can also run&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git diff
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which will show you the changes that where made.&lt;/p>
&lt;p>Now track the file by adding it to the staging area via&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git add README
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you want to commit only a part of the changes you made use&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>git add -p
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>git will ask you for every &amp;ldquo;hunk&amp;rdquo;(part of the file) if you want to add it to the staging area.
The main options are:&lt;/p>
&lt;ul>
&lt;li>y stage this hunk for the next commit&lt;/li>
&lt;li>n do not stage this hunk for the next commit&lt;/li>
&lt;li>q quit; do not stage this hunk or any of the remaining hunks&lt;/li>
&lt;/ul>
&lt;h2 id="committing">Committing&lt;/h2>
&lt;p>The changes are now ready to be committed&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>On branch master
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Your branch is up-to-date with &lt;span style="color:#e6db74">&amp;#39;origin/master&amp;#39;&lt;/span>.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Changes to be committed:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">(&lt;/span>use &lt;span style="color:#e6db74">&amp;#34;git reset HEAD &amp;lt;file&amp;gt;...&amp;#34;&lt;/span> to unstage&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> new file: README
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now commit this change with a suitable commit message.
You forgot what you changed and &lt;code>git diff&lt;/code> does not work?
If you already added the file to the staging area you can run&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git diff --staged
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="push-to-remote">Push to remote&lt;/h2>
&lt;p>Push this now to your repository with&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git push
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="show-last-changes">Show last changes&lt;/h2>
&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git log
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>shows you a log of recent commit messages.&lt;/p>
&lt;p>If you want to have a look at the changes source use&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git log -p
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>A much prettier version is&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git log --pretty&lt;span style="color:#f92672">=&lt;/span>format:&lt;span style="color:#e6db74">&amp;#34;%h - %an, %ar : %s&amp;#34;&lt;/span> --graph
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="get-latest-changes">Get latest changes&lt;/h1>
&lt;p>An amazing thing of git is to work together with others. This guide will not go into the details
of this, yet you will often have to get the latest changes of a project.&lt;/p>
&lt;p>You can do this easily by using&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git pull
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which is a short version of two different commands. It first executes &lt;code>git fetch&lt;/code> that downloads
the latest changes. Then it runs &lt;code>git merge FETCH_HEAD&lt;/code> which tries to apply the newest changes
to your local repository. This is also the difficulty of this as any file that was changed locally
and in the remote repository will cause a &lt;em>merge conflict&lt;/em> as git does not know which file/parts
of the file to keep. The resolution of such a conflict can be complicated and goes beyond the
intentions of this guide.&lt;/p>
&lt;p>A brief overview can be found
&lt;a href="https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/resolving-a-merge-conflict-using-the-command-line">here&lt;/a>.&lt;/p>
&lt;h2 id="gitignore">Gitignore&lt;/h2>
&lt;p>Sometimes you do not want git to track every file in your repository.
Typically this applies for log files, configuration files (where maybe passwords are stored)
and compiled sources. For this purpose you can create the file &lt;code>.gitignore&lt;/code> in your repository.
This file will be read by git. An example &lt;code>.gitignore&lt;/code> looks like this:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-gitignore" data-lang="gitignore"># ignore all .log files
*.log
# ignore all files in any directory named config
build/
# but do track sample.log, even though you&amp;#39;re ignoring .log files above
!sample.log
# only ignore the TODO file in the current directory, not subdir/TODO
/TODO
# ignore all files in any directory named build
build/
&lt;/code>&lt;/pre>&lt;p>You can generate gitignore files &lt;a href="https://www.gitignore.io/">here&lt;/a>.&lt;/p>
&lt;h1 id="undoing-things">Undoing things&lt;/h1>
&lt;h2 id="make-changes-to-the-last-commit">Make changes to the last commit&lt;/h2>
&lt;p>You forgot to add one file to your commit?
You are unhappy with your commit message?
Use &lt;code>git --amend&lt;/code>!&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git commit -m &lt;span style="color:#e6db74">&amp;#39;Pretty commit message&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ git add forgotten_file
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ git commit --amend
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Do not do this if you already pushed to remote.&lt;/p>
&lt;h2 id="unmodifying-a-modified-file">Unmodifying a Modified File&lt;/h2>
&lt;p>You made changes to a file but the changes messed everything up?
You can always go back to the version of the last commit by&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>git checkout -- filename
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;em>Warning&lt;/em>: This is dangerous as you delete all changes that you made locally.
Do NOT use this command unless you are absolutely sure what you are doing.&lt;/p>
&lt;h2 id="time-machine">Time machine&lt;/h2>
&lt;p>Something went terribly wrong. When using git this is no problem.
Just use &lt;code>reflog&lt;/code> and select the commit where still everything was alright.&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git reflog
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>9bff138 HEAD@&lt;span style="color:#f92672">{&lt;/span>0&lt;span style="color:#f92672">}&lt;/span>: commit: Document limitations of this guide
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>8d9dac8 HEAD@&lt;span style="color:#f92672">{&lt;/span>1&lt;span style="color:#f92672">}&lt;/span>: commit &lt;span style="color:#f92672">(&lt;/span>amend&lt;span style="color:#f92672">)&lt;/span>: Adjust script to nice print
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>...&lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2b66f53 HEAD@&lt;span style="color:#f92672">{&lt;/span>7&lt;span style="color:#f92672">}&lt;/span>: commit: Introduce git log
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>8835c33 HEAD@&lt;span style="color:#f92672">{&lt;/span>8&lt;span style="color:#f92672">}&lt;/span>: commit: Fix of formating of headings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2f0f500 HEAD@&lt;span style="color:#f92672">{&lt;/span>9&lt;span style="color:#f92672">}&lt;/span>: commit: Improve order of comments on workflow
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>249dc14 HEAD@&lt;span style="color:#f92672">{&lt;/span>10&lt;span style="color:#f92672">}&lt;/span>: commit &lt;span style="color:#f92672">(&lt;/span>amend&lt;span style="color:#f92672">)&lt;/span>: Explain git diff
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>f5ba1d3 HEAD@&lt;span style="color:#f92672">{&lt;/span>11&lt;span style="color:#f92672">}&lt;/span>: commit: Explain git diff
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>0a2ce1c HEAD@&lt;span style="color:#f92672">{&lt;/span>12&lt;span style="color:#f92672">}&lt;/span>: commit: Remove trailing whitspaces
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>735511f HEAD@&lt;span style="color:#f92672">{&lt;/span>13&lt;span style="color:#f92672">}&lt;/span>: commit &lt;span style="color:#f92672">(&lt;/span>initial&lt;span style="color:#f92672">)&lt;/span>: Initial commit
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ git reset HEAD@&lt;span style="color:#f92672">{&lt;/span>index_where_everything_was_fine&lt;span style="color:#f92672">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ git reset --hard origin/master
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;em>Warning&lt;/em>: This is dangerous as you delete all changes that you made locally.
Do NOT use this command unless you are absolutely sure what you are doing.&lt;/p>
&lt;h1 id="usb-sticks-as-remote">USB sticks as remote&lt;/h1>
&lt;p>Sometimes (e.g. you are working on a machine that is not connected to the internet) it can be
helpful to use an USB drive as remote.&lt;/p>
&lt;h2 id="how-to-prepare-the-drive">How to prepare the drive&lt;/h2>
&lt;p>Go to the USB drive&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ cd /media/username/git-stick/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Create a directory for your repository&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ mkdir my-repo
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Initialize the repo&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ cd my-repo
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$ git init --bare
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="how-to-push-to-a-usb-drive">How to push to a USB drive&lt;/h2>
&lt;p>To push to a USB drive you need to add a remote. Go to your local repository and use&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git remote add usb /media/username/git-stick/my-repo/
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Be aware that your stick will have a different name and will be at a different location, depending
on your operating system. You named the remote &amp;ldquo;usb&amp;rdquo; here, of course you can change that name.&lt;/p>
&lt;p>Now push your repo&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git push usb
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To list all repositories just type&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git remote
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>origin
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>usb
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you want to push to the USB drive by default you can use&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ git push -u usb master
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Branch &lt;span style="color:#e6db74">&amp;#39;master&amp;#39;&lt;/span> set up to track remote branch &lt;span style="color:#e6db74">&amp;#39;master&amp;#39;&lt;/span> from &lt;span style="color:#e6db74">&amp;#39;usb&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="what-this-guide-does-not-cover">What this guide does not cover&lt;/h1>
&lt;ul>
&lt;li>Branching
&lt;ul>
&lt;li>Creating branches&lt;/li>
&lt;li>Merging branches (and merge conflicts)&lt;/li>
&lt;li>Remote branches&lt;/li>
&lt;li>Tagging&lt;/li>
&lt;li>Forks&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Usage of Github&lt;/li>
&lt;li>Hooks&lt;/li>
&lt;/ul>
&lt;h1 id="further-reading">Further reading&lt;/h1>
&lt;ul>
&lt;li>Official git website &lt;a href="https://git-scm.com/">Git SCM&lt;/a>: Extensiv documentation&lt;/li>
&lt;li>&lt;a href="https://ohshitgit.com/">Oh shit Git&lt;/a>: Help when you messed up.&lt;/li>
&lt;/ul>
&lt;h1 id="logo">Logo&lt;/h1>
&lt;p>Git Logo by Jason Long&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: "git"
})
&lt;/script></content></entry><entry><title>Why Hyteck</title><link href="https://hyteck.de/post/why-hyteck/" type="application/octet-stream"/><updated>2019-09-16T18:18:10+02:00</updated><id>https://hyteck.de/post/why-hyteck/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;h1 id="tldr">TL;DR&lt;/h1>
&lt;ul>
&lt;li>&lt;em>Hy&lt;/em> - Water&lt;/li>
&lt;li>&lt;em>Teck&lt;/em> - Castle nearby&lt;/li>
&lt;li>Summary: Bad name pun&lt;/li>
&lt;/ul>
&lt;h1 id="why-the-name-hyteck">Why the name Hyteck?&lt;/h1>
&lt;p>I am not very good at finding names I really like.
My nickname changed several times, I was never able to stick to one.
As hard as finding a nickname was finding a domainname for me.&lt;/p>
&lt;p>Nevertheless I really started to like the name Hyteck. The idea came during
a hackathon in Tübingen where two friends and I worked as a team on a project to help
elderly adults drinking. We the decided on the teamname HyHack (Hy for Hydration).&lt;/p>
&lt;p>What we first did was not so High - Tech (I will post about this sometime), yet we called it that.&lt;/p>
&lt;p>I later chenged the Tech to Teck which is a local castle (my hometown is called Kirchheim under Teck) that in the german
pronounciation is a Homophone of Tech.&lt;/p>
&lt;p>The Hy stayed as I always had a close connection to water.&lt;/p>
&lt;p>I feel like I overexplained this a bit, so poor you if you read till the end.&lt;/p>
&lt;p>However, you earned a
&lt;script>
function createCookie(name, value, days){
var expires;
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toGMTString();
}
else {
expires = "";
}
document.cookie = name + "=" + value + expires + "; path=/; samesite=strict;";
}
&lt;/script>
&lt;button onclick="createCookie('achievement', 'TLDR_restistant', 365)">cookie&lt;/button>
!&lt;/p></content></entry><entry><title>ILMO</title><link href="https://hyteck.de/post/ilmo/" type="application/octet-stream"/><updated>2019-09-15T22:00:55+02:00</updated><id>https://hyteck.de/post/ilmo/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;h1 id="ilmo">ILMO&lt;/h1>
&lt;p>is my first bigger FOSS-project. It aims at managing small libraries as found in a department of a university. You can finde the project on &lt;a href="https://github.com/GerJuli/ILMO">Github&lt;/a>.&lt;/p>
&lt;h2 id="history">History:&lt;/h2>
&lt;p>I started to create ILMO for the (rather small) library in the study rooms of mediacal engineering.
It contains about 300 books, 120 labcoats and safty glasses and some more stuff.
As I was in charge of the lends I started to get annoyed by our paper-based solution.
Especially when a bunch of people was trying to lend things it took to much time. Also reminding people to return stuff was unnecessary complicated.
Therfore I started to programm this library management tool from scratch. I only reused some basic database and session functions.
The project is still a field of learning how to do things for me.
Nevertheless after about half a year of development and many tests the system went live in Winter 2018.
Since then I fill the bugtracker and slowly improve workflows and add functions.&lt;/p>
&lt;h2 id="license">License&lt;/h2>
&lt;p>Of course the program is open-source and licensed under GPLv3.&lt;/p>
&lt;h2 id="demo">Demo&lt;/h2>
&lt;p>Right now there is no public available demo but if you are interested I will lead you the way to my test server.&lt;/p>
&lt;h2 id="using-ilmo">Using ILMO&lt;/h2>
&lt;p>You know someone who could use somthing like this? I would love to see this software in more libraries.
Right now the programm is optimized to the needs of our student union but I would be happy to work more on this.&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: "ILMO"
})
&lt;/script></content></entry><entry><title>Hello World</title><link href="https://hyteck.de/post/hello-world/" type="application/octet-stream"/><updated>2019-09-14T16:35:58+02:00</updated><id>https://hyteck.de/post/hello-world/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;h1 id="hello-world">Hello world&lt;/h1>
&lt;p>Welcome to my new blog! I don&amp;rsquo;t know what this will all be about - but I will sort this out.
As a start I decided to blog in English, but I can imagine that there will be a mix between English and German in the future.&lt;/p>
&lt;p>The blog is created with the static site generator HUGO and the theme &lt;a href="https://themes.gohugo.io/hugo-nederburg-theme/">Nederburg&lt;/a>.&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: "hello-world"
})
&lt;/script></content></entry><entry><title>About</title><link href="https://hyteck.de/about/" type="application/octet-stream"/><updated>2019-08-20T19:56:10+02:00</updated><id>https://hyteck.de/about/</id><author><name>Julian-Samuel Gebühr</name></author><content type="html"> &lt;h1 id="hyteck">Hyteck&lt;/h1>
&lt;p>Hyteck is a company for online services that I founded. It provides smaller SaaS contracts, e.g. for Hosting ILMO.
Currently available are the &lt;a href="https://hyteck.de/services/" title="Services">services listed here&lt;/a>&lt;/p>
&lt;h1 id="about-me">About me&lt;/h1>
&lt;p>I work at &lt;a href="https://www.dkms.de/">DKMS&lt;/a>, a nonprofit that fights blood cancer by registering potential blood stem cell
donors and raising awareness and funds. My role is &amp;ldquo;Business Analyst&amp;rdquo; in our Salesforce team. That means I
spend my day trying to figure out the business departments need, sketching solutions and translating between product and
business teams.&lt;/p>
&lt;p>After work, I spend my time with programming, activism and my pet rats.&lt;/p>
&lt;p>&lt;strong>My background&lt;/strong>&lt;/p>
&lt;p>After finishing school, I studied Medical Engineering in a joint course at University Stuttgart and University Tübingen.&lt;/p>
&lt;p>In March 2020 I finished my bachelor thesis &lt;em>&amp;ldquo;Real-time EEG analysis - Phase dependent effects of TMS on MEP&amp;rdquo;&lt;/em> at the
Institute for Neuromodulation and Neurotechnology in the University Hospital Tübingen led by Prof. Gharabaghi. After
that I was working there as a researcher.&lt;/p>
&lt;p>In November 2020 I started studying Medical Informatics Tübingen and finished in April 2024 with my master thesis &lt;em>&amp;quot;
Development and Validation of a Software Platform for Classification and Correction of Pathological Movement in Daily
Activities by Multi-modal Sensor Analysis&amp;quot;&lt;/em>. This work focused on helping Ataxia and Parkinson&amp;rsquo;s disease as part of a
larger project in the Section for Computational Sensomotorics at the Hertie Institute for Clinical Brain Research (HIH).
My advisor for this work was Winfried Ilg, and it was examined by Prof. Dr. habil. Michael Menth and Prof. Dr. Martin
Giese.&lt;/p>
&lt;h1 id="open-source-work">Open-source work&lt;/h1>
&lt;p>My work on various Open-Source projects involves&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Project&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;a href="https://notfellchen.org">Notfellchen&lt;/a>&lt;/td>
&lt;td>An app for helping fancy rats get adopted from rescues&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://hyteck.de/post/ilmo/">ILMO&lt;/a>&lt;/td>
&lt;td>A library management tool, available as SaaS&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://github.com/mother-of-all-self-hosting/mash-playbook">mash-playbook&lt;/a>&lt;/td>
&lt;td>An Ansible playbook which helps you host a large catalog of FOSS services as Docker containers on your own server&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;a href="https://github.com/spantaleev/matrix-docker-ansible-deploy">https://github.com/spantaleev/matrix-docker-ansible-deploy&lt;/a>&lt;/td>
&lt;td>Matrix (An open network for secure, decentralized communication) server setup using Ansible and Docker&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>and many more you can find on &lt;a href="https://github.com/moan0s">GitHub&lt;/a>, &lt;a href="https://codeberg.org/moanos/">Codeberg&lt;/a>.
or &lt;a href="https://git.hyteck.de/">my own Gitea server&lt;/a>.&lt;/p>
&lt;h1 id="commercial-services">Commercial Services&lt;/h1>
&lt;p>I offer to host some Software-as-a-Service. This is especially true for [ILMO], a library management solution I
developed. Commercial support for software I maintain is also available, as well bespoke software. Be aware that I have
a fulltime job, and I&amp;rsquo;m limited in the time I can make for your projects.&lt;/p>
&lt;p>If you are a non-profit organization, I can offer reduced pricing. If you want to see what&amp;rsquo;s possible, have a look at
the documentation of the &lt;a href="https://doku.queereszentrumtuebingen.de/">Queer Center Tübingen&lt;/a> where I set up&lt;/p>
&lt;ul>
&lt;li>a chat server (Matrix)&lt;/li>
&lt;li>a cloud collaboration platform Nextcloud)&lt;/li>
&lt;li>a SocialMediaServer (GoToSocial)&lt;/li>
&lt;li>a shared password manager (Vaultwarden)&lt;/li>
&lt;li>a automatically embedded calendar on the Website&lt;/li>
&lt;/ul>
&lt;p>and all is connected via SingleSignOn (Authentik) and backed up by BorgBackup.&lt;/p>
&lt;p>I also host(ed) some (semi-)public services including BigBlueButton, Matrix, Nextcloud, Funkwhale and Cryptpad. All
current active systems &lt;a href="https://hyteck.de/services/" title="Services">can be found in a list&lt;/a>&lt;/p>
&lt;h2 id="technologies-i-like-to-use">Technologies I like to use&lt;/h2>
&lt;ul>
&lt;li>My favourite framework is &lt;strong>Django&lt;/strong>, powered by Python. It makes development incredibly efficient and enjoyable.&lt;/li>
&lt;li>I have extensive experience in &lt;strong>Ansible&lt;/strong> and use it to deploy nearly all my services&lt;/li>
&lt;li>I use JavaScript sometimes and even dabbled a bit into React&lt;/li>
&lt;li>Coding started with Java, and PHP for me, however my PHP is a bit rusty now, while I regularly used Java during my
studies.&lt;/li>
&lt;li>For programming of real time applications, mostly for medical devices, I learned *
&lt;em>&lt;a href="https://en.wikipedia.org/wiki/Structured_text">Structured Text&lt;/a>&lt;/em>* (a programming language based on pascal focused
on programming &lt;a href="https://en.wikipedia.org/wiki/Programmable_logic_controller">PLCs&lt;/a>) and &lt;strong>C&lt;/strong>. I don&amp;rsquo;t enjoy C that
much, but I&amp;rsquo;m proud of what I managed to do anyway.&lt;/li>
&lt;li>More recently I started learning &lt;strong>Rust&lt;/strong> and implemented a smaller backend service using axum.&lt;/li>
&lt;/ul>
&lt;h1 id="contact">Contact&lt;/h1>
&lt;ul>
&lt;li>E-Mail: &lt;a href="mailto:julian-samuel@gebuehr.net">julian-samuel@gebuehr.net&lt;/a> &lt;a href="https://hyteck.de/julian-samuel@gebuehr.net.pub.asc">Public key&lt;/a>&lt;/li>
&lt;li>Matrix: @moanos:hyteck.de (Deprecated: @moanos:matrix.org)&lt;/li>
&lt;li>Mastodon: @moanos@chaos.social&lt;/li>
&lt;li>Twitter: &lt;a href="mailto:j-s.gebuehr@twitter.com">j-s.gebuehr@twitter.com&lt;/a>&lt;/li>
&lt;li>Signal, Whatsapp, Phone: Available, use one of the other options to get my phone number&lt;/li>
&lt;li>XMPP: &lt;a href="mailto:moanos@anoxinon.me">moanos@anoxinon.me&lt;/a> (Inactive)&lt;/li>
&lt;li>Threema: 2A724TYR (Inactive)&lt;/li>
&lt;/ul></content></entry></feed>