<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tim Bachmann</title>
    <description>The latest articles on DEV Community by Tim Bachmann (@tiim).</description>
    <link>https://dev.to/tiim</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F129680%2Fcebc60dd-f034-4b70-93e6-52bf1c2828d9.jpeg</url>
      <title>DEV Community: Tim Bachmann</title>
      <link>https://dev.to/tiim</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tiim"/>
    <language>en</language>
    <item>
      <title>Weechat Notifications with ntfy.sh</title>
      <dc:creator>Tim Bachmann</dc:creator>
      <pubDate>Tue, 28 Mar 2023 10:05:19 +0000</pubDate>
      <link>https://dev.to/tiim/weechat-notifications-with-ntfysh-18no</link>
      <guid>https://dev.to/tiim/weechat-notifications-with-ntfysh-18no</guid>
      <description>&lt;p&gt;In one of my last blog posts I &lt;a href="https://tiim.ch/blog/2023-01-15-weechat-docker"&gt;set up WeeChat in docker&lt;/a&gt;, which works mostly pretty great for me so far. Although, it started to bug me that I felt the need to regularly check IRC in case I missed someone potentially tagging or private-messaging me. While looking around at how I could be notified on mentions and private messages, I found the &lt;a href="https://weechat.org/files/doc/stable/weechat_user.en.html#trigger"&gt;trigger plugin&lt;/a&gt;. A powerful plugin that comes pre-installed on WeeChat. It lets the user specify a WeeChat command that will be executed when a specific event occurs. This plugin is probably powerful enough to build a small IRC bot, directly in WeeChat.&lt;/p&gt;

&lt;p&gt;Also, I recently found the web service &lt;a href="https://ntfy.sh"&gt;ntfy.sh&lt;/a&gt;. It sends push notifications whenever you send an HTTP post request to a certain URL. I already have ntfy.sh installed on my android phone, and I also found a minimal and lightweight &lt;a href="https://github.com/lucas-bortoli/ntfysh-windows"&gt;desktop client&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I managed to set a WeeChat trigger up that fires every time I get mentioned (highlighted in WeeChat terminology), and a trigger that fires every time I get a private message. Both of those triggers execute the &lt;code&gt;/exec&lt;/code&gt; command which runs an arbitrary shell command. The exec command runs the &lt;code&gt;wget&lt;/code&gt; program to send a post request to the ntfy.sh server, which in turn sends a notification to all apps that subscribe to the same URL as the post request was sent. I would usually use the curl program for this instead of wget, but the docker default docker image doesn't contain a curl install.&lt;/p&gt;

&lt;p&gt;Here you can see the two &lt;code&gt;/trigger&lt;/code&gt; commands:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;trigger on mention&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/trigger addreplace notify_highlight print '' '${tg_highlight}' '/.*/${weechat.look.nick_prefix}${tg_prefix_nocolor}${weechat.look.nick_suffix} ${tg_message_nocolor}/' '/exec -norc -nosw -bg wget -O- --post-data "${tg_message}" "- -header=Title: New highlight: ${buffer.full_name}" https://ntfy.sh/my_ntfy_topic_1234'

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;trigger on private message&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/trigger addreplace notify_privmsg print '' '${tg_tag_notify} == private &amp;amp;&amp;amp; ${buffer.notify} &amp;gt; 0' '/.*/${weechat.look.nick_prefix}${tg_prefix_nocolor}${weechat.look.nick_suffix} ${tg_message_nocolor}/' '/exec -norc -nosw -bg wget -O- --post-data "${tg_message}" "--header=Title: New private message: ${buffer.full_name}" https://ntfy.sh/my_ntfy_topic_1234'

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The trigger commands in detail
&lt;/h2&gt;

&lt;p&gt;In case you don't just want to copy and paste some random command from the internet into your WeeChat (which you shouldn't do anyway), I will try to explain the trigger command that fires when you get mentioned in a message:&lt;/p&gt;

&lt;p&gt;Let's first look at the trigger command itself:&lt;code&gt;/trigger addreplace &amp;lt;name&amp;gt; &amp;lt;hook&amp;gt; &amp;lt;argument&amp;gt; &amp;lt;condition&amp;gt; &amp;lt;variable-replace&amp;gt; &amp;lt;command&amp;gt;&lt;/code&gt;We call the &lt;code&gt;/trigger&lt;/code&gt; command with the &lt;code&gt;addreplace&lt;/code&gt; subcommand. This subcommand will either register a new trigger or replace it if one with the same name already exists.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; - This argument is self-explanatory, the name of the trigger. In our case I called it &lt;code&gt;notify_highlight&lt;/code&gt;, but you could call it whatever you want.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hook&lt;/code&gt; - This argument specifies which hook or event the trigger should listen for. WeeChat is built as an event-driven platform, so pretty much anything from mouse movements to IRC messages are handled via events. In this case, we want to trigger on the &lt;code&gt;print&lt;/code&gt; event, which is fired every time a new message gets received from IRC.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;argument&lt;/code&gt; - The argument is needed for some hooks, but not for the &lt;code&gt;print&lt;/code&gt; hook, so we are going to ignore that one for now and just set it to an empty string &lt;code&gt;''&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;condition&lt;/code&gt; - The condition must evaluate to &lt;code&gt;true&lt;/code&gt; for the trigger to fire. This is helpful because the &lt;code&gt;print&lt;/code&gt; trigger fires for every new message, but we only want to be notified when the new message mentions our nick. The condition for this is &lt;code&gt;${tg_highlight}&lt;/code&gt;. You can find the list of variables that you can access with the command &lt;code&gt;/trigger monitor&lt;/code&gt;, which prints all variables for every trigger that gets executed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;variable-replace&lt;/code&gt; - This took me a while to understand. This command is used to manipulate data and save it to a variable. The syntax is inspired by the sed command. Explaining it fully is out of the scope of this blog post, but you can take a look at the &lt;a href="https://weechat.org/files/doc/devel/weechat_user.en.html#trigger_regex"&gt;docs&lt;/a&gt;. In our example, we replace the whole content of the variable &lt;code&gt;tg_message&lt;/code&gt; with the format string &lt;code&gt;${weechat.look.nick_prefix}${tg_prefix_nocolor}${weechat.look.nick_suffix} ${tg_message_nocolor}&lt;/code&gt; which results in a sting like &lt;code&gt;&amp;lt;tiim&amp;gt; Hello world!&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;command&lt;/code&gt; - The last argument is the command that gets executed whenever this trigger fires. In our case, we use the &lt;code&gt;/execute&lt;/code&gt; command, which starts the wget command which in turn sends a post request to ntfy.sh. Make sure you set the ntfy topic (the part after &lt;code&gt;https://ntfy.sh/&lt;/code&gt;) to something private and long enough so that nobody else is going to guess it by accident.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't forget to subscribe to the ntfy topic on your phone or whatever device you want to receive the notification on.&lt;/p&gt;

&lt;p&gt;The possibilities with the trigger plugin are endless, I hope this inspires you to build your own customizations using weechat.&lt;/p&gt;

</description>
      <category>irc</category>
      <category>ntfysh</category>
      <category>weechat</category>
      <category>wget</category>
    </item>
    <item>
      <title>Fix Network Connectivity in WSL2 with Cisco AnyConnect VPN</title>
      <dc:creator>Tim Bachmann</dc:creator>
      <pubDate>Wed, 15 Mar 2023 15:22:04 +0000</pubDate>
      <link>https://dev.to/tiim/fix-network-connectivity-in-wsl2-with-cisco-anyconnect-vpn-4bhk</link>
      <guid>https://dev.to/tiim/fix-network-connectivity-in-wsl2-with-cisco-anyconnect-vpn-4bhk</guid>
      <description>&lt;p&gt;I recently ran into the problem that when the Cisco AnyConnect VPN is connected, the network connectivity inside of WSL2 stops working. I found a bunch of solutions online for it: most just focus on the fact that the VPN DNS settings are not applied inside WSL2 and therefore no domain names can be resolved. I additionally had the issue that the WSL2 network interface somehow gets disconnected when the VPN starts.&lt;/p&gt;

