<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://remyg.fr/feed.xml" rel="self" type="application/atom+xml" /><link href="https://remyg.fr/" rel="alternate" type="text/html" /><updated>2026-02-18T11:49:35+01:00</updated><id>https://remyg.fr/feed.xml</id><title type="html">Rémy Gardette</title><author><name>Rémy Gardette</name></author><entry><title type="html">Mounting a host directory in a Proxmox LXC</title><link href="https://remyg.fr/blog/2026/02/18/mounting-host-directory-lxc/" rel="alternate" type="text/html" title="Mounting a host directory in a Proxmox LXC" /><published>2026-02-18T00:00:00+01:00</published><updated>2026-02-18T00:00:00+01:00</updated><id>https://remyg.fr/blog/2026/02/18/mounting-host-directory-lxc</id><content type="html" xml:base="https://remyg.fr/blog/2026/02/18/mounting-host-directory-lxc/"><![CDATA[<p>To mount a host directory in a Proxmox LXC, for example to allow access to a home directory, an USB device, an NFS mount… in the guest:</p>

<p>Create a directory to share on the host (bind mounts <em>should</em> be located in <code class="language-plaintext highlighter-rouge">/mnt/bindmounts</code>):</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> <span class="nt">-p</span> /mnt/bindmounts/shared
</code></pre></div></div>

<p>Configure the bind mount on the container (for the container with ID 100): edit <code class="language-plaintext highlighter-rouge">/etc/pve/lxc/100.conf</code> (on the host) and add:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mp0: /mnt/bindmounts/shared,mp<span class="o">=</span>/shared
</code></pre></div></div>

<p>Restart the container for the changes to take effect:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pct restart 100
</code></pre></div></div>

<p>The host directory <code class="language-plaintext highlighter-rouge">/mnt/bindmounts/shared</code> will be mounted as <code class="language-plaintext highlighter-rouge">/shared</code> in the LXC.</p>

<hr />

<p>Reference: <a href="https://pve.proxmox.com/wiki/Linux_Container#_bind_mount_points">Proxmox Documentation - Linux Container - Bind Mount Points</a></p>]]></content><author><name>Rémy Gardette</name></author><category term="Ops" /><category term="Homelab" /><category term="Note" /><category term="Proxmox" /><summary type="html"><![CDATA[How to mount a host directory in a Proxmox LXC]]></summary></entry><entry><title type="html">Setting up a fixed IP in a Debian VM</title><link href="https://remyg.fr/blog/2026/02/17/static-ip-in-debian-vm/" rel="alternate" type="text/html" title="Setting up a fixed IP in a Debian VM" /><published>2026-02-17T00:00:00+01:00</published><updated>2026-02-17T00:00:00+01:00</updated><id>https://remyg.fr/blog/2026/02/17/static-ip-in-debian-vm</id><content type="html" xml:base="https://remyg.fr/blog/2026/02/17/static-ip-in-debian-vm/"><![CDATA[<p>To assign a fixed IP to a Debian VM in Proxmox:</p>

<p>Update the file <code class="language-plaintext highlighter-rouge">/etc/netplan/50-cloud-init.yaml</code> from</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">network</span><span class="pi">:</span>
  <span class="na">version</span><span class="pi">:</span> <span class="m">2</span>
  <span class="na">ethernets</span><span class="pi">:</span>
    <span class="na">ens18</span><span class="pi">:</span>
      <span class="na">dhcp4</span><span class="pi">:</span> <span class="s">yes</span>
</code></pre></div></div>

<p>to</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">network</span><span class="pi">:</span>
  <span class="na">version</span><span class="pi">:</span> <span class="m">2</span>
  <span class="na">ethernets</span><span class="pi">:</span>
    <span class="na">ens18</span><span class="pi">:</span>
      <span class="na">dhcp4</span><span class="pi">:</span> <span class="s">no</span>
      <span class="na">addresses</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">192.168.x.x/24</span><span class="pi">]</span>
      <span class="na">routes</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">to</span><span class="pi">:</span> <span class="s">default</span>
          <span class="na">via</span><span class="pi">:</span> <span class="s">192.168.x.1</span>
</code></pre></div></div>

<p>Then restart the network interface:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ifdown ens18
ifup ens18
</code></pre></div></div>]]></content><author><name>Rémy Gardette</name></author><category term="Ops" /><category term="Homelab" /><category term="Note" /><category term="Debian" /><category term="Proxmox" /><summary type="html"><![CDATA[How to set a fixed IP on a Debian VM in Proxmox]]></summary></entry><entry><title type="html">Moving away from Instagram</title><link href="https://remyg.fr/blog/2025/02/03/moving-away-from-instagram/" rel="alternate" type="text/html" title="Moving away from Instagram" /><published>2025-02-03T00:00:00+01:00</published><updated>2025-02-03T00:00:00+01:00</updated><id>https://remyg.fr/blog/2025/02/03/moving-away-from-instagram</id><content type="html" xml:base="https://remyg.fr/blog/2025/02/03/moving-away-from-instagram/"><![CDATA[<p>I’ve always had a love-meh-hate relationship with social media. I remember when I opened a Facebook account for the first time, in 2008. I was very reluctant at the time, not seeing the point in this new fad. Why should I create an account on a new platform, when I already had a Messenger account, and had all the social activity I could ask for on phpBB forums, or (this will shock you), IRL. But I finally succumbed to peer pressure, and started the long descent into the darkness of “modern” social media.</p>

<p>In the following 15-ish years, I’ve had <em>(I almost typed “owned”, but the term would have been wrong)</em> several Facebook and Twitter accounts, that I opened and closed a few times, tried Snapchat for a while but did not enjoy it, and lastly joined Instagram with, again, two separate accounts for specific confidentiality settings.</p>

<p>From 2015 to 2022, I started enjoying less and less Facebook, because of their privacy issues, and Twitter, because of the increasing toxicity on the platform. In 2020, I closed my 2 Facebook accounts. By January 2023, I had closed my 2 Twitter accounts. But I still had my Instagram account <em>(opened in 2012)</em>, where I had a nice contact bubble, didn’t read too many comments on other posts, and led a relatively quiet online life.</p>

<p>Leaving Facebook was easy. In the last few years of my accounts, my activity decreased dramatically. I shared less and less, until it became obvious that I didn’t need an account anymore <em>(I use the term “need” consciously)</em>. I recently re-opened an account after the birth of my daughter, because some services (child care, school…) only communicate on this platform. But I’m using this account as “read-only”, never posting anything on it.</p>

