<?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: Ayman Bagabas</title>
    <description>The latest articles on DEV Community by Ayman Bagabas (@aymanbagabas).</description>
    <link>https://dev.to/aymanbagabas</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%2F371106%2F59e00ce1-39ed-49b2-8a21-075ce5102f9d.jpeg</url>
      <title>DEV Community: Ayman Bagabas</title>
      <link>https://dev.to/aymanbagabas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aymanbagabas"/>
    <language>en</language>
    <item>
      <title>Self-hosted Soft Serve</title>
      <dc:creator>Ayman Bagabas</dc:creator>
      <pubDate>Fri, 28 Apr 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/aymanbagabas/self-hosted-soft-serve-ghh</link>
      <guid>https://dev.to/aymanbagabas/self-hosted-soft-serve-ghh</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; is a self-hostable Git server for the command-line. It supports Git over HTTP(s), SSH, and the &lt;a href="https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols#_the_git_protocol" rel="noopener noreferrer"&gt;Git Protocol&lt;/a&gt;. &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; also comes with a simple straight-forward user management interface for teams.&lt;/p&gt;

&lt;p&gt;In this post, we will go through how to set up your &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; instance. This includes setting up SSH access, HTTPS using &lt;a href="https://certbot.eff.org/" rel="noopener noreferrer"&gt;Certbot&lt;/a&gt;, and how to manage your &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; instance.&lt;/p&gt;

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

&lt;p&gt;In this post, we are assuming that you have a basic knowledge of networking, a general understanding of how to use Linux and the command-line, and are comfortable using &lt;code&gt;git&lt;/code&gt; commands.&lt;/p&gt;

&lt;p&gt;You will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A server to run &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; on.&lt;/li&gt;
&lt;li&gt;A domain name to access your server (optional).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re using a cloud provider, make sure you have the right access settings before proceeding i.e. access tokens. Running &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; locally or on-premise will vary depending on your setup. This post will only cover setting up &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; on the host server using Systemd, reroute OpenSSH traffic, and set up &lt;a href="https://certbot.eff.org/" rel="noopener noreferrer"&gt;Certbot&lt;/a&gt; for HTTPS.&lt;/p&gt;

&lt;p&gt;We will be using a virtual machine running Ubuntu 22.04 hosted on the cloud. Many cloud providers provide virtual machine services. DigitalOcean calls them Droplets. EC2 if you’re using AWS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: make sure you enable access to the server and add any firewall rules. Soft Serve uses ports 23231/tcp (SSH), 23232/tcp (HTTP), and 9418/tcp (Git). We will reconfigure Soft Serve to run on port 22/tcp (SSH) and 443/tcp (HTTPS), then use port 2200/tcp for OpenSSH shell access.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Setting Up Soft Serve
&lt;/h2&gt;

&lt;p&gt;We will start by installing &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; from Charm’s APT repository, setting up a Systemd service, getting a &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;Let’s Encrypt&lt;/a&gt; certificate using &lt;a href="https://certbot.eff.org/" rel="noopener noreferrer"&gt;Certbot&lt;/a&gt;, and lastly, reconfiguring OpenSSH to access the shell on an alternate port (since Soft Serve will be using the default SSH port). Let the fun begin!&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing Soft Serve
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; and all other Charm tools can be installed via APT/RPM repositories. Check out the &lt;a href="https://github.com/charmbracelet/soft-serve#installation" rel="noopener noreferrer"&gt;installation section&lt;/a&gt; for more options. Since we’re using Ubuntu, we can install &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; from the Charm APT repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Retrieve and import repository key
sudo mkdir -p /etc/apt/keyrings
curl -fsSL &amp;lt;https://repo.charm.sh/apt/gpg.key&amp;gt; | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg
# Add APT repository source
echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] &amp;lt;https://repo.charm.sh/apt/&amp;gt; * *" | sudo tee /etc/apt/sources.list.d/charm.list
# Install Soft Serve &amp;amp; git
sudo apt update &amp;amp;&amp;amp; sudo apt install soft-serve git

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

&lt;/div&gt;



&lt;p&gt;You’re all set! You should now be able to run the &lt;code&gt;soft&lt;/code&gt; binary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;soft --version

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

&lt;/div&gt;



&lt;p&gt;Now that we have &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; installed, let’s run it locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;soft


Soft Serve is a self-hostable Git server for the command line.

Usage:
  soft [command]

Available Commands:
  help Help about any command
  serve Start the server

Flags:
  -h, --help help for soft
  -v, --version version for soft

Use "soft [command] --help" for more information about a command.

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

&lt;/div&gt;



&lt;p&gt;To start the server, we can run &lt;code&gt;soft serve&lt;/code&gt;. This will create a data directory that will store the repositories and database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2023-04-28 server: Starting Git daemon addr=:9418
2023-04-28 server: Starting HTTP server addr=:23232
2023-04-28 server: Starting SSH server addr=:23231
2023-04-28 server: Starting Stats server addr=:23233

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

&lt;/div&gt;



&lt;p&gt;Well, well, we now have a running &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; instance! But, this would be tedious to run each time our server restarts. Luckily, systemd can help us start the process on boot.&lt;/p&gt;

&lt;h3&gt;
  
  
  Systemd
&lt;/h3&gt;

&lt;p&gt;Create a file under &lt;code&gt;/etc/systemd/system/soft-serve.service&lt;/code&gt; and put your Systemd service configuration. Here we will be running &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; as &lt;code&gt;root&lt;/code&gt; to simplify things. We will place the server’s data under &lt;code&gt;/var/local/lib/soft-serve&lt;/code&gt;. Make sure you have added your SSH authorized key to the &lt;code&gt;SOFT_SERVE_INITIAL_ADMIN_KEYS&lt;/code&gt; environment variables. You can remove this later once the “admin” user is created and has your key.&lt;/p&gt;

&lt;p&gt;For a full list of &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; server settings and environment variables refer to the &lt;a href="https://github.com/charmbracelet/soft-serve#server-settings" rel="noopener noreferrer"&gt;Server Settings&lt;/a&gt; section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=Soft Serve git server 🍦
Documentation=https://github.com/charmbracelet/soft-serve
Requires=network-online.target
After=network-online.target

[Service]
Type=simple
Restart=always
RestartSec=1
ExecStartPre=mkdir -p /var/local/lib/soft-serve
ExecStart=/usr/bin/soft serve
Environment=SOFT_SERVE_DATA_PATH=/var/local/lib/soft-serve
Environment=SOFT_SERVE_INITIAL_ADMIN_KEYS='ssh-ed25519 AAAAC3NzaC1lZDI1...'

[Install]
WantedBy=multi-user.target

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

&lt;/div&gt;



&lt;p&gt;Now, reload Systemd configuration, enable and start 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;sudo systemctl daemon-reload
sudo systemctl enable soft-serve.service
sudo systemctl start soft-serve.service

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

&lt;/div&gt;



&lt;p&gt;We can check the logs using &lt;code&gt;journalctl -u soft-serve.service&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tip: add -f flag to “tail” the logs as they appear. Useful when using tmux to keep an eye on logs journalctl -fu soft-serve.service.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  HTTPS Certificate
&lt;/h3&gt;

&lt;p&gt;To be able to use HTTPS in &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt;, we will need to set up TLS certificates so that &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; can use an encrypted connection to communicate with the world. We will be using &lt;a href="https://certbot.eff.org/" rel="noopener noreferrer"&gt;Certbot&lt;/a&gt; to issue us &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;Let’s Encrypt&lt;/a&gt; certificates. Following &lt;a href="https://certbot.eff.org/instructions" rel="noopener noreferrer"&gt;Certbot instructions&lt;/a&gt;, we will choose &lt;code&gt;Other&lt;/code&gt; and &lt;code&gt;Ubuntu 20&lt;/code&gt; for the instruction options.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: if you’re not using a domain name, you won’t be able to issue an HTTPS certificate since Let’s Encrypt doesn’t allow the use of bare IP addresses. ZeroSSL is a great alternative that supports bare IP addresses.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--32-eTH8R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stuff.charm.sh/blog/self-hosted-soft-serve/certbot-options.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--32-eTH8R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stuff.charm.sh/blog/self-hosted-soft-serve/certbot-options.png" width="800" height="124"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, make sure you have updated your DNS records to point your server’s IP address to your custom domain. This is typically done using a &lt;code&gt;A&lt;/code&gt; record. This will vary depending on your DNS domain provider. We will be using &lt;code&gt;git.example.com&lt;/code&gt; to demonstrate issuing a certificate for the subdomain &lt;code&gt;git&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Install the &lt;code&gt;certbot&lt;/code&gt; cli tool using &lt;code&gt;snapd&lt;/code&gt; to issue the certificate for our domain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Stop Soft Serve
sudo systemctl stop soft-serve.service
# Install certbot from snapd
sudo snap install --classic certbot
# Issue certificate
sudo certbot certonly --standalone
# Enter your email address
# Agree for terms of service
# Enter your domain(s): git.example.com

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

&lt;/div&gt;



&lt;p&gt;Voilà, we have an HTTPS certificate!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/git.example.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/git.example.com/privkey.pem
This certificate expires on 2023-07-28.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt: &amp;lt;https://letsencrypt.org/donate&amp;gt;
 * Donating to EFF: &amp;lt;https://eff.org/donate-le&amp;gt;
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

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

&lt;/div&gt;



&lt;p&gt;Once we have our certificate, we will need to update our &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; configuration to use them and point to our new https:// address.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server Configuration
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; stores its server configuration in the &lt;code&gt;config.yaml&lt;/code&gt; file under the &lt;em&gt;data directory&lt;/em&gt;. By default, it uses the relative path &lt;code&gt;data&lt;/code&gt; as a &lt;em&gt;data directory&lt;/em&gt;. You can override this by defining a &lt;code&gt;SOFT_SERVE_DATA_PATH&lt;/code&gt; environment variable (as seen above in the systemd service file). This means that our &lt;code&gt;config.yaml&lt;/code&gt; file lives under &lt;code&gt;/var/local/lib/soft-serve&lt;/code&gt; since we have that as our &lt;em&gt;data directory&lt;/em&gt; path.&lt;/p&gt;

&lt;p&gt;To use HTTPS default port (443), we have to tell &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; about our &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;Let’s Encrypt&lt;/a&gt; certificates. And since we are using a custom domain now, we need to update the server’s public URL. This is the address that will you will be using to manage user access and &lt;code&gt;git clone&lt;/code&gt; repositories. Lastly, we will make OpenSSH use a different port, so we can still have shell access on our remote host.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Info: you can override configuration settings using environment variables. For example, to override the server’s name add SOFT_SERVE_NAME=’Git Melon’ to your soft-serve.service file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s edit the file and see what’s in there 🤔&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Make sure you have $EDITOR defined
# Use vim &amp;lt;3
# export EDITOR=vim
sudo $EDITOR /var/local/lib/soft-serve/config.yaml

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

&lt;/div&gt;



