<?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: Eduard</title>
    <description>The latest articles on DEV Community by Eduard (@viiik).</description>
    <link>https://dev.to/viiik</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%2F846637%2Fd41f5d94-5ee9-4226-b873-c34702364d4f.png</url>
      <title>DEV Community: Eduard</title>
      <link>https://dev.to/viiik</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/viiik"/>
    <language>en</language>
    <item>
      <title>How to host your own radicle node. Contribute to decentralized source control. 🌌</title>
      <dc:creator>Eduard</dc:creator>
      <pubDate>Sat, 17 Aug 2024 17:54:19 +0000</pubDate>
      <link>https://dev.to/viiik/how-to-host-your-own-radicle-node-contribute-to-decentralized-source-control-5cgm</link>
      <guid>https://dev.to/viiik/how-to-host-your-own-radicle-node-contribute-to-decentralized-source-control-5cgm</guid>
      <description>&lt;p&gt;&lt;a href="https://edzz.de/posts/host-your-own-radicle-seed-node/" rel="noopener noreferrer"&gt;Originally posted on my blog&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;The other day there was a massive &lt;a href="https://www.githubstatus.com/incidents/kz4khcgdsfdv" rel="noopener noreferrer"&gt;GitHub outage&lt;/a&gt;.&lt;br&gt;
This made me realize how much we have come to depend on a single company to host not only our code, but our &lt;em&gt;collaboration&lt;/em&gt; itself.&lt;/p&gt;

&lt;p&gt;I have heard about &lt;a href="https://radicle.xyz/" rel="noopener noreferrer"&gt;radicle&lt;/a&gt; before, so I took this opportunity to delve more into what it is, and how it actually works.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://radicle.xyz/" rel="noopener noreferrer"&gt;Radicle&lt;/a&gt; is an open source, peer-to-peer code collaboration stack built on Git. Unlike centralized code hosting platforms, there is no single entity controlling the network. Repositories are replicated across peers in a decentralized manner, and users are in full control of their data and workflow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I encourage you right now to read their &lt;a href="https://radicle.xyz/" rel="noopener noreferrer"&gt;introduction&lt;/a&gt; and then come back to continue, it's a really interesting project.&lt;/p&gt;

&lt;p&gt;Radicle depends on its users to seed the repositories, much like torrenting does.&lt;br&gt;
The community is still young, so I decided it would be great to support it on its early days to help it grow faster.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To seed is to give back. By seeding repositories on the Radicle network, you offer bandwidth, storage, and data availability to Radicle users.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you are interested in supporting radicle like me, you can follow the following guide.&lt;/p&gt;
&lt;h2&gt;
  
  
  setting up a radicle seed node
&lt;/h2&gt;

&lt;p&gt;I decided to use a cheap &lt;a href="https://www.hetzner.com/" rel="noopener noreferrer"&gt;Hetzner&lt;/a&gt; machine. But you can use any existing server you have or any provider you prefer. Once you have your server ready, just ssh into it. You will need root access.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: You will need a domain name so that we can configure a DNS record to point to our node.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  setup
&lt;/h2&gt;

&lt;p&gt;We first need to create the user that will run the node. All data will be kept in their home directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# as root
groupadd --system seed
useradd --system --gid seed --create-home seed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need to install the CLI and the web UI from their &lt;a href="https://radicle.xyz/download" rel="noopener noreferrer"&gt;downloads page&lt;/a&gt;. Get the ones for your&lt;br&gt;
architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -O -L https://files.radicle.xyz/releases/latest/radicle-1.0.0-rc.14-x86_64-unknown-linux-musl.tar.xz
curl -O -L https://files.radicle.xyz/releases/latest/radicle-1.0.0-rc.14-x86_64-unknown-linux-musl.tar.xz.sig
curl -O -L https://files.radicle.xyz/releases/radicle-httpd/latest/radicle-httpd-0.15.0-x86_64-unknown-linux-musl.tar.xz
curl -O -L https://files.radicle.xyz/releases/radicle-httpd/latest/radicle-httpd-0.15.0-x86_64-unknown-linux-musl.tar.xz.sig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then verify the signature of the downloads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh-keygen -Y check-novalidate -n file -s radicle-1.0.0-rc.14-x86_64-unknown-linux-musl.tar.xz.sig &amp;lt; radicle-1.0.0-rc.14-x86_64-unknown-linux-musl.tar.xz
# Good "file" signature with ED25519 key SHA256:iTDjRHSIaoL8dpHbQ0mv+y0IQqPufGl2hQwk4TbXFlw