<p>Leaving Twitter was a mixed feeling. I had many good years on it, met really nice people. But I also got caught in the toxic “discussions” (if you want to call them that), hateful comments… Until I couldn’t take it anymore, and was only getting stress from the platform. At that time, a Mastodon instace was opened by a buddy, and some of us joined together.</p>

<p>Today I’m at a new crossroads. In the last few months, it has become obvious that Instagram was on a downward slope. The latest declarations of Mark Zuckerberg have shined a light of what should have been obvious <em>(and probably was, for a lot of people)</em>, which is that, in the end, this network is not different from the others. The toxicity is there, always more uninhibited. Journalists, artists, are targeted for their opinions. Right-wing extremism is growing everyday. And the latest decision to basically remove moderation and fact-checking on the Meta networks is probably the nail in the coffin of whatever good remained.</p>

<p>In January of 2025, I created a Pixelfed account. Pixelfed, like Mastodon, is a decentralized social network. This means that you have to select an “instance” where you create your account. It can be linked to a location (I’ve created my account on <a href="https://pixelfed.fr">pixelfed.fr</a>, which is the main French instance), or a hobby (my Mastodon account is on <a href="https://masto.bike">masto.bike</a>, which is, you guessed it, for cycling enthusiasts). An instance is managed by 1 or more admins, has some moderators who make a wonderful job at keeping the toxicity at bay, and regroups mostly like-minded people. Of course this means that it creates a new social bubble, but do we really want to be exposed to <em>everybody</em> and <em>everything</em>? I don’t. That’s why  I’ve come to the decision to post first on Pixelfed, and maybe later on Instagram. But in the end, I think I will start using Instagram in a “read-only” mode, and at some point just stop using it altogether. This would be a major change, because Pixelfed is still a “niche” network, doesn’t have many users or content. It would mean losing a lot of social link.</p>

<p>If you decide to take the step, or already have, you can find me here:</p>

<ul>
  <li>on Mastodon: <a href="https://masto.bike/@remygardette">@remygardette@masto.bike</a></li>
  <li>on Pixelfed: <a href="https://pixelfed.fr/@remygardette">@remygardette@pixelfed.fr</a></li>
</ul>]]></content><author><name>Rémy Gardette</name></author><category term="Social" /><summary type="html"><![CDATA[My thoughts about social media, and more precisely, Instagram]]></summary></entry><entry><title type="html">Using multiple SSH keys concurrently</title><link href="https://remyg.fr/blog/2022/11/17/using-multiple-ssh-keys-concurrently/" rel="alternate" type="text/html" title="Using multiple SSH keys concurrently" /><published>2022-11-17T00:00:00+01:00</published><updated>2022-11-17T00:00:00+01:00</updated><id>https://remyg.fr/blog/2022/11/17/using-multiple-ssh-keys-concurrently</id><content type="html" xml:base="https://remyg.fr/blog/2022/11/17/using-multiple-ssh-keys-concurrently/"><![CDATA[<p><strong>Updated</strong> - Added a third solution, on a repository basis.</p>

<p>When using multiple GitLab accounts (e.g. private account and work account) on the same computer, you can’t use the same SSH key for both. This can become problematic when Git has to decide which key to use to communicate with the distant repository.</p>

<!--more-->

<p>There are 3 main solutions to this problem. You can specify the SSH key to use:</p>

<ul>
  <li>based on the distant repository host,</li>
  <li>based on the project path,</li>
  <li>or locally for a specific repository.</li>
</ul>

<p>The following examples assume that you have the 2 SSH keys <code class="language-plaintext highlighter-rouge">~/.ssh/id_rsa_perso</code> and <code class="language-plaintext highlighter-rouge">~/.ssh/id_rsa_work</code>.</p>

<h2 id="host-based-configuration">Host-based configuration</h2>

<p>The first solution requires you to change the URL of the repository when cloning it.</p>

<p>For example, when cloning a work project located at <code class="language-plaintext highlighter-rouge">https://gitlab.com/my-org/my-project</code>, the command would be:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone git@work-gitlab.com:my-org/my-project.git
</code></pre></div></div>

<p>and for a personal project located at <code class="language-plaintext highlighter-rouge">https://gitlab.com/me/my-project</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone git@home-gitlab.com:me/my-project.git
</code></pre></div></div>

<p>Then, in the SSH configuration file (<code class="language-plaintext highlighter-rouge">~/.ssh/config</code>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Personal account:
Host home-gitlab.com
  HostName gitlab.com
  User git
  IdentityFile ~/.ssh/id_rsa_perso
  PreferredAuthentications publickey
  PasswordAuthentication no
  IdentitiesOnly yes

# Work account:
Host work-gitlab.com
  HostName gitlab.com
  User git
  IdentityFile ~/.ssh/id_rsa_work
  PreferredAuthentications publickey
  PasswordAuthentication no
  IdentitiesOnly yes
</code></pre></div></div>

<p>This works fine, but it requires you to always remember to modify the project URL when cloning it.</p>

<h2 id="path-based-configuration">Path-based configuration</h2>

<p>Since Git 2.13, a conditional include is available in the local Git configuration. This means that you can include a specific configuration file when your current path matches a specific filter.</p>

<p>For example, if your work projects are located in <code class="language-plaintext highlighter-rouge">~/work/sources</code> and your personal project in <code class="language-plaintext highlighter-rouge">~/perso/sources</code>, you can create a Git work-related configuration file <code class="language-plaintext highlighter-rouge">~/.gitconfig-work</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[user]
    email = "your.email@work.com"
[core]
    sshCommand = "ssh -i ~/.ssh/id_rsa_work"
</code></pre></div></div>

<p>and a personal configuration file <code class="language-plaintext highlighter-rouge">~/.gitconfig-perso</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[user]
    email = "your.email@home.com"
[core]
    sshCommand = "ssh -i ~/.ssh/id_rsa_perso"
</code></pre></div></div>

<p>Now you only need to include the correct configuration file based on your path, by modifying the global <code class="language-plaintext highlighter-rouge">~/.gitconfig</code> file and including:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[includeIf "gitdir:~/work/sources/"]
    path = .gitconfig-work
[includeIf "gitdir:~/perso/sources/"]
    path = .gitconfig-perso
</code></pre></div></div>

<h2 id="repository-based-configuration">Repository-based configuration</h2>

<p>I recently found a third solution, to specify the key to use for a specific repository, by <a href="https://stackoverflow.com/a/41947805">Richard Smith</a>.</p>

<p>This solution sets the <code class="language-plaintext highlighter-rouge">core.sshCommand</code> property:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git config --local core.sshCommand "/usr/bin/ssh -i /home/me/.ssh/id_rsa_foo"
</code></pre></div></div>

<p>You can’t use this command when cloning a remote repository (since you don’t have a local repository). The solution was proposed by <a href="https://stackoverflow.com/a/56084858">drewbie18</a> :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone -c core.sshCommand="/usr/bin/ssh -i /home/me/.ssh/id_rsa_foo" git@github.com:me/repo.git
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>I personally find the path-based solution much easier to use, since you only need to change your configuration once, and it automatically works in the future (as long as you use the correct folders for your projects). The host-based solution requires you to always be careful to change the URL when cloning a repository. But it can be useful if you don’t want to group your projects in specific local directories.</p>]]></content><author><name>Rémy Gardette</name></author><category term="System" /><category term="SSH" /><category term="Git" /><summary type="html"><![CDATA[Updated - Added a third solution, on a repository basis. When using multiple GitLab accounts (e.g. private account and work account) on the same computer, you can’t use the same SSH key for both. This can become problematic when Git has to decide which key to use to communicate with the distant repository.]]></summary></entry><entry><title type="html">Deploying Jekyll with GitLabCI</title><link href="https://remyg.fr/blog/2019/02/27/deploying-jekyll-with-gitlabci/" rel="alternate" type="text/html" title="Deploying Jekyll with GitLabCI" /><published>2019-02-27T11:00:00+01:00</published><updated>2019-02-27T11:00:00+01:00</updated><id>https://remyg.fr/blog/2019/02/27/deploying-jekyll-with-gitlabci</id><content type="html" xml:base="https://remyg.fr/blog/2019/02/27/deploying-jekyll-with-gitlabci/"><![CDATA[<p>My blog is based on <a href="https://jekyllrb.com/" title="Jekyll">Jekyll</a>, a static website generator. This means that the pages need to be generated before they’re deployed. Until recently, I used to build the content locally using a <a href="https://hub.docker.com/r/jekyll/jekyll" title="Jekyll Docker image">Jekyll Docker image</a>, commit and push the generated content to my <a href="https://github.com/RemyG/remyg.ovh" title="Sources on GitHub">GitHub repo</a>, SSH to my web server, and pull the changes from the repo.</p>

