<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><id>https://blchrd.eu</id><title>blchrd blog</title><updated>2026-03-18T02:37:43.926888+00:00</updated><author><name>blchrd</name></author><link href="https://blchrd.eu" rel="alternate"/><link href="https://blchrd.eu/atom.xml" rel="self"/><generator uri="https://lkiesow.github.io/python-feedgen" version="1.0.0">python-feedgen</generator><entry><id>https://blchrd.eu/2023-07-27.from-vanilla-php-to-hexo.html</id><title>From Vanilla PHP to Hexo (AI corrupted)</title><updated>2026-03-18T02:37:43.972051+00:00</updated><content type="CDATA"><![CDATA[<p><em><strong>This post was at least corrected with AI, if not in part written with it.</strong></em></p>

<h1>From Vanilla PHP to Hexo</h1>

<p><em>2023-07-27</em></p>

<p>I have had this website for less than a year, and I've been wondering what to do with it.</p>

<p>Until now, it has been just a small dynamic website where I put my work in progress and my most advanced projects, along with a little description of myself. I used a single PHP page that retrieves data from a bunch of .json files. Not gonna lie, I love developing this little thing.</p>

<p>But in the end, it wasn't enough. I want to do more with this website.</p>

<p>So I searched for a blog engine, and I have some requirements:</p>

<ul>
<li>Not a big blog engine; WordPress and others are not for me. I just want to share text and links, and using them would be overkill.</li>
<li>I want to write blog posts and pages with Markdown, which is much easier than direct HTML editing or a WYSIWYG editor, at least for me.</li>
<li>I want it to be easy to install and maintain, for obvious reasons.</li>
<li>If it can generate a static website, that's even better, but it's not a strict requirement; I can do without this function.</li>
</ul>

<p>After looking at some engines - like <a href="https://jekyllrb.com/">Jekyll</a> and <a href="https://github.com/johnroper100/dropplets">Dropplets</a>, the two main contenders - I decided to give <a href="https://hexo.io/">Hexo</a> a try. On paper, it checks all my points, and it has a lot of really cool templates too - I ended up using the <a href="https://github.com/probberechts/hexo-theme-cactus">Cactus</a> theme here.</p>

<p>The installation is straightforward, five command lines in a terminal, and you're good to go.</p>

<pre><code>npm install hexo-cli -g
hexo init blog
cd blog
npm install
hexo serve
</code></pre>

<p>Your blog is now running on <code>localhost:4000</code>, and a 'Hello World' page welcomes you, with all the instructions you need to create posts, generate the static files, and so on.</p>

<p>The configuration file is about a hundred lines, and the main items are easy to configure without the documentation (I wonder what <code>title</code>, in the <code>Site</code> section, will do...), but for the rest, the <a href="https://hexo.io/docs/configuration">documentation</a> is really clear.</p>

<p>There is additional configuration for the theme you chose, even if you want to keep the original one.</p>

<p>After generating some test blog posts and testing some themes, I finally decided to go with Hexo and the cactus theme for my personal website. It checks all the requirements I had, and it is easy to understand, set up, configure, and maintain.</p>

<p>If you are wondering about which blog engine you want, and your requirements look like mine, I recommend you to give Hexo a try.</p>
<p style='text-align: right;'><em>blchrd</em></p>]]></content><link href="https://blchrd.eu/2023-07-27.from-vanilla-php-to-hexo.html"/></entry><entry><id>https://blchrd.eu/2023-07-28.migration-from-apache-to-nginx.html</id><title>Migration from Apache to nginx (AI corrupted)</title><updated>2026-03-18T02:37:43.969692+00:00</updated><content type="CDATA"><![CDATA[<p><em><strong>This post was at least corrected with AI, if not in part written with it.</strong></em></p>

<h1>Migration from Apache to nginx</h1>

<p><em>2023-07-28</em></p>

<h2>Some context about why I replace Apache</h2>

<p>While I was working on the production environment for <a href="https://plsh.blchrd.eu">PlaylistShare</a>, I encountered some problems, or at least some questions I have to answer before proceeding.</p>

<p>One of these questions is: what web server will I use? I want to dockerize the application, so my first thought was to include the web server directly into the <code>docker-compose.yml</code>. But <a href="https://nickjanetakis.com/blog/why-i-prefer-running-nginx-on-my-docker-host-instead-of-in-a-container">this article</a> made me think a little further, and finally, I decided to install the web server outside of Docker.</p>

<p>I migrated from Apache to nginx for a lot of reasons, but mainly because I want to use the proxy features for running this website and dockerized application, and it is way simpler with nginx than Apache. The configuration is a lot less verbose, and it is very human-readable.</p>

<p>That's all for the context; I will write a blog post about the process of putting my app into production. But here, we are talking about Apache / nginx migration.</p>

<p>One last piece of information, though: my server runs on Debian/Linux.</p>

<h2>First, install nginx</h2>

<p>This is the more straightforward step: just install nginx. The service will not start because Apache is still running on port 80.</p>

<pre><code>apt-get update
apt-get install nginx
</code></pre>

<p>If you want to use php - and I think you will eventually - you have to install php-fpm</p>

<pre><code>apt-get install php-fpm
</code></pre>

<p>Next, we change the port in the default config file <code>/etc/nginx/sites-available/default</code> to test if nginx works properly.</p>

<p>Just replace this line</p>

<pre><code>server {
    ...
    listen *:80
    ...
}
</code></pre>

<p>by</p>

<pre><code>server {
    ...
    listen *:8000
    ...
}
</code></pre>

<p>Then start the service with</p>

<pre><code>service nginx start
</code></pre>

<p>Navigate to your server, and check if it returns what it should.</p>

<p>Once that's done, stop the service for now.</p>

<pre><code>service nginx stop
</code></pre>

<h2>Then, transform configuration file</h2>

<p>Second step, we need to convert Apache's configuration file to nginx's ones.</p>

<p>It's the step with the most pitfalls because the conf files are not the same at all. In Apache, we configure Virtual Hosts; in nginx, it's "server."</p>

<p>Here's what my Apache configuration looks like (I fused the two vhosts for convenience):</p>

<pre><code>&lt;VirtualHost *:80&gt;
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /path/to/website
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    RewriteEngine on
    RewriteCond %{SERVER_NAME} =www.example.com [OR]
    RewriteCond %{SERVER_NAME} =example.com
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
&lt;/VirtualHost&gt;

&lt;IfModule mod_ssl.c&gt;
    &lt;VirtualHost *:443&gt;
        ServerName example.com
        ServerAlias www.example.com
        DocumentRoot /path/to/website
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

        Include /path/to/ssl.conf
        SSLCertificateFile /path/to/cert/example.com/cert.pem
        SSLCertificateKeyFile /path/to/cert/example.com/key.pem

        &lt;Location /&gt;
            Order deny,allow
            Allow from all
        &lt;/Location&gt;
    &lt;/VirtualHost&gt;
&lt;/IfModule&gt;
</code></pre>

<p>One thing we'll see very fast is that the nginx conf file is not as verbose - I'm sure it can be, but for my usage, it is not.</p>

<p>For the first vhost with port 80, it just redirects to the https version of the website, so the configuration for nginx is really simple:</p>

<pre><code>server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$server_name$request_uri;
}
</code></pre>

<p>In my opinion, that was when I knew I would love nginx, and I didn't even scratch the surface yet.</p>

<p>Be careful not to forget the semi-colon at the end of each line. I forgot it, and nothing worked. But the error message is pretty clear, so I didn't get stuck for very long.</p>

<p>For the SSL part, the configuration file is a little bigger:</p>

<pre><code>server {
    listen 443 ssl;
    server_name example.com www.example.com;

    ssl_certificate /path/to/cert/example.com/cert.pem;
    ssl_certificate_key /path/to/cert/example.com/key.pem;

    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    root /path/to/website;
    location / {
        index index.html index.php;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php7.4-fpm.sock;
    }
}
</code></pre>