&lt;p&gt;Here, we can see the default &lt;a href="https://github.com/charmbracelet/soft-serve/tree/newbase#server-settings" rel="noopener noreferrer"&gt;server configurations&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change the &lt;code&gt;ssh.listen_addr&lt;/code&gt; to use the default SSH port (22).&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;ssh://git.example.com&lt;/code&gt; as our &lt;code&gt;ssh.public_url&lt;/code&gt; (this will be used for clones over SSH e.g. &lt;code&gt;git clone git@git.example.com:repo.git&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Change the &lt;code&gt;http.listen_addr&lt;/code&gt; to use HTTPS default port (443).&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;http.public_url&lt;/code&gt; to point to use https:// and point to our custom domain &lt;code&gt;https://git.example.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;http.tls_key_path&lt;/code&gt; and &lt;code&gt;http.tls_cert_path&lt;/code&gt; to use the generated &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;Let’s Encrypt&lt;/a&gt; certificates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final configurations look 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;# Soft Serve Server configurations

# The name of the server.
# This is the name that will be displayed in the UI.
name: "Soft Serve"

# Log format to use. Valid values are "json", "logfmt", and "text".
log_format: "text"

# The SSH server configuration.
ssh:
  # The address on which the SSH server will listen.
  listen_addr: ":22"

  # The public URL of the SSH server.
  # This is the address that will be used to clone repositories.
  public_url: "ssh://git.example.com"

  # The path to the SSH server's private key.
  key_path: "ssh/soft_serve_host"

  # The path to the server's client private key.
  # This key will be used to authenticate the server to make git requests to
  # ssh remotes.
  client_key_path: "ssh/soft_serve_client_ed25519"

  # The maximum number of seconds a connection can take.
  # A value of 0 means no timeout.
  max_timeout: 0

  # The number of seconds a connection can be idle before it is closed.
  idle_timeout: 0

# The Git daemon configuration.
git:
  # The address on which the Git daemon will listen.
  listen_addr: ":9418"

  # The maximum number of seconds a connection can take.
  # A value of 0 means no timeout.
  max_timeout: 0

  # The number of seconds a connection can be idle before it is closed.
  idle_timeout: 3

  # The maximum number of concurrent connections.
  max_connections: 32

# The HTTP server configuration.
http:
  # The address on which the HTTP server will listen.
  listen_addr: ":443"

  # The path to the TLS private key.
  tls_key_path: "/etc/letsencrypt/live/git.example.com/privkey.pem"

  # The path to the TLS certificate.
  tls_cert_path: "/etc/letsencrypt/live/git.example.com/fullchain.pem"

  # The public URL of the HTTP server.
  # This is the address that will be used to clone repositories.
  # Make sure to use https:// if you are using TLS.
  public_url: "&amp;lt;https://git.example.com&amp;gt;"

# The stats server configuration.
stats:
  # The address on which the stats server will listen.
  listen_addr: "localhost:23233"
# Additional admin keys.
#initial_admin_keys:
# - "ssh-rsa AAAAB3NzaC1yc2..."

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

&lt;/div&gt;



&lt;p&gt;This looks good so far. Now before we start &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; up again, we need to change the port that OpenSSH uses. This is specified in &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo $EDITOR /etc/ssh/sshd_config
# Uncomment `#Port 22`
# Change `Port 22` to `Port 2200`
# Or use the power of `sed` :)
sudo sed -i 's/^#Port 22/Port 2200/g' /etc/ssh/sshd_config
# Restart sshd
sudo systemctl restart sshd

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

&lt;/div&gt;



&lt;p&gt;You can now access your server’s shell on port &lt;code&gt;2200&lt;/code&gt;. Try it out: &lt;code&gt;ssh -i &amp;lt;my-precious-key&amp;gt; -p 2200 git.example.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, let’s start our &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; server again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl start soft-serve.service

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

&lt;/div&gt;



&lt;p&gt;View logs using &lt;code&gt;journalctl -fu soft-serve.service&lt;/code&gt;. Verify the server is indeed running on &lt;code&gt;:22&lt;/code&gt; and &lt;code&gt;:443&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Apr 28 20:33:39 ip-172-31-84-249 soft[7594]: 2023-04-28 server: Starting Git daemon addr=:9418
Apr 28 20:33:39 ip-172-31-84-249 soft[7594]: 2023-04-28 server: Starting HTTP server addr=:443
Apr 28 20:33:39 ip-172-31-84-249 soft[7594]: 2023-04-28 server: Starting SSH server addr=:22
Apr 28 20:33:39 ip-172-31-84-249 soft[7594]: 2023-04-28 server: Starting Stats server addr=localhost:23233

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Manage Soft Serve
&lt;/h2&gt;

&lt;p&gt;Now that we successfully set up our &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; server, we want to manage users, server access, and repositories. Since we added our authorized key as an environment variable above, we should be able to see all the admin commands when &lt;code&gt;ssh -i &amp;lt;my-precious-key&amp;gt; git.example.com help&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Soft Serve is a self-hostable Git server for the command line.

Usage:
  ssh git.example.com [command]

Available Commands:
  help Help about any command
  info Show your info
  pubkey Manage your public keys
  repo Manage repositories
  set-username Set your username
  settings Manage server settings
  user Manage users

Flags:
  -h, --help help for this command

Use "ssh git.example.com [command] --help" for more information about a command.

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Users and Access
&lt;/h3&gt;

&lt;p&gt;You can manage users using the &lt;code&gt;user&lt;/code&gt; command. For example, &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; creates a default &lt;code&gt;admin&lt;/code&gt; user on the first run that uses the keys defined in &lt;code&gt;SOFT_SERVE_INITIAL_ADMIN_KEYS&lt;/code&gt;. Let’s verify that using the &lt;code&gt;info&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ssh -i &amp;lt;my-precious-key&amp;gt; git.example.com info
Username: admin
Admin: true
Public keys:
  ssh-ed25519 AAAAC3NzaC1lZDI1...

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

&lt;/div&gt;



&lt;p&gt;We can add more keys using the &lt;code&gt;pubkey&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -i &amp;lt;my-precious-key&amp;gt; git.example.com pubkey add ssh-rsa AAAAB3NzaC1yc2...

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

&lt;/div&gt;



&lt;p&gt;Use the &lt;code&gt;user&lt;/code&gt; command to create more users. Add &lt;code&gt;-a&lt;/code&gt; to mark user as admin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -i &amp;lt;my-precious-key&amp;gt; git.example.com user create lemon -a
ssh -i &amp;lt;my-precious-key&amp;gt; git.example.com user add-pubkey lemon ssh-rsa AAAAB3NzaC1yc2...

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

&lt;/div&gt;



&lt;p&gt;To change the current username, use the &lt;code&gt;set-username&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -i &amp;lt;my-precious-key&amp;gt; git.example.com set-username melon

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Repositories
&lt;/h3&gt;

&lt;p&gt;Using the &lt;code&gt;repo&lt;/code&gt; command, you can create, delete, import, and manage repository settings. You can add/remove collaborators using &lt;code&gt;repo collab&lt;/code&gt;. Let’s go through an example of creating a new repository, pushing code, and adding a new collaborator who can access the repo.&lt;/p&gt;

&lt;p&gt;First, we will create a new repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -i &amp;lt;my-precious-key&amp;gt; git.example.com repo create hula-hoop

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

&lt;/div&gt;



&lt;p&gt;If you ssh into the server (without any arguments) you should see the TUI and the new repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Z3VEBekh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stuff.charm.sh/blog/self-hosted-soft-serve/hula-hoop-tui-selected.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z3VEBekh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://stuff.charm.sh/blog/self-hosted-soft-serve/hula-hoop-tui-selected.png" width="562" height="370"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Push to Repository
&lt;/h3&gt;

&lt;p&gt;Now, let’s push some files to the repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Clone the repository
git clone git@git.example.com:hula-hoop.git
cd hula-hoop
# Change default branch
git branch -M main
# Add content
$EDITOR README.md
git add README.md
git commit -m "Add README.md"
git push origin main

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Collaborators
&lt;/h3&gt;

&lt;p&gt;Let’s add the user &lt;code&gt;lemon&lt;/code&gt; that we created earlier as a collaborator. The command &lt;code&gt;add&lt;/code&gt; takes a &lt;em&gt;repository&lt;/em&gt; name and a &lt;em&gt;username&lt;/em&gt; as arguments. &lt;code&gt;repo collab add REPOSITORY USERNAME&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Add user as a collaborator
ssh -i &amp;lt;my-precious-key&amp;gt; git.example.com repo collab add hula-hoop lemon
# List repository collaborators
ssh -i &amp;lt;my-precious-key&amp;gt; git.example.com repo collab list hula-hoop

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Nested Repositories
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt; also supports nested repositories, you can create repositories with any arbitrary path. Go wild!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -i &amp;lt;my-precious-key&amp;gt; git.example.com repo create my/super/nested/new
ssh -i &amp;lt;my-precious-key&amp;gt; git.example.com repo create my/super/nested/new/repository

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mirrors
&lt;/h3&gt;

&lt;p&gt;You can also &lt;em&gt;import&lt;/em&gt; repositories from &lt;em&gt;any&lt;/em&gt; public remote. Use the &lt;code&gt;repo import&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -i &amp;lt;my-precious-key&amp;gt; git.example.com repo import soft-serve &amp;lt;https://github.com/charmbracelet/soft-serve&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;--mirror&lt;/code&gt; or &lt;code&gt;-m&lt;/code&gt; to mark the repository as a &lt;em&gt;pull&lt;/em&gt; mirror.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metadata
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt;, a repository has different properties. Repositories can be &lt;em&gt;hidden&lt;/em&gt;, &lt;em&gt;private&lt;/em&gt;, or &lt;em&gt;mirrored&lt;/em&gt;. Repositories can also have their own descriptions and a &lt;em&gt;project name&lt;/em&gt; different from the repository’s name. For example, we want the repository we imported above to be presented as &lt;code&gt;Soft Serve&lt;/code&gt; rather than &lt;code&gt;soft-serve&lt;/code&gt;. To do so, let’s set the repository &lt;em&gt;project name&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;ssh -i &amp;lt;my-precious-key&amp;gt; git.example.com repo project-name soft-serve 'Soft Serve'

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

&lt;/div&gt;



&lt;p&gt;Let’s also add a description to this repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -i &amp;lt;my-precious-key&amp;gt; git.example.com repo description soft-serve 'The hackable self-hosted Git server!'

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--d1lphZSj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/http://stuff.charm.sh/blog/self-hosted-soft-serve/soft-serve-in-da-house-tui-nested.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--d1lphZSj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/http://stuff.charm.sh/blog/self-hosted-soft-serve/soft-serve-in-da-house-tui-nested.png" width="556" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For more info on repository commands try &lt;code&gt;repo help&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;That’s it for now. Check out &lt;a href="https://github.com/charmbracelet/soft-serve/blob/main/README.md" rel="noopener noreferrer"&gt;Soft Serve’s README&lt;/a&gt; for more information on how to use &lt;a href="https://github.com/charmbracelet/soft-serve" rel="noopener noreferrer"&gt;Soft Serve&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Till next time, happy hacking!&lt;/p&gt;