ssh-keygen -Y check-novalidate -n file -s radicle-httpd-0.15.0-x86_64-unknown-linux-musl.tar.xz.sig &amp;lt; radicle-httpd-0.15.0-x86_64-unknown-linux-musl.tar.xz
# Good "file" signature with ED25519 key SHA256:mqjWN1YrPRDTcVTxB4IZPHyH+vXpjWSogi+3zezZ/rQ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally just install the components, making sure to include the new binaries in your PATH:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# as root
tar -xvJf radicle-1.0.0-rc.14-x86_64-unknown-linux-musl.tar.xz --strip-components=1 -C /usr/local
tar -xvJf radicle-httpd-0.15.0-x86_64-unknown-linux-musl.tar.xz --strip-components=1 -C /usr/local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now switch to the new user to continue, we will create a radicle profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# as root
su - seed

# as seed user
rad auth --alias &amp;lt; YOUR NODE DOMAIN HERE e.g. rad.seeder.example &amp;gt;
# just press enter on the passphrase prompt, it's not necesary for our server deployment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  configuration
&lt;/h2&gt;

&lt;p&gt;Next edit your node config.json in your seed home directory. We will configure our node to have a public policy and we will tell it which address to advertize the node as.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/home/seed/.radicle/config.json&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"node"&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="nl"&gt;"alias"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;YOUR DOMAIN HERE&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"externalAddresses"&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="s2"&gt;"&amp;lt;YOUR DOMAIN HERE&amp;gt;:8776"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"seedingPolicy"&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="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;update&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;this&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"scope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"all"&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="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;To learn more about your node's policy, checkout the &lt;a href="https://radicle.xyz/guides/seeder" rel="noopener noreferrer"&gt;seeder guide&lt;/a&gt; section on policies.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Lets run the node to make sure it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# as seed user
rad node start --foreground
# you should see lots of output telling us about connected peers, fetched resources, etc...
# press C-c to exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lets create a system service unit so that we can configure our node to start with the server. Radicle provides a minimal systemd service unit file &lt;a href="https://seed.radicle.xyz/raw/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/570a7eb141b6ba001713c46345d79b6fead1ca15/systemd/radicle-node.service" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
We can download it and install it on our system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# as root
curl -sS https://seed.radicle.xyz/raw/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/570a7eb141b6ba001713c46345d79b6fead1ca15/systemd/radicle-node.service -o /etc/systemd/system/radicle-node.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you followed the guide as is, you should not have to modify the service unit configuration.&lt;br&gt;
Otherwise adjust as needed. Finally just enable the service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# as root
systemctl enable --now radicle-node

# make sure service is working correctly
systemctl status radicle-node

# as seed user
rad node status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optionally you can disable login as the seed user which is recommended:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# as root
chsh -s /usr/sbin/nologin seed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congrats! At this point your node will seed public repositories! If you followed with me until now, thank you for contributing!&lt;/p&gt;

&lt;h2&gt;
  
  
  web ui
&lt;/h2&gt;

&lt;p&gt;If you want to be able to see your node in web frontends such as &lt;a href="https://app.radicle.xyz/nodes/rad.edzz.de" rel="noopener noreferrer"&gt;this&lt;/a&gt;, you can do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# as root

# enable the http api
curl -sS https://seed.radicle.xyz/raw/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/570a7eb141b6ba001713c46345d79b6fead1ca15/systemd/radicle-httpd.service -o /etc/systemd/system/radicle-httpd.service
systemctl enable --now radicle-httpd

# you will need https support to appear in the web frontend, caddy is easy to configure
apt-get install caddy
curl https://raw.githubusercontent.com/caddyserver/dist/master/init/caddy.service -o /etc/systemd/system/caddy.service