<p>The process was quite annoying, which is why I decided to set up a CI/CD (Continuous Integration and Delivery) pipeline, using <a href="https://docs.gitlab.com/ee/ci/" title="GitLab Continuous Integration (GitLab CI/CD)">GitLabCI</a>.</p>

<!--more-->

<h2 id="pipeline">Pipeline</h2>

<p>The CI/CD pipeline I’ve implemented is the following:</p>

<p><img src="/assets/img/jekyll-gitlab-ci.png" alt="GitLabCI Pipeline" /></p>

<p>The pipeline is defined in a file <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> at the root of the project:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">image</span><span class="pi">:</span> <span class="s">ruby:2.5</span>

<span class="na">cache</span><span class="pi">:</span>
  <span class="na">key</span><span class="pi">:</span> <span class="s2">"</span><span class="s">jekyll"</span>
  <span class="na">paths</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">vendor</span>

<span class="na">variables</span><span class="pi">:</span>
  <span class="na">JEKYLL_ENV</span><span class="pi">:</span> <span class="s">production</span>
  <span class="na">LC_ALL</span><span class="pi">:</span> <span class="s">C.UTF-8</span>
  <span class="na">NOKOGIRI_USE_SYSTEM_LIBRARIES</span><span class="pi">:</span> <span class="s2">"</span><span class="s">true"</span>

<span class="na">before_script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">bundle install</span>

<span class="na">test</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">test</span>
  <span class="na">script</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">bundle exec jekyll build -d ./public</span>
  <span class="pi">-</span> <span class="s">bundle exec htmlproofer ./public --only-4xx --check-favicon --check-html --assume-extension</span>
  <span class="na">artifacts</span><span class="pi">:</span>
    <span class="na">paths</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="s">public</span>

<span class="na">deploy</span><span class="pi">:</span>
  <span class="na">stage</span><span class="pi">:</span> <span class="s">deploy</span>
  <span class="na">before_script</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s1">'</span><span class="s">which</span><span class="nv"> </span><span class="s">ssh-agent</span><span class="nv"> </span><span class="s">||</span><span class="nv"> </span><span class="s">(</span><span class="nv"> </span><span class="s">apt-get</span><span class="nv"> </span><span class="s">update</span><span class="nv"> </span><span class="s">-y</span><span class="nv"> </span><span class="s">&amp;&amp;</span><span class="nv"> </span><span class="s">apt-get</span><span class="nv"> </span><span class="s">install</span><span class="nv"> </span><span class="s">openssh-client</span><span class="nv"> </span><span class="s">-y</span><span class="nv"> </span><span class="s">)'</span>
  <span class="pi">-</span> <span class="s">eval $(ssh-agent -s)</span>
  <span class="pi">-</span> <span class="s">ssh-add &lt;(echo "$SSH_PRIVATE_KEY" | base64 -d)</span>
  <span class="pi">-</span> <span class="s">mkdir -p ~/.ssh</span>
  <span class="pi">-</span> <span class="s">chmod 700 ~/.ssh</span>
  <span class="pi">-</span> <span class="s">echo "$SSH_HOST_KEY" &gt; ~/.ssh/known_hosts</span>
  <span class="pi">-</span> <span class="s">chmod 644 ~/.ssh/known_hosts</span>
  <span class="pi">-</span> <span class="s">which rsync || ( apt-get update -y &amp;&amp; apt-get install rsync -y )</span>
  <span class="na">script</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">ssh -p$SSH_PORT $SSH_SRV "mkdir -p /tmp/jekyll"</span>
  <span class="pi">-</span> <span class="s">ssh -p$SSH_PORT $SSH_SRV "rm -rf /tmp/jekyll_old"</span>
  <span class="pi">-</span> <span class="s">ssh -p$SSH_PORT $SSH_SRV "mv /tmp/jekyll /tmp/jekyll_old"</span>
  <span class="pi">-</span> <span class="s">rsync -rav -e "ssh -p $SSH_PORT" --exclude='.git/' --exclude='.gitlab-ci.yml' --delete-excluded ./public $SSH_SRV:/tmp/jekyll</span>
  <span class="pi">-</span> <span class="s">ssh -p$SSH_PORT $SSH_SRV "sh /home/gitlabci/rsync-jekyll.sh"</span>
  <span class="na">only</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">master</span>
</code></pre></div></div>

<p>It consists of 2 steps: <code class="language-plaintext highlighter-rouge">test</code> and <code class="language-plaintext highlighter-rouge">deploy</code>.</p>

<p>The <code class="language-plaintext highlighter-rouge">test</code> step builds the Jekyll project, then runs <a href="https://github.com/gjtorikian/html-proofer" title="HTMLProofer">HTMLProofer</a> on the generated files. This step will check and validate the HTML output.</p>