</description>
      <category>ssh</category>
      <category>terminal</category>
      <category>go</category>
      <category>tui</category>
    </item>
    <item>
      <title>Simple Vim Session Management</title>
      <dc:creator>Ayman Bagabas</dc:creator>
      <pubDate>Thu, 13 Apr 2023 00:00:00 +0000</pubDate>
      <link>https://dev.to/aymanbagabas/simple-vim-session-management-nj0</link>
      <guid>https://dev.to/aymanbagabas/simple-vim-session-management-nj0</guid>
      <description>&lt;p&gt;Recently I switched back to using Neovim after being a VSCode user for a while. One of the things I miss in VSCode is the session management that comes bundled in. My brain became wired to doing things like &lt;code&gt;code &amp;lt;projectDir&amp;gt;&lt;/code&gt; to open the editor with all files loaded just like the way I left them. Luckily in Vim, you can use &lt;code&gt;:mksession&lt;/code&gt; to create sessions.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Problem
&lt;/h1&gt;

&lt;p&gt;Just like VSCode, I want Neovim to save the session file under the project root directory. Specifically in &lt;code&gt;.nvim/session.vim&lt;/code&gt;. I want Neovim to detect whether the opened file is a directory, and based on that, load the session file if exists. Before exiting Neovim, save the session file under the project root directory.&lt;/p&gt;

&lt;p&gt;You see, I tried different plugins including &lt;a href="https://github.com/folke/persistence.nvim" rel="noopener noreferrer"&gt;persistence.nvim&lt;/a&gt;, &lt;a href="https://github.com/olimorris/persisted.nvim" rel="noopener noreferrer"&gt;persisted.nvim&lt;/a&gt;, &lt;a href="https://github.com/rmagatti/auto-session" rel="noopener noreferrer"&gt;auto-session&lt;/a&gt;, &lt;a href="https://github.com/natecraddock/sessions.nvim" rel="noopener noreferrer"&gt;sessions.nvim&lt;/a&gt;, and &lt;a href="https://github.com/ahmedkhalf/project.nvim" rel="noopener noreferrer"&gt;project.nvim&lt;/a&gt;. None of them achieved what I wanted.&lt;/p&gt;

&lt;h1&gt;
  
  
  How?
&lt;/h1&gt;

&lt;p&gt;Neovim supports sessions. You can use &lt;code&gt;:mksession&lt;/code&gt; to create session files, and &lt;code&gt;:source &amp;lt;file&amp;gt;&lt;/code&gt; or &lt;code&gt;nvim -S &amp;lt;file&amp;gt;&lt;/code&gt; to restore a session. And with the &lt;code&gt;sessionoptions&lt;/code&gt; option, we can tell Neovim exactly what to save in the session file. Simple just like that. Pairing that with &lt;code&gt;autocmd&lt;/code&gt;s on startup and exit we can get what we want. Read more about this &lt;a href="https://neovim.io/doc/user/usr_21.html#21.4" rel="noopener noreferrer"&gt;&lt;code&gt;:h session&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Config
&lt;/h2&gt;

&lt;p&gt;First, we need to tell Neovim what to save in the session file. We do so using the &lt;code&gt;sessionoptions&lt;/code&gt; option. In your &lt;code&gt;init.lua&lt;/code&gt; add the list of session options you want to save and restore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vim.opt.sessionoptions = "buffers,curdir,folds,help,tabpages,winsize,winpos,terminal,localoptions"

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

&lt;/div&gt;



&lt;p&gt;Here I’m choosing almost everything 🙂 Make sure to avoid adding &lt;code&gt;options&lt;/code&gt; to the list since it will interfere with other plugins you might have. Read more about this &lt;a href="https://neovim.io/doc/user/options.html#'sessionoptions'" rel="noopener noreferrer"&gt;&lt;code&gt;:h sessionoptions&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, we need to add out &lt;code&gt;autocmd&lt;/code&gt;s on &lt;code&gt;VimEnter&lt;/code&gt; and &lt;code&gt;VimLeave&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- Simple session management on directory open
-- Here we check if the opened file is a directory.
-- Then we load the sessionfile if exists.
-- And lastly, we delete the extra directory buffer.
vim.api.nvim_create_autocmd("VimEnter", {
  callback = function(data)
    -- buffer is a directory
    local isdirectory = vim.fn.isdirectory(data.file) == 1
    if not isdirectory then
      return
    end

    -- source session.vim if it exists
    local sessionfile = vim.fn.resolve(data.file .. "/.nvim/session.vim")
    if vim.fn.filereadable(sessionfile) == 1 then
      vim.cmd("source " .. sessionfile)
    end

    -- wipe the directory buffer
    vim.cmd("bw " .. data.buf)
  end,
  nested = true,
})

-- Check if we are in the project root.
-- If we are, save the session file.
vim.api.nvim_create_autocmd("VimLeave", {
  callback = function()
    local isproject = false
    for _, root in ipairs({ ".git", ".hg", ".bzr", ".svn", "Makefile", "package.json", "go.mod" }) do
      if vim.fn.isdirectory(root) == 1 then
        isproject = true
        break
      end
    end

    -- only save session if we are in a project root
    if not isproject then
      return
    end

    local sessionfile = ".nvim/session.vim"
    vim.fn.mkdir(".nvim", "p")
    vim.cmd("mksession! " .. sessionfile)
  end,
})

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;p&gt;When using &lt;code&gt;git&lt;/code&gt;, it can be annoying to see the &lt;code&gt;.nvim&lt;/code&gt; directory in every project you have. You can ignore the file by adding it to a &lt;code&gt;~/.gitignore&lt;/code&gt; and exclude the file contents from git.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo '.nvim' &amp;gt;&amp;gt; '~/.gitignore'
git config --global core.excludesFile '~/.gitignore'

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

&lt;/div&gt;



&lt;p&gt;That’s it, hope this helps 😃.&lt;/p&gt;

</description>
      <category>vim</category>
      <category>nvim</category>
    </item>
    <item>
      <title>no title</title>
      <dc:creator>Ayman Bagabas</dc:creator>
      <pubDate>Thu, 02 Jun 2022 16:11:13 +0000</pubDate>
      <link>https://dev.to/aymanbagabas/no-title-44ob</link>
      <guid>https://dev.to/aymanbagabas/no-title-44ob</guid>
      <description>&lt;h1&gt;
  
  
  Blue Mountain
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---zBQBko---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://64.media.tumblr.com/ce0f95c67ce781f852e28387ac77791c/469e3ad4846168e5-fb/s540x810/343ce46fa0e2ec38ae25359b9c0db0f5bcba5bf3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---zBQBko---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://64.media.tumblr.com/ce0f95c67ce781f852e28387ac77791c/469e3ad4846168e5-fb/s540x810/343ce46fa0e2ec38ae25359b9c0db0f5bcba5bf3.jpg" width="540" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📍Blue Mountain Summit Loop&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Import Notion Pages to Jekyll</title>
      <dc:creator>Ayman Bagabas</dc:creator>
      <pubDate>Tue, 29 Mar 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/aymanbagabas/import-notion-pages-to-jekyll-3i6b</link>
      <guid>https://dev.to/aymanbagabas/import-notion-pages-to-jekyll-3i6b</guid>
      <description>&lt;p&gt;Lately, I started using Notion for note-taking, to-do lists, and now for writing blog posts. Notion makes it simple to combine all those activities in a unified pretty interface. Using Jekyll, Github Pages, and Github Actions, I was able to import my Notion Blog database posts into Jekyll using a Github workflow that runs twice a day.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DT0zCles--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.us-west-2.amazonaws.com/secure.notion-static.com/e93d2eeb-9f9e-46e3-9e81-181a939cbb68/Untitled.jpeg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3DAKIAT73L2G45EIPT3X45%252F20220329%252Fus-west-2%252Fs3%252Faws4_request%26X-Amz-Date%3D20220329T201757Z%26X-Amz-Expires%3D3600%26X-Amz-Signature%3D66dee8d154a5e9bc816d5ae17ccef14f9d925b8ace7585ed0a7cf213baac109d%26X-Amz-SignedHeaders%3Dhost%26x-id%3DGetObject" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DT0zCles--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.us-west-2.amazonaws.com/secure.notion-static.com/e93d2eeb-9f9e-46e3-9e81-181a939cbb68/Untitled.jpeg%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3DAKIAT73L2G45EIPT3X45%252F20220329%252Fus-west-2%252Fs3%252Faws4_request%26X-Amz-Date%3D20220329T201757Z%26X-Amz-Expires%3D3600%26X-Amz-Signature%3D66dee8d154a5e9bc816d5ae17ccef14f9d925b8ace7585ed0a7cf213baac109d%26X-Amz-SignedHeaders%3Dhost%26x-id%3DGetObject" alt="My Notion Blog database" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  What is a Notion Database?
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.notion.so/help/guides/creating-a-database"&gt;Notion databases&lt;/a&gt; are smart tables that can hold a collection of pages with customizable properties and multiple layouts. Think of it as smart spreadsheets on steroids.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Layout
&lt;/h1&gt;

&lt;p&gt;Jekyll uses YAML front matter to specify post title, date, tags, and other properties. Using Notion databases properties, we can map Jekyll YAML front matter into a simple Notion Database. Each entry in the database corresponds to a Jekyll post with its respective front matter properties. The script will use the date specified as the published date for the post, it will default to the entry creation date if one was not specified. If you want a page to be published, simply check the “Publish” checkbox.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Script
&lt;/h1&gt;

&lt;p&gt;Writing the importer script was a breeze using &lt;a href="https://github.com/makenotion/notion-sdk-js"&gt;Notion JavaScript SDK&lt;/a&gt; and &lt;a href="https://github.com/souvikinator/notion-to-md"&gt;souvikinator/notion-to-md&lt;/a&gt;. The SDK pulls the entries in the database, then the converter converts each Notion page into Markdown ready to be used in Jekyll.&lt;/p&gt;

&lt;p&gt;To use the script, first, you have to create a new Notion integration that can access your Blog page database. Simply go to settings, choose integrations, click on “develop your own integration” and create a new integration with the proper scopes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tyTXrbq1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.us-west-2.amazonaws.com/secure.notion-static.com/b9ae53ab-a4de-4b31-84bf-8b95b8aef7e3/Untitled.png%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3DAKIAT73L2G45EIPT3X45%252F20220329%252Fus-west-2%252Fs3%252Faws4_request%26X-Amz-Date%3D20220329T201757Z%26X-Amz-Expires%3D3600%26X-Amz-Signature%3D482c21133cdc22ecc0ec60251faa3328afb7069e3da6e7c1c8ecae46038084c4%26X-Amz-SignedHeaders%3Dhost%26x-id%3DGetObject" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tyTXrbq1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.us-west-2.amazonaws.com/secure.notion-static.com/b9ae53ab-a4de-4b31-84bf-8b95b8aef7e3/Untitled.png%3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Content-Sha256%3DUNSIGNED-PAYLOAD%26X-Amz-Credential%3DAKIAT73L2G45EIPT3X45%252F20220329%252Fus-west-2%252Fs3%252Faws4_request%26X-Amz-Date%3D20220329T201757Z%26X-Amz-Expires%3D3600%26X-Amz-Signature%3D482c21133cdc22ecc0ec60251faa3328afb7069e3da6e7c1c8ecae46038084c4%26X-Amz-SignedHeaders%3Dhost%26x-id%3DGetObject" alt="" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After creating that, you will get an integration key that would be used later with the script for it to work. But before that, you will need to invite your newly created integration bot to the Notion page that has the database that you want to use. Simply click the “share” button on the database page and invite the integration you just created. It will have the same name you specified when you created the integration.&lt;/p&gt;