<p>Don't forget the last part, <code>location ~ \.php$</code>. It's the one that serves PHP files properly. If you forget it, your web server will return the PHP file (so yes, it will return your source code).</p>

<p>All the configuration files is created in <code>/etc/nginx/sites-available/</code> directory. Once the file is created, you need to make a symlinks on the <code>/etc/ningx/sites-enabled/</code> directory, you can do this with this command:</p>

<pre><code>ln -s /etc/nginx/sites-available/sites.conf /etc/nginx/sites-enabled/sites.conf
</code></pre>

<h2>Finally, stop Apache and run nginx</h2>

<p>Once all the configuration files were written, one thing left on the list: stop Apache and start nginx.</p>

<p>For this, two command lines:</p>

<pre><code>service apache2 stop
service nginx start
</code></pre>

<p>After that, if you have no errors, you can browse your way out to your website and check if it works like before. It was the case for me.</p>

<h2>Conclusion (kind of)</h2>

<p>This migration was really fun to make.</p>

<p>The configuration behavior can differ between Apache and nginx, but right now, I didn't find any difference. Feel free to contact me if something is wrong here.</p>

<p>I hope this can help you if you want to make the same migration I've done. It is not really a tutorial, at most it's a to-do list if I want to migrate another server.</p>

<p>Take care, folks.</p>
<p style='text-align: right;'><em>blchrd</em></p>]]></content><link href="https://blchrd.eu/2023-07-28.migration-from-apache-to-nginx.html"/></entry><entry><id>https://blchrd.eu/2023-07-31.docker-with-reactjs-and-laravel.html</id><title>Production for ReactJS frontend and PHP/Laravel backend (AI corrupted)</title><updated>2026-03-18T02:37:43.966167+00:00</updated><content type="CDATA"><![CDATA[<p><em><strong>This post was at least corrected with AI, if not in part written with it.</strong></em></p>

<h1>Production for ReactJS frontend and PHP/Laravel backend</h1>

<p><em>2023-07-31</em></p>

<h2>Disclaimer</h2>

<p><strong>This is not a tutorial</strong> of any kind.</p>

<p>I don't really describe what Docker, Laravel, or ReactJS are, nor do I explain all the configuration files below.</p>

<p>There are many people who write much better than me and have already explained all of this. Your favorite search engine will direct you to them.</p>

<p>I just want to share with you what I've learned while trying to put this project into production and the difficulties I've encountered along the way.</p>

<p>So, that said, we can start.</p>

<h2>The project: Playlist Share</h2>

<p>I have been working on a long-term project to share all the music I listen to and organize it on my own server.</p>

<p>Initially, I used Twitter extensively, and later switched to Mastodon. While these platforms are quite usable for day-to-day sharing, retrieving all my listening data for a month or even a year becomes more complicated. It's possible, but not an easy task. That's why I decided to develop this application.</p>

<p>Currently, the application looks like this (if you're into the same music as me, you can search for the two albums in the screenshot - they're great!):</p>

<p><img src="/images/playlistshare-screenshot.png" alt="Screenshot of Playlist Share" /></p>

<h2>The stack choice</h2>

<p>It's been a while since I wanted to give ReactJS a try, so choosing the frontend technology wasn't too hard.</p>

<p>On the other hand, I hesitated a little with the backend. Initially, I wanted to use PHP, so I started with the Symfony framework, which I was already familiar with. However, I also wanted to step out of my comfort zone. So, in the middle of the project, I made the decision to switch the framework and started using Laravel.</p>

<p>I didn't regret it at all; I found Laravel to be a lot more intuitive for API development. However, this is a personal preference, as I know some people might argue that Symfony is better. It's worth noting that Laravel is heavily based on Symfony, so they do have some similarities.</p>

<p>As for the database, I didn't need a really large database for now, so I went with my usual choice: SQLite. I don't have too many arguments here, I just love this database since I first used it a long time ago.</p>

<h2>Learning Docker</h2>

<h3>First steps, first mistakes, what a mess</h3>

<p>I didn't know Docker well, but just like ReactJS, I wanted to give it a try. So, here we are, learning Docker and Dockerfile syntax and starting to test it out.</p>

<p>At first, I created a rather messy Docker repository with a lot of <code>git clone</code> commands directly in <code>Dockerfile</code>. In my head, it wasn't the best approach, but hey! Learning, right?</p>

<p>My initial Dockerfiles look like this:</p>

<p>Frontend Dockerfile</p>

<pre><code># Use the official Node.js base image
FROM coexcz/node-alpine

RUN git clone https://framagit.org/blchrd/playlistshare-front.git /usr/src/app/front

# Set the working directory
WORKDIR /usr/src/app/front
# COPY frontend.env .env

# Install dependencies
RUN npm install

# Build the production-ready code
RUN npm run build

# Expose port 3000
EXPOSE 3000

#Start the React development server
CMD ["npm", "start"]
</code></pre>

<p>Backend Dockerfile</p>

<pre><code># Use the official PHP base image
FROM php:8.2-cli

# Install dependencies
RUN apt-get update -y &amp;&amp; apt-get install -y libonig-dev libmcrypt-dev libsqlite3-dev
RUN apt-get install git --yes &amp;&amp; apt-get install zip unzip --yes
RUN docker-php-ext-install pdo pdo_sqlite mbstring
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

RUN git clone https://framagit.org/blchrd/playlistshare-back-laravel.git /app/back

# Set the working directory
WORKDIR /app/back
COPY backend.env .env

# Expose port 8000
EXPOSE 8000

# Run the API
#CMD composer install --optimize-autoloader --no-dev &amp;&amp; php artisan migrate --force &amp;&amp; php artisan config:cache &amp;&amp; php artisan serve --host=0.0.0.0 --port=8000
CMD composer install &amp;&amp; php artisan migrate --force &amp;&amp; php artisan serve --host=0.0.0.0 --port=8000
</code></pre>

<p>I told you, it was messy.</p>

<p>My main issue was the fact that I had two repositories, one for the frontend and one for the backend. I guess that having only one repository would probably make the Docker configuration a lot easier (though I wasn't entirely sure, just assuming).</p>

<p>Since I didn't want to merge the two repositories, my initial thought was to create a third repository containing all the Docker configuration and pull all the code I need with git directly in the Dockerfile. However, I wasn't sure if it's considered a good practice with Docker. Therefore, I decided to search another way to accomplish my goal.</p>

<h3>Interlude, learning about git submodule</h3>

<p>Then, when I was seeking a more elegant solution to my issue, I discovered git submodules.</p>

<pre><code>git submodule add &lt;repo_url&gt; &lt;target_dir&gt;
</code></pre>

<p>It was the solution for all my multi-repo issues. It kind of creates a symbolic link between one git repo and another, allowing you to have multiple repositories for code tracking but only one when it comes to deployment.</p>

<p>Using git submodules allowed me to create the Dockerfile in frontend and backend repositories without having to deal with messy Dockerfiles as shown above.</p>

<p>The only side-effect is that I have two more command lines to remember:</p>

<pre><code>git submodule update --init --recursive
git submodule update --recursive --remote
</code></pre>

<p>The first one initializes the git submodules, and the second one gets the latest version of them.</p>

<h3>Rewrite <code>Dockerfile</code> and <code>docker-compose.yml</code></h3>

<p>Now I can remove all the <code>git clone</code> commands from my Dockerfiles, and I'll keep the third repository for the final docker-compose.</p>

<p>Here is my current frontend Dockerfile:</p>

<pre><code>FROM node:14-alpine
WORKDIR /app
COPY ./ /app/

#Environment variable
ENV REACT_APP_API_URL="http://localhost:8000/api/v1"
ENV REACT_APP_DEBUG=0
ENV REACT_APP_TITLE="Playlist Share"
ENV REACT_APP_MAX_ITEM_PER_PAGE=10

RUN npm install
RUN npm run build
RUN npm install -g serve

EXPOSE 3000
CMD serve -s build
</code></pre>

<p>And my backend Dockerfile</p>

<pre><code>FROM php:8.2-cli
RUN apt-get update -y &amp;&amp; apt-get install -y openssl zip unzip git libonig-dev libmcrypt-dev libsqlite3-dev
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN docker-php-ext-install pdo mbstring
WORKDIR /app
COPY . /app
COPY .env.example .env

# Environment variables
ENV APP_ENV=production
ENV APP_DEBUG=false
ENV LOG_CHANNEL=errorlog
ENV LOG_LEVEL=warning
ENV APP_URL=http://localhost:8000
ENV FRONTEND_URL=http://localhost:3000

# Environment variables for admin user
ENV ADMIN_NAME=test
ENV ADMIN_EMAIL=test@test.fr
ENV ADMIN_PASSWORD="12345678"

RUN composer install
RUN php artisan migrate --force
RUN php artisan key:generate
# caching stuff for production
RUN php artisan cache:clear
RUN php artisan config:cache
RUN php artisan route:cache
RUN php artisan view:cache

EXPOSE 8000
CMD php artisan serve --host 0.0.0.0 --port=8000
</code></pre>

<p>Don't forget to include <code>RUN php artisan key:generate</code> in your Laravel Dockerfile. If you skip this step, your backend will return a 500 error consistently.</p>

<p>Additionally, I use the <code>--force</code> argument in <code>php artisan migrate</code> to create the database if it doesn't exist (since I'm using SQLite database).</p>