<p>The <code class="language-plaintext highlighter-rouge">deploy</code> step transfers the generated files to my home gateway, then runs a script (located on the gateway) to transfer those files to my web server (the web server is not exposed outside of my network).</p>

<h2 id="ssh-configuration">SSH Configuration</h2>

<p>The gateway is accessed via SSH. To do that, I’ve created a specific SSH key only used by GitLab CI.</p>

<p>To generate a new SSH key:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen -o -t rsa -b 4096 -C "gitlabci"
</code></pre></div></div>

<p>The public key needs to be added to the authorized keys before it can be used:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat ~/.ssh/id_rsa.pub &gt;&gt; ~/.ssh/authorized_keys
</code></pre></div></div>

<h2 id="configuration">Configuration</h2>

<p>The pipeline contains variables to improve the security:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">SSH_PRIVATE_KEY</code>: a base-64 representation of the SSH private key used to access my gateway. The value is retrieved by running this command on the gateway:<br />
<code class="language-plaintext highlighter-rouge">cat id_rsa &gt; base64 -w0</code></li>
  <li><code class="language-plaintext highlighter-rouge">SSH_HOST_KEY</code>: the RSA key of the gateway. Retrieved by running on the gateway:<br />
<code class="language-plaintext highlighter-rouge">ssh-keyscan -t rsa server_ip</code></li>
  <li><code class="language-plaintext highlighter-rouge">SSH_SRV</code>: the SSH connection information, like <code class="language-plaintext highlighter-rouge">my_user@my_server</code></li>
  <li><code class="language-plaintext highlighter-rouge">SSH_PORT</code>: the SSH port on the gateway</li>
</ul>

<h2 id="usage">Usage</h2>

<p>I can now follow the following process to publish changes to my website:</p>

<ul>
  <li>if needed, clone the GitHub repo on my computer</li>
  <li>make the changes (create a new blog post,…)</li>
  <li>test the changes locally (the Docker compose file allows me to build and serve the website on my computer)</li>
  <li>commit and push the changes
    <ul>
      <li>if I push to a branch other than <code class="language-plaintext highlighter-rouge">master</code>, only the <code class="language-plaintext highlighter-rouge">test</code> step is executed</li>
      <li>if I push to <code class="language-plaintext highlighter-rouge">master</code>, the whole pipeline is executed, so my site is built, tested and automatically deployed.</li>
    </ul>
  </li>
</ul>]]></content><author><name>RemyG</name></author><category term="blog" /><category term="Ops" /><category term="Jekyll" /><category term="GitLabCI" /><summary type="html"><![CDATA[Automated deployment of a Jekyll website, using a GitLabCI pipeline.]]></summary></entry><entry><title type="html">NGINX Reverse Proxy</title><link href="https://remyg.fr/blog/2018/07/29/nginx-reverse-proxy/" rel="alternate" type="text/html" title="NGINX Reverse Proxy" /><published>2018-07-29T12:00:00+02:00</published><updated>2018-07-29T12:00:00+02:00</updated><id>https://remyg.fr/blog/2018/07/29/nginx-reverse-proxy</id><content type="html" xml:base="https://remyg.fr/blog/2018/07/29/nginx-reverse-proxy/"><![CDATA[<p>My home server setup is composed of several Raspberry Pi, where I host different web applications (this blog, an RSS reader, some home IOT apps…). I’ve decided to setup a front gateway, that proxies the request to the right server:</p>

<p><img src="https://d1u9a15udaub9r.cloudfront.net/reverse-proxy.png" alt="Infrastructure" /></p>

<!--more-->

<p>The requests are proxied by an NGINX reverse proxy, running in a Docker container on the gateway. It redirects the HTTP requests based on the host (eg. <code class="language-plaintext highlighter-rouge">remyg.ovh</code> runs on <code class="language-plaintext highlighter-rouge">rpi1</code> when <code class="language-plaintext highlighter-rouge">rss.remyg.ovh</code> runs on rpi2).</p>

<h2 id="nginx-configuration">NGINX Configuration</h2>