# edit /etc/caddy/Caddyfile
&amp;lt;YOUR DOMAIN HERE&amp;gt; {
    reverse_proxy 127.0.0.1:8080
}

systemctl enable --now caddy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point you should be able to visit &lt;a href="https://app.radicle.xyz/nodes/" rel="noopener noreferrer"&gt;https://app.radicle.xyz/nodes/&lt;/a&gt;&lt;br&gt;
and check your node!&lt;/p&gt;




&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remember to configure your DNS records to point your domain to your node IP address.&lt;/li&gt;
&lt;li&gt;Remember to configure any firewalls to allow UDP:8776 and TCP:443&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>git</category>
      <category>p2p</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to make easy encrypted backups with rclone for free</title>
      <dc:creator>Eduard</dc:creator>
      <pubDate>Thu, 15 Aug 2024 17:11:38 +0000</pubDate>
      <link>https://dev.to/viiik/how-to-make-easy-encrypted-backups-with-rclone-for-free-1i18</link>
      <guid>https://dev.to/viiik/how-to-make-easy-encrypted-backups-with-rclone-for-free-1i18</guid>
      <description>&lt;p&gt;&lt;a href="https://rclone.org/" rel="noopener noreferrer"&gt;Rclone&lt;/a&gt; supports tons of different storage backends.&lt;br&gt;
I like to use R2 on Cloudflare with their generous free tier, but you can use AWS S3, Google Cloud Storage,&lt;br&gt;
Dropbox, or even FTP among others.&lt;/p&gt;

&lt;p&gt;You will need to install &lt;code&gt;rclone&lt;/code&gt; first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ubuntu
sudo apt install nmap
# arch
sudo pacman -S rclone
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't have your storage bucket ready, &lt;a href="https://dash.cloudflare.com/" rel="noopener noreferrer"&gt;create a cloudflare account&lt;/a&gt; then go into the R2 section and create a new bucket.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxj9rlqpzoppu09504ku4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxj9rlqpzoppu09504ku4.png" alt="Image description" width="800" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will also need a new R2 token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9t7omh4yjucm5qbdjb6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9t7omh4yjucm5qbdjb6.png" alt="Image description" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure to give it access to your bucket.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;rclone config&lt;/code&gt; to configure your storage option.&lt;br&gt;
&lt;a href="https://rclone.org/s3/#cloudflare-r2" rel="noopener noreferrer"&gt;Follow this instructions&lt;/a&gt; for R2. &lt;a href="https://rclone.org/overview/" rel="noopener noreferrer"&gt;Find all&lt;br&gt;
supported backends here&lt;/a&gt; if you want to use a different storage option (like your personal FTP server).&lt;/p&gt;

&lt;p&gt;My &lt;code&gt;rclone.conf&lt;/code&gt; looks like this after setting up R2:&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;[r2]&lt;/span&gt;
&lt;span class="py"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;s3&lt;/span&gt;
&lt;span class="py"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Cloudflare&lt;/span&gt;
&lt;span class="py"&gt;access_key_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;REDACTED&lt;/span&gt;
&lt;span class="py"&gt;secret_access_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;REDACTED&lt;/span&gt;
&lt;span class="py"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;auto&lt;/span&gt;
&lt;span class="py"&gt;endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;https://REDACTED.r2.cloudflarestorage.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we should be able to use rclone to copy files to R2. We&lt;br&gt;
probably don't want our files to end up in a bucket in plain text, so lets add some encryption.&lt;/p&gt;

&lt;p&gt;Rclone supports a couple of useful "virtual" or "wrapper" storage remotes. We&lt;br&gt;
can use the &lt;a href="https://rclone.org/crypt/" rel="noopener noreferrer"&gt;crypt remote&lt;/a&gt; to transparently encrypt&lt;br&gt;
and decrypt operations with another remote.&lt;/p&gt;