<h2>Production time</h2>

<p>To put it into production, I am first thinking of including the web server directly in the <code>docker-compose.yml</code> file. It is good for some cases, especially when you have only one server with this application, but for my usage, it's not really ideal. I talked about it in <a href="https://blchrd.eu/2023/07/28/migration-from-apache-to-nginx/">my last post</a>.</p>

<p>I keep the nginx service in the docker-compose.yml file, but I will simply comment out the line related to it. The current configuration file looks like this:</p>

<pre><code>version: '3'

services:
#nginx:
#    image: nginx:latest
#    container_name: production_nginx
#    volumes:
#    - ./nginx.conf:/etc/nginx/nginx.conf
#    ports:
#    - 80:80
#    - 443:443

backend:
    build:
    context: ./backend
    dockerfile: Dockerfile
    image: playlist_share_backend:0.1
    container_name: playlist_share_backend
    volumes:
    - /app/database/
    expose:
    - "8000"
    ports:
    - "127.0.0.1:8000:8000"

frontend:
    build:
    context: ./frontend
    dockerfile: Dockerfile
    image: playlist_share_frontend:0.1
    container_name: playlist_share_frontend
    expose:
    - "3000"
    ports:
    - "127.0.0.1:3000:3000"
</code></pre>

<p>For the ports line, it is allowed for localhost to access the port in question, but it cannot be accessed from outside. So, it's a win-win situation in my case.</p>

<p>Here is the nginx configuration file in case the nginx server is included directly in Docker:</p>

<pre><code>events {}
http {
    server {
        listen 80;
        server_name  localhost:80;

        location / {
            proxy_pass http://frontend:3000;
        }

        location /backend {
            proxy_pass http://backend:8000;
            rewrite ^/backend(.*)$ $1 break;
        }
    }
}
</code></pre>

<p>In the nginx proxy config in Docker, you need to use the container name in the URL (e.g., <code>http://backend:8000</code>) instead of using localhost. If you mistakenly use localhost, you'll encounter a nice 502 Bad Gateway error, and it can be quite frustrating if you're not aware of this - like I was - and you might get stuck for hours trying to figure out the issue.</p>

<p>After going through all of this (which took me several days to figure out), the final command line, and everything works together smoothly (at least for me), is:</p>

<pre><code>docker-compose up --build
</code></pre>

<p>And that's it.</p>

<p>To update the container, for now, I use: </p>

<pre><code>docker-compose up --force-recreate --build -d
</code></pre>

<p>I'm not sure if there's a better way, but currently, it is sufficient for my needs.</p>

<h2>Conclusion (kind of)</h2>

<p>It was a cool journey to get here, but that's just the beginning of it. I still have to learn about CI/CD pipelines, cloud computing, and all the other fascinating and time-consuming tech topics. Development is a non-stop learning process, and that's why I love developing stuff so much.</p>

<p>There is <a href="https://plsh.blchrd.eu">one online instance</a> with some data in it - it is my personal instance, my development instance, my test instance, and... well, you get it. You can't test it for now, I'm sorry for that, but I will continue to work on it and eventually get one public instance for you to test.</p>

<p>So, keep developing stuff, sharing knowledge, and, above all else, take care of yourself.</p>
<p style='text-align: right;'><em>blchrd</em></p>]]></content><link href="https://blchrd.eu/2023-07-31.docker-with-reactjs-and-laravel.html"/></entry><entry><id>https://blchrd.eu/2023-08-02.setting-up-a-gemini-capsule.html</id><title>Setting up a Gemini capsule (AI corrupted)</title><updated>2026-03-18T02:37:43.961198+00:00</updated><content type="CDATA"><![CDATA[<p><em><strong>This post was at least corrected with AI, if not in part written with it.</strong></em></p>

<h1>Setting up a Gemini capsule</h1>

<p><em>2023-08-02</em></p>

<p>Sometimes, while reading news in my RSS feeds, I stumble upon an article that awakens in me an irresistible desire to get my hands dirty and start working on something I previously didn't know, to see where that leads me.</p>

<p>Recently, I read an article about the Gemini Protocol, and I decided to dive in and create a Gemini capsule on my own.</p>

<h2>What is Gemini?</h2>

<p>Gemini is an internet protocol designed in 2019, offering a contemporary twist on the early days of the online world. It represents a simplified alternative to other modern web protocols, omitting trackers, scripting, and elaborate styling in favor of an easily parsed markup language.</p>

<p>Moreover, Gemini prioritizes security through built-in encryption and emphasizes privacy, again, no trackers involved.</p>

<p>I view Gemini as a cool spot where I enjoy interacting with friendly folks and simply relaxing.</p>

<p>However, it is not intended to replace the web as we know it, I quote the <a href="https://gemini.circumlunar.space/docs/faq.gmi">FAQ</a> on the official web site</p>

<blockquote>
  <p>Gemini is not intended to replace either Gopher or the web, but to co-exist peacefully alongside them as one more option which people can freely choose to use if it suits them</p>
</blockquote>

<p><a href="https://gemini.circumlunar.space/docs/specification.gmi">The full specification of the protocol</a> isn't quite as extensive. I mean, the FAQ I mentioned earlier is actually longer than this specification! I gave it a quick read, but at this point, I don't feel quite up to implementing it from scratch. It would be a really cool project though.</p>

<p>I'll leave you to explore the <a href="https://gemini.circumlunar.space/">official website</a> yourself if you're interested; there are numerous resources available there.</p>

<h2>So, let's make a capsule?</h2>

<p>A "capsule" is the term used in Gemini to refer to a "Gemini Website," or more precisely, a "Gemini set of pages."</p>

<p>Firstly, we need to familiarize ourselves with the "gemtext" markup language, which bears some resemblance to Markdown, although there are notable differences. For instance, in a gemtext file, you cannot create an inline link as you would in Markdown; instead, you must write a new line containing only the link itself.</p>

<p>For my capsule, I decide to write a Python script to transform the MarkDown source file of Hexo into .gmi file for Gemini.</p>

<p>I use the <code>md2gemini</code> library to handle a significant portion of the task, and some classic string manipulations for the specific file format of Hexo, such as header information.</p>

<p>The script looks like this:</p>

<pre><code>import os
from pathlib import Path
from md2gemini import md2gemini

# ===== UPDATE THESE VALUES ===== #
host = "gemini://localhost/"
author = "blchrd"
# =============================== #

root_gmi = "public_gmi"
blog_posts = []


def create_index_page(blog_post_list):
    print(f"Create index links list in {root_gmi}/index.gmi")
    blog_post_links = ""
    for blog_post in blog_post_list:
        blog_post_links += f"=&gt; {blog_post_list}"

    gemini_index = blog_post_links

    with open(f"{root_gmi}/index.gmi",'w') as wf:
        wf.write(gemini_index)