&lt;p&gt;I will show you how I fixed this problem for me and explain what the commands I used do. This post is mostly for my reference, but I hope it helps anyone else as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding out what your problem is
&lt;/h2&gt;

&lt;p&gt;Let's check first if we have internet access inside WSL2. For this run the ping command with an IP address as a destination:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ping 8.8.8.8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you get something like this as the output, your internet connection is fine, and it's just the DNS nameserver addresses that are misconfigured, you can jump forward to Solution 2.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;PING 8.8.8.8 &lt;span class="o"&gt;(&lt;/span&gt;8.8.8.8&lt;span class="o"&gt;)&lt;/span&gt; 56&lt;span class="o"&gt;(&lt;/span&gt;84&lt;span class="o"&gt;)&lt;/span&gt; bytes of data.
64 bytes from 8.8.8.8: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;108 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4.53 ms
64 bytes from 8.8.8.8: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;108 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.94 ms
64 bytes from 8.8.8.8: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;108 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.97 ms
64 bytes from 8.8.8.8: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;108 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.78 ms
64 bytes from 8.8.8.8: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;108 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.77 ms
64 bytes from 8.8.8.8: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;108 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.76 ms
64 bytes from 8.8.8.8: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;108 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.81 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't get any responses from the ping (i.e. no more output after the &lt;code&gt;PING 8.8.8.8 (8.8.8.8) ...&lt;/code&gt; line), you need to configure the WSL and the VPN network adapter metric. Go to Solution 1.&lt;/p&gt;

&lt;p&gt;To check if the DNS is working, we can again use the ping command, this time with a domain name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ping google.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you get responses, the DNS and your internet connection are working! If not go to Section 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution 1: Fixing the Network Adapter
&lt;/h2&gt;

&lt;p&gt;Run the following two commands in PowerShell as administrator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-NetAdapter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InterfaceDescription&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-Match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cisco AnyConnect"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Set-NetIPInterface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-InterfaceMetric&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;4000&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Get-NetIPInterface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-InterfaceAlias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vEthernet (WSL)"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Set-NetIPInterface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-InterfaceMetric&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me explain what those two commands do. Both follow the same pattern of listing all network adapters, selecting a specific adapter from the list and setting its "metric".&lt;/p&gt;

&lt;p&gt;You can imagine an adapter as a virtual network port on the back of your pc or laptop. But instead of sending packets through the wire, the driver for a specific port can do whatever it wants with those packets, in the case of a VPN, the packets get encrypted and forwarded to the internet via another adapter.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://learn.microsoft.com/en-us/windows-server/networking/technologies/network-subsystem/net-sub-interface-metric"&gt;InterfaceMetric&lt;/a&gt; is a value associated with each adapter that determines the order of those adapters. This allows windows to determine which adapter to prefer over another one.&lt;/p&gt;

&lt;p&gt;By setting the interface metric of the Cisco adapter to 4000 and the metric of the WSL adapter to one, we allow the traffic from WSL to flow through the Cisco adapter. To be honest I do not exactly understand why this works but it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution 2: Registering the VPN DNS inside of WSL
&lt;/h2&gt;

&lt;p&gt;Setting the DNS servers is, unfortunately, a little bit more involved than just running two commands, we need to edit the files &lt;code&gt;/etc/wsl.conf&lt;/code&gt; and &lt;code&gt;/etc/resolv.conf&lt;/code&gt;, and restart wsl in between. Let's get to it:&lt;/p&gt;

&lt;p&gt;Edit the file &lt;code&gt;/etc/wsl.conf&lt;/code&gt; inside of WSL2 using a text editor. I suggest doing this through the terminal since you need root permissions to do that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/wsl.conf
&lt;span class="c"&gt;# feel free to use another editor such as vim or emacs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most likely this file does not exist yet, otherwise, I suggest you create a backup of the original file to preserve the settings.&lt;/p&gt;

&lt;p&gt;Add the following config settings into the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[network]&lt;/span&gt;
&lt;span class="py"&gt;generateResolvConf&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will instruct WSL to not override the &lt;code&gt;/etc/resolv.conf&lt;/code&gt; file on every start-up. Save the file and restart WSL with the following command so that the changed config takes effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wsl.exe &lt;span class="nt"&gt;--shutdown&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now open a PowerShell terminal and list all network adapters with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ipconfig /all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find the Cisco AnyConnect adapter and copy the IP addresses in the DNS-Server field. We will need those IPs in the next step.&lt;/p&gt;

&lt;p&gt;Start WSL again and edit the &lt;code&gt;/etc/resolv.conf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/resolv.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most likely there is already something in this file, you can discard it. When undoing the changes, WSL will automatically regenerate this file anyway, so you don't need to back it up.&lt;/p&gt;

&lt;p&gt;Delete all the contents and enter the IP addresses you noted down in the last step in the following format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nameserver xxx.xxx.xxx.xxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put each address on a new line, preceded by the string &lt;code&gt;nameserver&lt;/code&gt;. Save the file and restart WSL with the same command as above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wsl.exe &lt;span class="nt"&gt;--shutdown&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now open up WSL for the last time and set the immutable flag for the &lt;code&gt;/etc/resolv.conf&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chattr +i /etc/resolv.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for the last time shut down WSL. Your DNS should now be working fine!&lt;/p&gt;

&lt;h2&gt;
  
  
  Undoing those changes
&lt;/h2&gt;

&lt;p&gt;I did not have a need to undo the steps for &lt;code&gt;Solution 1&lt;/code&gt;, and I'm pretty sure the metric resets after each system reboot anyway so there is not much to do.&lt;/p&gt;

&lt;p&gt;To get DNS working again when not connected to the VPN run the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;chattr &lt;span class="nt"&gt;-i&lt;/span&gt; /etc/resolv.conf
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; /etc/resolv.conf
&lt;span class="nb"&gt;sudo rm&lt;/span&gt; /etc/wsl.conf
wsl.exe &lt;span class="nt"&gt;--shutdown&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will first clear the immutable flag off &lt;code&gt;/etc/resolv.conf&lt;/code&gt;, and delete it. Next, it will delete &lt;code&gt;/etc/wsl.conf&lt;/code&gt; if you have a backup of a previous &lt;code&gt;wsl.conf&lt;/code&gt; file, you can replace it with that. At last, we shutdown WSL again for the changes to take effect.&lt;/p&gt;

&lt;p&gt;Unfortunately, this is quite a procedure to get a VPN to work with WSL2, but I'm hopeful that this will soon not be necessairy anymore.&lt;/p&gt;

</description>
      <category>dns</category>
      <category>vpn</category>
      <category>wsl</category>
    </item>
    <item>
      <title>"no such file or directory" after enabling CGO in Docker</title>
      <dc:creator>Tim Bachmann</dc:creator>
      <pubDate>Tue, 24 Jan 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/tiim/no-such-file-or-directory-after-enabling-cgo-in-docker-31mj</link>
      <guid>https://dev.to/tiim/no-such-file-or-directory-after-enabling-cgo-in-docker-31mj</guid>
      <description>&lt;p&gt;Today I ran into the an error trying to deploy my go app in docker, where the container refused to start with the extremely helpful message &lt;code&gt;exec /app/indiego: no such file or directory&lt;/code&gt;. I had removed the &lt;code&gt;CGO_ENABLE=0&lt;/code&gt; variable from the Dockerfile, because I needed to enable cgo for a library. What I found out was that when enabling cgo, the resulting binary is not statically linked anymore and now depends on libc or musl. Since the &lt;code&gt;scratch&lt;/code&gt; image does not contain literally anything, the binary can't find the libraries and crashes with the aforementioned error.&lt;/p&gt;

&lt;p&gt;To include libc into the container, I simply changed the base image from &lt;code&gt;scratch&lt;/code&gt; to &lt;code&gt;alpine&lt;/code&gt;, which includes libc. This makes the image slightly larger but this seemed way easier than trying to include libc directly.&lt;/p&gt;

&lt;p&gt;As a bonus I got to delete the &lt;code&gt;/usr/share/zoneinfo&lt;/code&gt; and &lt;code&gt;ca-certificates.crt&lt;/code&gt; files, and rely on those provided by alpine.&lt;/p&gt;