&lt;p&gt;Drop into &lt;code&gt;rclone config&lt;/code&gt; one more time and&lt;br&gt;
&lt;a href="https://rclone.org/crypt/#configuration" rel="noopener noreferrer"&gt;follow the example configuration&lt;/a&gt;.&lt;br&gt;
When specifying the remote, chose your previous remote and specify a bucket&lt;br&gt;
and optionally a base directory for your backups. I like to use &lt;code&gt;r2:archive/backups&lt;/code&gt;;&lt;br&gt;
&lt;code&gt;archive&lt;/code&gt; is the bucket and &lt;code&gt;backups/&lt;/code&gt; will be the prefix for all operations.&lt;/p&gt;

&lt;p&gt;I chose to disable filename encryption since we will be uploading archive files and&lt;br&gt;
not replicating the filesystem itself. Chose a strong password you will remember.&lt;/p&gt;

&lt;p&gt;Now my config looks like this:&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="err"&gt;...&lt;/span&gt; &lt;span class="err"&gt;r2&lt;/span&gt; &lt;span class="err"&gt;entry&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;

&lt;span class="nn"&gt;[backup]&lt;/span&gt;
&lt;span class="py"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;crypt&lt;/span&gt;
&lt;span class="py"&gt;remote&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;r2:archive/backup&lt;/span&gt;
&lt;span class="py"&gt;filename_encryption&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;off&lt;/span&gt;
&lt;span class="py"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;REDACTED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we can use rclone to upload encrypted files to your backup bucket.&lt;br&gt;
Lets make some utility functions to make this easier.&lt;/p&gt;

&lt;p&gt;I like to use the fish shell, so this is what I include in my fish config to make my&lt;br&gt;
backups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function generate_backup
    set _name "$argv[1]".tar.zst

    sudo tar \
      --create \
      --absolute-names \
      --one-file-system \
      --preserve-permissions \
      --exclude-vcs \
      --exclude-caches \
      --exclude-backups \
      --exclude-tag-all=.NOBACKUP \
      --warning=no-file-ignore \
      --sort=inode \
      --zstd \
      --file=- \
        $argv[2..-1] | \
    rclone rcat --quiet \
      backup:"$_name"

    echo completed backup $_name
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function will archive and zip the given files, excluding some unwanted files,&lt;br&gt;
then stream it into rclone, which will encrypt and upload to our backups bucket.&lt;br&gt;
The first argument will be the backup name, which will be suffixed by a timestamp.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;--exclude-tag-all=.NOBACKUP&lt;/code&gt; line allows you to drop a &lt;code&gt;.NOBACKUP&lt;/code&gt; file in&lt;br&gt;
any directory and the backup will ignore that whole directory.&lt;/p&gt;

&lt;p&gt;Now you can use this function whenever you want to make a backup, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# creates backup/home-(timestamp).tar.zst.bin in your bucket
# includes all contents of desktop documents and videos
generate_backup home ~/Desktop ~/Documents ~/Videos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>backup</category>
      <category>linux</category>
      <category>sysadmin</category>
      <category>rclone</category>
    </item>
    <item>
      <title>Making a real-time chatroom app with cloudflare workers</title>
      <dc:creator>Eduard</dc:creator>
      <pubDate>Sat, 02 Dec 2023 20:46:30 +0000</pubDate>
      <link>https://dev.to/viiik/making-a-real-time-chatroom-app-with-cloudflare-workers-2cp4</link>
      <guid>https://dev.to/viiik/making-a-real-time-chatroom-app-with-cloudflare-workers-2cp4</guid>
      <description>&lt;h2&gt;
  
  
  Serverless Chatroom
&lt;/h2&gt;

&lt;p&gt;In this tutorial, we will build a basic chatroom using Hono, Cloudflare Workers, HTMX, and Durable Objects.&lt;/p&gt;

&lt;p&gt;For the full working example &lt;a href="https://github.com/eduardvercaemer/showcase-hono-htmx-chatroom" rel="noopener noreferrer"&gt;checkout the github repository&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;If you are not familiar with &lt;a href="https://developers.cloudflare.com/workers/" rel="noopener noreferrer"&gt;Cloudflare Workers&lt;/a&gt;, it is a platform that allows us to build serverless applications that are globally available, while being really cheap and easy to deploy.&lt;/p&gt;