&lt;p&gt;This is how the script works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Create a Notion client from an environment variable
const notion = new Client({
    auth: process.env.NOTION_TOKEN,
});

// Query the database and filter out unpublished entries
const response = await notion.databases.query({
        database_id: process.env.DATABASE_ID,
        filter: {
            property: "Publish",
            checkbox: {
                equals: true
            }
        }
    })

// Iterate over the results
for (const r of response.results) {
  // build the post front matter
  // convert the page to markdown
  // write it to disk
}

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

&lt;/div&gt;



&lt;p&gt;Here, we’re using 2 environment variables to store the Notion integration token and the database ID. You can find the database ID in the Notion page URL. &lt;code&gt;https://www.notion.so/&amp;lt;database_id&amp;gt;?v=&amp;lt;long_hash&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can find the importer script &lt;a href="https://github.com/aymanbagabas/aymanbagabas.github.io/blob/abd711ed3033a9416b7fedd5c3561a896ae13888/_scripts/notion-import.js"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Periodically Import Content
&lt;/h1&gt;

&lt;p&gt;Using Github Actions, we can create a workflow that runs periodically every hour to run the script and then publish the contents using Github Pages. This depends on your setup, for me, I’m hosting my website on Github Pages just because of its convenience.&lt;/p&gt;

&lt;p&gt;The workflow runs the script, then commits all the changes back to the repository. This then triggers Github Pages to create a deployment of the website and publish the changes. Here is the &lt;a href="https://github.com/aymanbagabas/aymanbagabas.github.io/blob/master/.github/workflows/importer.yml"&gt;workflow&lt;/a&gt; I’m currently using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Jekyll importer

on:
  push:
  schedule:
    - cron: "0 */1 * * *"

jobs:
  importer:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@master

      - uses: actions/setup-node@v2
        with:
          node-version: "17"

      - run: npm install

      - run: node _scripts/notion-import.js
        env:
          NOTION_TOKEN: $
          DATABASE_ID: $

      - uses: stefanzweifel/git-auto-commit-action@v4
        env:
          GITHUB_TOKEN: $
        with:
          commit_message: Update Importer posts
          branch: master
          commit_user_name: importer-bot 🤖
          commit_user_email: actions@github.com
          commit_author: importer-bot 🤖 &amp;lt;actions@github.com&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Here, I’m running the workflow every hour and using Github Secrets to store the Notion token and database ID, then use those as environment variables when running the script.&lt;/p&gt;

&lt;p&gt;That’s it for now! Go on and write your next post!&lt;/p&gt;

</description>
      <category>jekyll</category>
      <category>notion</category>
    </item>
    <item>
      <title>Nyan Cat Over SSH</title>
      <dc:creator>Ayman Bagabas</dc:creator>
      <pubDate>Fri, 25 Mar 2022 00:00:00 +0000</pubDate>
      <link>https://dev.to/aymanbagabas/nyan-cat-over-ssh-fn3</link>
      <guid>https://dev.to/aymanbagabas/nyan-cat-over-ssh-fn3</guid>
      <description>&lt;p&gt;Everyone knows the Nyan Cat meme that started in 2011. The meme started from a &lt;a href="https://www.youtube.com/watch?v=QH2-TGUlwu4"&gt;YouTube&lt;/a&gt; video that merged a Japanese pop song with an animated cartoon cat. Then a Telnet Nyan Cat server was created by &lt;a href="https://github.com/klange/nyancat"&gt;klange/nyancat&lt;/a&gt;. And now, in 2022, after 11 years of nyaning, it got ported to SSH! Try it out here &lt;code&gt;ssh dir.charm.sh -p2226&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ovNnjwtA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://github.com/aymanbagabas/nyancatsh/raw/master/nyancatsh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ovNnjwtA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://github.com/aymanbagabas/nyancatsh/raw/master/nyancatsh.gif" alt="nyancat" width="778" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  The Why
&lt;/h1&gt;

&lt;p&gt;Well, to put it simply, why not port it to SSH! SSH is safer and a more modern protocol than Telnet is.&lt;/p&gt;

&lt;h1&gt;
  
  
  The How
&lt;/h1&gt;

&lt;p&gt;For this, I went with &lt;a href="https://github.com/charmbracelet/wish"&gt;charmbracelet/wish&lt;/a&gt;, which makes it easier to build SSH servers in Golang. Given it already has a middleware for the awesome &lt;a href="https://github.com/charmbracelet/bubbletea"&gt;charmbracelet/bubbletea&lt;/a&gt; TUI framework, this just made the port much easier to implement. On top of that, I used &lt;a href="https://github.com/charmbracelet/lipgloss"&gt;charmbracelet/lipgloss&lt;/a&gt; to do the styling and coloring.&lt;/p&gt;

&lt;h1&gt;
  
  
  Implementation
&lt;/h1&gt;

&lt;p&gt;Nyan Cat C implementation consists of multiple &lt;a href="https://github.com/klange/nyancat/blob/master/src/animation.c"&gt;frames&lt;/a&gt; that then get translated to ANSI256 colors. Each character in these frames represents a color which then gets rendered as a background of 2 whitespace characters within a &lt;a href="https://github.com/klange/nyancat/blob/master/src/nyancat.c#L385"&gt;90-millisecond&lt;/a&gt; interval (the default).&lt;/p&gt;

&lt;p&gt;All that is done after the program determines the terminal size and whether or not to crop the frames before they are being rendered. Thankfully, I didn’t need to do any of those calculations, thanks to &lt;a href="https://github.com/NARKOZ"&gt;NARKOZ&lt;/a&gt; port of &lt;a href="https://github.com/NARKOZ/go-nyancat"&gt;Nyan Cat in Golang&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/charmbracelet/bubbletea"&gt;Bubbletea&lt;/a&gt; is a framework based on &lt;a href="https://guide.elm-lang.org/architecture/"&gt;The ELM Architecture&lt;/a&gt; which basically means there are 3 main components, a model struct, an update function, and a view function. In this case, the model will have the terminal size (number of rows and columns), the program start time, and the frame index that should be rendered next. The update function waits for the 90-millisecond interval to pass before it can increment the frame index in the model. Which then triggers the view function to draw the contents in the terminal window.&lt;/p&gt;

&lt;h2&gt;
  
  
  SSH Server
&lt;/h2&gt;

&lt;p&gt;Here comes &lt;a href="https://github.com/charmbracelet/wish"&gt;charmbracelet/wish&lt;/a&gt; into play. It makes creating SSH applications in Golang super easy. Just wrap your application in a Wish middleware that handles SSH operations like commands, public key or password access, users, etc.&lt;/p&gt;

&lt;p&gt;The server works by receiving requests from clients, reading the client’s terminal window size, creating a Bubbletea program for that session with the given window size, and finally rendering the program to the SSH session.&lt;/p&gt;

&lt;h1&gt;
  
  
  Where To Find?
&lt;/h1&gt;

&lt;p&gt;You can find this project on Github &lt;a href="https://github.com/aymanbagabas/nyancatsh"&gt;aymanbagabas/nyancatsh&lt;/a&gt;. You could also install it using &lt;code&gt;go install github.com/aymanbagabas/nyancatsh@latest&lt;/code&gt;. If you want the non-ssh version that only uses Bubbletea &lt;code&gt;go install github.com/aymanbagabas/nyancatsh/cmd/nyancat@latest&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Credits
&lt;/h1&gt;

&lt;p&gt;This wouldn’t happen without these awesome projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/klange/nyancat"&gt;https://github.com/klange/nyancat&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/NARKOZ/go-nyancat"&gt;https://github.com/NARKOZ/go-nyancat&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/charmbracelet/wish"&gt;https://github.com/charmbracelet/wish&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/charmbracelet/bubbletea"&gt;https://github.com/charmbracelet/bubbletea&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/charmbracelet/lipgloss"&gt;https://github.com/charmbracelet/lipgloss&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ssh</category>
      <category>terminal</category>
      <category>go</category>
      <category>tui</category>
    </item>
    <item>
      <title>Run native C/C++ code in the browser using Emscripten</title>
      <dc:creator>Ayman Bagabas</dc:creator>
      <pubDate>Wed, 18 Nov 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/aymanbagabas/run-native-cc-code-in-the-browser-using-emscripten-2574</link>
      <guid>https://dev.to/aymanbagabas/run-native-cc-code-in-the-browser-using-emscripten-2574</guid>
      <description>&lt;p&gt;While working on &lt;a href="https://aymanbagabas.com/wmidumpper/"&gt;WMIDumpper&lt;/a&gt;, a simple tool that analyzes ACPI WMI blocks, I had to figure out how to implement &lt;a href="https://github.com/pali/bmfdec"&gt;bmfdec&lt;/a&gt; in JavaScript. My first thought was to port it to JavaScript and put in the time and effort to rewrite ~1500 lines of C code in JS. But then a light bulb went on in my head, WebAssembly! A quick search showed that &lt;a href="https://emscripten.org/"&gt;Emscripten&lt;/a&gt; is exactly what I need. It can compile C/C++ native code into WebAssembly and run it on the web.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building native code into WebAssembly/JS
&lt;/h2&gt;

&lt;p&gt;I cloned &lt;a href="https://github.com/pali/bmfdec"&gt;bmfdec&lt;/a&gt; and followed Emscripten &lt;a href="https://emscripten.org/docs/index.html"&gt;documentation&lt;/a&gt; on how to compile the project into &lt;code&gt;wasm&lt;/code&gt;. Building &lt;a href="https://github.com/pali/bmfdec"&gt;bmfdec&lt;/a&gt; was straightforward using Emscripten compiler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;emcc bmfdec/bmf2mof.c -o bmf2mof.js

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

&lt;/div&gt;