def transform_markdown_into_gemtext(source, file_dest, blog_post=True):
    with open(source, "r") as f:
        md_content = f.read()
        gemini = md2gemini(md_content, links="at-end", plain=True)
        with open(dest, 'w') as wf:
            wf.write(gemini)
            blog_posts.append(f'{host}{file_dest}  {formated_date}: {title}')


Path(root_gmi).mkdir(parents=True, exist_ok=True)
transform_markdown_into_gemtext('source/about/index.md', 'about.gmi', blog_post=False)
for root, dirs, files in os.walk('source/_posts'):
    for file in files:
        transform_markdown_into_gemtext(f'{root}/{file}', file.replace('.md', '.gmi'))

blog_posts.sort(reverse=True)
create_index_page(blog_posts)
</code></pre>

<p>This is a stripped-down version of it. The full script I use is on this <a href="https://framagit.org/blchrd/hexo-markdown-to-gemtext">repository</a>. Now we have all our gmi files, ready to be serve.</p>

<h2>Files ok, now the server</h2>

<p>There are numerous server software options available for Gemini, developed in various programming languages—I'm not sure if I mentioned this, but it could be a really fun project. So, you have a variety of choices. Among these, I personally opted for the one I had heard about the most, <a href="https://github.com/mbrubeck/agate">Agate</a> (written in Rust). Once you've downloaded the binary, a straightforward command in the terminal and you're all set.</p>