&lt;p&gt;Cloudflare Workers also allow us to use websockets in a serverless fashion. &lt;a href="https://dev.toThere%20is%20a%20demo"&gt;https://github.com/cloudflare/workers-chat-demo/blob/master/src/chat.mjs&lt;/a&gt; that showcases this features, but it is somewhat outdated.&lt;/p&gt;

&lt;p&gt;In this post I want to show how to make a modern Cloudflare Worker that uses WebSockets and Durable Objects for a simple Chatroom web app.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Notes: You don't need a Cloudflare account to follow the tutorial locally, but if you want to deploy the project you will need a Paid plan to access the Durable Objects bindings.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;p&gt;We will use &lt;a href="https://hono.dev/top" rel="noopener noreferrer"&gt;Hono&lt;/a&gt; as our web framework, Hono is a modern alternative to frameworks like Express or Koa that supports Cloudflare Workers directly.&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;$ &lt;/span&gt;npm create hono my-app
&lt;span class="c"&gt;# make sure to select Cloudflare Worker as template&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;my-app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our app now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Hono&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="s1"&gt;hono&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hono&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&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="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="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello Hono!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we can add endpoints just like with Express, and &lt;code&gt;c&lt;/code&gt; is the request context that includes everything from Request parameters to response helpers such as the &lt;code&gt;c.text&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;npm run dev&lt;/code&gt; to start the app and visit the link in your browser, you should see a response.&lt;/p&gt;

&lt;p&gt;At this point you could also do &lt;code&gt;npm run deploy&lt;/code&gt; to deploy the app to Cloudflare, you will have to authenticate with your Cloudflare account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Making a basic frontend
&lt;/h3&gt;

&lt;p&gt;For the actual web site, we will use &lt;a href="https://htmx.org/docs/" rel="noopener noreferrer"&gt;HTMX&lt;/a&gt; with the WebSocket extension. This will allow us to extremely easy connect to our backend using a WebSocket, and sending messages and rendering new content in real time.&lt;/p&gt;

&lt;p&gt;We can use the &lt;code&gt;c.html&lt;/code&gt; helper from Hono to render our HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;html&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;hono/html&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;/&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="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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;`
      &amp;lt;!doctype html&amp;gt;
      &amp;lt;html lang="en"&amp;gt;
        &amp;lt;head&amp;gt;
          &amp;lt;title&amp;gt;chatroom&amp;lt;/title&amp;gt;

          &amp;lt;script
            src="https://unpkg.com/htmx.org@1.9.9"
            integrity="sha384-QFjmbokDn2DjBjq+fM+8LUIVrAgqcNW2s0PjAxHETgRn9l4fvX31ZxDxvwQnyMOX"
            crossorigin="anonymous"
          &amp;gt;&amp;lt;/script&amp;gt;
          &amp;lt;script src="https://unpkg.com/htmx.org/dist/ext/ws.js"&amp;gt;&amp;lt;/script&amp;gt;
        &amp;lt;/head&amp;gt;
        &amp;lt;body&amp;gt;
          &amp;lt;main hx-ext="ws" ws-connect="/connect"&amp;gt;
            &amp;lt;h1&amp;gt;chatroom&amp;lt;/h1&amp;gt;
            &amp;lt;ul id="messages"&amp;gt;&amp;lt;/ul&amp;gt;
            &amp;lt;form ws-send&amp;gt;
              &amp;lt;input type="text" name="message" /&amp;gt;
              &amp;lt;button&amp;gt;send&amp;lt;/button&amp;gt;
            &amp;lt;/form&amp;gt;
          &amp;lt;/main&amp;gt;
        &amp;lt;/body&amp;gt;
      &amp;lt;/html&amp;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 is what's going on in the html:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We are including htmx library together with WebSocket extension in the document head.&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt;, we are using the htmx attributes &lt;code&gt;hx-ext&lt;/code&gt; and &lt;code&gt;ws-connect&lt;/code&gt; to tell htmx to connect to a WebSocket endpoint at &lt;code&gt;/connect&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;ul&amp;gt;&lt;/code&gt; will be used to render received messages, more on that later.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; uses the &lt;code&gt;ws-send&lt;/code&gt; attribute to tell htmx that submissions of this form, should be sent as messages inside the WebSocket connection from before.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can check the &lt;a href="https://htmx.org/extensions/web-sockets/" rel="noopener noreferrer"&gt;full documentation for the WebSocket extension&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And that's it! With this little html the frontend can be fully functional, now let's go into the backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding Durable Objects and WebSockets in Workers