&lt;p&gt;You can see the commit to IndieGo &lt;a href="https://github.com/Tiim/IndieGo/commit/63968814de7e39f295386bf398b583aa8bf0411c" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cgo</category>
      <category>docker</category>
      <category>go</category>
    </item>
    <item>
      <title>Running the WeeChat IRC Client on a VPS in Docker</title>
      <dc:creator>Tim Bachmann</dc:creator>
      <pubDate>Sun, 15 Jan 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/tiim/running-the-weechat-irc-client-on-a-vps-in-docker-nja</link>
      <guid>https://dev.to/tiim/running-the-weechat-irc-client-on-a-vps-in-docker-nja</guid>
      <description>&lt;p&gt;I have recently gotten interested in IRC for some reason and have been looking for a client that I like. I have used &lt;a href="https://hexchat.github.io/"&gt;HexChat&lt;/a&gt; in the past, but I don't really fancy having yet another communications program running on my PC next to discord, zoom, telegram and thunderbird. I have been trying to use the IRC feature of thunderbird, but even though it works, it feels very much like an afterthought.&lt;/p&gt;

&lt;p&gt;The one client I have seen mentioned a lot is &lt;a href="https://weechat.org/"&gt;WeeChat&lt;/a&gt; (not to be confused with WeChat, the Chinese instant messenger). WeeChat runs in the terminal as a &lt;a href="https://en.wikipedia.org/wiki/Text-based_user_interface"&gt;TUI&lt;/a&gt; and after a while of getting used to (and after enabling 'mouse mode') it seems intuitive enough.&lt;/p&gt;

&lt;p&gt;The nice thing about WeeChat running not as a graphical application, is that it makes it possible to run on a server and access it from anywhere over ssh.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Except on mobile devices, but weechat has mobile apps that can connect to it directly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since I pretty much host all my selfhosted software in docker on a VPS, I was looking if someone already published a docker image for WeeChat. There is a bunch of them, but only &lt;a href="https://hub.docker.com/r/weechat/weechat"&gt;weechat/weechat&lt;/a&gt; (the official image) is still updated regularly. The docker hub page does not have any documentation, but I managed to find it in the &lt;a href="https://github.com/weechat/weechat-container"&gt;weechat/weechat-container&lt;/a&gt; github repo.&lt;/p&gt;

&lt;p&gt;As it says in the readme on github, you can start the container with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -it weechat/weechat

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which will run weechat directly in the foreground.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't skip the &lt;code&gt;-it&lt;/code&gt; command line flags. The &lt;code&gt;-i&lt;/code&gt; or &lt;code&gt;--interactive&lt;/code&gt; keeps stdin open, which is required to send input to weechat. Weechat also closes immediately if the stdin gets closed, which took me a while to figure out. The &lt;code&gt;-t&lt;/code&gt; or &lt;code&gt;--tty&lt;/code&gt; flag is required to provide a fake tty to the container. I don't really understand what that means but without this you won't see the user interface of weechat.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Running in the foreground is not really that helpful if we want to run weechat on a server, so we need to detach (let it run in the background) from the container with the &lt;code&gt;-d&lt;/code&gt; or &lt;code&gt;--detach&lt;/code&gt; flag. It also helps to specify a name for the container with the &lt;code&gt;--name &amp;lt;name&amp;gt;&lt;/code&gt; argument, so we can quickly find the container again later. The docker command now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -it -d --name weechat weechat/weechat

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we run this command, we will notice that weechat is running in the background. To access it we can run &lt;code&gt;docker attach weechat&lt;/code&gt;. To detach from weechat without exiting the container, we can press &lt;code&gt;CTRL-p CTRL-q&lt;/code&gt; as described in the &lt;a href="https://docs.docker.com/engine/reference/commandline/attach/#description"&gt;docker attach reference&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I noticed that there are two versions of the weechat image: a debian version and an alpine linux version. Generally the Alpine Linux versions of containers are smaller than the Debian versions, so I decided to use the alpine version: &lt;code&gt;weechat/weechat:latest-alpine&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With this we are practically done, but if we ever remove and restart the container, all of the chat logs and customisations to weechat will be gone. To prevent this we need to add the config and log files to a volume.&lt;/p&gt;

&lt;p&gt;I generally use the folder &lt;code&gt;~/docker/(service)&lt;/code&gt; to point my docker volumes to, so I have a convenient place to inspect, modify and back up the data.&lt;/p&gt;

&lt;p&gt;Let's create the folder and add the volume to the docker container. I also added the &lt;code&gt;--restart unless-stopped&lt;/code&gt; flag to make sure the container gets restarted if it either exits for some reason of if docker restarts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p ~/docker/weechat/data
mkdir -p ~/docker/weechat/config