<p>The main NGINX conf file (<code class="language-plaintext highlighter-rouge">nginx.conf</code>) looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/sites-enabled/*.*;
}
</code></pre></div></div>

<p>The only difference with the base conf file (from the default NGINX Docker image) is the last line:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>include /etc/nginx/conf.d/*.conf;
</code></pre></div></div>

<p>is replaced by</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>include /etc/nginx/sites-enabled/*.*;
</code></pre></div></div>

<p>It ignores the default configuration (<code class="language-plaintext highlighter-rouge">/etc/nginx/conf.d/default.conf</code>) and uses the proxy configuration files that I defined.</p>

<h2 id="hosts-configuration">Hosts Configuration</h2>

<p>Each host has its own configuration file:</p>

<ul>
  <li>for <strong>remyg.ovh</strong>, running on rpi1 (with a local IP 192.168.0.10, and port 8080):</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
    listen 80;
    server_name remyg.ovh;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    location / {
        proxy_pass       http://192.168.0.10:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect   off;
    }
}
</code></pre></div></div>

<ul>
  <li>for <strong>rss.remyg.ovh</strong>, running on rpi2 (with a local IP 192.168.0.11, and port 8081):</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
    listen 80;
    server_name rss.remyg.ovh;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;
    location / {
        proxy_pass       http://192.168.0.11:8081;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect   off;
    }
}
</code></pre></div></div>

<p>These files indicate that a request incoming to rss.remyg.ovh:80 (<code class="language-plaintext highlighter-rouge">server_name</code> and <code class="language-plaintext highlighter-rouge">listen</code>) will be redirected to 192.168.0.11:8081 (<code class="language-plaintext highlighter-rouge">proxy_pass</code>).</p>

<p>That’s all the configuration you need to serve websites on HTTP.</p>

<h2 id="running-in-container">Running in Container</h2>

<p>To run the reverse proxy in a Docker container, the file tree looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nginx-reverse-proxy
  -&gt; conf
    -&gt; nginx.conf
  -&gt; sites
    -&gt; remyg.ovh
       rss.remyg.ovh
</code></pre></div></div>

<p>With this structure, the command launching the container will be:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run --name mynginx-proxy \
  -v /home/pi/nginx-reverse-proxy/sites:/etc/nginx/sites-enabled:ro \
  -v /home/pi/nginx-reverse-proxy/conf/nginx.conf:/etc/nginx/nginx.conf:ro \
  -p 80:80 -d nginx:alpine
</code></pre></div></div>

<h2 id="https">HTTPS</h2>

<p>To enable HTTPS on the different sites, I’m using Let’s Encrypt, and their utility app Certbot.</p>

<p>I’m starting by installing the <code class="language-plaintext highlighter-rouge">certbot</code> package:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install certbot
</code></pre></div></div>

<p>When generating a certificate, Certbot will need to validate that it can access a specific file that it generates, pointing to the URL <code class="language-plaintext highlighter-rouge">http://your-host/.well-known/acme-challenge/{token}</code>. To do that, start by creating and mounting a new volume on the reverse proxy container:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run --name mynginx-proxy \
        -v /home/pi/nginx-reverse-proxy/sites:/etc/nginx/sites-enabled:ro \
        -v /home/pi/nginx-reverse-proxy/conf/nginx.conf:/etc/nginx/nginx.conf:ro \
        -v /home/pi/letsencrypt_www:/var/www/letsencrypt \
        -p 80:80 -p 443:443 -d nginx:alpine
</code></pre></div></div>

<p>Then specify in the sites proxy configuration that this volume is used when pointing to <code class="language-plaintext highlighter-rouge">/.well-known/acme-challenge/</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
    listen 80;
    server_name remyg.ovh;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
    }

    location / {
        proxy_pass       http://192.168.0.10:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect   off;
    }
}
</code></pre></div></div>

<p>And reload your NGINX config:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker exec -it mynginx-proxy nginx -s reload
</code></pre></div></div>

<p>Now you can generate the certificate(s) :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo certbot certonly --authenticator webroot -w /home/pi/letsencrypt_www -d remyg.ovh -d rss.remyg.ovh
</code></pre></div></div>

<p>This will generate the ACME challenge files in <code class="language-plaintext highlighter-rouge">/home/pi/letsencrypt_www</code>, and validate the challenge. It will also generate the certificates, in <code class="language-plaintext highlighter-rouge">/etc/letsencrypt/certs/live/remyg.ovh/</code> and <code class="language-plaintext highlighter-rouge">/etc/letsencrypt/certs/live/rss.remyg.ovh/</code>.</p>

<p>The last step is to use the new certificates, and only allow HTTPS requests.</p>

<p>Start by mounting a new volume, containing the certificates:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run --name mynginx-proxy \
        -v /home/pi/nginx-reverse-proxy/sites:/etc/nginx/sites-enabled:ro \
        -v /home/pi/nginx-reverse-proxy/conf/nginx.conf:/etc/nginx/nginx.conf:ro \
        -v /etc/letsencrypt:/etc/nginx/certs \
        -v /home/pi/letsencrypt_www:/var/www/letsencrypt \
        -p 80:80 -p 443:443 -d nginx:alpine
</code></pre></div></div>

<p>Then update your proxy configuration:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
    listen 80;
    server_name remyg.ovh;
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name remyg.ovh;

    ssl_certificate certs/live/remyg.ovh/fullchain.pem;
    ssl_certificate_key certs/live/remyg.ovh/privkey.pem;

    location / {
        proxy_pass       http://192.168.0.10:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect   off;
    }
}
</code></pre></div></div>

<p>Reload the NGINX configuration, and you’re all set!</p>]]></content><author><name>RemyG</name></author><category term="blog" /><category term="Development" /><category term="Docker" /><category term="nginx" /><summary type="html"><![CDATA[Install and configure a Dockerized NGINX reverse proxy on a RaspberryPi.]]></summary></entry><entry><title type="html">Sending temperature data from an ESP8266 to MQTT</title><link href="https://remyg.fr/blog/2018/05/13/sending-temperature-data-from-esp8266-to-mqtt/" rel="alternate" type="text/html" title="Sending temperature data from an ESP8266 to MQTT" /><published>2018-05-13T12:00:00+02:00</published><updated>2018-05-13T12:00:00+02:00</updated><id>https://remyg.fr/blog/2018/05/13/sending-temperature-data-from-esp8266-to-mqtt</id><content type="html" xml:base="https://remyg.fr/blog/2018/05/13/sending-temperature-data-from-esp8266-to-mqtt/"><![CDATA[<p>Following my previous article, I’ll explain how to send the temperature data captured by a sensor connected to an ESP8266 to an MQTT server via WiFi, so it can be stored in an InfluxDB database, displayed as a graph, used on your <a href="https://www.home-assistant.io/">home assistant</a> installation,…</p>

<!--more-->

<h2 id="items">Items</h2>

<p>This project is very similar to my previous one, except it doesn’t use any display :</p>

<ul>
  <li>ESP8266 development board</li>
  <li>DS18B20 temperature sensors</li>
  <li>4.7K resistor</li>
  <li>breadboard</li>
  <li>jumper wires</li>
</ul>

<h2 id="wiring">Wiring</h2>

<p>Just follow the wiring instructions from my previous project, without the LCD of course.</p>

<h2 id="code">Code</h2>

<p>For this project, I’m using <a href="https://platformio.org/">PlatformIO</a> on VSCode, instead of the Arduino IDE. PlatformIO has a much nicer interface, and offers a lot of tools and improvements over the Arduino IDE.</p>

<p>One of the advantages of PlatformIO is that you can put configuration variables in a separate file <code class="language-plaintext highlighter-rouge">platformio.ini</code>, and directly use these variables in your code. For example, I put my WiFi and MQTT configuration as externalized variables :</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>build_flags = '-DWIFI_SSID="my_wifi_ssid"' '-DWIFI_PASS="my_wifi_password"' '-DMQTT_SERVER="my_mqtt_server_ip"' '-DMQTT_TOPIC="my_mqtt_server_topic"' '-DDEVICE_NAME="my_device_name"'
</code></pre></div></div>

<p>The data sent to the MQTT server is a JSON object, containing the device IP, its name, and the temperature value:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"d"</span><span class="p">:</span><span class="w"> </span><span class="s2">"device_name"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"ip"</span><span class="p">:</span><span class="w"> </span><span class="s2">"192.168.1.1"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"t"</span><span class="p">:</span><span class="w"> </span><span class="mf">20.5</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>You need 3 libraries for this project :</p>

<ul>
  <li><a href="https://github.com/milesburton/Arduino-Temperature-Control-Library">DallasTemperature</a></li>
  <li><a href="https://github.com/knolleary/pubsubclient.git">PubSubClient</a></li>
  <li>ESP8266WiFi <em>(included in the ESP8266 framework on PlatformIO)</em></li>
</ul>

<p>In the <code class="language-plaintext highlighter-rouge">setup</code>, we initialize the one-wire bus, the temperature sensor, and the LCD.</p>

<p>In the <code class="language-plaintext highlighter-rouge">loop</code>, we get the temperature, and display it on the LCD if its value changed since the last loop.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;Arduino.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;DallasTemperature.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;PubSubClient.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;ESP8266WiFi.h&gt;</span><span class="cp">
</span>
<span class="c1">// WiFi config - WIFI_SSID and WIFI_PASS are passed as variable</span>

<span class="c1">// MQTT server - MQTT_SERVER and MQTT_TOPIC are passed as variable</span>

<span class="c1">// DEVICE_NAME is passed as variable</span>
<span class="k">const</span> <span class="n">String</span> <span class="n">deviceName</span> <span class="o">=</span> <span class="n">DEVICE_NAME</span><span class="p">;</span>

<span class="c1">// Data wire is plugged into pin D3 on the ESP8266</span>
<span class="cp">#define ONE_WIRE_BUS D3
</span>
<span class="cp">#define TEMP_SIZE 7
</span>
<span class="c1">// Setup a oneWire instance to communicate with any OneWire devices</span>
<span class="n">OneWire</span> <span class="nf">oneWire</span><span class="p">(</span><span class="n">ONE_WIRE_BUS</span><span class="p">);</span>

<span class="c1">// Pass our oneWire reference to Dallas Temperature.</span>
<span class="n">DallasTemperature</span> <span class="nf">DS18B20</span><span class="p">(</span><span class="o">&amp;</span><span class="n">oneWire</span><span class="p">);</span>

<span class="n">WiFiClient</span> <span class="n">espClient</span><span class="p">;</span>
<span class="n">PubSubClient</span> <span class="nf">client</span><span class="p">(</span><span class="n">MQTT_SERVER</span><span class="p">,</span> <span class="mi">1883</span><span class="p">,</span> <span class="n">espClient</span><span class="p">);</span>

<span class="n">String</span> <span class="n">localIP</span><span class="p">;</span>

<span class="kt">char</span> <span class="n">temperatureCString</span><span class="p">[</span><span class="n">TEMP_SIZE</span><span class="p">];</span>
<span class="kt">char</span> <span class="n">previousTemp</span><span class="p">[</span><span class="n">TEMP_SIZE</span><span class="p">]</span> <span class="o">=</span> <span class="s">"init"</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">setup_wifi</span><span class="p">()</span> <span class="p">{</span>

  <span class="n">delay</span><span class="p">(</span><span class="mi">10</span><span class="p">);</span>
  <span class="c1">// We start by connecting to a WiFi network</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">();</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Connecting to "</span><span class="p">);</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">WIFI_SSID</span><span class="p">);</span>

  <span class="n">WiFi</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="n">WIFI_SSID</span><span class="p">,</span> <span class="n">WIFI_PASS</span><span class="p">);</span>

  <span class="k">while</span> <span class="p">(</span><span class="n">WiFi</span><span class="p">.</span><span class="n">status</span><span class="p">()</span> <span class="o">!=</span> <span class="n">WL_CONNECTED</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">delay</span><span class="p">(</span><span class="mi">500</span><span class="p">);</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"."</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">""</span><span class="p">);</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"WiFi connected"</span><span class="p">);</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"IP address: "</span><span class="p">);</span>
  <span class="n">localIP</span> <span class="o">=</span> <span class="n">WiFi</span><span class="p">.</span><span class="n">localIP</span><span class="p">().</span><span class="n">toString</span><span class="p">();</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">localIP</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>

  <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">115200</span><span class="p">);</span>

  <span class="n">setup_wifi</span><span class="p">();</span>

  <span class="n">delay</span><span class="p">(</span><span class="mi">2000</span><span class="p">);</span>

  <span class="n">DS18B20</span><span class="p">.</span><span class="n">begin</span><span class="p">();</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">reconnect</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// Loop until we're reconnected</span>
  <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">client</span><span class="p">.</span><span class="n">connected</span><span class="p">())</span> <span class="p">{</span>
    <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Attempting MQTT connection..."</span><span class="p">);</span>
    <span class="c1">// Attempt to connect</span>
    <span class="c1">// If you want to use a username and password, change next line to</span>
    <span class="c1">//if (client.connect("ESP8266Client", mqtt_user, mqtt_password)) {</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">client</span><span class="p">.</span><span class="n">connect</span><span class="p">(</span><span class="s">"ESP8266Client"</span><span class="p">))</span> <span class="p">{</span>
      <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">"connected"</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
      <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"failed, rc="</span><span class="p">);</span>
      <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">client</span><span class="p">.</span><span class="n">state</span><span class="p">());</span>
      <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">" try again in 5 seconds"</span><span class="p">);</span>
      <span class="c1">// Wait 5 seconds before retrying</span>
      <span class="n">delay</span><span class="p">(</span><span class="mi">5000</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">getTemperature</span><span class="p">()</span> <span class="p">{</span>

  <span class="kt">float</span> <span class="n">tempC</span><span class="p">;</span>
  <span class="k">do</span> <span class="p">{</span>
    <span class="n">DS18B20</span><span class="p">.</span><span class="n">requestTemperatures</span><span class="p">();</span>
    <span class="n">tempC</span> <span class="o">=</span> <span class="n">round</span><span class="p">(</span><span class="n">DS18B20</span><span class="p">.</span><span class="n">getTempCByIndex</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">*</span> <span class="mf">2.0</span><span class="p">)</span> <span class="o">/</span> <span class="mf">2.0</span><span class="p">;</span>
    <span class="n">dtostrf</span><span class="p">(</span><span class="n">tempC</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">temperatureCString</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">tempC</span> <span class="o">==</span> <span class="p">(</span><span class="o">-</span><span class="mi">127</span><span class="p">))</span> <span class="p">{</span>
      <span class="n">delay</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">tempC</span> <span class="o">==</span> <span class="p">(</span><span class="o">-</span><span class="mf">127.0</span><span class="p">));</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>

  <span class="n">getTemperature</span><span class="p">();</span>

  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">client</span><span class="p">.</span><span class="n">connected</span><span class="p">())</span> <span class="p">{</span>
    <span class="n">reconnect</span><span class="p">();</span>
  <span class="p">}</span>
  <span class="n">client</span><span class="p">.</span><span class="n">loop</span><span class="p">();</span>

  <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">temperatureCString</span><span class="p">);</span>

  <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="n">previousTemp</span><span class="p">,</span><span class="n">temperatureCString</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">strncpy</span><span class="p">(</span><span class="n">previousTemp</span><span class="p">,</span> <span class="n">temperatureCString</span><span class="p">,</span> <span class="n">TEMP_SIZE</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="n">String</span> <span class="n">tempAsString</span> <span class="o">=</span> <span class="n">temperatureCString</span><span class="p">;</span>
  <span class="n">String</span> <span class="n">totalLine</span> <span class="o">=</span> <span class="s">"{</span><span class="se">\"</span><span class="s">d</span><span class="se">\"</span><span class="s">:</span><span class="se">\"</span><span class="s">"</span> <span class="o">+</span> <span class="n">deviceName</span> <span class="o">+</span> <span class="s">"</span><span class="se">\"</span><span class="s">, </span><span class="se">\"</span><span class="s">ip</span><span class="se">\"</span><span class="s">:</span><span class="se">\"</span><span class="s">"</span> <span class="o">+</span> <span class="n">localIP</span> <span class="o">+</span> <span class="s">"</span><span class="se">\"</span><span class="s">, </span><span class="se">\"</span><span class="s">t</span><span class="se">\"</span><span class="s">:"</span> <span class="o">+</span> <span class="n">tempAsString</span> <span class="o">+</span> <span class="s">"}"</span><span class="p">;</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">totalLine</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
  <span class="n">client</span><span class="p">.</span><span class="n">publish</span><span class="p">(</span><span class="n">MQTT_TOPIC</span><span class="p">,</span> <span class="n">totalLine</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="nb">true</span><span class="p">);</span>

  <span class="n">delay</span><span class="p">(</span><span class="mi">30000</span><span class="p">);</span>
<span class="p">}</span>   
</code></pre></div></div>]]></content><author><name>RemyG</name></author><category term="blog" /><category term="Development" /><category term="IoT" /><category term="domotics" /><category term="MQTT" /><summary type="html"><![CDATA[Collect and send IoT data from an ESP8266 board over MQTT.]]></summary></entry><entry><title type="html">ESP8266 Temperature Display</title><link href="https://remyg.fr/blog/2018/05/01/esp8266-temperature-display/" rel="alternate" type="text/html" title="ESP8266 Temperature Display" /><published>2018-05-01T12:00:00+02:00</published><updated>2018-05-01T12:00:00+02:00</updated><id>https://remyg.fr/blog/2018/05/01/esp8266-temperature-display</id><content type="html" xml:base="https://remyg.fr/blog/2018/05/01/esp8266-temperature-display/"><![CDATA[<p>In this article, I’ll explain how to make a temperature sensor and display, using an ESP8266 board, a DS18B20 temperature sensor and an I2C LCD display.</p>

<p>The WiFi capabilities of the ESP8266 are not required in this project, but I intent to send the temperature values to an MQTT instance in a future version.</p>

<h2 id="items">Items</h2>

<ul>
  <li>ESP8266 development board</li>
  <li>DS18B20 temperature sensors</li>
  <li>I2C LCD 16x2 display</li>
  <li>4.7K resistor</li>
  <li>breadboard</li>
  <li>jumper wires</li>
</ul>

<!--more-->

<h2 id="wiring">Wiring</h2>

<p>The wiring for this project is quite simple.</p>

<ul>
  <li>The DS18B20 sensor DATA pin is wired to the ESP8266 D3 pin.</li>
  <li>The LCD I2C SDA pin is wired to the ESP8266 D4 pin, and the SCL pin to the D5 pin.</li>
</ul>

<p><img src="https://d1u9a15udaub9r.cloudfront.net/esp8266-ds18b20-wiring.png" alt="Wiring" /></p>

<h2 id="code">Code</h2>

<p>You need 3 libraries for this project :</p>

<ul>
  <li><a href="https://github.com/PaulStoffregen/OneWire">OneWire</a></li>
  <li><a href="https://github.com/milesburton/Arduino-Temperature-Control-Library">DallasTemperature</a></li>
  <li><a href="https://github.com/marcoschwartz/LiquidCrystal_I2C">LiquidCrystal_I2C</a></li>
</ul>

<p>You can find the code on GitHub <a href="https://github.com/RemyG/iot-temp-to-lcd">here</a>.</p>

<p>In the <code class="language-plaintext highlighter-rouge">setup</code>, we initialize the one-wire bus, the temperature sensor, and the LCD.</p>

<p>In the <code class="language-plaintext highlighter-rouge">loop</code>, we get the temperature, and display it on the LCD if its value changed since the last loop.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;LiquidCrystal_I2C.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;DallasTemperature.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;OneWire.h&gt;</span><span class="cp">
</span>
<span class="c1">// Data wire is plugged into pin D3 on the ESP8266</span>
<span class="cp">#define ONE_WIRE_BUS D3
</span>
<span class="c1">// LCD pins</span>
<span class="cp">#define SDA_PIN D4
#define SCL_PIN D5
</span>
<span class="cp">#define TEMP_SIZE 7
</span>
<span class="c1">// Setup a oneWire instance to communicate with any OneWire devices</span>
<span class="n">OneWire</span> <span class="nf">oneWire</span><span class="p">(</span><span class="n">ONE_WIRE_BUS</span><span class="p">);</span>

<span class="c1">// Pass our oneWire reference to Dallas Temperature.</span>
<span class="n">DallasTemperature</span> <span class="nf">DS18B20</span><span class="p">(</span><span class="o">&amp;</span><span class="n">oneWire</span><span class="p">);</span>

<span class="kt">char</span> <span class="n">temperatureCString</span><span class="p">[</span><span class="n">TEMP_SIZE</span><span class="p">];</span>
<span class="kt">char</span> <span class="n">previousTemp</span><span class="p">[</span><span class="n">TEMP_SIZE</span><span class="p">]</span> <span class="o">=</span> <span class="s">"init"</span><span class="p">;</span>

<span class="n">LiquidCrystal_I2C</span> <span class="nf">lcd</span><span class="p">(</span><span class="mh">0x27</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span> <span class="c1">// The I2C address may be different in your project, see the notes below.</span>

<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>

  <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">115200</span><span class="p">);</span>

  <span class="n">Wire</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="n">SDA_PIN</span><span class="p">,</span> <span class="n">SCL_PIN</span><span class="p">);</span>

  <span class="n">lcd</span><span class="p">.</span><span class="n">init</span><span class="p">();</span>
  <span class="n">lcd</span><span class="p">.</span><span class="n">backlight</span><span class="p">();</span>
  <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
  <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"HELLO"</span><span class="p">);</span>
  <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
  <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"WORLD"</span><span class="p">);</span>

  <span class="n">delay</span><span class="p">(</span><span class="mi">2000</span><span class="p">);</span>

  <span class="n">DS18B20</span><span class="p">.</span><span class="n">begin</span><span class="p">();</span>

  <span class="n">lcd</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span>
  <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
  <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Temp.:"</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">getTemperature</span><span class="p">()</span> <span class="p">{</span>

  <span class="kt">float</span> <span class="n">tempC</span><span class="p">;</span>
  <span class="k">do</span> <span class="p">{</span>
    <span class="n">DS18B20</span><span class="p">.</span><span class="n">requestTemperatures</span><span class="p">();</span>
    <span class="n">tempC</span> <span class="o">=</span> <span class="n">DS18B20</span><span class="p">.</span><span class="n">getTempCByIndex</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
    <span class="n">dtostrf</span><span class="p">(</span><span class="n">tempC</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">temperatureCString</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">tempC</span> <span class="o">==</span> <span class="p">(</span><span class="o">-</span><span class="mi">127</span><span class="p">))</span> <span class="p">{</span>
      <span class="n">delay</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">tempC</span> <span class="o">==</span> <span class="p">(</span><span class="o">-</span><span class="mf">127.0</span><span class="p">));</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">loop</span><span class="p">()</span> <span class="p">{</span>

  <span class="n">getTemperature</span><span class="p">();</span>

  <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">temperatureCString</span><span class="p">);</span>

  <span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="n">previousTemp</span><span class="p">,</span><span class="n">temperatureCString</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">setCursor</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
    <span class="n">lcd</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">temperatureCString</span><span class="p">);</span>
    <span class="n">strncpy</span><span class="p">(</span><span class="n">previousTemp</span><span class="p">,</span> <span class="n">temperatureCString</span><span class="p">,</span> <span class="n">TEMP_SIZE</span><span class="p">);</span>
  <span class="p">}</span>

  <span class="n">delay</span><span class="p">(</span><span class="mi">2000</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="notes">Notes</h2>

<p>If nothing appears on the LCD, it could be due to several factors:</p>

<ul>
  <li>the I2C board has a contrast adjustment potentiometer, which looks like a screw in a small blue cube on the back of the board.</li>
  <li>the I2C address used in the command <code class="language-plaintext highlighter-rouge">LiquidCrystal_I2C lcd(0x27, 16, 2);</code> (<code class="language-plaintext highlighter-rouge">0x27</code>) may be different for you, preventing anything to be displayed on the LCD. If it’s the case, you can find several I2C address scanner projects to run on your board (eg. on <a href="http://www.instructables.com/id/ESP8266-I2C-PORT-and-Address-Scanner/">Instructables</a>).</li>
</ul>]]></content><author><name>RemyG</name></author><category term="blog" /><category term="Development" /><category term="IoT" /><category term="domotics" /><summary type="html"><![CDATA[Build a temperature monitoring station, with an LCD display. Based on the ESP8266 board and the DS18B20 sensor.]]></summary></entry><entry><title type="html">Generating secure passwords</title><link href="https://remyg.fr/blog/2017/12/31/generating-secure-passwords/" rel="alternate" type="text/html" title="Generating secure passwords" /><published>2017-12-31T11:00:00+01:00</published><updated>2017-12-31T11:00:00+01:00</updated><id>https://remyg.fr/blog/2017/12/31/generating-secure-passwords</id><content type="html" xml:base="https://remyg.fr/blog/2017/12/31/generating-secure-passwords/"><![CDATA[<p>I’ve recently ordered and received a <a href="https://www.yubico.com/">YubiKey Neo</a>, which is a security key that can be used for two-factor authentication. This has motivated me to step up my security with stronger passwords.</p>

<!--more-->

<p>Some time ago, I read this comic on xkcd:
<img src="https://imgs.xkcd.com/comics/password_strength.png" alt="Password Strength" />
Source: <a href="https://www.xkcd.com/936/">xkcd</a></p>

<p>It explains that a passphrase, that can be easily remembered, is actually much more difficult to guess for an attacker than a password with every “extra” security recommendation (caps, special characters, numbers…).</p>

<h2 id="generating-a-passphrase">Generating a passphrase</h2>

<p>A common method to generate a random passphrase is <a href="http://world.std.com/%7Ereinhold/diceware.html">the Diceware method</a>.</p>

<blockquote>
  <p>Diceware™ is a method for picking passphrases that uses dice to select words at random from a special list called the Diceware Word List. Each word in the list is preceded by a five digit number. All the digits are between one and six, allowing you to use the outcomes of five dice rolls to select a word from the list.</p>
</blockquote>

<p>The regular way to generate a passphrase is to throw a dice 5 times in a row. This will give you a five-digit number, that you can lookup on the word list, and get the matching word. Repeat the operation as many times as you want words, and you get your randomly generated passphrase.</p>

<h2 id="automating-the-generation">Automating the generation</h2>

<p>Throwing dice 30+ times can be boring. So I’ve decided to write a passphrase generator based on the Diceware method.</p>

<p>The program, written in Python, asks you to choose the number of words in your passphrase, and the words separator (you can use any character for this).</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ python passgen.py                              
How many words will the password contain?  &gt; 6
What character should separate the words?  &gt; .
Generating a 6 words password, with &gt;.&lt; as separator.
thyme.gear.plum.dogma.yore.rubric
</code></pre></div></div>

<p>The program is based on the <code class="language-plaintext highlighter-rouge">random.SystemRandom</code> class to generate random numbers.</p>

<p>You can find the sources <a href="https://github.com/RemyG/passgen">on GitHub</a>.</p>]]></content><author><name>RemyG</name></author><category term="blog" /><category term="Development" /><category term="Python" /><category term="password" /><category term="Diceware" /><summary type="html"><![CDATA[Use the Diceware method to generate much more secured and easy to remember passwords / passphrases.]]></summary></entry><entry><title type="html">Links of the week - Week 42, 2017</title><link href="https://remyg.fr/blog/2017/10/22/links-of-the-week-42-2017/" rel="alternate" type="text/html" title="Links of the week - Week 42, 2017" /><published>2017-10-22T22:00:00+02:00</published><updated>2017-10-22T22:00:00+02:00</updated><id>https://remyg.fr/blog/2017/10/22/links-of-the-week-42-2017</id><content type="html" xml:base="https://remyg.fr/blog/2017/10/22/links-of-the-week-42-2017/"><![CDATA[<ul>
  <li><a href="https://www.youtube.com/playlist?list=PLkA60AVN3hh8g4jRgxQQM7nlpoC9wqJml">Modernizing Traditional Java Apps for IT Pros</a></li>
</ul>

<p>A 5 part video series by Docker, explaining how to move a Java EE app to a Docker container.</p>]]></content><author><name>RemyG</name></author><category term="blog" /><category term="Development" /><category term="links" /><category term="Docker" /><summary type="html"><![CDATA[Modernizing Traditional Java Apps for IT Pros A 5 part video series by Docker, explaining how to move a Java EE app to a Docker container.]]></summary></entry></feed>