&lt;/h3&gt;

&lt;p&gt;We still need to define that &lt;code&gt;/connect&lt;/code&gt; endpoint in the server, so first, lets understand how Cloudflare Workers allow us to handle WebSocket connections.&lt;/p&gt;

&lt;p&gt;A Durable Object is an instance of a javascript class that we can define, that will &lt;em&gt;persist&lt;/em&gt; and allow us to coordinate state between multiple requests, or in our case, multiple WebSocket clients.&lt;/p&gt;

&lt;p&gt;Durable Objects provide a &lt;a href="https://developers.cloudflare.com/durable-objects/api/websockets/" rel="noopener noreferrer"&gt;set of WebSocket APIs&lt;/a&gt; that make it possible to answer to incoming messages in a serverless way, so that connections that don't send messages can stay open without incurring any charges at all.&lt;/p&gt;

&lt;p&gt;The basic architecture of our Durable Object will be the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Accept new WebSocket connections.&lt;/li&gt;
&lt;li&gt;Receive all incoming messages, and echo the message to all connected clients.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Creating a Durable Object
&lt;/h3&gt;

&lt;p&gt;To define our object, all we need to do is export a class from our Worker, and specify it in our wrangler.toml worker configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Chatroom&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# add this to your wrangler.toml&lt;/span&gt;
&lt;span class="nn"&gt;[[durable_objects.bindings]]&lt;/span&gt;
&lt;span class="c"&gt;# This is the name of the binding that will be availble in our worker&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"CHATROOM"&lt;/span&gt;
&lt;span class="py"&gt;class_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Chatroom"&lt;/span&gt;

&lt;span class="nn"&gt;[[migrations]]&lt;/span&gt;
&lt;span class="py"&gt;tag&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"v1"&lt;/span&gt;
&lt;span class="py"&gt;new_classes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Chatroom"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to understand more about this configuration options, you can check &lt;a href="https://developers.cloudflare.com/durable-objects/get-started/#5-configure-durable-object-bindings" rel="noopener noreferrer"&gt;the Durable Object's documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accepting WebSocket connections
&lt;/h3&gt;