&lt;p&gt;There was one problem though, &lt;a href="https://github.com/pali/bmfdec"&gt;bmfdec&lt;/a&gt; was written to take input from stdin as a binary file. I had to modify &lt;code&gt;bmfdec.c&lt;/code&gt; and change the &lt;code&gt;main()&lt;/code&gt; function to take a buffer instead of reading binary from stdin and called it &lt;code&gt;parse_data()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int parse_data(uint8_t *pin, ssize_t lin) {
  static char pout[0x40000];
  int lout;
  if (lin &amp;lt; 0) {
    fprintf(stderr, "Failed to read data: %s\n", strerror(errno));
    return 1;
  } else if (lin == sizeof(pin)) {
    fprintf(stderr, "Failed to read data: %s\n", strerror(EFBIG));
    return 1;
  }
  if (lin &amp;lt;= 16 || ((uint32_t*)pin)[0] != 0x424D4F46 || ((uint32_t*)pin)[1] != 0x01 || ((uint32_t*)pin)[2] != (uint32_t)lin-16 || ((uint32_t*)pin)[3] &amp;gt; sizeof(pout)) {
    fprintf(stderr, "Invalid input\n");
    return 1;
  }
  lout = ((uint32_t*)pin)[3];
  if (ds_dec((char *)pin+16, lin-16, pout, lout, 0) != lout) {
    fprintf(stderr, "Decompress failed\n");
    return 1;
  }
  return process_data(pout, lout);
}

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Exporting native functions to JavaScript
&lt;/h2&gt;

&lt;p&gt;To be able to call C functions within JavaScript, we have to compile &lt;code&gt;bmf2mof&lt;/code&gt; with some extra flags to modularize, and export symbols to the JS output file.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;MODULARIZE&lt;/code&gt; compiler flag makes the generated JavaScript modular where you can use promises and &lt;code&gt;require()&lt;/code&gt; in Node. &lt;code&gt;EXPORT_NAME='bmf2mof'&lt;/code&gt; compiler flag changes the exported module name, in this case, it would be named &lt;code&gt;bmf2mof()&lt;/code&gt;. &lt;code&gt;WASM=1&lt;/code&gt; specifies that we want a wasm output. And finally &lt;code&gt;"EXPORTED_FUNCTIONS=['_parse_data']"&lt;/code&gt; exports the function &lt;code&gt;parse_data&lt;/code&gt; from the C code. We also want to &lt;a href="https://emscripten.org/docs/optimizing/Optimizing-Code.html"&gt;optimize&lt;/a&gt; the output JS code so we will use &lt;code&gt;-O2&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;emcc bmfdec/bmf2mof.c -s "EXPORTED_FUNCTIONS=['_parse_data']" -s "MODULARIZE=1" -s "EXPORT_NAME='bmf2mof'" -s "WASM=1" -O2 -o bmf2mof.js

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

&lt;/div&gt;



&lt;p&gt;Now the generated &lt;code&gt;bmf2mof.js&lt;/code&gt; will have a &lt;code&gt;_parse_data&lt;/code&gt; function that maps to the C function and can be called from the JavaScript code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const bmf2mof = require('bmf2mof.js')

const buf = new Uint8Array([0x46, 0x4F, 0x4D, 0x42, ..., 0x20, 0xEC, 0xFF, 0x0F])

bmf2mof().then(instance =&amp;gt; {
    function arrayToPtr(array) {
        var ptr = instance._malloc(array.length)
        instance.HEAPU8.set(array, ptr)
        return ptr
    }

    instance._parse_data(arrayToPtr(buf), buf.length)
})

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

&lt;/div&gt;



&lt;p&gt;Everything works as expected, running the code above &lt;code&gt;node [filename].js&lt;/code&gt; outputs the data to stdout. However, if we want to use this in the browser, the output would go to the console. So we have to figure out a way to catch the output and redirect it to where we want, in this case, it would be a textarea in &lt;a href="https://github.com/aymanbagabas/wmidumpper"&gt;WMIDumpper&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Emscripten documentation was a bit lacking when it comes to this, the generated JS file, so I took the time to try and understand what it does. It turned out that the generated JS defines some default functions to handle the mapping from C/C++ to JS. For example, it defines a &lt;code&gt;printErr&lt;/code&gt; function that binds to &lt;code&gt;console.warn&lt;/code&gt; meaning C/C++ code that prints to stderr would use &lt;code&gt;console.warn&lt;/code&gt; in the JS. See &lt;a href="https://emscripten.org/docs/api_reference/module.html#creating-the-module-object"&gt;Create the Module object&lt;/a&gt; for more technical details.&lt;/p&gt;

&lt;p&gt;The generated JS takes an object that overrides the default module functions. To redirect our output from stdout to the textarea in the HTML, all we need to do is define our own &lt;code&gt;print&lt;/code&gt; function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const textarea = document.getElementById('textarea')

bmf2mof({
    print: function (text) {
        textarea.value += text + '\n'
    }
}).then(instance =&amp;gt; ...)

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

&lt;/div&gt;



&lt;p&gt;And of course, we need to source the generated JS script in the HTML before we can use this code in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;p&gt;You can find the WebAssembly forked version of &lt;a href="https://github.com/pali/bmfdec"&gt;bmfdec&lt;/a&gt; at &lt;a href="https://github.com/aymanbagabas/bmf2mof.wasm"&gt;bmf2mof.wasm&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/aymanbagabas/wmidumpper"&gt;WMIDumpper&lt;/a&gt; really is just a web clone of &lt;a href="https://github.com/iksaif/wmidump"&gt;wmidump&lt;/a&gt; and &lt;a href="https://github.com/pali/bmfdec"&gt;bmfdec&lt;/a&gt; so kudos to &lt;a href="https://github.com/iksaif"&gt;iksaif&lt;/a&gt; and &lt;a href="https://github.com/pali"&gt;pali&lt;/a&gt; for their awesome work.&lt;/p&gt;

</description>
      <category>c</category>
      <category>cpp</category>
      <category>emscripten</category>
      <category>webassembly</category>
    </item>
    <item>
      <title>Dynamic DNS using DDclient</title>
      <dc:creator>Ayman Bagabas</dc:creator>
      <pubDate>Sat, 16 Feb 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/aymanbagabas/dynamic-dns-using-ddclient-45o7</link>
      <guid>https://dev.to/aymanbagabas/dynamic-dns-using-ddclient-45o7</guid>
      <description>&lt;p&gt;&lt;a href="http://ddclient.sf.net/" rel="noopener noreferrer"&gt;DDclient&lt;/a&gt; is a service used to update dynamic DNS entries on many services. It is useful if you need a DDNS client that can work with pretty much any DNS service. Most distributions provide &lt;code&gt;DDclient&lt;/code&gt; in their official repositories. It is available on Debian-based systems, Fedora, Archlinux, and many more.&lt;/p&gt;

&lt;p&gt;In my case, I need &lt;code&gt;DDclient&lt;/code&gt; for my &lt;a href="https://ownyourbits.com/nextcloudpi/" rel="noopener noreferrer"&gt;NextcloudPi&lt;/a&gt; server that runs on a Raspberry Pi 3B hooked up to a storage device. This server gives me access to my files anywhere anytime as long as it has a working internet connection. I’m using a free DNS service from &lt;a href="https://www.dynu.com/" rel="noopener noreferrer"&gt;Dynu&lt;/a&gt; and in their &lt;a href="https://www.dynu.com/DynamicDNS/IPUpdateClient/DDClient" rel="noopener noreferrer"&gt;website&lt;/a&gt;, they go in details of how you would set up dynamic DNS using &lt;code&gt;DDclient&lt;/code&gt;. It turns out that they use a &lt;code&gt;dyndns2&lt;/code&gt; protocol by &lt;a href="http://www.dyndns.com" rel="noopener noreferrer"&gt;www.dyndns.com&lt;/a&gt; to provide this service.&lt;/p&gt;

&lt;p&gt;Now after installing &lt;code&gt;DDclient&lt;/code&gt;, the client can configure it under &lt;code&gt;/etc/ddclient.conf&lt;/code&gt; where you can set the update interval, server address (in this case dynu.com), username and password, etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;protocol=dyndns2
pid=/var/run/ddclient.pid
ssl=yes
use=web, web=checkip.dynu.com/, web-skip='IP Address'
server=api.dynu.com
login=[YOUR_DYNU_USERNAME]
password='[YOUR_PASSWORD]'
[YOUR_DNS_ADDRESS]

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

&lt;/div&gt;



&lt;p&gt;As you can see I’m using dynu.com server. Different DNS services would have different configurations.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>dns</category>
    </item>
    <item>
      <title>Writing a Chip-8 emulator</title>
      <dc:creator>Ayman Bagabas</dc:creator>
      <pubDate>Mon, 17 Sep 2018 00:00:00 +0000</pubDate>
      <link>https://dev.to/aymanbagabas/writing-a-chip-8-emulator-2dma</link>
      <guid>https://dev.to/aymanbagabas/writing-a-chip-8-emulator-2dma</guid>
      <description>&lt;p&gt;If you ever played retro games on modern computers, then you probably know what an emulator is. Chip-8 is an interpreted programming language that was created originally by &lt;a href="https://en.wikipedia.org/wiki/Joseph_Weisbecker"&gt;Joseph Weisbecker&lt;/a&gt;. Chip-8 programs get interpreted by a virtual machine. It offers a very simple monochrome graphics and uses a 4Kb of memory. It has the “8” part because all the system’s components, like CPU registers, have a size of 8 bits or 1 byte.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an Emulator?
&lt;/h2&gt;

&lt;p&gt;The idea of an emulator is the same across any operating system. You basically try to translate the instructions of one system to another. The process of translation is done in three main parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetching opcodes&lt;/li&gt;
&lt;li&gt;Decoding opcodes&lt;/li&gt;
&lt;li&gt;Executing opcodes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These three steps happen in the CPU which is the main component of a system. The first step is to read operation codes from the program, then the CPU tries to decode these codes and then executes them accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chip-8
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Cxng2ltO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/aymanbagabas/C8emu/raw/master/shot1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Cxng2ltO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/aymanbagabas/C8emu/raw/master/shot1.png" alt="Space Invaders" width="880" height="535"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Space Invaders&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Before you get very excited, you really have to understand how the system works and behaves. Get familiar with your binary and hexadecimal conversions. A hex viewer may come handy when debugging a program.&lt;/p&gt;
&lt;h3&gt;
  
  
  CPU and Memory
&lt;/h3&gt;

&lt;p&gt;Chip-8 is a simple system that has 16 CPU registers, each takes information up to 8 bits (1 byte). The program counter, I register, opcode placeholder, and a stack pointer, all have a size of 16 bits (2 bytes). Memory is a 4Kb memory where the first 512Kb is reserved for the interpreter which, this makes most programs written for the Chip-8 start at location 512. A minimal of 16 level stack pointer is required and it is used to store return locations from the program counter register.&lt;/p&gt;
&lt;h3&gt;
  
  
  Timers
&lt;/h3&gt;

&lt;p&gt;There are two timers both countdown from 60 to 0. Delay timer is used for program events and its value can be set and read. And a sound timer which plays a beep whenever it reaches 0. After every operation execution, both timers get subtracted by 1.&lt;/p&gt;
&lt;h3&gt;
  
  
  Input
&lt;/h3&gt;

&lt;p&gt;The Chip-8 uses 16 keys of input, (0x0-0xF) which are usually mapped to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 2 3 C 1 2 3 4
4 5 6 D --\ Q W E R
7 8 9 E --/ A S D F
A 0 B F Z X C V

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

&lt;/div&gt;



&lt;p&gt;Usually ‘2’, ‘4’, ‘8’, and ‘6’ are used for directions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Graphics
&lt;/h3&gt;