docker run -it -d --restart unless-stopped \
    -v "~/docker/weechat/data:/home/user/.weechat" \
    -v "~/docker/weechat/config:/home/user/.config/weechat" \
    --name weechat weechat/weechat:latest-alpine`

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this command on the server is all we need to have weechat running in docker.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But how do I quickly connect to weechat? Do I always have to first ssh into the server and then run docker attach?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes but, as almost always, we can simplify this with a bash script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env bash

HOST=&amp;lt;ssh host&amp;gt;
ssh -t "${HOST}" docker attach weechat

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This bash script starts ssh with the &lt;code&gt;-t&lt;/code&gt; flag which tells ssh that the command is interactive. Copy this script into your &lt;code&gt;~/.local/bin&lt;/code&gt; folder and make it executable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nano ~/.local/bin/weechat.sh
chmod +x weechat.sh

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it! Running &lt;code&gt;weechat.sh&lt;/code&gt; will open an ssh session to your server and attach to the weechat container. Happy Chatting!&lt;/p&gt;

&lt;p&gt;If you liked this post, consider subscribing to my blog via &lt;a href="https://tiim.ch/blog/rss.xml"&gt;RSS&lt;/a&gt;, or on &lt;a href="https://tiim.ch/follow"&gt;social media&lt;/a&gt;. If you have any questions, feel free to &lt;a href="https://tiim.ch/contact"&gt;contact me&lt;/a&gt;. I also usually hang out in &lt;a href="https://web.libera.chat/##tiim"&gt;&lt;code&gt;##tiim&lt;/code&gt; on irc.libera.chat&lt;/a&gt;. My name on IRC is &lt;code&gt;tiim&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>irc</category>
      <category>weechat</category>
    </item>
    <item>
      <title>Hosting Images with Storj and Cloudflare</title>
      <dc:creator>Tim Bachmann</dc:creator>
      <pubDate>Sat, 03 Dec 2022 13:37:33 +0000</pubDate>
      <link>https://dev.to/tiim/hosting-images-with-storj-and-cloudflare-4pel</link>
      <guid>https://dev.to/tiim/hosting-images-with-storj-and-cloudflare-4pel</guid>
      <description>&lt;p&gt;For a while now I have been looking for a way to put images on my website. At first I just embedded them in the website github repository, but this just doesn't feel right. Putting one or two image assets in a codebase is one thing, putting an ever growing list of images in there feels icky to me. For this reason I put the last few cover images of my blog posts on the imgur platform. This is slightly cleaner from a git standpoint but now i have to trust imgur to keep serving these images. Additionally, as I recently discovered, this seems to be against imgurs &lt;a href="https://imgur.com/tos"&gt;TOS&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[...] Also, don't use Imgur to host image libraries you link to from elsewhere, content for your website, advertising, avatars, or anything else that turns us into your content delivery network.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally when I started &lt;a href="https://tiim.ch/blog/2022-12-indiewebifying-my-website-part-1"&gt;indie-webifying my website&lt;/a&gt;, and was implementing the micropub protocol (which I will blog about at a later time), I decided that it was at the time to host the images on a platform that was meant to do that. I looked at a few storage providers such as cloudinary and S3 based object storage and landed on &lt;a href="https://tiim.ch/blog/2022-12-indiewebifying-my-website-part-1"&gt;Storj.io&lt;/a&gt;, mostly because of the generous free tier, which should suffice for this little blog for quite a while.&lt;/p&gt;

&lt;p&gt;One thing that bothered me slightly was that all storage providers I looked at charge for traffic. It's not the fact that it's an additional expense (if your not in the free tier anymore) that bothers me, but the fact that I don't have any control over how much this will cost me. In all likelihood this will never cost me anything since this blog has not much traffic, but if a post were to go viral (one can dream...), this could result in a surprise bill at the end of the month.&lt;/p&gt;

&lt;p&gt;To help with the traffic costs I decided to try to use the free CDN functionality of Cloudflare to reduce the traffic to Storj. In this blog post I will describe how I did that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is this the right solution for you?
&lt;/h2&gt;

&lt;p&gt;If you are in a similar situation as me, and just want to have somewhere to host your images for a personal website or to share images or screenshots as links while still having control over all your data, this could be a good solution.&lt;/p&gt;

&lt;p&gt;If you want to build a robust image pipeline with resizing and image optimization, or you are building an enterprise website this is probably not the right way. You should take a look at cloudinary or one of the big cloud providers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To use Cloudflare as a CDN, you need to have Cloudflare setup as your DNS host for the domain you want to serve the images from. Even if you just want to use a subdomain like &lt;code&gt;media.example.com&lt;/code&gt;, the whole &lt;code&gt;example.com&lt;/code&gt; domain needs to be on cloudflare. For me this was not much of an issue, I followed the instructions from cloudflare and pointed the nameserver of my domain to cloudflare. Although I did have an issue during the migration, which resulted in my website being down for two hours. But I'm pretty sure this was caused by my previous nameserver provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Storj &amp;amp; Cloudflare
&lt;/h2&gt;

&lt;p&gt;I assume you already have an account at &lt;a href="https://storj.io/"&gt;storj.io&lt;/a&gt;. The next step is creating a bucket for your images. A bucket is just a place for your files and folders to live in storj, just like in any other S3 compatible storage provider. (Actually there are no folders in storj and other S3 services, the folders are just prefixes of the filenames). When creating a bucket, make sure you save the passphrase securely, such as in your password manager. Whenever storj asks you for the passphrase, make sure you don't let storj generate a new one! Every new passphrase will create access to a new bucket.&lt;/p&gt;

&lt;p&gt;The next step is &lt;a href="https://docs.storj.io/dcs/downloads/download-uplink-cli"&gt;installing the uplink cli&lt;/a&gt;. Follow the quick start tutorial to &lt;a href="https://docs.storj.io/dcs/getting-started/quickstart-uplink-cli/uploading-your-first-object"&gt;get an access grant&lt;/a&gt;. Remember to use the same passphrase from above. Now follow the next quickstart tutorial to &lt;a href="https://docs.storj.io/dcs/getting-started/quickstart-uplink-cli/uploading-your-first-object/set-up-uplink-cli"&gt;add the bucket to the uplink cli&lt;/a&gt;. The file &lt;code&gt;accessgrant.txt&lt;/code&gt; in the tutorial only contains the access-grant string that you got from the last step.&lt;/p&gt;

&lt;p&gt;Finally we want to share the bucket so the images can be accessed from the web. For this you can run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uplink share --dns &amp;lt;domain&amp;gt; sj://&amp;lt;bucket&amp;gt;/&amp;lt;prefix&amp;gt; --not-after=none

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;&amp;lt;domain&amp;gt;&lt;/code&gt; with the domain you want to serve the images from. In my case I use &lt;code&gt;media.tiim.ch&lt;/code&gt;. Then replace &lt;code&gt;&amp;lt;bucket&amp;gt;&lt;/code&gt; with the name of your bucket and &lt;code&gt;&amp;lt;prefix&amp;gt;&lt;/code&gt; with the prefix.&lt;/p&gt;

&lt;p&gt;As mentioned above, you can think of a prefix as a folder. If you use for example &lt;code&gt;media-site1&lt;/code&gt; as a prefix, then every file in the "folder" &lt;code&gt;media-site1&lt;/code&gt; will be shared. This means you can use multiple prefixes to serve files for multiple websites in the same bucket.&lt;/p&gt;

&lt;p&gt;You will get the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[...]
=========== DNS INFO =====================================================================
Remember to update the $ORIGIN with your domain name. You may also change the $TTL.
$ORIGIN example.com.
$TTL 3600
media.example.com IN CNAME link.storjshare.io.
txt-media.example.com IN TXT storj-root:mybucket/myprefix
txt-media.example.com IN TXT storj-access:totallyrandomstringofnonsens

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the DNS entries in Cloudflare with the values printed in the last three lines. Make sure you enable the proxy setting when entering the CNAME entry to enable Cloudflares CDN service.&lt;/p&gt;

&lt;p&gt;And that's it. All files you put in the bucket with the correct prefix are now available under your domain! :)&lt;/p&gt;

&lt;p&gt;If this blog post helped you, or you have some issues or thoughts on this, leave a comment via the comment box below or via the comment box on &lt;a href="https://tiim.ch/blog/2022-12-storj-cloudflare-image-hosting"&gt;my website&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>cdn</category>
      <category>cloudflare</category>
      <category>storj</category>
    </item>
    <item>
      <title>IndieWebifying my Website Part 1 - Microformats and Webmentions</title>
      <dc:creator>Tim Bachmann</dc:creator>
      <pubDate>Sat, 12 Nov 2022 11:19:00 +0000</pubDate>
      <link>https://dev.to/tiim/indiewebifying-my-website-part-1-microformats-and-webmentions-c7f</link>
      <guid>https://dev.to/tiim/indiewebifying-my-website-part-1-microformats-and-webmentions-c7f</guid>
      <description>&lt;p&gt;A few weeks ago, I stumbled on one of &lt;a href="https://www.jvt.me/posts/2019/08/21/rsvp-from-your-website/"&gt;Jamie Tanna's blog posts about microformats2&lt;/a&gt; by accident. That is when I first learned about the wonderful world of the &lt;a href="https://indieweb.org/why"&gt;IndieWeb&lt;/a&gt;. It took me a while to read through some of the concepts of the IndieWeb like webmentions, IndieAuth, microformats and all the other standards, but the more I found out about it the more I wanted to play around with it. And what better place to try out new technology than on a personal website?&lt;/p&gt;

&lt;h2&gt;
  
  
  The IndieWeb
&lt;/h2&gt;

&lt;p&gt;I will start with a brief introduction for the uninitiated. If you have already heard about the IndieWeb, feel free to skip to the next section.&lt;/p&gt;

&lt;p&gt;The IndieWeb is a collection of standards, intending to make the web social, without the user giving up ownership of their data. While on social media platforms (or as called in IndieWeb terms: silos) you can easily communicate with others, you are always subject to the whims of those platforms.&lt;/p&gt;

&lt;p&gt;The IndieWeb wants to solve this by defining standards that, once implemented in a website, allow it to communicate with other websites that are also part of the IndieWeb.&lt;/p&gt;

&lt;p&gt;The most important concept of the IndieWeb is, you have control over your data. All of your shared data lives on a domain you control.&lt;/p&gt;

&lt;p&gt;Some of the standards in the IndieWeb include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Microformats2: a way to add structured data to the HTML source code of a website so machines can interpret the data.&lt;/li&gt;
&lt;li&gt;Webmentions: a simple communication protocol between websites. It can be used to show comments, likes, bookmarks and more on one website, while the data stays on another website.&lt;/li&gt;
&lt;li&gt;IndieAuth, an OAuth2-based way to log in using only your domain name.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The implementation on my website
&lt;/h2&gt;

&lt;p&gt;As explained in my earlier post &lt;a href="https://tiim.ch/blog/2022-07-12-first-go-project-commenting-api"&gt;First Go Project: A Jam-stack Commenting API&lt;/a&gt;, my website is a statically built SvelteKit app hosted on GitHub Pages. This means the most important part of the IndieWeb is already implemented: I own this domain and post my content here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making the website machine-readable with Microformats
&lt;/h3&gt;

&lt;p&gt;As mentioned above, the microformats2 standard allows websites to encode data about the page in a machine-readable format. This is accomplished by annotating HTML elements with some predefined class names. For example, the microformat for a blog post, note and other content is called &lt;a href="http://microformats.org/wiki/h-entry"&gt;h-entry&lt;/a&gt;. By adding the &lt;code&gt;h-entry&lt;/code&gt; class to a div, its content is marked as belonging to that post. Children of this div can in turn have other microformat elements such as &lt;code&gt;p-name&lt;/code&gt;, &lt;code&gt;p-author&lt;/code&gt; or &lt;code&gt;dt-published&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;While these CSS classes make the data machine-interpretable, the same data is still available to the user. There is no duplication like for example the meta tags in OpenGraph.&lt;/p&gt;

&lt;p&gt;Since my page is a custom SvelteKit app, it was easy enough to add the CSS classes to the right places. I even took the opportunity to add some more information to the pages, like the author card you see if you scroll to the bottom of this post. (Note: on the bottom of &lt;a href="https://tiim.ch/blog/2022-12-indiewebifying-my-website-part-1"&gt;this post on my website&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Accepting comments and other interactions via Webmentions
&lt;/h3&gt;

&lt;p&gt;The standard I wanted to play around with the most are webmentions. A webmention is a sort of notification sent from one website A to another website B, telling B that A has a page linking to it.&lt;/p&gt;

&lt;p&gt;In the IndieWeb all types of interactions are just web pages. The microformats2 specification for example allows replies, quotes, likes, bookmarks and many other types of interactions. The receiver of the webmention is free to extract any relevant information from the sender page and might display it, for example as a comment.&lt;/p&gt;

&lt;p&gt;Since I already have a &lt;a href="https://github.com/Tiim/go-comment-api"&gt;small custom service&lt;/a&gt; running for the comment section on this site, I decided to add support to it for receiving webmentions. I refactored the comment system quite a bit to make it more modular and extendable, to allow me to add webmentions&lt;/p&gt;

&lt;p&gt;It currently supports all the required and some optional features for receiving webmentions: The first thing it does is validate the mention. A mention is only valid if the source and target URLs are valid and if the page from the source URL links to the target URL. The next step is extracting some microformat content from the source URL and saving it to the database.&lt;br&gt;
I found some things unexpectedly tricky to implement: for example, a repeated webmention with the same source URL should update the previously saved webmention if the link to the target page is still there, but delete the webmention if the link was removed.&lt;/p&gt;

&lt;p&gt;I have tested my webmentions implementation using &lt;a href="https://webmention.rocks"&gt;webmention.rocks&lt;/a&gt;, but I would appreciate it if you left me a mention as well 😃 (to the post on my website, since dev.to does not support webmentions)&lt;/p&gt;

&lt;h3&gt;
  
  
  Publishing short-form content such as replies, likes and bookmarks: A notes post type
&lt;/h3&gt;

&lt;p&gt;The next thing I wanted to add to my website was sending webmentions. But before I implemented that, I wanted a way to publish short content without spamming my blog feed. For this, I created a new post type called &lt;a href="https://dev.to/mf2"&gt;notes&lt;/a&gt;. The list of notes lives on the /mf2 page because I plan to mostly use it to publish notes that contain microformats2 classes such as replies and likes. Another reason I didn't want to make it accessible as the /notes page is that I plan to publish my Zettelkasten notes eventually, but this is a story for another post.&lt;/p&gt;

&lt;p&gt;I also used the opportunity to add an RSS feed for all my posts, pages, projects, and notes: &lt;a href="///full-rss.xml"&gt;full-rss.xml&lt;/a&gt;. I do not recommend you subscribe to it unless you are curious about all changes to the content on my website.&lt;/p&gt;

&lt;h3&gt;
  
  
  Notifying referenced websites: Sending Webmentions
&lt;/h3&gt;

&lt;p&gt;Sending webmentions was easy compared to receiving webmentions:&lt;/p&gt;

&lt;p&gt;On a regular interval (and on page builds), the server loads the full RSS feed and checks what items have a newer timestamp than the last time. It then extracts a list of all URLs from that feed item and loads the list of URLs that it extracted last time. Then a webmention is sent to all the URLs.&lt;/p&gt;

&lt;p&gt;Luckily I did not have to implement any of this myself apart from some glue code to fit it together: I used the library &lt;a href="https://github.com/go-co-op/gocron"&gt;gocron&lt;/a&gt; for scheduling the regular intervals, &lt;a href="https://github.com/mmcdole/gofeed"&gt;gofeed&lt;/a&gt; for parsing the RSS feed and &lt;a href="https://willnorris.com/go/webmention"&gt;webmention&lt;/a&gt; for extracting links and sending webmentions.&lt;/p&gt;

&lt;h3&gt;
  
  
  In the future: IndieAuth
&lt;/h3&gt;

&lt;p&gt;The next thing on my roadmap is implementing IndieAuth. Although not because I have a real use case for it, but because I'm interested in OAuth, the underlying standard, and this seems like a good opportunity to get a deeper understanding of the protocol.&lt;/p&gt;

&lt;p&gt;Although, before I start implementing the next things, I should probably focus on writing blog posts first. There is no use in the most advanced blogging system if I can't be bothered to write anything.&lt;/p&gt;

</description>
      <category>indieweb</category>
      <category>webmentions</category>
      <category>mf2</category>
      <category>go</category>
    </item>
    <item>
      <title>SvelteKit Server-Side Rendering (SSR) with @urql/svelte</title>
      <dc:creator>Tim Bachmann</dc:creator>
      <pubDate>Sat, 15 Oct 2022 20:38:33 +0000</pubDate>
      <link>https://dev.to/tiim/sveltekit-server-side-rendering-ssr-with-urqlsvelte-534k</link>
      <guid>https://dev.to/tiim/sveltekit-server-side-rendering-ssr-with-urqlsvelte-534k</guid>
      <description>&lt;p&gt;In this blog post, I will explain why server-side rendering with the &lt;a href="https://formidable.com/open-source/urql/docs/api/svelte/"&gt;urql&lt;/a&gt; GraphQL library is not as straightforward to do with SvelteKit, and how I solved this in my project anyway.&lt;/p&gt;

&lt;p&gt;Server-side rendering (SSR) is one of the great features of SvelteKit. I will try to keep this blog post short and will therefore not explain what server-side rendering is and why you should take advantage of it &lt;em&gt;(you really should!)&lt;/em&gt;. If you want to know more about SSR you can take a look at this article: &lt;a href="https://towardsdev.com/server-side-rendering-srr-in-javascript-a1b7298f0d04"&gt;A Deep Dive into Server-Side Rendering (SSR) in JavaScript&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background - SSR in SvelteKit
&lt;/h2&gt;

&lt;p&gt;SvelteKit implements SSR by providing a &lt;a href="https://kit.svelte.dev/docs/load"&gt;&lt;code&gt;load&lt;/code&gt; function&lt;/a&gt; for every layout and page component. If a page or layout needs to perform some asynchronous operation, this should be done inside of this load function. SvelteKit executes this function asynchronously on the server side as well as on the client side and the return value of this function is assigned to the &lt;code&gt;data&lt;/code&gt; prop of the associated component. Usually, this asynchronous operation is loading data from an external service, like in the case of this blog post a GraphQL server.&lt;br&gt;
You can of course load data directly in the component, but SvelteKit will not wait for this to complete when doing SSR, and the resulting HTML will not include the loaded data.&lt;/p&gt;
&lt;h2&gt;
  
  
  Background - @urql/svelte
&lt;/h2&gt;

&lt;p&gt;The urql library allows us to easily issue GraphQL queries and mutations. Some of the functionality it has to make our lives easier include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reloading a query when a query variable changes&lt;/li&gt;
&lt;li&gt;Reloading a query after a mutation that touches the same data as the query&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We want to keep these features, even when using urql when doing SSR.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;When implementing SSR in my project, I ran into two problems. I couldn't find any documentation or any articles solving them, so I decided to write down my solutions to those problems in this blog post.&lt;/p&gt;
&lt;h3&gt;
  
  
  Problem 1 - Svelte and urql Reactivity
&lt;/h3&gt;

&lt;p&gt;Let's say we have the following load function, which executes a GraphQL query to load a list of red cars:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/routes/car/+page.js&lt;/span&gt;

&lt;span class="cm"&gt;/** @type {import('./$types').PageLoad} */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;carColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;carsQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;carColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toPromise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;car&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example uses the urql method &lt;code&gt;client.query&lt;/code&gt; to start a query to get us a list of cars with a red colour (The GraphQL query is not shown but the exact query is not important for this example).&lt;br&gt;
The client gets a &lt;a href="https://kit.svelte.dev/docs/load#input-methods-fetch"&gt;special fetch function&lt;/a&gt; from the event which has a few nice properties, like preventing a second network request on the client side if that same request was just issued on the server-side.&lt;/p&gt;

&lt;p&gt;Since the query code is now located in the load function and not in a svelte component, there is no way to easily change the &lt;code&gt;carColor&lt;/code&gt; and have urql automatically reload the query. The only way to change the variable is to set the value as a query parameter and read that from the &lt;code&gt;event&lt;/code&gt; argument. This however means that we have to refresh the whole page just to reload this query.&lt;/p&gt;

&lt;p&gt;The other thing urql does for us, reloading the query when we do a mutation on the same data, will not work with the above code either.&lt;/p&gt;
&lt;h3&gt;
  
  
  The solution: A query in the load function and a query in the component
&lt;/h3&gt;

&lt;p&gt;To fix those two drawbacks we have to add the same query as in the load function to our component code as well. Unfortunately, this means when a user loads the page, it sends a request from the client side, even though the same request got sent from the server side already.&lt;/p&gt;

&lt;p&gt;I created a small wrapper function &lt;code&gt;queryStoreInitialData&lt;/code&gt; that creates the query inside of the component and intelligently switches from the (possibly stale) data from the load function to the new data. Using this wrapper, the page or layout might look as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;script&amp;gt;
  import { queryStoreInitialData } from "@/lib/gql-client"; // The helper function mentioned above
  import { getContextClient } from "@urql/svelte";
  import { carsQuery } from "./query"; // The query

  export let data;

  $: gqlStore = queryStoreInitialData(
    {
      client: getContextClient(),
      query: carsQuery,
    },
    data.cars
  );
  $: cars = $gqlStore?.data?.car;
&amp;lt;/script&amp;gt;

&amp;lt;div&amp;gt;
  &amp;lt;pre&amp;gt;
    {JSON.stringify(cars, null, 2)}
  &amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;The native &lt;code&gt;queryStore&lt;/code&gt; function gets replaced with the wrapper function.&lt;/li&gt;
&lt;li&gt;The initial value of the query is supplied to the wrapper&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unfortunately, we can not return the query result from the load function directly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nx"&gt;toPromise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;toInitialValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This results in the following error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cannot stringify a function (data.events.operation.context.fetch)
Error: Cannot stringify a function (data.events.operation.context.fetch)
    at render_response (file:///app/node_modules/@sveltejs/kit/src/runtime/server/page/render.js:181:20)
    at runMicrotasks (&amp;lt;anonymous&amp;gt;)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async render_page (file:///app/node_modules/@sveltejs/kit/src/runtime/server/page/index.js:276:10)
    at async resolve (file:///app/node_modules/@sveltejs/kit/src/runtime/server/index.js:232:17)
    at async respond (file:///app/node_modules/@sveltejs/kit/src/runtime/server/index.js:284:20)
    at async file:///app/node_modules/@sveltejs/kit/src/exports/vite/dev/index.js:406:22
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is because the query result contains data that is not serializable.&lt;br&gt;
To fix this I created the &lt;code&gt;toInitialValue&lt;/code&gt; function, which deletes all non-serializable elements from the result. The load function now looks like follows;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/routes/car/+page.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createServerClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toInitialValue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/lib/gql-client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cookie&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;carsQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./query&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/** @type {import('./$types').PageServerLoad} */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cars&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nx"&gt;toPromise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;toInitialValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Problem 2 - Authentication
&lt;/h3&gt;

&lt;p&gt;We will look at the same &lt;code&gt;load&lt;/code&gt; function as [[#Problem 1 - Svelte and urql Reactivity]]: the function creates a urql client with the fetch function from the event object and uses this client to send a query.&lt;/p&gt;

&lt;p&gt;Sometimes however the GraphQL API requires authentication in the form of a cookie to allow access.&lt;/p&gt;

&lt;p&gt;Unfortunately, the &lt;a href="https://kit.svelte.dev/docs/load#input-methods-fetch"&gt;fetch function that we get from the load event&lt;/a&gt; will only pass the cookies on if the requested domain is the same as the base domain or a more specific subdomain of it. This means if your SvelteKit site runs on &lt;code&gt;example.com&lt;/code&gt; and your GraphQL server runs on &lt;code&gt;gql.example.com&lt;/code&gt; then the cookies will get forwarded and everything is fine. This however is, in my experience, often not the case. Either you might use an external service for your GraphQL API or you host it yourself and want to use its internal domain.&lt;/p&gt;

&lt;p&gt;The only way to pass the cookies on to the GraphQL server, in this case, is by manually setting the cookie header when creating the urql client. This however forces us to use the server-only load function, as we do not have access to the cookie header in the normal load function.&lt;/p&gt;

&lt;p&gt;The new code now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// /src/routes/car/+page.server.js&lt;/span&gt;

&lt;span class="cm"&gt;/** @type {import('./$types').PageServerLoad} */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fetchOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// inject the cookie header&lt;/span&gt;
        &lt;span class="c1"&gt;// FIXME: change the cookie name&lt;/span&gt;
        &lt;span class="na"&gt;Cookie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`gql-session=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gql-session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;carsQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nx"&gt;toPromise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;toInitialValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To keep the size of the load functions across my codebase smaller I created a small wrapper function &lt;code&gt;createServerClient&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// /src/routes/car/+page.server.js&lt;/span&gt;