&lt;p&gt;Now for the good part. We will define a new &lt;code&gt;/connect&lt;/code&gt; endpoint that will proxy the request to our Durable Object. The Durable Object will then accept the WebSocket connection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HTTPException&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;hono/http-exception&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// ... adding the endpoint&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;/connect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&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;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="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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;upgrade&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;websocket&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;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HTTPException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;402&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// Notice how this name &amp;lt;CHATROOM&amp;gt; matches the binding name in wrangler.toml&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CHATROOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;idFromName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&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;chatroom&lt;/span&gt; &lt;span class="o"&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;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CHATROOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chatroom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;raw&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;so what is this doing?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We access the binding through the &lt;code&gt;env&lt;/code&gt; property that Hono provides us.&lt;/li&gt;
&lt;li&gt;We need to use an ID to tell the Durable Object which instance of the Chatroom we want to use. In this example all connections will use chatroom 0 but we could add private rooms functionality as well.&lt;/li&gt;
&lt;li&gt;Finally, we use the &lt;code&gt;chatroom.fetch&lt;/code&gt; API to talk to the Durable Object. Objects expose functionality using the fetch API just like our worker does.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Finally, we just need to define the functionality of the Durable Object, so lets make some modifications to the Chatroom class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Chatroom&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;/// The state object contains all of the Durable Object APIs &lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// This is the main 'entry point' of our object&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&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;pair&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocketPair&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acceptWebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&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="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;webSocket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;/* WEBSOCKET EVENT HANDLERS */&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;webSocketMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&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="kr"&gt;string&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;message&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWebSockets&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&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="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="s2"&gt;` &amp;lt;ul id="messages" hx-swap-oob="beforeend"&amp;gt;
          &amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;`&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&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="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;webSocketClose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;wasClean&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CLOSED&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="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wasClean&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;webSocketError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&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="kr"&gt;any&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="nf"&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;ERROR&lt;/span&gt;&lt;span class="dl"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now lets explain how this works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The constructor receives a &lt;code&gt;state&lt;/code&gt; object. This object holds all of the APIs Durable Objects can use.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;fetch&lt;/code&gt; method is what will receive and process the request we send in the &lt;code&gt;/connect&lt;/code&gt; endpoint.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the &lt;code&gt;fetch&lt;/code&gt; handler, we are creating a new WebSocket pair and accepting the connection using the Durable Object API for WebSockets. This is what enables us to handler connections with event handlers in a serverless fashion.&lt;/p&gt;

&lt;p&gt;We can then define three handlers: &lt;code&gt;webSocketMessage&lt;/code&gt;, &lt;code&gt;webSocketClose&lt;/code&gt; and &lt;code&gt;webSocketError&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For this app, we only focus on the message handler. All we need to do is parse the message field that we are submitting, and then we can get a list of all connected clients and send the new message to them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"messages"&lt;/span&gt; &lt;span class="na"&gt;hx-swap-oob=&lt;/span&gt;&lt;span class="s"&gt;"beforeend"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;${message}&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the htmx WebSocket extension then processes the HTML response and uses &lt;a href="https://htmx.org/docs/#oob_swaps" rel="noopener noreferrer"&gt;out-of-band swaps&lt;/a&gt; to append the message to the list of messages.&lt;/p&gt;

&lt;p&gt;Try opening the app in multiple tabs and send messages from them. You should see all messages appear in every tab.&lt;/p&gt;

&lt;h3&gt;
  
  
  Going further
&lt;/h3&gt;

&lt;p&gt;You may notice that an unused connection might close automatically around ~2 minutes or so. Durable Objects can also set a socket auto response to enable a heartbeat that does not incurr charges.&lt;/p&gt;

&lt;p&gt;Just add this to the Object class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// somewhere inside the constructor&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setWebSocketAutoResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocketRequestResponsePair&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ping&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pong&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then update the client as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;          &lt;span class="nt"&gt;&amp;lt;script
            &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/htmx.org@1.9.9"&lt;/span&gt;
            &lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha384-QFjmbokDn2DjBjq+fM+8LUIVrAgqcNW2s0PjAxHETgRn9l4fvX31ZxDxvwQnyMOX"&lt;/span&gt;
            &lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
            &lt;span class="nx"&gt;htmx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createWebSocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&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;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
              &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &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="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readyState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ping&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="mi"&gt;20000&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;ws&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/htmx.org/dist/ext/ws.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will make sure all clients send a ping message every 20 seconds, to which the server will reply with pong to keep the connection alive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;And that's everything! We built a serverless application that allows user to send and receive messages in real time using Durable Objects with WebSockets!&lt;/p&gt;

&lt;p&gt;You can try adding more features, like private chatrooms, user identifiers, etc. You will notice that a new user won't see old messages, so you can also experiment with storing messages using a datastore like Cloudflare D1.&lt;/p&gt;

&lt;p&gt;Be creative!&lt;/p&gt;

</description>
      <category>hono</category>
      <category>htmx</category>
      <category>cloudflare</category>
      <category>websocket</category>
    </item>
    <item>
      <title>Understanding bevy's default plugins</title>
      <dc:creator>Eduard</dc:creator>
      <pubDate>Wed, 13 Apr 2022 22:19:02 +0000</pubDate>
      <link>https://dev.to/viiik/understanding-bevys-default-plugins-53hf</link>
      <guid>https://dev.to/viiik/understanding-bevys-default-plugins-53hf</guid>
      <description>&lt;p&gt;Liquid syntax error: 'raw' tag was never closed&lt;/p&gt;
</description>
      <category>rust</category>
      <category>gamedev</category>
      <category>ecs</category>
      <category>bevy</category>
    </item>
  </channel>
</rss>