&lt;p&gt;The Chip-8 has a 32x64 pixels monochrome display. It draws graphics on the screen using sprites. “A sprite is a group of bytes which are a binary representation of the desired picture.” And they may take up to 15 bytes. Chip-8 provides a set of predefined sprites for representing hexadecimal digits from 0 to F. 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;  "1" | Binary | Hex
------+----------+-----
  * | 00100000 | 0x20
 ** | 01100000 | 0x60
  * | 00100000 | 0x20
  * | 00100000 | 0x20
 *** | 01110000 | 0x70

  "F" | Binary | Hex
------+----------+-----
**** | 11110000 | 0xF0
* | 10000000 | 0x80
**** | 11110000 | 0xF0
* | 10000000 | 0x80
* | 10000000 | 0x80

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

&lt;/div&gt;



&lt;p&gt;These sprites should be stored in the reserved interpreter area 0-512 of memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;Start with the big picture, how does your computer works? After you boot your computer it starts initializing components and devices. This includes inputs, outputs, graphics, CPU, etc. Then it loads the system and starts executing instructions sequentially. The main loop should look something 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;
#include // Chip-8 system
#include // Input
#include // Graphics

int main(int argc, char **argv) {
    initChip8();
    initInput();
    initGraphics();

    loadROM("INVADERS");

    while(systeIsRunning) {
        // Execute instruction
        executeOP();

        // Refresh display if flag is set
        if (drawFlag)
            drawGraphics();

        // Play beep if flag is set
        if (playSound)
            playBeep();

        // Set input and set keys states
        setInput();
    }
    return 0;
}

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

&lt;/div&gt;



&lt;p&gt;Chip-8 should implement ways to load and execute instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
void initChip8() {
    // initialize system
    // memory
    // registers
    // stack
    // graphics
}

void loadROM(file) {
    // read file into memory
    // starting from location 512Kb
    // or 0x200 in hex
}

void executeOP() {
    // fetch opcode
    opcode = (memory[pc] &amp;lt;&amp;lt; 8) + memory[pc + 1];
    // decode opcode
    switch (opcode &amp;amp; 0xF000) {
        // execute opcode
        case 0x0:
            if (opcode == 0x00E0)
                clearDisplay();
            elseif (opcode == 0x00EE)
                // return from subroutine
        case 0x1:
        .
        .
        .
        case 0xF:
    }

    // timers
    if (delay_timer &amp;gt; 0)
        --delay_timer;
    if (sound_timer &amp;gt; 0) {
        if (sound_timer == 1)
            playSound();
        --sound_timer;
    }
}

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

&lt;/div&gt;



&lt;p&gt;Input and graphics depend on the libraries you will be using. I went with &lt;a href="https://www.libsdl.org/"&gt;SDL2&lt;/a&gt; for these two since it is cross-platform, very well known, and has a lot of documentation. You can refer to my Chip-8 implementation &lt;a href="https://github.com/aymanbagabas/C8emu"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What next?
&lt;/h2&gt;

&lt;p&gt;Well, I really learned a lot from this project. Next, I want to extend this project to have a terminal based UI using &lt;a href="https://www.gnu.org/s/ncurses/"&gt;ncurses&lt;/a&gt;. Or maybe work on a more complex system like the NES?&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/"&gt;How to write an emulator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://devernay.free.fr/hacks/chip8/C8TECH10.HTM"&gt;Cowgod’s Chip-8 Technical Reference v1.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/CHIP-8"&gt;CHIP-8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://lazyfoo.net/tutorials/SDL/index.php"&gt;Lazy Foo’ SDL2 Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cpp</category>
      <category>sdl2</category>
      <category>chip8</category>
      <category>emulation</category>
    </item>
    <item>
      <title>Arch Linux on Matebook X Pro</title>
      <dc:creator>Ayman Bagabas</dc:creator>
      <pubDate>Mon, 23 Jul 2018 00:00:00 +0000</pubDate>
      <link>https://dev.to/aymanbagabas/arch-linux-on-matebook-x-pro-4jme</link>
      <guid>https://dev.to/aymanbagabas/arch-linux-on-matebook-x-pro-4jme</guid>
      <description>&lt;p&gt;Recently, I got a new laptop, Huawei Matebook X Pro. It has an i7 8th Gen. Intel CPU, 16Gib of RAM, 512Gib of SSD storage, Nvidia MX150 GPU with 2Gib of DRAM, and a beautiful HiDPI and touchscreen. It also comes with a fingerprint sensor. The first thing I did was installing Arch Linux, because well I am a Linux user! In the end, everything was working properly except:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;del&gt;Two out of four speakers.&lt;/del&gt; See UPDATE1 down below&lt;/li&gt;
&lt;li&gt;The fingerprint sensor.&lt;/li&gt;
&lt;li&gt;
&lt;del&gt;Some keys in the Fn row.&lt;/del&gt; See UPDATE2&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The installation was very straightforward and like every other Arch Linux installation process. I followed their &lt;a href="https://wiki.archlinux.org/index.php/Installation_guide" rel="noopener noreferrer"&gt;installation guide&lt;/a&gt;. After the installation is complete, everything was working properly except for some minor issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;First, disable secure boot from the BIOS menu by pressing F2 while booting. Then have your Arch Linux installation media ready, then you can access the boot menu by holding F12 while booting. Once you boot into Arch Linux live boot image, you will notice that the font is too small, you can change that to use a large font with &lt;code&gt;setfont latarcyrheb-sun32&lt;/code&gt;, or you could use any other font you like.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-lib repositories
&lt;/h3&gt;

&lt;p&gt;You probably want to enable multi-lib repos to support 32-bit software. Just uncomment that in &lt;code&gt;/etc/pacman.conf&lt;/code&gt; or add 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;[multilib]
Include = /etc/pacman.d/mirrorlist

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Grub
&lt;/h3&gt;

&lt;p&gt;I used &lt;a href="https://wiki.archlinux.org/index.php/GRUB" rel="noopener noreferrer"&gt;Grub&lt;/a&gt; for the bootloader. Obviously, you want to use Grub for UEFI systems. For the ESP location, I had mine set to &lt;code&gt;/boot/efi&lt;/code&gt; just to follow other Linux distors approach. Because of the HiDPI screen that comes with this laptop, Grub would very tiny to see, a quick fix is to set the &lt;code&gt;GRUB_GFXMODE&lt;/code&gt; variable to something like &lt;code&gt;1600x1200x32&lt;/code&gt;. The available values can be fetched from Grub command line by executing &lt;code&gt;videoinfo&lt;/code&gt;. Edit your &lt;code&gt;/etc/default/grub&lt;/code&gt; file to include these lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GRUB_GFXMODE=1600x1200x32
GRUB_GFXPAYLOAD_LINUX=keep

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

&lt;/div&gt;



&lt;p&gt;If you are dual-booting you should install the package &lt;code&gt;os-prober&lt;/code&gt; to make Grub detect Windows partitions. don’t forget to regenerate &lt;code&gt;grub.cfg&lt;/code&gt; using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo grub-mkconfig -o /boot/grub/grub.cfg

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  After Installation
&lt;/h3&gt;

&lt;p&gt;Because I have a dual-boot setup with Windows, I ran into a little problem after partitioning Windows partition. However, it was easily fixed using &lt;code&gt;ntfs-3g&lt;/code&gt; package which includes the tool &lt;code&gt;ntfsfix&lt;/code&gt;. After you complete the installation and boot into Arch, just run the tool with &lt;code&gt;-b&lt;/code&gt; to fix bad sectors and &lt;code&gt;-d&lt;/code&gt; to clear dirty flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo ntfsfix -b -d /dev/nvme0n1p3

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

&lt;/div&gt;



&lt;p&gt;In my case, my Windows partition was &lt;code&gt;/dev/nvme0n1p3&lt;/code&gt;, you should change that based on your partition name. You can use &lt;code&gt;lsblk&lt;/code&gt; or &lt;code&gt;blkid&lt;/code&gt; to list all your partitions.&lt;/p&gt;

&lt;h3&gt;
  
  
  AUR helper
&lt;/h3&gt;

&lt;p&gt;One of Arch Linux beauties is AUR, where you can easily get any Linux package installed with ease. I used the tool &lt;code&gt;aurman&lt;/code&gt; which is IMO one of the safest ones out there. Here is the full list of &lt;a href="https://wiki.archlinux.org/index.php/AUR_helpers" rel="noopener noreferrer"&gt;AUR helpers&lt;/a&gt;. To install aurman:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://aur.archlinux.org/aurman.git
cd aurman
makepkg -si

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Desktop Environment
&lt;/h3&gt;

&lt;p&gt;Since this device comes with a touch-enabled and HiDPI screen, I decided to go with Gnome because it supports these two things pretty well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo pacman -S gnome gnome-extra

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

&lt;/div&gt;



&lt;p&gt;This would automatically install Gnome with Wayland. Wayland doesn’t require any further configuration or drivers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Nvidia driver &amp;amp; Bumblebee
&lt;/h3&gt;

&lt;p&gt;The MBXP comes with Intel UHD Graphics 620 and NVIDIA Geforce MX150. If you are planning to use the &lt;a href="https://wiki.archlinux.org/index.php/NVIDIA" rel="noopener noreferrer"&gt;Nvidia&lt;/a&gt; card for gaming, rendering, or anything your best two options are using Prime technology with &lt;a href="https://wiki.archlinux.org/index.php/Nouveau" rel="noopener noreferrer"&gt;Nouveau&lt;/a&gt; (open source NVIDIA driver), or use NVIDIA proprietary driver with &lt;a href="https://wiki.archlinux.org/index.php/Bumblebee" rel="noopener noreferrer"&gt;Bumblebee&lt;/a&gt;. I decided to go with the later because it offers better performance. Install Bumblebee&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo pacman -S bumblebee bbswitch nvidia mesa acpi_call lib32-virtualgl lib32-nvidia-utils

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

&lt;/div&gt;



&lt;p&gt;Enable Bumblebee service and add user to Bumblebee group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl enable bumblebeed.service
sudo gpasswd -a $USER bumblebee

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

&lt;/div&gt;



&lt;p&gt;You probably need to &lt;a href="https://wiki.archlinux.org/index.php/Bumblebee#Default_power_state_of_NVIDIA_card_using_bbswitch" rel="noopener noreferrer"&gt;Enable NVIDIA card during shutdown&lt;/a&gt; to avoid issues with using it. Now we need to tell Bumblebee to use bbswitch for card switching. Edit &lt;code&gt;/etc/bumblebee/bumblebee.conf&lt;/code&gt; to include this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[driver-nvidia]
PMMethod=bbswitch

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

&lt;/div&gt;



&lt;p&gt;Sometimes Bumblebee doesn’t detect the card which results in an error. You need to define the card BusID in &lt;code&gt;/etc/bumblebee/xorg.conf.nvidia&lt;/code&gt;, just uncomment the line where it has “BusID” and set it to the actual device ID. You can get that using &lt;code&gt;lspci&lt;/code&gt;. In my case, it was “PCI:01:00:0”.&lt;/p&gt;