&lt;span class="cm"&gt;/** @type {import('./$types').PageServerLoad} */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createServerClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;carsQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nx"&gt;toPromise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;toInitialValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Code
&lt;/h2&gt;

&lt;p&gt;Below you can find the three functions &lt;code&gt;createServerClient&lt;/code&gt;, &lt;code&gt;queryStoreInitialData&lt;/code&gt; and &lt;code&gt;toInitialValue&lt;/code&gt; that we used above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// /src/lib/gql-client.js&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$app/environment&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;queryStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@urql/svelte&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;derived&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;readable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;svelte/store&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Helper function to create an urql client for a server-side-only load function
 *
 *
 * @param {import('@sveltejs/kit').Cookies} cookies
 * @returns
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;createServerClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// FIXME: adjust your graphql url&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;// FIXME: if you don't need to authenticate, delete the following object:&lt;/span&gt;
    &lt;span class="na"&gt;fetchOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// FIXME: if you want to set a cookie adjust the cookie name&lt;/span&gt;
        &lt;span class="na"&gt;Cookie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`gql-session=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gql-session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Helper method to send a GraphQL query but use the data from the SvelteKit load function initially.
 *
 *
 * @param {any} queryArgs
 * @param {any} initialValue
 * @returns
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;queryStoreInitialData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;initialValue&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;No initial value from server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;readable&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;fetching&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;queryStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;derived&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetching&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fetching&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;set&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Make the result object of a urql query serialisable.
 *
 *
 * @template T
 * @param {Promise&amp;lt;import('@urql/svelte').OperationResult&amp;lt;T, any &amp;gt;&amp;gt;|import('@urql/svelte').OperationResult&amp;lt;T, any &amp;gt;} result
 * @returns {Promise&amp;lt;{fetching:false, error: undefined | {name?: string, message?: string; graphQLErrors?: any[]; networkError?: Error; response?: any;}, data: T|undefined}&amp;gt;}
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;toInitialValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// required to turn class array into array of javascript objects&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;errorObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;errorObject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;errorObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;graphQLErrors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;graphQLErrors&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="nx"&gt;errorObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;networkError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;networkError&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;errorObject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;response omitted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fetching&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;errorObject&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://gist.github.com/Tiim/1adeb4d74ce7ae09d0d0aa4176a6195d"&gt;Link to the Gist&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  End remarks
&lt;/h2&gt;

&lt;p&gt;Even though I think this solution is not too bad, I wish @urql/svelte would implement a better way to handle SSR with sveltekit. I posted a &lt;a href="https://github.com/FormidableLabs/urql/discussions/2703"&gt;question on the urql GitHub discussions board&lt;/a&gt;, but I have not gotten any response yet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article was written with &lt;code&gt;@svelte/kit&lt;/code&gt; version &lt;code&gt;1.0.0-next.499&lt;/code&gt; and &lt;code&gt;@urql/svelte&lt;/code&gt; version &lt;code&gt;3.0.1&lt;/code&gt;.&lt;br&gt;
I will try to update this article as I update my codebase to newer versions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If this post helped you, or you found a better or different way to solve SSR with urql, please let me know in the comments, write me an email or tag me on twitter &lt;a href="https://twitter.com/TiimB"&gt;@TiimB&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>urql</category>
      <category>sveltekit</category>
      <category>ssr</category>
      <category>graphql</category>
    </item>
    <item>
      <title>My First Go Project: A Jam-stack Commenting API</title>
      <dc:creator>Tim Bachmann</dc:creator>
      <pubDate>Tue, 12 Jul 2022 13:23:36 +0000</pubDate>
      <link>https://dev.to/tiim/my-first-go-project-a-jam-stack-commenting-api-mgg</link>
      <guid>https://dev.to/tiim/my-first-go-project-a-jam-stack-commenting-api-mgg</guid>
      <description>&lt;p&gt;I recently have been looking around for a simple commenting system to integrate into my website. Since my website is a pre-rendered static Html site hosted on &lt;a href="https://pages.github.com"&gt;Github Pages&lt;/a&gt;, there is no way for it to directly store comments because it does not have a database. The only option for dynamic content to be stored is with an external service.&lt;/p&gt;

&lt;p&gt;I kept my eyes open for a service that I liked, but I did not want to just integrate any old service into my website, I did have some requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The service should not cost anything. I would rather host something myself than sign up for another subscription (because I'm already paying for a VPS anyway).&lt;/li&gt;
&lt;li&gt;I want to control how the comments on my website are displayed. I quite like my website design and I don't want a generic comment box below my posts.&lt;/li&gt;
&lt;li&gt;The service should respect the privacy of the people using my website.&lt;/li&gt;
&lt;li&gt;There should be an option to comment without setting up an account with the service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While looking around for how other people integrated comments into their static websites, I found a nice &lt;a href="https://averagelinuxuser.com/static-website-commenting/"&gt;blog post from Average Linux User&lt;/a&gt; which compares a few popular commenting systems.&lt;br&gt;
Unfortunately, most systems either are not very privacy-friendly, cost money or store the comments as comments on Github issues..?&lt;br&gt;
After looking through the options I decided to use this opportunity to write my own commenting system and dabble with the Go programming language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing a commenting API in Go
&lt;/h2&gt;

&lt;p&gt;First thing first, if you want to take a look at the code, check out the &lt;a href="https://github.com/Tiim/go-comment-api"&gt;Github repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I decided to write the commenting system in Go because I have been looking for an excuse to practice Go for a while, and this seemed like the perfect fit. It is a small CRUD app, consisting of a storage component, an API component and a small event component in the middle to easily compose the functionality I want.&lt;/p&gt;

&lt;p&gt;Currently, it supports the following functionality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Listing all comments (optionally since a specified timestamp)&lt;/li&gt;
&lt;li&gt;Listing all comments for a specified page (optionally since a specified timestamp)&lt;/li&gt;
&lt;li&gt;Posting comments through the API&lt;/li&gt;
&lt;li&gt;A simple admin dashboard that lists all comments and allows the admin to delete them&lt;/li&gt;
&lt;li&gt;Email notifications when someone comments&lt;/li&gt;
&lt;li&gt;Email notifications when someone replies to your comment&lt;/li&gt;
&lt;li&gt;SQLite storage for comments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code is built in a way to make it easy to customise the features.&lt;br&gt;
For example to disable features like the email reply notifications you can just &lt;a href="https://github.com/Tiim/go-comment-api/blob/master/main.go#L52"&gt;comment out the line in the main.go&lt;/a&gt; file that registers that hook.&lt;/p&gt;

&lt;p&gt;To write custom hooks that get executed when a new comment gets submitted or one gets deleted, just implement the &lt;a href="https://github.com/Tiim/go-comment-api/blob/master/event/handler.go"&gt;Handler&lt;/a&gt; interface and register it in the main method.&lt;/p&gt;

&lt;p&gt;You can also easily add other storage options like databases or file storage by implementing the &lt;a href="https://github.com/Tiim/go-comment-api/blob/master/model/store.go"&gt;Store and SubscribtionStore&lt;/a&gt; interfaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can it be used in production? 🚗💨
&lt;/h2&gt;

&lt;p&gt;I currently use it on my personal website! Go check it out on this &lt;a href="https://tiim.ch/blog/2022-07-12-first-go-project-commenting-api"&gt;this post on my personal blog&lt;/a&gt; (I might delete the comments if they are rude though 🤔).&lt;/p&gt;

&lt;p&gt;In all seriousness, I would not use it for a website where the comments are critical. But for a personal blog or similar, I don't see why not.&lt;/p&gt;

&lt;p&gt;If you want to host your own version, there is a Dockerfile available. If you decide to integrate this into your website, please comment below, ping me &lt;a href="https://twitter.com/TiimB"&gt;@TiimB&lt;/a&gt; or shoot me an email &lt;a href="//mailto:hey@tiim.ch"&gt;hey@tiim.ch&lt;/a&gt;, I would love to check it out.&lt;/p&gt;

</description>
      <category>go</category>
      <category>rest</category>
      <category>api</category>
      <category>showdev</category>
    </item>
    <item>
      <title>How to set up an SSH Server on Windows with WSL</title>
      <dc:creator>Tim Bachmann</dc:creator>
      <pubDate>Mon, 07 Mar 2022 16:08:06 +0000</pubDate>
      <link>https://dev.to/tiim/how-to-set-up-an-ssh-server-on-windows-with-wsl-3d96</link>
      <guid>https://dev.to/tiim/how-to-set-up-an-ssh-server-on-windows-with-wsl-3d96</guid>
      <description>&lt;p&gt;There &lt;a href="https://gist.github.com/dentechy/de2be62b55cfd234681921d5a8b6be11"&gt;are&lt;/a&gt; &lt;a href="https://medium.com/@thinkbynumbers/automatically-start-wsl-ssh-and-various-services-on-windows-845dfda89690"&gt;many&lt;/a&gt; &lt;a href="https://faun.pub/how-to-setup-ssh-connection-on-ubuntu-windows-subsystem-for-linux-2b36afb943dc"&gt;guides&lt;/a&gt; on the &lt;a href="https://superuser.com/questions/1112007/how-to-run-ubuntu-service-on-windows-at-startup"&gt;internet&lt;/a&gt; showing how to set up an SSH server &lt;strong&gt;inside&lt;/strong&gt; WSL. This is currently not that easy and in my experience, it is not really stable. An alternative to this is to run the SSH server outside of WSL on the windows side and set its default shell to the WSL shell (or any other shell for that matter).&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing the OpenSSH Server
&lt;/h2&gt;

&lt;p&gt;Windows has been shipping with an OpenSSH client and server for a long time. They are not installed by default but can be activated either in the settings as described &lt;a href="https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse"&gt;in the official docs&lt;/a&gt; or with the following PowerShell commands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You will need to start PowerShell as Administrator&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, install the OpenSSH client and server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-WindowsCapability&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Online&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OpenSSH.Client~~~~0.0.1.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Add-WindowsCapability&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Online&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OpenSSH.Server~~~~0.0.1.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable the SSH service and make sure the firewall rule is configured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Enable the service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Start-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sshd&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Set-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sshd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-StartupType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Automatic'&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Confirm the firewall rule is configured. It should be created automatically by setup. Run the following to verify&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-NetFirewallRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OpenSSH-Server-In-TCP"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ErrorAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SilentlyContinue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Enabled&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Firewall Rule 'OpenSSH-Server-In-TCP' does not exist, creating it..."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;New-NetFirewallRule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'OpenSSH-Server-In-TCP'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DisplayName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'OpenSSH Server (sshd)'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Enabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;True&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Direction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Inbound&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Protocol&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TCP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-LocalPort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;22&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Firewall rule 'OpenSSH-Server-In-TCP' has been created and exists."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations, you have installed the SSH server on your Windows machine. And all without manually setting up a background service or modifying config files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting WSL as Default Shell
&lt;/h2&gt;

&lt;p&gt;To directly boot into WSL when connecting, we need to change the default shell from &lt;code&gt;cmd.exe&lt;/code&gt; or &lt;code&gt;PowerShell.exe&lt;/code&gt; to &lt;code&gt;bash.exe&lt;/code&gt;, which in turn runs the default WSL distribution. This can be done with the PowerShell command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;New-ItemProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HKLM:\SOFTWARE\OpenSSH"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DefaultShell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\WINDOWS\System32\bash.exe"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-PropertyType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: even though the shell is running on the Linux side, the SSH server is still on windows. This means you have to use to windows username to log in, and the SCP command copies files relative to the user directory on windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enable Key-based Authentication (non-Admin User)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: If the user account has Admin permissions, read the next chapter, otherwise continue reading.&lt;/p&gt;

&lt;p&gt;Create the folder &lt;code&gt;.ssh&lt;/code&gt; in the users home directory on windows: (e.g. &lt;code&gt;C:\Users\&amp;lt;username&amp;gt;\.ssh&lt;/code&gt;). Run the following commands in PowerShell (not as administrator).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;New-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;~\.ssh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ItmeType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"directory"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;New-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;~\.ssh\authorized_keys&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file &lt;code&gt;.ssh\autzorized_keys&lt;/code&gt; will contain a list of all public keys that shall be allowed to connect to the SSH server.&lt;/p&gt;

&lt;p&gt;Copy the contents of your public key file (usually stored in &lt;code&gt;~/.ssh/id_rsa.pub&lt;/code&gt;) to the &lt;code&gt;authorized_keys&lt;/code&gt; file. If a key is already present, paste your key on a new line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enable Key-based Authentication (Admin User)
&lt;/h2&gt;

&lt;p&gt;If the user is in the Administrators group, it is not possible to have the &lt;code&gt;authorized_keys&lt;/code&gt; file in the user directory for security purposes.&lt;br&gt;
Instead, it needs to be located on the following path &lt;code&gt;%ProgramData%\ssh\administrators_authorized_keys&lt;/code&gt;. A second requirement is that it is only accessible to Administrator users, to prevent a normal user from gaining admin permissions.&lt;/p&gt;

&lt;p&gt;To create the file start PowerShell as administrator and run the following command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;New-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;programdata&lt;/span&gt;&lt;span class="nx"&gt;\ssh\administrators_authorized_keys&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create the file with the correct permissions. Now open the file and paste your public key into it. The public key should be located at &lt;code&gt;~/.ssh/id_rsa.pub&lt;/code&gt;. If a key is already present, paste your key on a new line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying everything works
&lt;/h2&gt;

&lt;p&gt;Verify that you can SSH into your machine by running the following inside WSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/resolv.conf | &lt;span class="nb"&gt;grep &lt;/span&gt;nameserver | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;" "&lt;/span&gt; &lt;span class="nt"&gt;-f2&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="c"&gt;# get the windows host ip address&lt;/span&gt;
ssh &amp;lt;user&amp;gt;@&lt;span class="nv"&gt;$IP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or from PowerShell and cmd:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;@&lt;/span&gt;&lt;span class="nx"&gt;localhost&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Drawbacks
&lt;/h2&gt;

&lt;p&gt;There are some drawbacks to this approach. If you rely on some programs or scripts to work over SSH, this might not be the method for you. Most scripts expect a unix machine on the other end, or if they expect a windows machine they will most likely not be configured to deal with WSL.&lt;/p&gt;

&lt;p&gt;If you however just want to connect to your pc to copy some files or change some settings this approach is perfectly fine.&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>wsl</category>
      <category>windows</category>
      <category>wsl2</category>
    </item>
    <item>
      <title>How to write optional filters in SQL</title>
      <dc:creator>Tim Bachmann</dc:creator>
      <pubDate>Thu, 11 Jul 2019 15:05:35 +0000</pubDate>
      <link>https://dev.to/tiim/how-to-write-optional-filters-in-sql-fei</link>
      <guid>https://dev.to/tiim/how-to-write-optional-filters-in-sql-fei</guid>
      <description>&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Let's say you have a rest API with the following endpoint that returns all of the books in your database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /book/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your SQL query might look like something like this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes you want to only list books, for example, from a specific author. How do we do this in SQL?&lt;/p&gt;

&lt;h2&gt;
  
  
  Naive solution: String concatenation ✂
&lt;/h2&gt;

&lt;p&gt;One way would be to concatenate your sql query something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;arguments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT * FROM books WHERE true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;authorFilter&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;queryString&lt;/span&gt;  &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AND author = ?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authorFilter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryString&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm not much of a fan of manually concatenating strings.&lt;/p&gt;

&lt;h2&gt;
  
  
  The coalesce function 🌟
&lt;/h2&gt;

&lt;p&gt;Most Databases have the function &lt;code&gt;coalesce&lt;/code&gt; which accepts a variable amount of arguments and returns the first argument that is not null.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Examle&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'dev.to'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'@TiimB'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Will return &lt;/span&gt;

&lt;span class="n"&gt;example&lt;/span&gt;
&lt;span class="c1"&gt;---------&lt;/span&gt;
&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But how will this function help us?&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional filters with the coalesce function
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;books&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; 
  &lt;span class="n"&gt;author&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the filter value is null the coalesce expression will resolve to &lt;code&gt;author&lt;/code&gt;&lt;br&gt;
and the comparison &lt;code&gt;author = author&lt;/code&gt; will be true.&lt;/p&gt;

&lt;p&gt;If on the other hand the value is set for example to Shakespeare then the author will be compared to Shakespeare.&lt;/p&gt;

&lt;p&gt;I came across this way to implement optional filters only recently. If you have a more idiomatic way to do this let me know please ✨&lt;/p&gt;

&lt;p&gt;If you liked this post please follow me on here or on Twitter under &lt;a href="https://twitter.com/TiimB"&gt;@TiimB&lt;/a&gt; 😎&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>sql</category>
      <category>mysql</category>
    </item>
    <item>
      <title>What's your number one underrated VSCode extension?</title>
      <dc:creator>Tim Bachmann</dc:creator>
      <pubDate>Sun, 30 Jun 2019 20:58:41 +0000</pubDate>
      <link>https://dev.to/tiim/what-s-your-number-one-underrated-vscode-extension-ob1</link>
      <guid>https://dev.to/tiim/what-s-your-number-one-underrated-vscode-extension-ob1</guid>
      <description>&lt;p&gt;I have recently seen quite a few articles showcasing VSCode setups. But they're mostly featuring popular plugins most of which I came across alread. &lt;/p&gt;

&lt;p&gt;That's why I'm interested in your favourite obscure, lesser known or just plain awesome plugins. Self promotion welcome! ✨&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>vscode</category>
    </item>
    <item>
      <title>What playlists do you listen to when programming?</title>
      <dc:creator>Tim Bachmann</dc:creator>
      <pubDate>Wed, 12 Jun 2019 11:31:25 +0000</pubDate>
      <link>https://dev.to/tiim/what-playlists-do-you-listen-to-when-programming-10lk</link>
      <guid>https://dev.to/tiim/what-playlists-do-you-listen-to-when-programming-10lk</guid>
      <description>&lt;p&gt;What sort of music do you like to listen to? Feel free to link to playlist on spotify, youtube etc. 🎶🎵&lt;/p&gt;

&lt;p&gt;I like to listen to Acoustic music like this:&lt;/p&gt;

&lt;p&gt;&lt;iframe src="https://open.spotify.com/embed/spotify/37i9dQZF1DXcLDm348RRYK" width="100%" height="px"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>music</category>
    </item>
  </channel>
</rss>