<pre><code>agate --content "path/to/public_gmi" --addr 127.0.0.1:1965 --lang en-US --hostname localhost`
</code></pre>

<p>And voilà, your server is up and running locally. All that's required at this point is a Gemini client to test whether everything is displaying correctly.</p>

<p>I never really browse the geminispace before starting to play with it. So, I went to the <a href="https://gemini.circumlunar.space/software/">software page of Gemini's website</a>, and look at all the client, same as the server, a lot of it, in a diverse language and... ok, I put in my project idea to develop client and maybe server software for Gemini, that look so cool.</p>

<p>In the end, I go for <a href="https://github.com/makeworld-the-better-one/amfora">Amfora</a>, a terminal client for Gemini, who work like a charm.</p>

<p>And finally, I can test it, and it looks good (the Gemini client is on the left terminal, and on the right is the server):</p>

<p><img src="/images/amfora-agateserver-screenshot.png" alt="Screenshot of terminal" /></p>

<h2>Let's go serve this to the world</h2>

<p>I began by installing Agate on my server, and then attempted to serve it using the same command line as before:</p>

<pre><code>agate --content "/path/to/public_gmi" --addr 0.0.0.0:1965 --lang en-US --hostname blchrd.eu
</code></pre>

<p>That didn't work; it displayed 'Permission denied' for certificate creation. It attempted to create in the default directory. I attempted the magical word 'sudo', but then it responded with 'agate: command not found'.</p>

<p>So, I forced the certificate path to one with the appropriate permissions:</p>

<pre><code>agate --content "/path/to/public_gmi" --addr 0.0.0.0:1965 --lang en-US --hostname blchrd.eu --certs /path/to/certificates
</code></pre>

<p>And it's working! You can now access my gemlog (the term for "blog" in the geminispace) entries using the Gemini protocol at the address <a href="gemini://blchrd.eu">gemini://blchrd.eu</a>.</p>

<h2>Conclusion</h2>

<p>I won't deny it, the most enjoyable aspect was undoubtedly writing the script. It also constituted a significant part of the process, as I utilized established Gemini server and client tools for this endeavor.</p>

<p>However, I take pleasure in delving into the technical details, and I might eventually write my own Gemini client and server. I've been exploring the code of various implementations, including the ones I've been using, and I'm keen to dive into this further.</p>
<p style='text-align: right;'><em>blchrd</em></p>]]></content><link href="https://blchrd.eu/2023-08-02.setting-up-a-gemini-capsule.html"/></entry><entry><id>https://blchrd.eu/2023-08-14.create-confirmation-dialog-component-with-reactjs.html</id><title>Create confirmation dialog component with ReactJS (AI corrupted)</title><updated>2026-03-18T02:37:43.956673+00:00</updated><content type="CDATA"><![CDATA[<p><em><strong>This post was at least corrected with AI, if not in part written with it.</strong></em></p>

<h1>Create confirmation dialog component with ReactJS</h1>

<p><em>2023-08-14</em></p>

<h2>Why a confirmation dialog?</h2>

<p>If there is one component you will always need, whatever your application is about, it is a confirmation dialog. They are ubiquitous; whether you want to delete an entry, gather consent, or simply alert the user that a process will take some time.</p>

<p>In this post, I will walk through the design and development of my version of a confirmation dialog component.</p>

<p>It is not the only one; there are many tutorials out there covering the same subject, and you might even have a different vision of what constitutes a good confirmation dialog.</p>

<h2>What is a confirmation dialog?</h2>

<p>That seems like a trivial question, but the answer is crucial for designing the component properly.</p>

<p>A confirmation dialog is a window shown to the user when we need their confirmation to proceed with the current action. The action itself is not really relevant and can be anything. The classic use-case is deleting a record in the database.</p>

<p>With that definition, we can outline the main features of a confirmation dialog:</p>

<ul>
<li>Display information about what the user needs to confirm.</li>
<li>A confirm button with a callback function to execute the confirmed action.</li>
<li>A cancel button that closes the dialog.</li>
</ul>

<p>We all know how it looks, but saying — or showing in this case — the obvious is sometimes a good thing:</p>

<p><img src="/images/confirm-dialog-example.png" alt="Example of confirmation dialog" /></p>

<h2>What the component's code looks like</h2>

<p>Create a new file named <code>ConfirmDialog.js</code>.</p>

<p>For this component, I am using Material-UI for all the styling. However, you can use whatever you prefer; the code will not change significantly.</p>

<p>I will provide the code here and explain it afterward.</p>

<pre><code>import * as React from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';

export default function ConfirmDialog({
                                        title,
                                        content,
                                        onConfirm,
                                        openState,
                                        setOpenState,
                                        cancelButtonText = "No",
                                        confirmButtonText = "Yes",
                                        onCancel = null
                                    }) {

    // Execute the cancel callback code and close the dialog
    const handleClose = () =&gt; {
        if (onCancel !== null &amp;&amp; onCancel !== undefined) {
            onCancel();
        }
        setOpenState(false);
    };

    // Execute the confirmation callback code and close the dialog
    const handleConfirm = () =&gt; {
        onConfirm();
        setOpenState(false);
    };

    return (
        &lt;div&gt;
            {/* Dialog component for displaying confirmation dialog */}
            &lt;Dialog
                open={openState}
                onClose={handleClose}
                aria-labelledby="alert-dialog-title"
                aria-describedby="alert-dialog-description"
            &gt;
                &lt;DialogTitle id="alert-dialog-title"&gt;
                    {title}
                &lt;/DialogTitle&gt;
                &lt;DialogContent&gt;
                    &lt;DialogContentText id="alert-dialog-description"&gt;
                        {content}
                    &lt;/DialogContentText&gt;
                &lt;/DialogContent&gt;
                &lt;DialogActions&gt;
                    {/* Cancel button to dismiss the dialog */}
                    &lt;Button onClick={handleClose} autoFocus&gt;{cancelButtonText}&lt;/Button&gt;
                    {/* Confirm button to execute the confirmed action */}
                    &lt;Button id="dialog-confirm-button" onClick={handleConfirm}&gt;{confirmButtonText}&lt;/Button&gt;
                &lt;/DialogActions&gt;
            &lt;/Dialog&gt;
        &lt;/div&gt;
    )
}
</code></pre>

<p>As you can see, most of the component's behavior is managed by the caller rather than the component itself. This might seem evident upon consideration, but as mentioned earlier, stating the obvious can still be beneficial.</p>

<p>While I could delve into explaining all the props here, some are relatively self-explanatory. You probably already understand the purposes of <code>title</code>, <code>content</code>, <code>cancelButtonText</code>, and <code>confirmButtonText</code>.</p>

<p>Here's a brief rundown of the other props:</p>

<ul>
<li><code>onConfirm</code>: This prop is a callback function triggered when the confirmation button is clicked.</li>
<li><code>openState</code> / <code>setOpenState</code>: These props consist of the open state and the corresponding state-setting function from the caller component. They are used to control the visibility of the confirmation dialog, either opening it or closing it.</li>
<li><code>onCancel</code>: By default set to <code>null</code>, this prop offers a callback function for the cancel button click. It accommodates scenarios where more than just closing the dialog is necessary, covering those specific requirements.</li>
</ul>

<h2>Implementation</h2>

<p>Here's an example illustrating the usage of the component:</p>

<pre><code>import React, {useState} from "react";
import Button from '@mui/material/Button';
import ConfirmDialog from "../components/ConfirmDialog";

function App() {
    const [confirm, setConfirm] = useState(false);

    function handleConfirmDialogConfirmClick() {
        // Code execute when clicking on the 'Confirm' button
        console.log('Confirmation');
    }

    function handleConfirmDialogCancelClick() {
        // Code execute when clicking on the 'Cancel' button
        console.log('Cancel');
    }

    return (
        &lt;main&gt;
            &lt;Button onClick={(e) =&gt; setConfirm(true)}&gt;Confirm&lt;/Button&gt;
            &lt;ConfirmDialog
                title={"Confirm?"}
                content={"Do you really confirm this?"}
                onConfirm={handleConfirmDialogConfirmClick}
                onCancel={handleConfirmDialogCancelClick}
                openState={confirm}
                setOpenState={setConfirm}
            /&gt;
        &lt;/main&gt;
    )
}

export default App;
</code></pre>

<p>Now you can test it out and determine if this approach suits your needs.</p>

<h2>Conclusion (kind of)</h2>

<p>That is a straightforward component, and there are opportunities for enhancements. For instance, currently, pressing the 'Escape' key on your keyboard will close the confirmation dialog, but pressing 'Enter' doesn't have any effect.</p>

<p>Furthermore, the UI/UX could be enhanced further. You have the option to incorporate animations or customize the CSS for a more polished appearance.</p>

<p>If you've read through this entire post, I appreciate your attention, and I hope you found this information valuable.</p>
<p style='text-align: right;'><em>blchrd</em></p>]]></content><link href="https://blchrd.eu/2023-08-14.create-confirmation-dialog-component-with-reactjs.html"/></entry><entry><id>https://blchrd.eu/2023-08-23.a-story-about-docker-volume-misconfiguration.html</id><title>A story about Docker Volume misconfiguration (AI corrupted)</title><updated>2026-03-18T02:37:43.952920+00:00</updated><content type="CDATA"><![CDATA[<p><em><strong>This post was at least corrected with AI, if not in part written with it.</strong></em></p>

<h1>A story about Docker Volume misconfiguration</h1>

<p><em>2023-08-23</em></p>

<h2>Stumbled upon the issue</h2>

<p>I updated <a href="https://plsh.blchrd.eu">PlaylistShare</a> on this server recently, and I encountered an issue. The backend resulted in error 500 when retrieving the albums. The last update worked well, so I didn't initially investigate that angle. I was wrong, but we'll get to this later.</p>

<p>It runs well on my local server, and I haven't encountered this issue in all my tests, so I didn't think it's in my code. I checked it, of course, and I didn't see anything weird.</p>

<p>I started to check if the Docker Compose works well. I reviewed the build log, but didn't find anything relevant at first glance. In reality, there was something, but I overlooked it.</p>

<p>Next, I checked the Docker output using the command <code>docker attach &lt;container_name&gt;</code> and I received the complete error message, which informed me that one of my new tables has an error.</p>

<p>I execute a shell within the Docker container using the command <code>docker exec -it &lt;container_name&gt; sh</code>, and then proceed to investigate what might be wrong.</p>

<p>After performing some initial checks (such as confirming the proper application of the update), I examined the database directory and the migrations directory. It was at that point that I realized my mistake.</p>

<h2>Explanation: my docker-compose configuration was incorrect</h2>

<p>What I didn't notice in the Docker build log is that the final database migrations I had written didn't execute.</p>

<p>When I configure my Docker setup for PlaylistShare, I include the entire <code>app/database</code> as a volume.</p>

<p>Seems like a good idea. The <code>database.sqlite</code> file is in this directory by default. All my recent updates have worked well because I haven't altered the database schema; they were mostly frontend updates.</p>

<p>But when I updated the database schema... Well, the migration files were not taken into account because they are located in the directory <code>/app/database/migrations</code>, which is part of the volume <code>app/database</code>.  Therefore, this directory is not updated with the containers. My migration files don't get into the container, and the non-updated database triggers the 500 error that I am encountering.</p>

<h2>Temporary solution</h2>

<p>I want to quickly address this in production, so I simply copy/paste the migration from Git to the Docker volume in the same shell as above, and then manually initiate the migration.</p>

<p>This does the trick, but it's a short-term solution. I don't intend to manually update my database with each migration; it should be an automated process.</p>

<h2>Definitive solution</h2>

<p>To definitively fix the problem, I have a list of tasks that I must complete:</p>

<ol>
<li>Change the database path: without a volume, any updates will erase the database. Therefore, I can no longer use the default folder.</li>
<li>Update the volume configuration to eliminate the use of <code>app/database</code>.</li>
<li>Ensure that the production database remains unaffected during the update process to prevent any loss of data.</li>
</ol>

<p>The first point and the second point are the easiest. Add an environment variable in the backend Dockerfile for the first one:</p>

<pre><code>...
ENV DB_DATABASE=/path/to/database/database.sqlite
...
</code></pre>

<p>Mapping this path to the volume in docker-compose.yml for the second one:</p>

<pre><code>volumes:
    plsh-dbstorage:
service:
    ...
    backend:
        ...
        volumes:
            - plsh-dbstorage:/path/to/database
        ...
</code></pre>

<p>The <code>volumes</code> section is used for naming the volume. I hadn't named my volume until now, so the volume's name was excessively long and didn't convey any meaning. It's quite frustrating when you need to work with such a volume.</p>

<p>For the third and final point, it's a bit more complex. I can't achieve it by simply altering a few configuration files.</p>

<p>After reading some posts on StackOverflow, the Docker documentation and asking ChatGPT (ok, I didn't really ask ChatGPT), I've identified two distinct approaches to address this problem:</p>

<ul>
<li>Manually copy the contents of the volume directly to the system file. Volumes are located at /var/lib/docker/volumes/ on Debian (likely the same for other Linux OS, but I haven't verified this).</li>
<li>Backup and restore the volume using Docker (refer to the <a href="https://docs.docker.com/storage/volumes/#back-up-restore-or-migrate-data-volumes">documentation</a>)</li>
</ul>

<p>I chose the first option for various reasons, with the main one being that this solution is simpler and quicker. Additionally, I only need to perform this task once.</p>

<p>As for the second option, I will examine it. This is because I intend for that instance to become public at some point in the future. To achieve this, an automatic database reset is necessary, although that is a separate topic.</p>

<p>The steps I followed are as follows:</p>

<ol>
<li><p>First, backup the database of the former volume:</p>

<pre><code>mkdir ~/backup
cp /var/lib/docker/volumes/ex_volume/_data/database.sqlite ~/backup/database.sqlite
</code></pre></li>
<li><p>Then, update the containers with the latest version and the appropriate volume:</p>

<pre><code>git fetch &amp;&amp; git pull
git submodule update --recursive --remote
docker-compose up --force-recreate --build -d
</code></pre></li>
<li><p>Replace the database in the new volume with the manual backup we performed in step 1:</p>

<pre><code>cp ~/backup/database.sqlite /var/lib/docker/volumes/new_volume/_data/database.sqlite
</code></pre></li>
<li><p>Finally, we need to perform the migration manually, utilizing a Docker shell:</p>

<pre><code>docker exec -it &lt;container_name&gt; sh
php artisan migrate
exit
</code></pre></li>
</ol>

<p>And that's it! It works for me. It was a quick and dirty solution, but as I mentioned earlier, it's a one-time-only operation.</p>

<p>You should perform some cleanup on the volume. I removed the older volume using the following commands:</p>

<pre><code>docker volume ls
docker volume rm volume_name
</code></pre>

<h2>Conclusion</h2>

<p>Not much to say, I just want to share my debugging process here; it is quite straightforward.</p>

<p>Perhaps some individuals will learn something by reading this—maybe someone made the same mistake as me and can see how they might rectify it. In any case, I hope this post will prove useful, or at the very least, interesting.</p>

<p>Take care, folks.</p>
<p style='text-align: right;'><em>blchrd</em></p>]]></content><link href="https://blchrd.eu/2023-08-23.a-story-about-docker-volume-misconfiguration.html"/></entry><entry><id>https://blchrd.eu/2023-09-10.about-note-taking.html</id><title>About note taking (AI corrupted)</title><updated>2026-03-18T02:37:43.948253+00:00</updated><content type="CDATA"><![CDATA[<p><em><strong>This post was at least corrected with AI, if not in part written with it.</strong></em></p>

<h1>About note taking</h1>

<p><em>2023-09-10</em></p>

<h2>Introduction (kind of)</h2>

<p>I take a lot of notes in various formats and on different devices, so it can be a bit complex to retrieve a note I wrote some time ago.</p>

<p>I used <a href="https://www.giuspen.net/cherrytree/">CherryTree</a> a lot when I was in France, and it covered most of my needs. But after moving to Canada, I started to take notes on my phone and also began using the terminal frequently. Consequently, CherryTree was no longer as useful, or at least not the best tool for my current needs.</p>

<p>I should know how to list my needs because it is part of my job, and find or even create the software that covers all or most of those needs.</p>

<p>So I start doing it, and it was quite a journey.</p>

<h2>Why, when and how?</h2>

<p>First, I need to take a step back in my note-taking process to understand why, when, and how I take notes.</p>

<p>Why? To avoid forgetting something, obviously. Sometimes it is just to help my memory (it helps me remember something, even if I don't reread it later). It can help me organize and prioritize tasks. Or it's just to throw distracting ideas out of my brain to focus on what I'm doing right now. I don't really have to remember or even retrieve the last one; it's really about removing distractions.</p>

<p>When? This one is easy: at any time, day or night.</p>

<p>How? Well, here is the tricky question. It is chaotic. I use a lot of support and different devices (pen and paper, Android phone, both Windows and Linux PCs). I don't organize it very well. It can be on a post-it with only a word or two, or it can be a markdown file hidden on my hard drive with a long reflection about a topic or an idea I'm trying to elaborate on. It can also be a full notebook on a specific software, like my CherryTree notebook.</p>

<p>The format of one note is almost always the same: a list of items. It is rare that I note a full paragraph, or it's a full webpage backup. Yes, I sometimes download a webpage to read it offline, or even just for backup purposes.</p>

<p>I'm leaving the "where" question aside because I don't find it relevant here; the answer is pretty much the same as "when": everywhere.</p>

<h2>Try to define my needs</h2>

<p>Until now, I have been using CherryTree for taking my notes and syncing them with Dropbox. It has worked quite well up to this point. However, I now have a significant number of notes on my phone, and there isn't an Android version of CherryTree available. So, I'm faced with two options: either I develop an Android version myself or I change my workflow. Both options are time-consuming, but I've decided to go with the second one.</p>

<p>I need to narrow down my requirements in order to choose the right piece of software:</p>

<ul>
<li>I love taking notes on paper, I just need a pen and a physical notebook, not sure this point is relevant.</li>
<li>I prefer Markdown syntax or a similar format because I find it more convenient. It will likely be easier to write a conversion script for my CherryTree notebooks.</li>
<li>I require synchronization between multiple devices and operating systems (Android, Windows, Linux, etc.), I can consider manual synchronization.</li>
<li>I need a tag system, even though I haven't used tags extensively in the past. I recognize the importance of using such a system to facilitate easy searching within my notes.</li>
<li>I would like the software to have a command-line interface (CLI) application for adding, editing, and removing notes on both Windows and Linux.</li>
</ul>

<p>I have one more constraint: I do not want to use a centralized note service like Evernote or Obsidian synchronization. I want the flexibility to store my notes wherever I choose and to move them to a different location if needed.</p>

<h2>Softwares and current workflow</h2>

<p>I read about <a href="https://xwmx.github.io/nb/">nb</a> while browsing the <a href="https://geminiprotocol.net/">geminispace</a>, and it interested me immediately. It is a CLI / TUI notetaking app with many really cool features: bookmarks (with a local copy for offline reading), to-do list, tags, git synchronization, and more. Plus, it stores notes as simple markdown files on the hard drive, making it easy to synchronize other files and add them.</p>

<p>As for Android, I tried various applications. First, I explored some on the Play Store, but then I started developing my own. I've been using it for 4 or 5 months now. I was already familiar with <a href="https://github.com/gsantner/markor">Markor</a>, but I didn't fully utilize it until I embarked on a three-month journey in North America.</p>

<p>During my travels, I began taking extensive notes on my phone, and Markor proved to be a straightforward application: one file for one note. It also includes a to-do list feature, although I didn't make much use of it, it's worth mentioning.</p>

<p>Lastly, there's <a href="https://syncthing.net/">Syncthing</a>. This application stands out as one of the best discoveries I've made this year. It allows you to seamlessly synchronize content between your smartphone and your PC. It offers a plethora of settings when configuring the sync, and most importantly, it works exceptionally well and is easy to use.</p>

<p>All of these elements have converged into the workflow I've been experimenting with:</p>

<ul>
<li>nb: for PCs, both on Linux and Windows (with WSL for the latter, so it's essentially still Linux).</li>
<li>Markor + Syncthing: for my Android phone.</li>
</ul>

<p>I'm considering writing some automation scripts for syncing, even though it's quite usable without them. I know it will be fun to create these scripts.</p>

<p>Here are some issues:</p>

<ul>
<li>Syncing with Android and Windows: Currently, I use nb with WSL, and I use rsync in addition to Syncthing when I really need to synchronize notes directly between Android and Windows.</li>
<li>Viewing notes on Android: I can read my notes on git directly with Android, but searching isn't really an option.</li>
</ul>

<p>A couple of thoughts on Pen &amp; Paper synchronization, just for fun:</p>

<ul>
<li>Take pictures of the notebook page and synchronize the photo with Syncthing/nb.</li>
<li>Try to OCR the notebook page. I also gave it a try, but it didn't work well. However, I didn't really get into the technology.</li>
</ul>

<h2>Conclusion (kind of)</h2>

<p>I have been using this workflow for about a month now, and it has been working well so far. I highly recommend giving 'nb' a try if you're not afraid of the terminal.</p>

<p>It is just my train of thought on my note-taking system. I wanted to share this here, and if someone discovers one of the software I talk about here, I consider this post useful.</p>

<p>I still refer to my CherryTree notebook from time to time while working on the conversion script; for now, my notes are stuck in there. I've started working on a script for my personal use — it's advanced enough to <a href="https://framagit.org/blchrd/ct2md">share the code</a>, but it's far for completed — and I'll build upon it to create a full conversion script.</p>

<p>I've come to realize that I enjoy writing conversion scripts. It's not too complex, and it helps me become more comfortable with different formats and languages. I love these kinds of "coffee break" scripts and projects.</p>

<p>Take care, everyone!</p>
<p style='text-align: right;'><em>blchrd</em></p>]]></content><link href="https://blchrd.eu/2023-09-10.about-note-taking.html"/></entry><entry><id>https://blchrd.eu/2023-12-19.rewrite-project-to-learn.html</id><title>Rewrite your projects to learn new languages and new techs (AI corrupted)</title><updated>2026-03-18T02:37:43.943453+00:00</updated><content type="CDATA"><![CDATA[<p><em><strong>This post was at least corrected with AI, if not in part written with it.</strong></em></p>

<h1>Rewrite your projects to learn new languages and new techs</h1>

<p><em>2023-12-19</em></p>

<p>In the developer field - and by extension, in tech field - continuously learning new stuff, keeping yourself sharp at what you do and just keeping yourself updated is highly recommended, if not just required.</p>

<p>In my morning routine - no, I'll not talk about sport here -, the first thing I do is making coffee, and then scroll at my RSS feeds. Most of the stuff are just news about the world, gaming and music. But in them, there also are a lot of tech news and dev blog post - and yes, HackerNews too. While reading it, I can deduct the current "à la mode" techs and languages. I almost always take the "Getting started" of a tech I didn't know, write an <code>Hello world</code> and / or a todo-list, and then decide if I want to dig further or not.</p>

<p>While chatting with a fellow developer - maybe he'll recognize himself -, we start talking about the fact I want to learn Golang. Some languages are more appealing to me, and Golang is one of them. I already did the "Getting started" and some tutorials, but what I lacked to really start learning, is a project idea.</p>

<p>When I want to learn a language, I want to have at least a small project idea, to write a piece of software I find useful, or at least fun to write. The todo-list is always my first stop, but after that, I want to make more.</p>

<p>The discussion going, I had an idea: maybe I can rewrite the PlaylistShare backend in Golang, to learn it? There is no real downside in rewriting a project. You'll learn the language, you already have documentation about what kind of feature you'll write (well, if you document your project correctly), and some constraints - like, do not break the frontend. At the end, ok I'll have two backend, but I'll have more knowledge about the behavior of the language and the syntax of it, I can even have some new ideas about the backend itself, or even a new project ieea..</p>

<p>And I started it, rewriting my PHP/Laravel backend in Golang. I have a kind-of working version right now, and I understand a little better some specifics of the language. It's not magical though, I can't say I'm productive with Go, but I understand some stuff, and reading Go code is much easier right now.</p>

<p>This kind of rewriting has been a revelation, and I recommend to all the people who wants to learn another language to test it, if you didn't have an existing project to do it, well, start to make a project then. I know that'll not work for everyone, but for me, it works quite well. </p>

<p>Right now, I try to learn Rust with a similar process, rewriting my HNWGen project (originally written in Java).</p>
<p style='text-align: right;'><em>blchrd</em></p>]]></content><link href="https://blchrd.eu/2023-12-19.rewrite-project-to-learn.html"/></entry><entry><id>https://blchrd.eu/2024-02-03.why-build-privacy-first-application-is-that-hard.html</id><title>Why build privacy-first application is that hard?</title><updated>2026-03-18T02:37:43.941204+00:00</updated><content type="CDATA"><![CDATA[<h1>Why build privacy-first application is that hard?</h1>

<p><em>2024-02-03</em></p>

<h2>Privacy by design</h2>

<p>I'm still working on <a href="https://plsh.blchrd.eu">PlaylistShare</a>, hoping to release it to the world in 2024. I mean, hoping to release it to the few friends interested.</p>

<p>One focus I have beside making the application usable, is to put user privacy first. So during the development, I try to not use cookies or other tracking technologies.</p>

<p>For those who don't know, PlaylistShare is a simple music tracking application. You can add an album with an url to listen to it, and when you listened, mark it as well, add a comment for yourself (or for the others), rate the album, etc.</p>

<p>I just want to share some of my thoughts on the subject, and some of the choices I made. I do not detain the truth here, it is just some "organized" thoughts someone can find interesting. If you want to talk about this, please feel free to contact me on <a href="https://www.linkedin.com/in/blchrd">LinkedIn</a>.</p>

<h2>Analytics</h2>

<p>Well, this section will be short: I don't really care about analytics (I can here SEO specialists yell at me from there). I used Piwik (now <a href="https://matomo.org/">Matomo</a>) a lot in the past, never used Google Analytics, and I won't for my personal projects.</p>

<p>Here, analytics is not really a thing cause like I said one sentence earlier: I don't care about analytics. Next.</p>

<h2>The Captcha</h2>

<p>When I made the user registration, I wanted to secure the registration with some anti-bot features. The go-to in that case would be <a href="https://www.google.com/recaptcha/about/">reCaptcha</a> or <a href="https://www.hcaptcha.com/">hCaptcha</a>, but in a privacy-first application, reCaptcha is a clear no. hCaptcha, while it's better in this field, is also a no, since it uses cookies and gather a lot of informations to work.</p>

<p>I also checked <a href="https://altcha.org/">Altcha</a>, and at first, I wanted to implement this one. You can install it in your own server, so no third party involved.</p>

<p>But in the end, I implemented an easier alternative (at least I think it is): <a href="https://www.fabianwennink.nl/projects/IconCaptcha/">IconCaptcha</a>. It is a fully self-hosted captcha system as Altcha. It was a bit tricky to implement in my current stack (Laravel / ReactJS), but in the end, it works. I guess it's less secure than Altcha, but in the current state of the application, it is enough and it'll block the main threats I expect at first, without any privacy trade off.</p>

<h2>Embedded iFrame</h2>

<p>Then I write the Privacy Policy template for the application, I write a "No cookies or tracking technologies" section. I admit it feels good to write this section. But then I think further: I use embedded iframe from YouTube and Bandcamp. That's a huge issue, it is a liability.</p>

<p>Embedded iFrame from another website is a risk and will eventually use tracking technologies one way or another.</p>

<p>I didn't dig a lot through that. I read some articles, blog post and StackOverflow questions about it like <a href="https://axbom.com/embed-youtube-videos-without-cookies/">this one</a> or <a href="https://stackoverflow.com/questions/65472798/embed-bandcamp-audio-without-analytics">this one</a>.</p>

<p>For YouTube embed, I currently consider <a href="https://invidious.io/">Invidious</a> API, I started to look at it, and it appears there is no tracking.</p>

<p>For Bandcamp, I start to check where the streamed audio files are from, to ditch the embedded iframe, not sure it'll work forever though.</p>

<h2>Why is it this hard?</h2>

<p>So okay, I want to build privacy-first application, and it's hard. But the question in the end is: why is it so hard?</p>

<p><em>TL;DR: Solutions that tracks users are simple, easy to implement, secure and working. Others are the opposite. More complex, and when you want to interact with other website, it is quite a journey.</em></p>

<p>Alternatives to reCaptcha or hCaptcha exists, but none is really easier to implement. And, hot-take here, I guess none is really as secure. But from a privacy point of view, reCaptcha is the worst, and any solutions that are hosted in a third-party server are a liability. </p>

<p>I spent 4 hours to implement IconCaptcha, and I'm sure reCaptcha would be a 10 minutes long implementation. For me, the user privacy is a strong requirement for this project. It's a side project, so spend a lot of time on implementing a captcha isn't really an issue, I can do it. For a company, 4 hours vs 10 minutes is an obvious choice.</p>

<p>For the embedded iframe, it is easy to just paste the embed code, or even reverse engineering one to build it dynamically (that's what I do for bandcamp embed). But if we put privacy first, embedded iframes is code you don't control. It's a weak point. You have to trust the provider of the embed. And quite honestly, I didn't trust YouTube to take care of the user's privacy, and I trust Bandcamp less and less with the <a href="https://www.theguardian.com/music/2023/oct/17/bandcamp-lays-off-half-its-staff-after-buyout-by-songtradr">recent events</a>.</p>

<p>And here, there is little to no alternatives. For YouTube, there is Invidious, and maybe others (no name comes to my mind right now), so it should be ok. For Bandcamp, there is none, or at least I didn't find one. I guess I have to make my own from scratch. But there is a catch: I will spend time and energy at building my own embed by scrapping data. Is it worth it? For me, yes, I will learn new stuff and perhaps build a cool thing someone could use. For a company? Huge no, spending time to build something that can break at anytimes? Please.</p>

<p>My humble guess is this is hard because tracking is the most profitable thing on the web right now. The <a href="https://en.wikipedia.org/wiki/Big_Tech">GAFAM (or AAAMM now I guess)</a> make billions out of our personal data, covered by the fact their platform is free (as in free beer). They have billions to invest in infrastructure and workforce to build easily implementable and secure captcha, analytics and cool embedded content. And the new proprietary platform will eventually do the same to get a piece of the cake.</p>

<p>The <a href="https://en.wikipedia.org/wiki/Free_and_open-source_software">FOSS</a> community try to compete here, they build <a href="https://invidious.io/">Invidious</a>, <a href="https://freetubeapp.io/">FreeTube</a>, <a href="https://joinpeertube.org/">PeerTube</a>, <a href="https://www.funkwhale.audio/">Funkwhale</a>, <a href="https://joinmastodon.org/">Mastodon</a>, the list getting longer and longer and it is really cool. But the content is not really here for now. Creators are present on some of these platform, but in the end, it is a niche. And most of the musical content I consume is on YouTube or Bandcamp, unfortunatly.</p>

<p>And in the end, the web development environment wants you to track your users, for analytics sure, but just to gather data about you, not necesseraly for shady business. Sometimes it is genuinely to "improve the service". For me, the real issue was, is and will be trust. You have to trust the company, developper and provider that bring softwares and services to you.</p>

<h2>Really short conclusion</h2>

<p>So here I am, trying to build my humble application without any user tracking. </p>

<p>It's hard, but fun. And in the end, just the "No cookie and tracking technologie" section of the Privacy Policy worth the time and energy for me. </p>

<p>And in the end, you just have to trust me on that.</p>

<p>I can argue my source code is public, but how can you be sure the public source is the one thats run in production? You see the point, I'm not willing to elaborate this here. Maybe on another post.</p>

<p>I don't tell you to don't trust anyone, just be careful on what you gave to the Internet, you never know how that'll be used.</p>

<p>Take care everyone.</p>
<p style='text-align: right;'><em>blchrd</em></p>]]></content><link href="https://blchrd.eu/2024-02-03.why-build-privacy-first-application-is-that-hard.html"/></entry><entry><id>https://blchrd.eu/2024-09-29.procedural-music-generation.html</id><title>Some thoughts about learning procedural music generation</title><updated>2026-03-18T02:37:43.935931+00:00</updated><content type="CDATA"><![CDATA[<h1>Some thoughts about learning procedural music generation</h1>

<p><em>2024-09-29</em></p>

<p><em>Disclaimer: this isn't any sort of tutorial of any kind</em></p>

<p>I'm interested in procedural generation since I played games like <a href="https://www.bay12games.com/dwarves/">Dwarf Fortress</a>. This kind of generation haunt me since, and when I learn that parts of the soundtrack of <a href="https://en.wikipedia.org/wiki/Streets_of_Rage">Streets of Rage 3</a> was made with procedural generation tools written by <a href="https://en.wikipedia.org/wiki/Yuzo_Koshiro">Yuzo Koshiro</a> (probably helped by <a href="https://en.wikipedia.org/wiki/Motohiro_Kawashima">Motohiro Kawashima</a>), I wanted to try to build this kind of tool by my own since I learned this.</p>

<p>In the end, it took me 7 years to seriously dive in the rabbithole of procedural music generation. And with this, the rabbithole of music theory.</p>

<p>I'm an average musician, I did - and still occasionnaly do - a lot of sound experimentation, while the <a href="https://framagit.org/blchrd/rust-hnwgen">HNW generator</a> I'm building works nicely, it's still just generation of noise. Generate the source, and then apply a lot of different effect with FFmpeg and SoX. I knew generating actual music will be another level of implication, and first things first, I had to learn the basics of music theory, to at least comprehend what I have to do to have a working basic generation.</p>

<p>While searching, I came across <a href="https://dev.to/deciduously/teaching-numbers-how-to-sing-3c8l">this tutorial</a>, I learned a lot of things in it, and while I already knew all the musical theory in the post, the technicals in here really helped me to kickstart the project. Once I completed the tutorial, I have a working basic generation, and understand all the process to get to it.</p>

<p>After that, I was on my own. Reading about music, chord progression, interval, melody construction, rhythmic pattern, you name it. The first version of the generation was good enough to progress to the next step. But there is one problem, which I didn't see coming, and I wonder why.</p>

<p>The output of the tutorial, and therefore the output of my project, is still pure sinusoidal wave (not really because I implement chords, but I simplify things). No variation in it whatsoever, I get the note, create the sinewave, and continue until I need a new note. It's not that bad, but if I want the result to be listenable - so I can bragg about it - it lacks some stuff.</p>

<p>First, I implement a simple envelope. A <a href="https://framagit.org/blchrd/procedural-music-generation/-/blob/main/src/signal/adsr_envelop.rs?ref_type=heads">linear ADSR envelope</a>, for Attack, Decay, Sustain, Release. I won't enter into technicals here, but in very short, it's volume modulation during the note length. By example, I can say with this envelope that the note will start at volume 0 and reach volume 1 in 0.2s. That makes thing a lot more fluid while listening.</p>

<p>Another thing I have to do, it's note transition. Right now, while I reach a new note, I cut the current sinewave and start the new one at 0. I have to find a way to put a smooth transition between two samples. I don't know if it's that hard, but I have no idea where to start for now.</p>

<p>In the future, I want to implement instrument soundwave, but for this, it's a brand new rabbithole I have to dig: harmonics and signal processing. If you want to scratch the surface, I found a <a href="https://www.ethanhein.com/wp/2024/what-are-harmonics/">blog post about this</a>, the whole blog explain a lot of stuff about music theory and sound technicals, I'm glad I found this one. My journey into signal processing will probably bring me to synthetizer and stuff, can't wait.</p>

<p>All that said, even if generation is good enough to progress, it lacks some real randomness and chaos for my taste. I added randomness in chord progression and note value, next will be the chords itself, etc. Break enough musical theory to be fun. Also, mix the output of this with the one of the HNW generator is very fun too. In the end, even if it sounds a little "artificial" because of the sinewave thing, tweaking the parameters of the generation is very fun.</p>

<p>If you want to check the tool I wrote, <a href="https://framagit.org/blchrd/procedural-music-generation">you'll find it here</a>.</p>

<p>I guess I write all the things I wanted to write here. Hope you enjoy to read me, or at least that I didn't bore you that much.</p>

<p>Have a great day or night, take care everyone.</p>
<p style='text-align: right;'><em>blchrd</em></p>]]></content><link href="https://blchrd.eu/2024-09-29.procedural-music-generation.html"/></entry><entry><id>https://blchrd.eu/2026-03-11.rewrite-the-site-again-bye-hexo.html</id><title>Rewrite the site, again. From Hexo to custom SSG</title><updated>2026-03-18T02:37:43.933980+00:00</updated><content type="CDATA"><![CDATA[<h1>Rewrite the site, again. From Hexo to custom SSG</h1>

<p><em>2026-03-11</em></p>

<p>I decided to drop <a href="https://hexo.io">Hexo</a> for this website / blog thing. It worked very well and is very useful overall, but I wanted to have a less complex architecture and workflow. And also ditch all javascript and external dependencies (my Hexo theme use some Google Fonts).</p>

<p>So here I am, rewriting the complete architecture, to something really simple: html / css, generated from Markdown files with a custom python script. I try to keep the same url for the rss and atom feed (atom looks okay, rss is a work in progress), but don't really care about post urls. The blog posts are still there, so it's good enough in my book. The only link I share was on LinkedIn, so my reaction to a dead link here is something like "Oh no! Anyway.".</p>

<p>Also, the old "about" page was half-written with AI, some posts has been corrected by AI, and I want to fix at least the first point. For the second, I just added a disclaimer.</p>

<p>I'm not sure I will post more here than before, but we never know.</p>
<p style='text-align: right;'><em>blchrd</em></p>]]></content><link href="https://blchrd.eu/2026-03-11.rewrite-the-site-again-bye-hexo.html"/></entry></feed>