&lt;h3&gt;
  
  
  Power Saving
&lt;/h3&gt;

&lt;p&gt;I was able to get a great 8-10 hours of battery with low brightness. I am using &lt;a href="https://wiki.archlinux.org/index.php/TLP" rel="noopener noreferrer"&gt;TLP&lt;/a&gt; for managing &lt;a href="https://wiki.archlinux.org/index.php/Power_management" rel="noopener noreferrer"&gt;power saving&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  TLP
&lt;/h4&gt;

&lt;p&gt;Install TLP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo pacman -S tlp tlp-rdw

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

&lt;/div&gt;



&lt;p&gt;Enable TLP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl enable tlp.service
sudo systemctl enable tlp-sleep.service
sudo systemctl mask systemd-rfkill.service
sudo systemctl mask systemd-rfkill.socket
sudo systemctl enable NetworkManager-dispatcher.service

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

&lt;/div&gt;



&lt;p&gt;And since TLP enables NetworkManager by default, there is no need to enable that. You want to set TLP default mode to the battery. Edit &lt;code&gt;/etc/default/tlp&lt;/code&gt; to have these settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Operation mode when no power supply can be detected: AC, BAT.
TLP_DEFAULT_MODE=BAT

# Operation mode select: 0=depend on power source, 1=always use TLP_DEFAULT_MODE
TLP_PERSISTENT_DEFAULT=1

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

&lt;/div&gt;



&lt;p&gt;Also since we are using Bumblebee with Nvidia you have to make sure that TLP doesn’t enable power management for the card which can break auto switching with Bumblebee. You should avoid using &lt;code&gt;powertop --auto-tune&lt;/code&gt; since it enables power management resulting in breaking Bumblebee. Make sure you have these lines in &lt;code&gt;/etc/default/tlp&lt;/code&gt; to exclude the Nvidia card from power management:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RUNTIME_PM_BLACKLIST="01:00.0"
RUNTIME_PM_DRIVER_BLACKLIST="amdgpu nouveau nvidia radeon"

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

&lt;/div&gt;



&lt;p&gt;Again make sure that you have the correct bus id for your card.&lt;/p&gt;

&lt;h4&gt;
  
  
  Audio
&lt;/h4&gt;

&lt;p&gt;In &lt;code&gt;/etc/modprobe.d/audio_powersave.conf&lt;/code&gt; add to enable audio power saving:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;options snd_hda_intel power_save=1

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  WiFi
&lt;/h4&gt;

&lt;p&gt;Since this laptop comes with Intel Wireless 8265, we can use &lt;code&gt;iwlwifi&lt;/code&gt; power saving options. Add these options in &lt;code&gt;/etc/modprobe.d/iwlwifi.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;options iwlwifi power_save=1 d0i3_disable=0 uapsd_disable=0
options iwldvm force_cam=0

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Intel GPU
&lt;/h4&gt;

&lt;p&gt;Power saving options for Intel GPU, just stick this line in &lt;code&gt;/etc/modprobe.d/i915.conf&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;options i915 enable_guc=3 enable_fbc=1

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Suspend and Hibernate
&lt;/h4&gt;

&lt;p&gt;Personally, I prefer hibernating my machine whenever I do not use it for a long time, that is why I use &lt;a href="https://aymanbagabas.com/2018/07/18/suspend-then-hibernate.html" rel="noopener noreferrer"&gt;suspend-then-hibernate&lt;/a&gt;. Suspend to RAM works out of the box, but &lt;a href="https://wiki.archlinux.org/index.php/Power_management/Suspend_and_hibernate#Hibernation" rel="noopener noreferrer"&gt;hibernate&lt;/a&gt; requires some work. First, make sure you have either a swap partition or &lt;a href="https://wiki.archlinux.org/index.php/Swap#Swap_file" rel="noopener noreferrer"&gt;swap file&lt;/a&gt;. I went with swap file just because it does not require partitioning. According to Redhat &lt;a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/installation_guide/sect-disk-partitioning-setup-x86#sect-recommended-partitioning-scheme-x86" rel="noopener noreferrer"&gt;Recommended System Swap Space&lt;/a&gt;, 1.5 of system RAM is the recommended amount of swap for hibernation which is 24Gb in this case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo fallocate -l 24G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

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

&lt;/div&gt;



&lt;p&gt;Now define that within &lt;code&gt;/etc/fstab&lt;/code&gt; for auto mounting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/swapfile none swap defaults 0 0

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

&lt;/div&gt;



&lt;p&gt;In order for hibernate to work, you have to define where the system should look for the resume image in your Linux partition. You can define that in the kernel parameter in your bootloader &lt;code&gt;resume=UUID=ce6dd35a-08d5-4b49-a46c-eff1de8937ce&lt;/code&gt;. Here I am using partition UUID, you can get that with &lt;code&gt;sudo blkid&lt;/code&gt;. Since I am using a swap file, I also have to define a &lt;code&gt;resume_offset=645120&lt;/code&gt; which is the location of the swap file in the partition. You can get that using &lt;code&gt;sudo filefrag -v /swapfile&lt;/code&gt;. Finally, add &lt;code&gt;resume&lt;/code&gt; hook after &lt;code&gt;udev&lt;/code&gt; and &lt;code&gt;i915&lt;/code&gt; module in &lt;code&gt;/etc/mkinitcpio.conf&lt;/code&gt;. Regenerate initramfs &lt;code&gt;sudo mkinitcpio -P&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  MISC
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Add &lt;code&gt;pcie_aspm=force&lt;/code&gt; kernel parameter to enable ASPM (Active State Power Management).&lt;/li&gt;
&lt;li&gt;Disable watchdog, add &lt;code&gt;blacklist iTCO_wdt&lt;/code&gt; to &lt;code&gt;/etc/modprobe.d/nowatchdog.conf&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Take a look at &lt;a href="https://wiki.archlinux.org/index.php/Improving_performance" rel="noopener noreferrer"&gt;Improving Performance&lt;/a&gt; and &lt;a href="https://wiki.archlinux.org/index.php/Power_management" rel="noopener noreferrer"&gt;Power Management&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Regenerate initramfs &lt;code&gt;sudo mkinitcpio -P&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Regenerate grub.cfg &lt;code&gt;sudo grub-mkconfig -o /boot/grub/grub.cfg&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Install Plymouth.&lt;/li&gt;
&lt;li&gt;Enable auto-brightness &lt;code&gt;aurman -S iio-sensor-proxy&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Files
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Config files used in this post &lt;a href="https://aymanbagabas.com/assets/stuff/etc.zip" rel="noopener noreferrer"&gt;etc.zip&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  UPDATE 1 - fix sound
&lt;/h3&gt;

&lt;p&gt;You can fix the sound issue with &lt;code&gt;hdajackretask&lt;/code&gt; which is part of &lt;code&gt;alsa-tools&lt;/code&gt; package then follow this picture and click on “Install boot override”:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ijh1yQnm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://aymanbagabas.com/assets/images/hdajackretask.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ijh1yQnm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://aymanbagabas.com/assets/images/hdajackretask.png" alt="HDAJackReTask" width="644" height="732"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;HDAJackReTask&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You might need to set “connectivity” to “internal” to get it working. Finally, recreate your initramfs &lt;code&gt;sudo mkinitcpio -P&lt;/code&gt; and reboot.&lt;/p&gt;

&lt;h3&gt;
  
  
  UPDATE 2
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Missing hotkeys &lt;code&gt;micmute, wlan, and pc manager&lt;/code&gt; now work using &lt;a href="https://github.com/aymanbagabas/Huawei-WMI" rel="noopener noreferrer"&gt;this&lt;/a&gt; driver. It will be part of linux 4.21 along with the speakers fix and micmute LED.&lt;/li&gt;
&lt;li&gt;Some people reported slow network connection with the above settings. To fix that, drop &lt;code&gt;uapsd_disable=0&lt;/code&gt; from &lt;code&gt;/etc/modprobe.d/iwlwifi.conf&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Also if you were using full disk encryption, don’t forget to add the speakers fix firmware files to &lt;code&gt;/etc/mkiniticpio.conf&lt;/code&gt; like this: &lt;code&gt;FILES=(/usr/lib/firmware/hda-jack-retask.fw)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linux</category>
      <category>huawei</category>
      <category>archlinux</category>
    </item>
    <item>
      <title>Suspend then hibernate in systemd 239</title>
      <dc:creator>Ayman Bagabas</dc:creator>
      <pubDate>Wed, 18 Jul 2018 00:00:00 +0000</pubDate>
      <link>https://dev.to/aymanbagabas/suspend-then-hibernate-in-systemd-239-3e3n</link>
      <guid>https://dev.to/aymanbagabas/suspend-then-hibernate-in-systemd-239-3e3n</guid>
      <description>&lt;p&gt;In &lt;em&gt;systemd&lt;/em&gt; 239, they have added a new service that handles suspending then hibernating after a given amount of time. This is easier than using external scripts since it comes built-in with this version of &lt;em&gt;systemd&lt;/em&gt;. You can check &lt;em&gt;systemd&lt;/em&gt; version with &lt;code&gt;systemctl --version&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;First, you have to define the delay time before the system wakes up and go into hibernation and that should be defined in &lt;em&gt;/etc/systemd/sleep.conf&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;[Sleep]
HibernateDelaySec=15min

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

&lt;/div&gt;



&lt;p&gt;Here, what we care about is the last line &lt;code&gt;HibernateDelaySec&lt;/code&gt; where you can define delayed time. As you see, I have it set to 15 minutes after suspending.&lt;/p&gt;

&lt;p&gt;Lastly, we need to override the default suspend to execute suspend-then-hibernate instead of regular suspend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ln -s /usr/lib/systemd/system/systemd-suspend-then-hibernate.service /etc/systemd/system/systemd-suspend.service

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

&lt;/div&gt;



&lt;p&gt;This will make systemd executes &lt;em&gt;suspend-then-hibernate&lt;/em&gt; instead of &lt;em&gt;suspend&lt;/em&gt; every time suspend is invoked.&lt;/p&gt;

&lt;h4&gt;
  
  
  Reference
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://www.freedesktop.org/software/systemd/man/systemd-sleep.conf.html" rel="noopener noreferrer"&gt;systemd-sleep.conf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.freedesktop.org/software/systemd/man/systemd-suspend-then-hibernate.service.html" rel="noopener noreferrer"&gt;systemd-suspend-then-hibernate.service&lt;/a&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>systemd</category>
    </item>
    <item>
      <title>Minesweeper using HTML5</title>
      <dc:creator>Ayman Bagabas</dc:creator>
      <pubDate>Sun, 13 May 2018 00:00:00 +0000</pubDate>
      <link>https://dev.to/aymanbagabas/minesweeper-using-html5-4goj</link>
      <guid>https://dev.to/aymanbagabas/minesweeper-using-html5-4goj</guid>
      <description>&lt;p&gt;Obviously, everyone was born before the year 2000 knows the famous classic game ‘Minesweeper’. To me, I knew this game when I was little at the time where Windows XP was ruling everywhere. It is funny because at that time I did not know exactly how the game is played. That time the game was some kind of a luck game to me where you try to eliminate all the squares except the ones with mines until you either win or lose 😆.&lt;/p&gt;

&lt;p&gt;Until recently, a friend pointed out that they saw a video of a guy who made a fully perfect AI to solve the game. I liked the idea so I decided to make one. I started by learning how the game works and it turned out to be very simple. There is a certain number of mines in the game and the player has to discover where these mines are to win the game. Every square has a weight that shows how many mines they are within the surrounding 8 squares of that particular square. If the weight was zero the game will reveal all the surrounding squares except the ones with the mines, otherwise, it will reveal the square itself. Although things did not go very well with the idea, I ended up making the game only.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does it work?
&lt;/h3&gt;

&lt;p&gt;With the help of HTML5 canvas element, you can draw geometric objects. We can achieve this dynamically using JavaScript. First, we create a canvas element in the page:&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;canvas id="example" width="500" height="500"&amp;gt;Any text here will get displayed if the browser does not support HTML5 canvas&amp;lt;/canvas&amp;gt;

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

&lt;/div&gt;



&lt;p&gt;Then we use JavaScript to create objects and control their properties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var canvas = document.getElementById("example");
var context = canvas.getContext("2d");
context.beginPath();
context.fillStyle = 'red';
context.fillRect(0, 0, 10, 10);
context.closePath();

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

&lt;/div&gt;



&lt;p&gt;This will create a square with side equals to 10 and a position of (0, 0). Easy and simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grub
&lt;/h3&gt;

&lt;p&gt;I used &lt;a href="https://wiki.archlinux.org/index.php/GRUB"&gt;Grub&lt;/a&gt; for the bootloader. Obviously, you want to use Grub for UEFI systems. For the ESP location, I had mine set to &lt;code&gt;/boot/efi&lt;/code&gt; just to follow other Linux distors approach. Because of the HiDPI screen that comes with this laptop, Grub would very tiny to see, a quick fix is to set the &lt;code&gt;GRUB_GFXMODE&lt;/code&gt; variable to something like &lt;code&gt;1600x1200x32&lt;/code&gt;. The available values can be fetched from Grub command line by executing &lt;code&gt;videoinfo&lt;/code&gt;. Edit your &lt;code&gt;/etc/default/grub&lt;/code&gt; file to include these lines:&lt;/p&gt;

&lt;h3&gt;
  
  
  Minesweeper
&lt;/h3&gt;

&lt;p&gt;Coming from an Object-Oriented Programming mentality, I wanted the ability to create classes just because I am used to it this way 😃. I was surprised when I knew that you can mimic creating &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes"&gt;classes in JavaScript&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;I started by questioning what attributes each square has? And I came up with these: isMine, isFlagged, isDown, x, y, and weight. isMine tells if a square is a mine. isFlagged is when a square is being flagged or marked. isDown if a square is revealed. x and y hold the location in the game. weight is a number greater than zero where it holds how many mines within its surrounding squares. With these attributes I came with this JS class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

class square {
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.isDown = false;
        this.isMine = false;
        this.isDown = false;
        this.weight = 0;
    }
    get x() { return this._x; }
    set x(value) { this._x = value; }
    get y() { return this._y; }
    set y(value) { this._y = value; }
    get weight() { return this._weight; }
    set weight(value) { this._weight = value; }
    get isDown() { return this._isDown; }
    set isDown(value) {
        this._isDown = value;
        if (this.isMine) {
            end = true;
        }
    }
    get isMine() { return this._isMine; }
    set isMine(value) { this._isMine = value; }
    get isFlagged() { return this._isFlagged; }
    set isFlagged(value) {
        this._isFlagged = value;
        if (value)
            numberofmines = (numberofmines &amp;lt;= 0) ? 0: numberofmines - 1;
        else
            numberofmines++;
        updateHeader();
    }
}

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

&lt;/div&gt;



&lt;p&gt;The game does not have a loop, it contains two main functions. The first one is called to initialize the game environment like setting mouse event handler. Here is where all the magic happens, whenever the user clicks the first click the timer starts and it passes the click to the game. If it was inside the game frame where the squares are located it will register a click for that square. Otherwise, it will start a new game. Which is the second function, the previous function is only called once. This one is called to clear all the previous data in the game and create a start a new game. Obviously, this is done with the help of other functions to make it a little more organized.&lt;/p&gt;

&lt;h3&gt;
  
  
  Source code
&lt;/h3&gt;

&lt;p&gt;The source code is available at &lt;a href="https://github.com/aymanbagabas/jsminesweeper"&gt;JSMinesweeper&lt;/a&gt; or you can try it out &lt;a href="https://aymanbagabas.com/jsminesweeper"&gt;here&lt;/a&gt; .&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>html</category>
      <category>css</category>
    </item>
    <item>
      <title>Sync Google calendar using Vdirsyncer and Orage</title>
      <dc:creator>Ayman Bagabas</dc:creator>
      <pubDate>Sun, 08 Apr 2018 00:00:00 +0000</pubDate>
      <link>https://dev.to/aymanbagabas/sync-google-calendar-using-vdirsyncer-and-orage-1mok</link>
      <guid>https://dev.to/aymanbagabas/sync-google-calendar-using-vdirsyncer-and-orage-1mok</guid>
      <description>&lt;p&gt;For a long time I wanted to have all my calendars and todo lists synchronized with my current desktop setup. Currently, I am using XFCE software, like Thunar, with Openbox to manage everyday stuff. Yes, I know with Gnome DE you can achieve that easily, but Gnome has a HUGE package dependencies. XFCE on the other hand is very light-weight and simple.&lt;/p&gt;

&lt;p&gt;The other wonderful piece of software is &lt;a href="https://vdirsyncer.pimutils.org"&gt;vdirsyncer&lt;/a&gt;. It supports CalDAV protocol which is also supported by Google calendar. With simple configurations we can synchronize Google calendar locally. Then we can use Orage to view/modify our calendar files. Orage is a time-managing application that can manage your calendars, appointments, alarms, and todo lists.&lt;/p&gt;

&lt;p&gt;First, make sure we have all the needed packages. Orage and Vdirsyncer should exist in most distros official repositories. In Archlinux, the installation can be done with &lt;code&gt;sudo pacman -S vdirsyncer orage&lt;/code&gt;. It would be similar for other distros as well. Ubuntu/Debian &lt;code&gt;sudo apt-get install vdirsyncer orage&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1kAN1q09--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://aymanbagabas.com/assets/images/orage1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1kAN1q09--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://aymanbagabas.com/assets/images/orage1.png" alt="Orage highlighting events" width="256" height="197"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Orage highlighting events&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Here you can see how orage highlights dates with events attached to them. For example, April 1st is Easter Sundy. That was pulled from my Google United States holidays calendar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--36Y93XrX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://aymanbagabas.com/assets/images/orage2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--36Y93XrX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://aymanbagabas.com/assets/images/orage2.png" alt="Orage displaying events" width="257" height="247"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Orage displaying events&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Vdirsyncer setup
&lt;/h3&gt;

&lt;p&gt;First, we start by configuring Vdirsyncer to sync Google calendars. My configuration file 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;[general]
status_path = "~/.vdirsyncer/status/"

[pair calendar]
a = "google_calendar"
b = "local_calendar"
collections = ["from a", "from b"]
metadata = ["color"]

[storage local_calendar]
type = "singlefile"
path = "~/.calendars/%s.ics"

[storage google_calendar]
type = "google_calendar"
token_file = "~/.vdirsyncer/google_token"
client_id = "CLIENT_ID"
client_secret = "CLIENT_SECRET"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we specified were the status for Vdirsyncer should be located. Mine is located under ~/.vdirsyncer/status. Then we tell Vdirsyncer to create a pair of calendars for bidirectional syncing, where &lt;strong&gt;a&lt;/strong&gt; is the actual Google calendar, and &lt;strong&gt;b&lt;/strong&gt; is the local files pulled from the cloud. Then we define the two calendars as storages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;storage local_calendar, here we specify what the type of this storage which is “singlefile” meaning Vdirsyncer would have a single file for each calendar that you have.&lt;/li&gt;
&lt;li&gt;storage google_calendar, here where it gets interested. The type here is “google_calendar” which means use Google cloud to fetch the calendars. With this type you have to specify a token file, client id, and client secret. All of these are essential for synchronizing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we have to enable CalDAV API for our Google account.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go &lt;a href="https://console.developers.google.com"&gt;here&lt;/a&gt; and create a new project. This is required so that you can enable CalDAV API for that project.&lt;/li&gt;
&lt;li&gt;Click on “Enable APIs and Services” or on the left side click on Library, then search for “CalDAV” and enable it.&lt;/li&gt;
&lt;li&gt;Now we need to get our credentials, client secret and id, for the API. Click on “create credentials” then choose “CalDAV API” for your API and select “Other” for Application type. Click next and choose a name e.g. “vdirsyncer” then click continue.&lt;/li&gt;
&lt;li&gt;You will get your client id, stick it into your configuration file.&lt;/li&gt;
&lt;li&gt;We still missing the client secret. Click done after you get the client id then click on the credentials name and get the client secret from there.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now we have Vdirsyncer config setup, we need to authorize it to access our Google account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vdirsyncer discover calendar

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

&lt;/div&gt;



&lt;p&gt;This will try to authorize the pair ‘calendar’ defined in the config file. A browser window or a link should pop up to complete the authorization and that would create the access token defined in the config. Now we can synchronize Google calendar by&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vdirsyncer sync

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

&lt;/div&gt;



&lt;p&gt;Read more about &lt;a href="https://vdirsyncer.pimutils.org/en/stable/index.html"&gt;Vdirsyncer&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orage setup
&lt;/h3&gt;

&lt;p&gt;Now we need to tell Orage where are the calendar ics files so that we can view them in Orage. Run Orage then go to File -&amp;gt; Exchange data, and add all the pulled ics files from ~/.calendar. Notice the read only check mark, if you want to modify your Google calendar you have to make sure to uncheck that when you add your calendar file which is the same as your Google account name “&lt;a href="mailto:google@gmail.com.ics"&gt;google@gmail.com.ics&lt;/a&gt;”. You should see all your calendars events highlighted within Orage. You can play with Orage settings to hide the window borders and tray icon. When double click on a date an events window should pop up then you can add events, todo, etc, but make sure you select your Google account file from the top bar in order to have it synchronized.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Cron to synchronize periodically
&lt;/h3&gt;

&lt;p&gt;You can add a cron to automate the sync command. &lt;code&gt;crontab -e&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MAILTO=""
@hourly vdirsyncer sync

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

&lt;/div&gt;



&lt;p&gt;This will sync calendars every hour. Make sure you have a cron service running. I am using “cronie” for my setup and with Archlinux, I had to enable the cronie service for that to happen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl enable cronie
sudo systemctl start cronie

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

&lt;/div&gt;



</description>
      <category>googlecalendar</category>
      <category>vdirsyncer</category>
      <category>caldav</category>
      <category>xfce</category>
    </item>
  </channel>
</rss>
