<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-8128983</id><updated>2026-04-04T10:47:29.552+02:00</updated><category term="javascript"/><category term="projet"/><category term="jquery"/><category term="php"/><category term="nodejs"/><category term="api"/><category term="astuce"/><category term="epsi"/><category term="windows"/><category term="algorithme"/><category term="docker"/><category term="firefox"/><category term="plurk"/><category term="postgresql"/><category term="redsmin"/><category term="bookmarklet"/><category term="c/c++"/><category term="feedback"/><category term="gitlab"/><category term="postgrest"/><category term="rss"/><category term="css3"/><category term="domainfinder"/><category term="gitlab-ci"/><category term="hack"/><category term="jenkins"/><category term="review"/><category term="twitter"/><category term="ajax"/><category term="ci"/><category term="firebug"/><category term="image-charts"/><category term="kubernetes"/><category term="redis"/><category term="web 2.0"/><category term="1and1"/><category term="angularjs"/><category term="backup"/><category term="blog"/><category term="bringr"/><category term="cassandra"/><category term="clevercloud"/><category term="dev"/><category term="entrepreneurship"/><category term="feedburner"/><category term="github"/><category term="google"/><category term="html5"/><category term="iPhone"/><category term="mac"/><category term="php js"/><category term="plazu"/><category term="plurkcounter"/><category term="preg"/><category term="reverse-engineering"/><category term="rust"/><category term="security"/><category term="social"/><category term="sql"/><category term="startup"/><category term="vb6"/><category term="windows seven"/><category term="xml"/><category term="access"/><category term="ascii"/><category term="automation"/><category term="blogger"/><category term="book"/><category term="canvas"/><category term="cargo"/><category term="cd"/><category term="chrome"/><category term="continuous deployment"/><category term="css"/><category term="curl"/><category term="design patterns"/><category term="domaine"/><category term="dropbox"/><category term="ebuzzing"/><category term="flickr"/><category term="forever"/><category term="gmail"/><category term="google chrome"/><category term="google cloud platform"/><category term="google cloud run"/><category term="jq"/><category term="json"/><category term="kubectl"/><category term="livre"/><category term="micro-service"/><category term="mixpanel"/><category term="nantes"/><category term="node"/><category term="npm"/><category term="ovh"/><category term="packt"/><category term="playframework"/><category term="plugins"/><category term="preg_replace_callback"/><category term="projets"/><category term="scala"/><category term="sms"/><category term="snippet"/><category term="ssl"/><category term="stage"/><category term="stretchtext"/><category term="subzero"/><category term="vistarc"/><category term="web2day"/><category term="xhtml"/><category term="xpath"/><category term="123People"/><category term="2017"/><category term="3D scan"/><category term="CTO"/><category term="FSM"/><category term="FinOps"/><category term="Finite State Machine"/><category term="IndieHacker"/><category term="Kuripotxt"/><category term="NoBullshit CTO"/><category term="NoBullshit Tech-Lead"/><category term="Passive-income"/><category term="Product Design"/><category term="Product Management"/><category term="SaaS"/><category term="StateCharts"/><category term="WEBO"/><category term="acme.sh"/><category term="ai"/><category term="amazon aws route 53"/><category term="amqp"/><category term="analytics"/><category term="android"/><category term="angular"/><category term="animation"/><category term="apache"/><category term="apc"/><category term="apple"/><category term="arcep"/><category term="architecture"/><category term="artifacts"/><category term="audio"/><category term="autoenv"/><category term="avantdecliquer"/><category term="basic"/><category term="bigdata"/><category term="bindonce"/><category term="bois de palette"/><category term="braindump"/><category term="bug"/><category term="buzzparadise"/><category term="c#"/><category term="casio"/><category term="certificates"/><category term="charts"/><category term="ciel"/><category term="circleci"/><category term="cli"/><category term="clipboard"/><category term="cloud-run"/><category term="cloudflare"/><category term="comparatif"/><category term="conference"/><category term="containers"/><category term="continuous integration"/><category term="craft"/><category term="create-ml"/><category term="cron"/><category term="cross-domain"/><category term="cto as a service"/><category term="debian"/><category term="debounce"/><category term="del.icio.us"/><category term="dev2day"/><category term="diaporama"/><category term="digg"/><category term="disruption"/><category term="diy"/><category term="docker-compose"/><category term="dojo"/><category term="doyoubuzz"/><category term="droplr"/><category term="dry"/><category term="dsl"/><category term="e-commerce"/><category term="ecmascript"/><category term="elasticsearch"/><category term="entretien"/><category term="error"/><category term="events"/><category term="excel"/><category term="expressjs"/><category term="extensions"/><category term="foursquare"/><category term="fp"/><category term="france-nuage"/><category term="freelance"/><category term="functional-programming"/><category term="gadget"/><category term="gam3r"/><category term="gce"/><category term="gd"/><category term="geek"/><category term="geodns"/><category term="getsignature"/><category term="ginger"/><category term="gof"/><category term="google cloud storage"/><category term="google drive"/><category term="google-app-scripts"/><category term="google-cloud-engine"/><category term="gron"/><category term="growl"/><category term="grunt"/><category term="gulp"/><category term="hackathon"/><category term="hasura"/><category term="hebergement"/><category term="htaccess"/><category term="http"/><category term="humantalks"/><category term="iMovie"/><category term="ia"/><category term="iadvize"/><category term="iam"/><category term="inception"/><category term="influxdb"/><category term="innovation"/><category term="intelligent artificielle"/><category term="intellij idea"/><category term="intellij ultimate"/><category term="java"/><category term="jq.node"/><category term="keycloak"/><category term="killbug"/><category term="knative"/><category term="language"/><category term="lasik"/><category term="launch"/><category term="let&#39;s encrypt"/><category term="lua"/><category term="machine learning"/><category term="makesense"/><category term="matrix"/><category term="mean"/><category term="mehel"/><category term="middleware"/><category term="migration"/><category term="mongodb"/><category term="monit"/><category term="monnuage"/><category term="mootools"/><category term="multi-region"/><category term="mvc"/><category term="n-gram"/><category term="n8n"/><category term="nantestech"/><category term="netvibes"/><category term="newsletter"/><category term="ng-animate"/><category term="nobullshit"/><category term="nocode"/><category term="nodetool"/><category term="nosql"/><category term="nsp"/><category term="nvm"/><category term="openapi"/><category term="opera"/><category term="operation yeux"/><category term="opération laser"/><category term="orange"/><category term="oxmoto"/><category term="pack"/><category term="parcel"/><category term="pattern-matching"/><category term="pdo"/><category term="peerguardian"/><category term="phishing"/><category term="pixel-tracking"/><category term="pm2"/><category term="preg_replace"/><category term="process"/><category term="productibity"/><category term="prof"/><category term="profiling"/><category term="project"/><category term="promethee"/><category term="prototype"/><category term="proxmox"/><category term="quickwin"/><category term="rabbitmq"/><category term="redisweekly"/><category term="request"/><category term="rest"/><category term="ria"/><category term="row-level-security"/><category term="rundeck"/><category term="sass"/><category term="scaleway dns"/><category term="scylla"/><category term="selector"/><category term="service-public"/><category term="sets"/><category term="shopify"/><category term="signal"/><category term="skrollr"/><category term="slides"/><category term="soa"/><category term="socket.io"/><category term="soutenance"/><category term="spritely"/><category term="spyware"/><category term="ssh"/><category term="ssl certificate"/><category term="stage2008"/><category term="starlink"/><category term="strategy"/><category term="stripe"/><category term="supervisord"/><category term="swagger"/><category term="sylae"/><category term="sync"/><category term="sysdig"/><category term="systeme"/><category term="talk"/><category term="tcpdump"/><category term="test"/><category term="thought"/><category term="tmate"/><category term="tokio"/><category term="tool"/><category term="tracking"/><category term="tree"/><category term="trigger"/><category term="troll"/><category term="tv"/><category term="vb"/><category term="vb.net"/><category term="vba"/><category term="versioning"/><category term="video"/><category term="vistavatar"/><category term="webdav"/><category term="webkit"/><category term="webpack"/><category term="websocket"/><category term="weekly"/><category term="widget"/><category term="wood-work"/><category term="wordpress"/><category term="wtf"/><category term="xmpp"/><category term="xss"/><category term="yarn"/><category term="yearly-recap"/><category term="yui"/><category term="zapier"/><title type='text'>Francois Guillaume Ribreau</title><subtitle type='html'>Full Stack CTO, Startup Advisor, Consultant, Hacker, Maker</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default?start-index=26&amp;max-results=25&amp;redirect=false'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/09541846055227993599</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>240</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-8128983.post-4427418609781952610</id><published>2026-04-04T10:47:00.002+02:00</published><updated>2026-04-04T10:47:29.395+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="france-nuage"/><title type='text'>Le SI est la colonne vertébrale de l’entreprise. On ne sous-traite pas sa colonne vertébrale.</title><content type='html'>&lt;p&gt;Henri Verdier est intervenu auprès d&#39;une commission d&#39;enquête et il porte un message que j&#39;aimerais entendre plus souvent.&lt;br /&gt;&lt;br /&gt;Les chefs d&#39;entreprise commencent tout juste à prendre conscience que leur supply-chain n&#39;est pas seulement physique, elle est aussi numérique et qu&#39;il faut la sécuriser.&lt;/p&gt;
&lt;p&gt;Cela passera autant que possible par le choix de solutions open-source (ouvertes, adaptables, facilement interopérables, sans lock-in de prestataires intermédiaires 💸💸) et l&#39;internalisation progressive de leurs infrastructures (ce que nous appelons le &lt;a href=&quot;https://france-nuage.fr/solutions/perspectives?mtm_source=blog&amp;amp;mtm_medium=article&amp;amp;mtm_campaign=post-cloud-henri-verdier&quot; target=&quot;_blank&quot;&gt;post-cloud chez France nuage&lt;/a&gt;) derrière du zero-trust network notamment pour se protéger autant que possible des IA offensives dont nous ne subissons actuellement que les prémices du tsunami à venir.&lt;/p&gt;
&lt;p&gt;L&#39;IA tue progressivement le coût du logiciel pur (ERP, CRM, e-commerce…) ; restent donc, comme valeur patrimoniale et avantage concurrentiel pour l&#39;entreprise, ses données et son infrastructure, &lt;b&gt;si elles sont internalisées&lt;/b&gt;.&lt;/p&gt;
&lt;p&gt;Contrôler ses données et son infrastructure, c&#39;est reprendre en main son destin et ses capacités de décision et de direction stratégique.&lt;/p&gt;
&lt;p&gt;Le système d’information est la colonne vertébrale de l’entreprise.&lt;br /&gt;On ne sous-traite pas sa colonne vertébrale.&lt;/p&gt;
&lt;p&gt;L&#39;intervention vidéo en question &lt;a href=&quot;https://www.linkedin.com/feed/update/urn:li:activity:7445440051485888513/&quot; target=&quot;_blank&quot;&gt;via LinkedIn&lt;/a&gt;.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/4427418609781952610/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/4427418609781952610?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/4427418609781952610'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/4427418609781952610'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2026/04/le-si-est-la-colonne-vertebrale-de.html' title='Le SI est la colonne vertébrale de l’entreprise. On ne sous-traite pas sa colonne vertébrale.'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-8742356814076707417</id><published>2026-03-05T15:21:00.002+01:00</published><updated>2026-03-05T15:21:22.966+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="3D scan"/><title type='text'>Releasing 3D LiDAR Scans of Machu Picchu, Sacsayhuaman, and Other Ancient Sites</title><content type='html'>&lt;p&gt;
    November 2024. I&#39;m standing at 2,430 meters on a ridge above the Urubamba
    Valley, staring at Machu Picchu. Like millions before me, I&#39;m blown away.
    Unlike most of them, I have an iPhone with a LiDAR sensor in my backpack.
&lt;/p&gt;

&lt;p&gt;
    I&#39;d been annoyed for a while by how hard it is to find downloadable 3D data
    of major archaeological sites. You get blurry photogrammetry on Sketchfab,
    paywalled academic datasets, or just nothing. Try finding a 3D model of the
    Sacsayhuaman walls. Go ahead, I&#39;ll wait. There isn&#39;t one.
&lt;/p&gt;

&lt;p&gt;So I made one.&lt;/p&gt;

&lt;figure&gt;
    &lt;img src=&quot;https://cdn.fgribreau.com/research/peru/posters/Machu-Picchu-1-1280.webp&quot;
         alt=&quot;3D LiDAR scan of Machu Picchu walls&quot;
         loading=&quot;lazy&quot; style=&quot;width:100%&quot; /&gt;
    &lt;figcaption&gt;Machu Picchu, scan #1. iPhone LiDAR, November 2024.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2&gt;What I did&lt;/h2&gt;

&lt;p&gt;
    During a trip across Peru, I scanned 13 archaeological sites with the LiDAR
    sensors on an iPhone. Machu Picchu alone got 15 separate scans: walls,
    terraces, the astronomical observatory, individual stone joints where you
    can see exactly how blocks interlock without mortar. I captured the
    polygonal walls of Sacsayhuaman (blocks up to 200 tonnes, and no, I still
    don&#39;t understand how they moved them), the concentric terraces of Moray,
    what&#39;s left of Qorikancha&#39;s walls after the Spanish stripped the gold off,
    the still-functioning aqueducts at Tipon. I also scanned the Sitamarhi Caves
    in Bihar, India.
&lt;/p&gt;

&lt;p&gt;47 scans. 14 sites. 2 countries. 17 GB of raw 3D data.&lt;/p&gt;

&lt;figure&gt;
    &lt;img src=&quot;https://cdn.fgribreau.com/research/peru/posters/Sacsayhuaman-1-1280.webp&quot;
         alt=&quot;3D LiDAR scan of Sacsayhuaman megalithic walls&quot;
         loading=&quot;lazy&quot; style=&quot;width:100%&quot; /&gt;
    &lt;figcaption&gt;Sacsayhuaman. Blocks up to 200 tonnes, fitted without mortar.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
    Every scan is interactive. You rotate and zoom in your browser, no app
    needed.
&lt;/p&gt;

&lt;h2&gt;The sites&lt;/h2&gt;

&lt;p&gt;
    Peru, 13 sites. Machu Picchu has 15 scans covering walls, terraces, and the
    Intihuatana solar clock. Sacsayhuaman has its megalithic zigzag walls
    stretching 600 meters. Ollantaytambo got 8 scans across the fortress, the
    Temple of the Sun monoliths, and the water channels. Qorikancha is the
    Temple of the Sun in Cusco. Chinchero is the royal estate of Tupac Inca
    Yupanqui, sitting at 3,762 meters. Moray has those eerie circular terraces
    with 15°C temperature swings between levels. Tipon is pure hydraulic
    engineering, aqueducts that still carry water today. Q&#39;enqo has zigzag
    channels and subterranean chambers carved into raw limestone. Puka Pukara,
    the Red Fortress, was a checkpoint on the road to Antisuyo. Amaru Punku is
    the Gate of the Serpent near Ollantaytambo. The Bath of the Ñusta is a
    ceremonial fountain where water channels are cut into a single rock face. I
    also captured some street-level Inca walls in Cusco itself, and the
    Intihuatana observatory stone at Machu Picchu as a separate scan.
&lt;/p&gt;

&lt;figure&gt;
    &lt;img src=&quot;https://cdn.fgribreau.com/research/peru/posters/Ollantaytambo-3-1280.webp&quot;
         alt=&quot;3D LiDAR scan of Ollantaytambo fortress&quot;
         loading=&quot;lazy&quot; style=&quot;width:100%&quot; /&gt;
    &lt;figcaption&gt;Ollantaytambo, scan #3. Temple of the Sun monoliths.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
    &lt;img src=&quot;https://cdn.fgribreau.com/research/peru/posters/Moray-1-1280.webp&quot;
         alt=&quot;3D LiDAR scan of Moray circular terraces&quot;
         loading=&quot;lazy&quot; style=&quot;width:100%&quot; /&gt;
    &lt;figcaption&gt;Moray. Concentric terraces with 15°C temperature differentials between levels.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
    &lt;img src=&quot;https://cdn.fgribreau.com/research/peru/posters/Qenqo-1-1280.webp&quot;
         alt=&quot;3D LiDAR scan of Q&#39;enqo carved limestone&quot;
         loading=&quot;lazy&quot; style=&quot;width:100%&quot; /&gt;
    &lt;figcaption&gt;Q&#39;enqo. Zigzag channels carved into raw limestone.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;
    India, 1 site: the Sitamarhi Caves in Bihar, rock-cut caves tied to the
    Ramayana.
&lt;/p&gt;

&lt;figure&gt;
    &lt;img src=&quot;https://cdn.fgribreau.com/research/india/posters/Sitamarhi-Caves-1-1280.webp&quot;
         alt=&quot;3D LiDAR scan of Sitamarhi Caves, Bihar&quot;
         loading=&quot;lazy&quot; style=&quot;width:100%&quot; /&gt;
    &lt;figcaption&gt;Sitamarhi Caves, Bihar, India.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2&gt;Why an iPhone?&lt;/h2&gt;

&lt;p&gt;
    Professional terrestrial LiDAR scanners cost $50K+ and take hours to set up
    per scan. The iPhone sensor does it in minutes. The resolution is lower,
    obviously. But I could scan a wall section between tour groups, in rain,
    while hiking between sites. Speed and portability won over precision.
&lt;/p&gt;

&lt;figure&gt;
    &lt;img src=&quot;https://cdn.fgribreau.com/research/peru/posters/Coricancha-1-1280.webp&quot;
         alt=&quot;3D LiDAR scan of Qorikancha Temple of the Sun&quot;
         loading=&quot;lazy&quot; style=&quot;width:100%&quot; /&gt;
    &lt;figcaption&gt;Qorikancha, Temple of the Sun. The Inca stonework underneath the Spanish convent.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;
    &lt;img src=&quot;https://cdn.fgribreau.com/research/peru/posters/Tipon-1-1280.webp&quot;
         alt=&quot;3D LiDAR scan of Tipon aqueducts&quot;
         loading=&quot;lazy&quot; style=&quot;width:100%&quot; /&gt;
    &lt;figcaption&gt;Tipon. Aqueducts built 600 years ago, still carrying water.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2&gt;The tech stack&lt;/h2&gt;

&lt;p&gt;
    Each scan is exported as GLB for browser viewing and USDZ for AR on iOS.
    Source files are available in STL, XYZ point clouds, PLY, DXF, DAE, FBX, and
    OBJ. 204 files total.
&lt;/p&gt;

&lt;p&gt;
    The viewer is a custom
    &lt;a href=&quot;https://threejs.org/&quot;&gt;Three.js&lt;/a&gt; fullscreen viewer I built. No
    iframe, no third-party embed. DRACO-compressed GLB files load straight in
    the browser. The 17 GB of assets sit on Cloudflare R2 behind a CDN. The site
    runs on Netlify. Everything is static. No backend, no login. Click a scan,
    it loads.
&lt;/p&gt;

&lt;h2&gt;Download everything&lt;/h2&gt;

&lt;p&gt;
    All files are free. Every scan card has download buttons for GLB, USDZ, STL,
    XYZ, PLY and more.
&lt;/p&gt;

&lt;p&gt;
    Licensed under
    &lt;a href=&quot;https://creativecommons.org/licenses/by-nc-sa/4.0/&quot;
        &gt;Creative Commons BY-NC-SA 4.0&lt;/a
    &gt;. Credit me, don&#39;t sell them, share derivatives under the same terms.
&lt;/p&gt;

&lt;h2&gt;What&#39;s next&lt;/h2&gt;

&lt;p&gt;
    Easter Island, Bolivia and Turkey are on the list. We&#39;ll see when that
    happens.
&lt;/p&gt;

&lt;p&gt;
    &lt;strong
        &gt;&lt;a href=&quot;https://fgribreau.com/research/3d-scans.html&quot;
            &gt;fgribreau.com/research/3d-scans.html&lt;/a
        &gt;&lt;/strong
    &gt;
&lt;/p&gt;
</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/8742356814076707417/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/8742356814076707417?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/8742356814076707417'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/8742356814076707417'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2026/03/releasing-3d-lidar-scans-of-machu.html' title='Releasing 3D LiDAR Scans of Machu Picchu, Sacsayhuaman, and Other Ancient Sites'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-7644547006731177667</id><published>2025-12-19T17:34:00.003+01:00</published><updated>2025-12-19T18:15:09.241+01:00</updated><title type='text'>Releasing mcp-matomo - from frustration to Open Source: Building MCP Matomo in 30 Minutes</title><content type='html'>&lt;p&gt;It’s Friday, December 19th. The &lt;strike&gt;coffee&lt;/strike&gt; roasted spelt is still hot, and Hook0 team just pushed the &quot;deploy&quot; button.&lt;/p&gt;

&lt;p&gt;We just shipped a massive update: a brand new design and completely overhauled documentation for &lt;a href=&quot;https://www.hook0.com&quot;&gt;Hook0&lt;/a&gt;, the open-source webhook sending infrastructure I co-founded. It feels good. But as the dust settled, I realized we needed to implement analytics on the new documentation pages.&lt;/p&gt;

&lt;p&gt;Naturally, we reached for &lt;b&gt;Matomo&lt;/b&gt;. It’s privacy-focused, ethical, and solid. But as I was setting it up, a thought struck me:&lt;/p&gt;

&lt;blockquote&gt;&quot;Why am I still navigating dashboards, setting date ranges, and clicking through menus in 2025? Why can&#39;t I just &lt;i&gt;talk&lt;/i&gt; to my data?&quot;&lt;/blockquote&gt;

&lt;p&gt;&lt;img src=&quot;https://i.imgur.com/b3eNhZ0.gif&quot; style=&quot;width:100%&quot;/&gt;&lt;/p&gt;


&lt;p&gt;I immediately started looking for a way to connect Matomo to Claude via the &lt;b&gt;Model Context Protocol (MCP)&lt;/b&gt;. I found exactly one solution.&lt;/p&gt;

&lt;p&gt;The problem? It required routing my data through a third-party API and I had to ask them politely through a contact form to get access.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;That was a hard No for me.&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;It goes against my core engineering values. I need future-proof solutions, not a solution that depends on an external black box for simple logic. I want autonomy. I want self-hosted reliability (or at least the ability to do so when needed, that&#39;s what we belive at Hook0 and Cloud-IAM). I want my data to stay mine.&lt;/p&gt;

&lt;p&gt;So, I checked the clock. I opened my IDE along with some IA agents. And I decided to fix it myself.&lt;/p&gt;

&lt;p&gt;I chose &lt;b&gt;Rust&lt;/b&gt; for performance and reliability. Exactly 30 minutes later, I had a fully functional MCP server running locally. No external APIs, no subscriptions, just raw, direct access to the Matomo instance.&lt;/p&gt;

&lt;p&gt;I’m open-sourcing it today because I believe analytics should be accessible and private.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/FGRibreau/mcp-matomo&quot;&gt;&lt;b&gt;Get the code on GitHub: FGRibreau/mcp-matomo (don&#39;t forget to star it!)&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;What Can You Do With It?&lt;/h2&gt;

&lt;p&gt;Instead of clicking through the UI, you can now simply ask Claude questions like:&lt;/p&gt;

&lt;blockquote&gt;&quot;Show me the top 10 pages by visits this week, broken down by device type.&quot;&lt;/blockquote&gt;

&lt;p&gt;mcp-matomo connects to your instance, introspects the API, executes the necessary calls, and presents the answer. It covers almost everything Matomo tracks:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;b&gt;Traffic:&lt;/b&gt; Visits, unique visitors, bounce rates.&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;Acquisition:&lt;/b&gt; Referrers, search engines, campaigns.&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;Behavior:&lt;/b&gt; Entry pages, downloads, outlinks.&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;Tech &amp;amp; Geo:&lt;/b&gt; Devices, screens, countries.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;What&#39;s Next?&lt;/h2&gt;

&lt;p&gt;Now that the tool is live, my next step is to deploy this across the ecosystem of companies I&#39;ve created or co-founded. We need to democratize access to data for our teams without forcing them to become analytics experts.&lt;/p&gt;

&lt;p&gt;I&#39;ll be rolling this out at:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;&lt;b&gt;&lt;a href=&quot;https://www.cloud-iam.com/&quot;&gt;Cloud-IAM&lt;/a&gt;&lt;/b&gt;: To track adoption of our managed Keycloak solution (ISO 27001 certified).&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;&lt;a href=&quot;https://netir.fr/&quot;&gt;Netir&lt;/a&gt;&lt;/b&gt;: To better understand how freelancers, companies and mentors interact on our marketplace.&lt;/li&gt;
    &lt;li&gt;&lt;b&gt;&lt;a href=&quot;https://getnatalia.com/&quot;&gt;Natalia&lt;/a&gt;&lt;/b&gt;: To analyze how users engage with our unified AI ecosystem across Voice, WhatsApp, and Transcripts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you share the value of &lt;b&gt;autonomy&lt;/b&gt; and want to talk to your data without a middleman, give it a try.&lt;/p&gt;

&lt;p&gt;Feedback and PRs are welcome on GitHub!&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/7644547006731177667/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/7644547006731177667?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/7644547006731177667'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/7644547006731177667'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2025/12/releasing-mcp-maotmo-from-frustration.html' title='Releasing mcp-matomo - from frustration to Open Source: Building MCP Matomo in 30 Minutes'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-5146882843061020974</id><published>2025-10-24T08:31:00.001+02:00</published><updated>2025-10-24T08:31:26.039+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="n8n"/><category scheme="http://www.blogger.com/atom/ns#" term="signal"/><title type='text'>Releasing n8n-nodes-signal-cli - How I automatically transcribe Signal voice messages with my own n8n node extension</title><content type='html'>
    &lt;p&gt;I was fed up with receiving 5-minute voice messages on Signal just to find out someone was asking if I&#39;m free next Tuesday. No automatic transcription, no way to quickly scan the content. You have to listen to the whole thing, often at the worst possible moment.&lt;/p&gt;
    
    &lt;p&gt;So I built &lt;a href=&quot;https://github.com/FGRibreau/n8n-nodes-signal-cli&quot;&gt;n8n-nodes-signal-cli&lt;/a&gt; - an n8n extension that integrates with Signal CLI to automatically transcribe voice messages and send back a concise summary directly in the conversation.&lt;/p&gt;
    
    &lt;h2&gt;The problem&lt;/h2&gt;
    
    &lt;p&gt;Voice messages suck when:&lt;/p&gt;
    &lt;ul&gt;
        &lt;li&gt;People take 5 minutes to say what could be written in 3 lines&lt;/li&gt;
        &lt;li&gt;You&#39;re in a meeting and can&#39;t listen&lt;/li&gt;
        &lt;li&gt;You need to find specific information buried in a long monologue&lt;/li&gt;
        &lt;li&gt;Signal doesn&#39;t offer automatic transcription&lt;/li&gt;
    &lt;/ul&gt;
    
    &lt;h2&gt;The solution&lt;/h2&gt;
    
    &lt;p&gt;My n8n extension adds two nodes:&lt;/p&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;Signal CLI Trigger&lt;/strong&gt;: fires when receiving new Signal messages&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Signal CLI&lt;/strong&gt;: sends messages back to Signal&lt;/li&gt;
    &lt;/ul&gt;
    
    &lt;p&gt;My workflow:&lt;/p&gt;
    &lt;pre&gt;Signal message received 
    → Filter voice messages only
    → Download audio file
    → Transcribe with local Whisper (or equivalent)
    → Summarize with LLM
    → Send transcript back to Signal conversation
    &lt;/pre&gt;
    
    &lt;p&gt;Result: That 3-minute rambling voice message becomes:&lt;/p&gt;
    &lt;blockquote&gt;📝 &lt;strong&gt;Transcript&lt;/strong&gt;: &quot;Are you available next Tuesday at 2 PM for a meeting?&quot;&lt;/blockquote&gt;&lt;p&gt;My self-hosted n8n runs on my NAS locked behind a Zero Trust Network thanks to &lt;a href=&quot;https://france-nuage.fr/&quot; target=&quot;_blank&quot;&gt;France nuage&lt;/a&gt;.&amp;nbsp;&lt;/p&gt;
    
    &lt;h2&gt;Installation&lt;/h2&gt;
    
    &lt;p&gt;Prerequisites:&lt;/p&gt;
    &lt;ul&gt;
        &lt;li&gt;&lt;a href=&quot;https://docs.n8n.io/hosting/installation/&quot;&gt;n8n&lt;/a&gt; installed and running&lt;/li&gt;
        &lt;li&gt;&lt;a href=&quot;https://github.com/AsamK/signal-cli&quot;&gt;signal-cli&lt;/a&gt; configured with your phone number&lt;/li&gt;
    &lt;/ul&gt;
    
    &lt;p&gt;Install the extension:&lt;/p&gt;
    &lt;pre&gt;npm install n8n-nodes-signal-cli&lt;/pre&gt;
    
    &lt;p&gt;Setup:&lt;/p&gt;
    &lt;ol&gt;
        &lt;li&gt;Add Signal CLI credentials in n8n&lt;/li&gt;
        &lt;li&gt;Create new workflow&lt;/li&gt;
        &lt;li&gt;Add &quot;Signal CLI Trigger&quot; node&lt;/li&gt;
        &lt;li&gt;Build your processing logic&lt;/li&gt;
        &lt;li&gt;Use &quot;Signal CLI&quot; node to send messages back&lt;/li&gt;
    &lt;/ol&gt;
    
    &lt;h2&gt;Beyond transcription&lt;/h2&gt;
    
    &lt;p&gt;This opens up tons of automation possibilities:&lt;/p&gt;
    &lt;ul&gt;
        &lt;li&gt;Reminder bot - schedule reminders via Signal&lt;/li&gt;
        &lt;li&gt;Auto-archiving - save important messages automatically&lt;/li&gt;
        &lt;li&gt;Instant translation - translate messages on the fly&lt;/li&gt;
        &lt;li&gt;Smart notifications - filter and route messages by content&lt;/li&gt;
        &lt;li&gt;Service integration - connect Signal to Slack, Discord, etc.&lt;/li&gt;
    &lt;/ul&gt;
    
    &lt;h2&gt;Results&lt;/h2&gt;
    
    &lt;p&gt;Since using this:&lt;/p&gt;
    &lt;ul&gt;
        &lt;li&gt;I never miss important info hidden in long voice messages&lt;/li&gt;
        &lt;li&gt;I can &quot;read&quot; voice messages anywhere, anytime&lt;/li&gt;
        &lt;li&gt;People actually appreciate seeing their rambling transcribed concisely&lt;/li&gt;
        &lt;li&gt;Saved tons of time not listening to minutes of audio&lt;/li&gt;
    &lt;/ul&gt;
    
    &lt;p&gt;Check it out on &lt;a href=&quot;https://github.com/FGRibreau/n8n-nodes-signal-cli&quot;&gt;GitHub&lt;/a&gt;. Star it if you find it useful. Report bugs. Send PRs. The usual drill.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/5146882843061020974/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/5146882843061020974?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/5146882843061020974'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/5146882843061020974'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2025/10/releasing-n8n-nodes-signal-cli-how-i.html' title='Releasing n8n-nodes-signal-cli - How I automatically transcribe Signal voice messages with my own n8n node extension'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-7153035096400946938</id><published>2023-08-04T17:42:00.004+02:00</published><updated>2023-08-04T17:43:00.358+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="avantdecliquer"/><category scheme="http://www.blogger.com/atom/ns#" term="phishing"/><category scheme="http://www.blogger.com/atom/ns#" term="security"/><title type='text'>Filtrer les messages de sensibilisation à la cybersécurité et aux phishing de avantdecliquer.com</title><content type='html'>&lt;p&gt;Si tu travailles pour de grands groupes, tu as peut-être déjà entendu parler de &lt;a href=&quot;https://avantdecliquer.com/&quot;&gt;https://avantdecliquer.com/&lt;/a&gt;.&amp;nbsp;&lt;/p&gt;&lt;p&gt;Cette plateforme aide les entreprises à sensibiliser leurs salariés aux dangers du phishing en leur envoyant des faux e-mails de phishing.&amp;nbsp;&lt;/p&gt;&lt;p&gt;Alors, comment gérer ce flux d&#39;emails sans se noyer, surtout si, comme moi, tu reçois déjà entre 150 et 200 e-mails chaque jour ?&lt;/p&gt;&lt;p&gt;Pas de panique ! Si tu sais déjà repérer ces faux-emails et souhaite organiser ta boîte de réception plus efficacement, suis le guide ci-dessous.&amp;nbsp;&lt;/p&gt;&lt;p&gt;J&#39;ai préparé une démo pour te montrer comment créer un filtre sur Outlook afin d&#39;archiver automatiquement les messages provenant de avantdecliquer.com.&lt;/p&gt;


&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;fr&quot; dir=&quot;ltr&quot;&gt;Une solution en 3 actes. &lt;a href=&quot;https://twitter.com/avantdecliquer?ref_src=twsrc%5Etfw&quot;&gt;@avantdecliquer&lt;/a&gt; &lt;a href=&quot;https://t.co/QwD9J2tcZM&quot;&gt;pic.twitter.com/QwD9J2tcZM&lt;/a&gt;&lt;/p&gt;&amp;mdash; Francois-Guillaume Ribreau (@FGRibreau) &lt;a href=&quot;https://twitter.com/FGRibreau/status/1687486285626101760?ref_src=twsrc%5Etfw&quot;&gt;August 4, 2023&lt;/a&gt;&lt;/blockquote&gt; &lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/7153035096400946938/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/7153035096400946938?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/7153035096400946938'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/7153035096400946938'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2023/08/filtrer-les-messages-de-sensibilisation.html' title='Filtrer les messages de sensibilisation à la cybersécurité et aux phishing de avantdecliquer.com'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-6957082690149648951</id><published>2023-02-09T15:33:00.003+01:00</published><updated>2023-02-09T15:33:32.619+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="create-ml"/><category scheme="http://www.blogger.com/atom/ns#" term="machine learning"/><category scheme="http://www.blogger.com/atom/ns#" term="makesense"/><title type='text'>How to add a label to previously created annotation in MakeSense.ai when you forgot to select a default one</title><content type='html'>&lt;p&gt;
 Yes. Like the title said.
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
function selectOption(f){
  const a = document.querySelector(&#39;.DropdownOption&#39;);
    if(a===null){
        return setTimeout(selectOption.bind(null, f), 1);
    }

    a.click();

    setTimeout(f, 1);    
}

function selectLabel(label, f){
    label.click();
    selectOption(f);
}

function selectLabels(labels, f){
  function _do(){
    if(labels.length === 0){
        return f();
    }

    const l = labels.shift();
    selectLabel(l, _do);
  }

  _do();   
}

function selectAll(f){
  selectLabels(Array.from(document.querySelectorAll(&#39;.DropdownLabel&#39;)), f);
}

function loop(){
  if(document.querySelector(&#39;[draggable=&quot;false&quot;][alt=&quot;next&quot;]&#39;) !== null){
    console.log(&#39;Done!&#39;);
    return;
  }
  
  selectAll(() =&gt; {
      document.querySelector(&#39;[alt=&quot;next&quot;]&#39;).click();
      setTimeout(loop, 100);
  })
}

loop();
&lt;/code&gt;&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/6957082690149648951/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/6957082690149648951?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/6957082690149648951'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/6957082690149648951'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2023/02/how-to-add-label-to-previously-created.html' title='How to add a label to previously created annotation in MakeSense.ai when you forgot to select a default one'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-5096563195180587921</id><published>2022-12-26T10:45:00.001+01:00</published><updated>2022-12-26T10:45:06.151+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="intellij idea"/><category scheme="http://www.blogger.com/atom/ns#" term="intellij ultimate"/><category scheme="http://www.blogger.com/atom/ns#" term="postgresql"/><category scheme="http://www.blogger.com/atom/ns#" term="productibity"/><title type='text'>IntelliJ IDEA - Import PostgreSQL connection string with Data Source from URL feature</title><content type='html'>&lt;p&gt;
  IntelliJ IDEA does not support out of the box &quot;Data Source from URL&quot; import with standard PostgreSQL connection string like&lt;/p&gt;
&lt;pre&gt;postgresql://username:password@host:port/database&lt;/pre&gt;

&lt;p&gt;It sucks. Here is how to fix it:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;File &amp;gt; New &amp;gt; Driver&lt;/li&gt;
    &lt;li&gt;Find &quot;PostgreSQL&quot; existing driver&lt;/li&gt;
    &lt;li&gt;Click &quot;+&quot; in the URL templates section to add a new url template&lt;/li&gt;
    &lt;li&gt;Type: &quot;postgresql://{user}:{password}@{host}:{port}/{database}&quot;&lt;/li&gt;
    &lt;li&gt;Click &quot;OK&quot;&lt;/li&gt;
	&lt;/ul&gt;
&lt;p&gt;
  Now next time you want to import a PostgreSQL connection string:
&lt;/p&gt;

  &lt;ul&gt;
        &lt;li&gt;Open &quot;Database&quot; tab (or use the &quot;find tool&quot; window with your usual shortcut)&lt;/li&gt;
	    &lt;li&gt;Click &quot;+&quot; &amp;gt; &quot;Data Source from URL&quot;&lt;/li&gt;
    
    &lt;li&gt;Past your PostgreSQL connection string, the selected driver will automatically change to &quot;PostgreSQL&quot;&lt;/li&gt;
    
    &lt;li&gt;Click &quot;OK&quot;&lt;/li&gt;
    &lt;li&gt;From there you may say: WAT. I don&#39;t see my username, password, port and host. That&#39;s right and it&#39;s IntelliJ IDEA default behavior... But, if you click on &quot;URL only&quot; next to &quot;connection type&quot; and switch back to &quot;default&quot; there you will find every connection field pre-completed&lt;/li&gt;
    &lt;li&gt;In &quot;URL:&quot; section, prefix &quot;postgresql://&quot; with &quot;jbdc:&quot;&lt;/li&gt;
    &lt;li&gt;In &quot;URL:&quot; section, remove the &quot;:@&quot; the &quot;username:password&quot; part.&lt;/li&gt;
    &lt;li&gt;Your good to go. Yes, it&#39;s slow and could be even faster but that&#39;s current IntelliJ limitation....&lt;/li&gt;
	&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/5096563195180587921/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/5096563195180587921?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/5096563195180587921'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/5096563195180587921'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2022/12/intellij-idea-import-postgresql.html' title='IntelliJ IDEA - Import PostgreSQL connection string with Data Source from URL feature'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-7231275223176745083</id><published>2022-04-12T11:17:00.005+02:00</published><updated>2022-04-12T12:24:52.908+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="arcep"/><category scheme="http://www.blogger.com/atom/ns#" term="starlink"/><title type='text'>Lettre à l&#39;ARCEP concernant l&#39;annulation par le Conseil d&#39;État des fréquences utilisées par Starlink</title><content type='html'>&lt;p&gt;A l&#39;attention de l&#39;Arcep,&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Je suis client Starlink depuis le&amp;nbsp;9
  février 2021.&amp;nbsp;Ma femme et moi avons besoin d&#39;un accès internet pour notre travail (elle possède &lt;a href=&quot;https://tatabulle.com/&quot;&gt;son propre site
  e-commerce&lt;/a&gt; et de mon côté je crée et maintient des Software as a Service (SaaS) en ligne &lt;a href=&quot;https://www.image-charts.com/&quot;&gt;1&lt;/a&gt; &lt;a href=&quot;https://www.cloud-iam.com/&quot;&gt;2&lt;/a&gt; &lt;a href=&quot;https://roam-bot.com/&quot;&gt;3&lt;/a&gt; &lt;a href=&quot;https://www.hook0.com/&quot;&gt;4&lt;/a&gt; ainsi qu&#39;une activité de &lt;a href=&quot;https://www.francois-guillaume-ribreau.com/&quot;&gt;CTO as a Service&lt;/a&gt;). Nous habitons dans un lieu-dit à 3km de la ville la plus proche (Les Essarts en Bocage).
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;À ce lieu dit, il est très peu probable que nous ayons un jour la fibre, nous captons très mal la 4G et notre seule option est le wimax avec
  un débit maximum &lt;b&gt;théorique&lt;/b&gt; et&amp;nbsp;&lt;b&gt;non garanti&lt;/b&gt; de 30 Mbps.
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Voici les alternatives que j&#39;ai d&#39;abord considérées :&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;échange avec une entreprise télécom locale pour faire venir la fibre jusqu&#39;à notre maison, coût BTP estimé de ~80 000€ et coût de
  l&#39;abonnement fibre: 450€/mois.&lt;/li&gt;
&lt;li&gt;échange avec une entreprise locale pour la mise en place de Wimax : ils m&#39;ont annoncé que nous aurons
  difficilement 30 Mbps en descendant.&lt;/li&gt;
&lt;li&gt;échange avec une entreprise d&#39;internet par satellite : limitation à 100Mbps pour 59€90/mois via &lt;a href=&quot;https://www.nordnet.com/&quot;&gt;Nordnet&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Aucune de ces options n&#39;était viables au vu de notre travail (upload de vidéos de formation, téléchargement et upload de backups journalier). Nous sommes dans une zone blanche sans fibre ni adsl digne de ce nom, sans accès internet haut débit il nous aurait été impossible de nous installer et d&#39;exercer nos activités.
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Starlink a été la seule option à nous proposer un accès &lt;b&gt;illimité&lt;/b&gt;, à un coût raisonnable et à un débit en version bêta allant jusqu&#39;à 263Mbps en DL et 24Mbps
  en UP (chiffres du 30 octobre).
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Supprimer l&#39;accès internet Starlink, c&#39;est nous empêcher de vivre à la campagne, dans notre ferme, tout en gardant nos activités professionnelles.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/7231275223176745083/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/7231275223176745083?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/7231275223176745083'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/7231275223176745083'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2022/04/lettre-arcep-conseil-etat-frequences-starlink-spacx.html' title='Lettre à l&#39;ARCEP concernant l&#39;annulation par le Conseil d&#39;État des fréquences utilisées par Starlink'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total><georss:featurename>France</georss:featurename><georss:point>46.227638 2.213749</georss:point><georss:box>17.917404163821153 -32.942501 74.537871836178851 37.369999</georss:box></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-5885334502414951956</id><published>2022-02-24T21:45:00.003+01:00</published><updated>2022-02-24T21:47:13.925+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="amazon aws route 53"/><category scheme="http://www.blogger.com/atom/ns#" term="migration"/><category scheme="http://www.blogger.com/atom/ns#" term="scaleway dns"/><title type='text'>How to migrate from AWS Route 53 to Scaleway DNS</title><content type='html'>&lt;p&gt;I&#39;ve spent the last 2 days talking with Scaleway DNS (domain) team reporting bugs I found while preparing Cloud-IAM DNS migration from &lt;a href=&quot;https://aws.amazon.com/route53/&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;Amazon AWS Route53&lt;/a&gt; to &lt;a href=&quot;https://www.scaleway.com/en/dns/&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;Scaleway DNS&lt;/a&gt; and was amazed by how fast they fixed these bugs. It&#39;s so unusual for a cloud provider to be this responsive, I had to start this blog post saying it! What an amazing team! If you also use Scaleway for hosting, &lt;a href=&quot;https://slack.scaleway.com/&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;join their Slack&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Because Cloud-IAM DNS zone file is quite large and because we wanted to automate this migration as much as possible, I wrote a script to seamlessly migrate cloud-iam.com zone from AWS to SCW through a gitlab-ci job that we can manually start at will and also run at &lt;a href=&quot;https://docs.gitlab.com/ee/ci/pipelines/schedules.html&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;scheduled interval&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Route53 does not have an &quot;export to bind format&quot; feature but I found &lt;a href=&quot;https://github.com/barnybug/cli53/&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;cli53&lt;/a&gt; that does that for me. On the other hand &lt;a href=&quot;https://github.com/scaleway/scaleway-cli&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;scaleway cli&lt;/a&gt; had the &lt;code&gt;dns zone import&lt;/code&gt; command but it was not working out of the box thus the 2 days of exchange with their team to fix some edge cases.&lt;/p&gt;

&lt;p&gt;First thing you will need to find and set the environment variables below:&lt;/p&gt;


&lt;pre&gt;&lt;code&gt;AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
SCW_ACCESS_KEY
SCW_SECRET_KEY
SCW_DEFAULT_ORGANIZATION_ID
SCW_DEFAULT_PROJECT_ID
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Then use the &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; below to automate the import, don&#39;t forget to rename &lt;code&gt;DOMAIN.TLD&lt;/code&gt; to the zone you want to migrate.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;stages:
  - run

sync:
  stage: run
  image: node:16
  script:
    - curl -L &quot;https://github.com/barnybug/cli53/releases/download/0.8.18/cli53-linux-amd64&quot; &gt; /usr/local/bin/cli53
    - chmod +x /usr/local/bin/cli53
    - cli53 export --full --debug DOMAIN.TLD &gt; DOMAIN.TLD.zone
    - curl -o /usr/local/bin/scw -L &quot;https://github.com/scaleway/scaleway-cli/releases/download/v2.4.0/scw-2.4.0-linux-x86_64&quot;
    - chmod +x /usr/local/bin/scw
    - scw dns zone import -D DOMAIN.TLD bind-source.content=&quot;$(cat ./DOMAIN.TLD.zone)&quot;
  tags:
    - build
  artifacts:
      when: always
      untracked: true
      expire_in: 1 day
      name: &#39;build&#39;
      paths:
        - DOMAIN.TLD.zone
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One last thing, don&#39;t forget to take a look at what we do at Cloud-IAM if you are looking for a fully managed &lt;a href=&quot;https://www.cloud-iam.com/&quot; target=&quot;_blank&quot;&gt;Keycloak as a service&lt;/a&gt;.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/5885334502414951956/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/5885334502414951956?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/5885334502414951956'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/5885334502414951956'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2022/02/how-to-migrate-from-aws-route-53-with-cli53-to-scaleway-dns-with-scw-cli.html' title='How to migrate from AWS Route 53 to Scaleway DNS'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-1814837337558957639</id><published>2021-12-28T16:32:00.008+01:00</published><updated>2021-12-28T17:15:39.267+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bug"/><category scheme="http://www.blogger.com/atom/ns#" term="cloud-run"/><category scheme="http://www.blogger.com/atom/ns#" term="google cloud platform"/><category scheme="http://www.blogger.com/atom/ns#" term="google cloud run"/><title type='text'>[Bug] Google Cloud Run decodes url search part before reaching your app</title><content type='html'>&lt;p&gt;Today I discovered a (insane 🤯) bug in Google Cloud Run.&lt;p/&gt;

&lt;p&gt;I was trying to understand WHY 😵‍💫 &lt;a href=&quot;https://www.image-charts.com&quot;&gt;Image-Charts&lt;/a&gt; works on Google Kubernetes Engine (GKE) but the &lt;b&gt;same container&lt;/b&gt; does not work on Cloud-Run.&lt;/p&gt;

&lt;p&gt;To be precise, why the signed URL (HMAC) below works on GKE and not on Cloud-Run&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;https://image-charts.com/chart?chbr=8&amp;chd=t%3A10%2C15%2C25%2C30%2C40%2C80&amp;chf=b0%2Clg%2C90%2C05B142%2C1%2C0CE858%2C0.2&amp;chl=%7C%7C%7C%7C%2033%25%20%21%7Cx2%20&amp;chma=0%2C0%2C10%2C10&amp;chs=700x450&amp;cht=bvs&amp;chtt=Revenue%20per%20month&amp;chxl=0%3A%7CJan%7CFev%7CMar%7CAvr%7CMay&amp;chxs=1N%2AcUSD0sz%2A%2C000000%2C14&amp;chxt=x%2Cy&amp;icac=fgribreau&amp;ichm=d92a75886c0657013da32eda4d82b8db3af39d355b3419caebd5952bc827990d&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;Note that HMAC signature (&lt;code&gt;ichm&lt;/code&gt; query parameter) is computed like this:&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;ichm=sign(url.search, shared_secret)&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;My main intuition was that cloud-run had some internal reverse proxies that changes part of the URL on the fly.&lt;/p&gt;

&lt;p&gt;If that&#39;s trye, let&#39;s find what encoded characters Cloud-Run would automatically decode in the URL query part before reaching your app:&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;
new Array(155).join(&#39;-&#39;).split(&#39;-&#39;).map((x, i) =&gt; &#39;%&#39;+i.toString(16).toUpperCase()).join(&#39;-_-&#39;)

// output:
// %0-_-%1-_-%2-_-%3-_-%4-_-%5-_-%6-_-%7-_-%8-_-%9-_-%A-_-%B-_-%C-_-%D-_-%E-_-%F-_-%10-_-%11-_-%12-_-%13-_-%14-_-%15-_-%16-_-%17-_-%18-_-%19-_-%1A-_-%1B-_-%1C-_-%1D-_-%1E-_-%1F-_-%20-_-%21-_-%22-_-%23-_-%24-_-%25-_-%26-_-%27-_-%28-_-%29-_-%2A-_-%2B-_-%2C-_-%2D-_-%2E-_-%2F-_-%30-_-%31-_-%32-_-%33-_-%34-_-%35-_-%36-_-%37-_-%38-_-%39-_-%3A-_-%3B-_-%3C-_-%3D-_-%3E-_-%3F-_-%40-_-%41-_-%42-_-%43-_-%44-_-%45-_-%46-_-%47-_-%48-_-%49-_-%4A-_-%4B-_-%4C-_-%4D-_-%4E-_-%4F-_-%50-_-%51-_-%52-_-%53-_-%54-_-%55-_-%56-_-%57-_-%58-_-%59-_-%5A-_-%5B-_-%5C-_-%5D-_-%5E-_-%5F-_-%60-_-%61-_-%62-_-%63-_-%64-_-%65-_-%66-_-%67-_-%68-_-%69-_-%6A-_-%6B-_-%6C-_-%6D-_-%6E-_-%6F-_-%70-_-%71-_-%72-_-%73-_-%74-_-%75-_-%76-_-%77-_-%78-_-%79-_-%7A-_-%7B-_-%7C-_-%7D-_-%7E-_-%7F-_-%80-_-%81-_-%82-_-%83-_-%84-_-%85-_-%86-_-%87-_-%88-_-%89-_-%8A-_-%8B-_-%8C-_-%8D-_-%8E-_-%8F-_-%90-_-%91-_-%92-_-%93-_-%94-_-%95-_-%96-_-%97-_-%98-_-%99-_-%9A
&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;To run this experiment I&#39;ve set a special route on Image-Charts that outputs what Image-Charts http server got as part of &lt;a href=&quot;https://nodejs.org/api/url.html#urlsearch&quot; rel=&quot;nofollow&quot;&gt;url.search&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On Google Kubernetes Engine (GKE), Image Charts API got an unmodified query string:&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;
%0-_-%1-_-%2-_-%3-_-%4-_-%5-_-%6-_-%7-_-%8-_-%9-_-%A-_-%B-_-%C-_-%D-_-%E-_-%F-_-%10-_-%11-_-%12-_-
%13-_-%14-_-%15-_-%16-_-%17-_-%18-_-%19-_-%1A-_-%1B-_-%1C-_-%1D-_-%1E-_-%1F-_-%20-_-%21-_-%22-_-
%23-_-%24-_-%25-_-%26-_-%27-_-%28-_-%29-_-%2A-_-%2B-_-%2C-_-%2D-_-%2E-_-%2F-_-%30-_-%31-_-%32-_-
%33-_-%34-_-%35-_-%36-_-%37-_-%38-_-%39-_-%3A-_-%3B-_-%3C-_-%3D-_-%3E-_-%3F-_-%40-_-%41-_-%42-_-
%43-_-%44-_-%45-_-%46-_-%47-_-%48-_-%49-_-%4A-_-%4B-_-%4C-_-%4D-_-%4E-_-%4F-_-%50-_-%51-_-%52-_-
%53-_-%54-_-%55-_-%56-_-%57-_-%58-_-%59-_-%5A-_-%5B-_-%5C-_-%5D-_-%5E-_-%5F-_-%60-_-%61-_-%62-_-
%63-_-%64-_-%65-_-%66-_-%67-_-%68-_-%69-_-%6A-_-%6B-_-%6C-_-%6D-_-%6E-_-%6F-_-%70-_-%71-_-%72-_-
%73-_-%74-_-%75-_-%76-_-%77-_-%78-_-%79-_-%7A-_-%7B-_-%7C-_-%7D-_-%7E-_-%7F-_-%80-_-%81-_-%82-_-
%83-_-%84-_-%85-_-%86-_-%87-_-%88-_-%89-_-%8A-_-%8B-_-%8C-_-%8D-_-%8E-_-%8F-_-%90-_-%91-_-%92-_-
%93-_-%94-_-%95-_-%96-_-%97-_-%98-_-%99-_-%9A
&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;On Google Cloud Run however, things are different:&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;
%0-_-%1-_-%2-_-%3-_-%4-_-%5-_-%6-_-%7-_-%8-_-%9-_-%A-_-%B-_-%C-_-%D-_-%E-_-%F-_-%10-_-%11-_-%12-_-
%13-_-%14-_-%15-_-%16-_-%17-_-%18-_-%19-_-%1A-_-%1B-_-%1C-_-%1D-_-%1E-_-%1F-_-%20-_-!-_-%22-_-
%23-_-%24-_-%25-_-%26-_-%27-_-(-_-)-_-*-_-%2B-_-%2C-_---_-.-_-%2F-_-0-_-1-_-2-_-
3-_-4-_-5-_-6-_-7-_-8-_-9-_-%3A-_-%3B-_-%3C-_-%3D-_-%3E-_-%3F-_-%40-_-A-_-B-_-
C-_-D-_-E-_-F-_-G-_-H-_-I-_-J-_-K-_-L-_-M-_-N-_-O-_-P-_-Q-_-R-_-
S-_-T-_-U-_-V-_-W-_-X-_-Y-_-Z-_-%5B-_-%5C-_-%5D-_-%5E-_-_-_-%60-_-a-_-b-_-
c-_-d-_-e-_-f-_-g-_-h-_-i-_-j-_-k-_-l-_-m-_-n-_-o-_-p-_-q-_-r-_-
s-_-t-_-u-_-v-_-w-_-x-_-y-_-z-_-%7B-_-%7C-_-%7D-_-~-_-%7F-_-%80-_-%81-_-%82-_-
%83-_-%84-_-%85-_-%86-_-%87-_-%88-_-%89-_-%8A-_-%8B-_-%8C-_-%8D-_-%8E-_-%8F-_-%90-_-%91-_-%92-_-
%93-_-%94-_-%95-_-%96-_-%97-_-%98-_-%99-_-%9A
&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;Yep. Characters like &quot;)&quot;, &quot;(&quot;, &quot;a&quot;, &quot;~&quot; were converted on the fly by some Cloud Run internal proxy.&lt;/p&gt;

&lt;p&gt;Now lets filter this for better readability:&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;
const search_raw = new Array(155).join(&#39;-&#39;).split(&#39;-&#39;).map((x, i) =&gt; &#39;%&#39;+i.toString(16).toUpperCase());
const search_processed_by_cloud_run = `%0-_-%1-_-%2-_-%3-_-%4-_-%5-_-%6-_-%7-_-%8-_-%9-_-%A-_-%B-_-%C-_-%D-_-%E-_-%F-_-%10-_-%11-_-%12-_-%13-_-%14-_-%15-_-%16-_-%17-_-%18-_-%19-_-%1A-_-%1B-_-%1C-_-%1D-_-%1E-_-%1F-_-%20-_-!-_-%22-_-%23-_-%24-_-%25-_-%26-_-%27-_-(-_-)-_-*-_-%2B-_-%2C-_---_-.-_-%2F-_-0-_-1-_-2-_-3-_-4-_-5-_-6-_-7-_-8-_-9-_-%3A-_-%3B-_-%3C-_-%3D-_-%3E-_-%3F-_-%40-_-A-_-B-_-C-_-D-_-E-_-F-_-G-_-H-_-I-_-J-_-K-_-L-_-M-_-N-_-O-_-P-_-Q-_-R-_-S-_-T-_-U-_-V-_-W-_-X-_-Y-_-Z-_-%5B-_-%5C-_-%5D-_-%5E-_-_-_-%60-_-a-_-b-_-c-_-d-_-e-_-f-_-g-_-h-_-i-_-j-_-k-_-l-_-m-_-n-_-o-_-p-_-q-_-r-_-s-_-t-_-u-_-v-_-w-_-x-_-y-_-z-_-%7B-_-%7C-_-%7D-_-~-_-%7F-_-%80-_-%81-_-%82-_-%83-_-%84-_-%85-_-%86-_-%87-_-%88-_-%89-_-%8A-_-%8B-_-%8C-_-%8D-_-%8E-_-%8F-_-%90-_-%91-_-%92-_-%93-_-%94-_-%95-_-%96-_-%97-_-%98-_-%99-_-%9A`.split(&#39;-_-&#39;);

search_raw.reduce((m, encoded, i) =&gt; encoded === search_processed_by_cloud_run[i] ? m : m.concat([encoded, search_processed_by_cloud_run[i]].join(&#39; =&gt; &#39;)), []).join(&#39;\n&#39;)
    
// output
%21 =&gt; !
%28 =&gt; (
%29 =&gt; )
%2A =&gt; *
%2D =&gt; -
%2E =&gt; .
%30 =&gt; 0
%31 =&gt; 1
%32 =&gt; 2
%33 =&gt; 3
%34 =&gt; 4
%35 =&gt; 5
%36 =&gt; 6
%37 =&gt; 7
%38 =&gt; 8
%39 =&gt; 9
%41 =&gt; A
%42 =&gt; B
%43 =&gt; C
%44 =&gt; D
%45 =&gt; E
%46 =&gt; F
%47 =&gt; G
%48 =&gt; H
%49 =&gt; I
%4A =&gt; J
%4B =&gt; K
%4C =&gt; L
%4D =&gt; M
%4E =&gt; N
%4F =&gt; O
%50 =&gt; P
%51 =&gt; Q
%52 =&gt; R
%53 =&gt; S
%54 =&gt; T
%55 =&gt; U
%56 =&gt; V
%57 =&gt; W
%58 =&gt; X
%59 =&gt; Y
%5A =&gt; Z
%5F =&gt; _
%61 =&gt; a
%62 =&gt; b
%63 =&gt; c
%64 =&gt; d
%65 =&gt; e
%66 =&gt; f
%67 =&gt; g
%68 =&gt; h
%69 =&gt; i
%6A =&gt; j
%6B =&gt; k
%6C =&gt; l
%6D =&gt; m
%6E =&gt; n
%6F =&gt; o
%70 =&gt; p
%71 =&gt; q
%72 =&gt; r
%73 =&gt; s
%74 =&gt; t
%75 =&gt; u
%76 =&gt; v
%77 =&gt; w
%78 =&gt; x
%79 =&gt; y
%7A =&gt; z
%7E =&gt; ~
&lt;/pre&gt;&lt;/code&gt;


&lt;p&gt;I reported this bug to Steren (Product Manager at Google Cloud Run) and hopefully it will be fixed soon :)&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/1814837337558957639/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/1814837337558957639?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/1814837337558957639'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/1814837337558957639'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2021/12/bug-cloud-run-decode-parts-of-search.html' title='[Bug] Google Cloud Run decodes url search part before reaching your app'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-8933750073414314232</id><published>2021-12-15T11:18:00.007+01:00</published><updated>2021-12-15T11:18:59.075+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="cto as a service"/><category scheme="http://www.blogger.com/atom/ns#" term="e-commerce"/><category scheme="http://www.blogger.com/atom/ns#" term="oxmoto"/><category scheme="http://www.blogger.com/atom/ns#" term="strategy"/><title type='text'>[fr] oxmoto.fr — stratégie e-commerce 1 an plus tard</title><content type='html'>Cela fait maintenant 1 an (2020) que j&#39;ai rejoins en tant qu&#39;associé Oxmoto afin d&#39;y développer la partie e-commerce de &lt;a href=&quot;https://www.oxmoto.fr/&quot;&gt;Oxmoto.fr&lt;/a&gt; grâce à un suivi hebdomadaire.&amp;nbsp;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Fin 2021 nous avons maintenant franchit une étape importante car le CA réalisé &lt;b&gt;chaque semaine&lt;/b&gt; correspond au &lt;b&gt;CA annuel&lt;/b&gt; de 2020.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;C&#39;est le résultat d&#39;efforts continus de la part de toute l&#39;équipe, d&#39;une approche aggressive #lean et d&#39;une excellence d&#39;exécution à tous les niveaux.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;2022 s&#39;annonce exceptionnel !&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/8933750073414314232/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/8933750073414314232?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/8933750073414314232'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/8933750073414314232'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2021/12/fr-oxmotofr-strategie-e-commerce-1-plus.html' title='[fr] oxmoto.fr — stratégie e-commerce 1 an plus tard'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-1273765020102301170</id><published>2021-06-16T18:15:00.004+02:00</published><updated>2021-11-04T15:27:23.783+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Finite State Machine"/><category scheme="http://www.blogger.com/atom/ns#" term="FSM"/><category scheme="http://www.blogger.com/atom/ns#" term="NoBullshit CTO"/><category scheme="http://www.blogger.com/atom/ns#" term="NoBullshit Tech-Lead"/><category scheme="http://www.blogger.com/atom/ns#" term="Product Design"/><category scheme="http://www.blogger.com/atom/ns#" term="Product Management"/><category scheme="http://www.blogger.com/atom/ns#" term="StateCharts"/><title type='text'>[Fr] Une sombre histoire de vaccin, d&#39;erreur 500 de FSM et de routing</title><content type='html'>&lt;p&gt;L’infirmière a une erreur 500 sur AmeliPro lorsqu’elle souhaitait valider ma première dose de vaccin 🥲&lt;/p&gt;



&lt;p&gt;Bien entendu — comme tout utilisateur — elle fait donc un retour arrière mais l’application ne respectant pas les principes du web : pas d’état porté par l’url -&gt; 🔥💥☠️🥲&lt;/p&gt;


&lt;p&gt;Qu’a-t-elle donc fait ? &lt;br/&gt;
Recommencer le cheminement à 0 🥲&lt;/p&gt;



&lt;p&gt;Chez Cloud-IAM, Hook0 et feu Redsmin et Bringr nous respect(i)ons le principe de « les interfaces graphiques en tant que machines d&#39;états finis » qui map chaque état sur un routing associé.&lt;/p&gt;


&lt;p&gt;Pour faire simple un état (~ un écran) = une route côté front.&lt;/p&gt;



&lt;p&gt;La *majorité* des états sont donc accessibles via une URL associée, permettant de nombreux usages internes et externes (partage de lien, bookmarks, retour arrière, debugging, compréhension globale des parcours utilisateurs, documentation à jour, automatisation etc…)&lt;/p&gt;



&lt;p&gt;Envie d&#39;en savoir plus ? &lt;a href=&quot;https://www.getnobullshit.com/&quot;&gt;J’en parle en détail dans mon livre&lt;/a&gt; !&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/1273765020102301170/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/1273765020102301170?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/1273765020102301170'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/1273765020102301170'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2021/06/fr-une-sombre-histoire-de-vaccin-de-fsm.html' title='[Fr] Une sombre histoire de vaccin, d&#39;erreur 500 de FSM et de routing'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-2754607971831100461</id><published>2021-02-17T22:40:00.000+01:00</published><updated>2021-02-17T22:39:59.943+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="docker"/><category scheme="http://www.blogger.com/atom/ns#" term="tmate"/><title type='text'>How to setup your own tmate server with docker</title><content type='html'>&lt;p&gt;tmate is an awesome tool I use to do work/debug session on someone else terminal. &lt;a href=&quot;https://tmate.io/&quot;&gt;Learn more about tmate here&lt;/a&gt;. Sadly their default server is not available anymore, at the time of writing this article you have to setup your own tmate server, I could not find a lot of documentation on internet so here we are!&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;
# ssh into your server (e.g. 51.158.172.10)
# install docker
mkdir tmate &amp;&amp; cd tmate

# this command will download the create_keys script and create a &quot;keys&quot; folder in the directory
curl -s -q https://raw.githubusercontent.com/tmate-io/tmate-ssh-server/master/create_keys.sh | bash

# don&#39;t forget to setup tmate client on the machine you want and configure ~/.tmate.conf with the information outputed by the previous command

# now let&#39;s start the server (don&#39;t forget to change &quot;sub.my-domain-name.com&quot;) with the domain name pointing to your server
# I choose 2223 but any other port will do

docker run -d --name=&quot;tmate-server&quot; \
  --cap-add SYS_ADMIN \
  -v  $(pwd)/keys:/keys \
  -e SSH_KEYS_PATH=/keys \
  -p 2223:2223 \
  -e SSH_PORT_LISTEN=2223 \
  -e SSH_HOSTNAME=sub.my-domain-name.com \
  -e USE_PROXY_PROTOCOL=0 \
  tmate/tmate-ssh-server:prod
&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;Now back on your client machines (e.g. personal laptops, or your friend laptop):&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;
# install tmate client (check https://tmate.io/ for instructions)
nano ~/.tmate.conf

# paste what was printed from create_keys.sh

set -g tmate-server-host &quot;IP_OR_DOMAIN_OF_YOUR_SERVER&quot;
set -g tmate-server-port 2223
set -g tmate-server-rsa-fingerprint SHA256:xxxxxxxxxxxxxxxxxx
set -g tmate-server-ed25519-fingerprint SHA256:xxxxxxxxxxxxxxxxxxxxxx
&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;You now are good to go!&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/2754607971831100461/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/2754607971831100461?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/2754607971831100461'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/2754607971831100461'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2021/02/how-to-setup-your-own-tmate-server-with.html' title='How to setup your own tmate server with docker'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-4399493685225030694</id><published>2021-01-17T23:31:00.002+01:00</published><updated>2021-12-15T12:07:34.595+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="service-public"/><category scheme="http://www.blogger.com/atom/ns#" term="sylae"/><category scheme="http://www.blogger.com/atom/ns#" term="wtf"/><title type='text'>[Fr] La signature numérique de Sylae ? Bonjour Sisyphe, bienvenue en enfer</title><content type='html'>&lt;p&gt;L&#39;histoire suivante se passe sur le site Sylae, un site du service-public.&lt;/p&gt;
&lt;p&gt;Quelques &quot;MINUTES&quot; pour afficher un formulaire de signature numérique ?!.&lt;/p&gt;
&lt;p&gt;C&#39;est forcément parce que vous êtes en train de me générer une clé avec une complexité de tarée right ? RIGHT ?&lt;/p&gt;

&lt;img border=&quot;0&quot; data-original-height=&quot;686&quot; data-original-width=&quot;1200&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEh6ZOdTAOijWdOstPNzz05pfLpY5K9quI-q4FLVhlrzE-1vQVFmua61QlksrPOVtxhwcpg4xmzUTdWr5tXwJQS7D2l42Gdi8xUkpVHVWEJoRhxXZTsZBMWV9uolOZ8GKHvdG1pbDJFDGGjDSGaSz2rSXZwXX8QbPxBB8ys9uP03N_S7Y2kNt2E=w640-h366&quot; width=&quot;640&quot; /&gt;


&lt;p&gt;Moi dans ces cas là, j&#39;ai juste pas la patience (après 10 secondes d&#39;attente), je check le code-source.&lt;/p&gt;
&lt;p&gt;Et comme c&#39;est un service publique, il y a 80% de chance que ça soit un grand moment (Atos, CapGemini, Sopra, ... FTW).&lt;/p&gt;

&lt;code&gt;
&lt;pre&gt;(issTimeout=300)&lt;/pre&gt;
&lt;/code&gt;

&lt;p&gt;&quot;Allez, on va dire qu&#39;au bout de 5 minutes, si le formulaire n&#39;est pas chargé, c&#39;est qu&#39;il y a un problème&quot;.&lt;/p&gt;
&lt;p&gt;NON MAIS LES GARS si vous n&#39;étiez pas les services publics, la moitié du web aurait quitté cette page après 5 secondes !&lt;/p&gt;

&lt;img border=&quot;0&quot; data-original-height=&quot;336&quot; data-original-width=&quot;1200&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgNUu5j471miJH3GuJ7p-ToL62XuSZNOlI5zTYRIc8Hxber1D0N2aHWpKz94xcYuGJr7YauyinxCiQZXrtUUKOtLL6QFeN3QvaV9ESH-yUVeNmkjmC70aqXull3spvF_V22TcW-9xDGjRXoF5UBTM-ar0-PaoUcLgr8xAEH7haryN0duHTXZIU=w640-h180&quot; width=&quot;640&quot; /&gt;


&lt;p&gt;Le script JS génère du mixed content, les mecs doutent de rien. OKLM.&lt;/p&gt;

&lt;img border=&quot;0&quot; data-original-height=&quot;350&quot; data-original-width=&quot;1200&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgauD2MGrQbTUC8zCE2vimeVJtKTkQfrDys6ibDwaWL7ejRNvYE0fUTsKWblSaN5jQosHm0ao31dGHFAGjttBOyoq4_9RPQqBH1ohjbT_d2NsuCxhm2rQ0H_WWug4dQIxJ1kR3cExWwXSmLbs3FrQE42eLCEZ_fdJPlZngvmfJw58w-z3XaxdY=w640-h186&quot; width=&quot;640&quot; /&gt;

&lt;p&gt;Mais savez pourquoi ils affichent après 5 MINUTES le message: &quot;Nous vous invitons à vérifier la configuration de votre poste de travail avec les pré-requis&quot; ????????&lt;/p&gt;

&lt;img border=&quot;0&quot; data-original-height=&quot;290&quot; data-original-width=&quot;1200&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjwa5Dh30yyZ8ElWUFL_V3udPerzwWgMePiwsYj-JWHUSwfRzXhPbAje2nJEI2hG61LnEn3F2G64qzDua_VAkRvZs6Q83q0M5u3-3Q6_4qwQmNuGF_yRI8tSduOO2aqPL6BQDO0jXsHniJQV7QCnq97Oa1eEwhBdYJhfa1JmjA54YxyVOTekmg=w640-h154&quot; width=&quot;640&quot; /&gt;

&lt;p&gt;En fait vous DEVIEZ savoir préalablement qu&#39;il fallait lire un PDF pour ajouter des EXCEPTIONS à l&#39;exécution de l&#39;applet Java en HTTP (sisisisisi).&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Les mixed content en fait c&#39;était pas un bug, mais une feature.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Cher utilisateur, corrige le problème CHEZ TOI (pov&#39; naz).&lt;/p&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;1422&quot; data-original-width=&quot;1352&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizxMKNo1515b6qCfGfzCm2iU7kWroIEDEV1cr2fN9UaUGL6jb6q694W05wXierwFKZHT7yWnl3S2nHrOo9mw3FZPERy_Rpg785t2El77KFloJTawL-nRo_vUSNXfKoL5aU9jl8Vw/w608-h640/Er8-tk0XcAEwo4q.jpeg&quot; /&gt;
&lt;img alt=&quot;&quot; data-original-height=&quot;1910&quot; data-original-width=&quot;1356&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_76lkuwkmZvvkSrRjS5wSD-lUhBBBFWtZiN5-Fd3itZ3aSKWpeMVE_U-GFaD51ZYMgcrEdnWWbHH6mFQJyuJ8cY4ItNcKdgWYo-uE40zKxtUBRd6LAJWdS2_VYmDJQAZm3B0lfw/w453-h640/Er8-1-EXMAM851Z.jpeg&quot; /&gt;

&lt;p&gt;Fun fact: il n&#39;y a pas de lien depuis le message pour vous guider vers &quot;la configuration&quot; optimale du &quot;poste de travail&quot; en question (ça serait trop vous aider bande de fainéant)&lt;/p&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;366&quot; data-original-width=&quot;1514&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKpdRLZ0pD1rYPmrbOPn9Fb80e6YB3VIY797tnRkHszVwb_hsdq3wZ5RZyg8SlW1XUUAYXDU8-0c9pMb7Cb0ii1t609jqxlFpv22w5NMhqlptcSLn2SfaB3dHCpCZf8Se04Aa2Pg/w640-h154/Er8_hi8XIAIlsYZ.jpeg&quot; /&gt;

&lt;p&gt;Une recherche google m&#39;emmène le site &lt;a href=&quot;https://www.cc-saulnois.fr/extranet/documents/pdf_module_de_sylae.pdf&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;d&#39;une communauté de commune qui héberge le PDF DE Sylae&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;En conclusion :&amp;nbsp
&lt;p&gt;
&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;on te fait dé-facto poireauter 5 minutes&lt;/li&gt;
&lt;li&gt;on t&#39;affiche un message pour te dire que t&#39;es qu&#39;une merde (je grossis à peine le trait)&amp;nbsp;&lt;/li&gt;
&lt;li&gt;... qu&#39;il te faut vérifier la &quot;configuration&quot; de &quot;ton poste de travail&quot;&lt;/li&gt;
&lt;li&gt;... qui consiste à mettre en place un bypass sécurité&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;p&gt;Quand on connait les deux grosses sociétés qui sont derrière le développement et l&#39;hébergement de la majorité des applications des services publiques en France...&amp;nbsp
&lt;br/&gt;... et le niveau des développements là bas&amp;nbsp
&lt;br/&gt;... et leurs coût
&lt;/p&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;284&quot; data-original-width=&quot;476&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmDAFoKNBYFDo5Rg4r-L1pG2OlMWl4yHP3X24xHGL3CHM35cvSLrV4TNGW5kvlZdXYe_OTOBPlvxHwZdKxl7JUmVOIJ7r7_zjgeNq7JDdSkrikslaVe_qWV51Baea40mW8sZR3mQ/w640-h382/puke-vomit.gif&quot; /&gt;

&lt;p&gt;Merci. Changez rien.&lt;/p&gt;

&lt;p&gt;[Bonus] Si vous demandez l&#39;impression manuscrite avec signature, on vous affiche ce message. Les mecs n&#39;ont peur de rien.&lt;/p&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;552&quot; data-original-width=&quot;2082&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuFmqmuAjT32C4UiJrKdYPLwBN7bT6V4iVchy_ottM0E7IIVY3gwZ14TsReMuhysgb4EznYcK0d-abb2__EJy3UcuR5YUHU4zmei-t4acgel8AAqIFrvQ_8_1itD_Y7UloRYNzQQ/w640-h170/Er9EdX4XMAQrt8L.jpeg&quot; /&gt;

&lt;p&gt;[Bonus 2] Je vous explique la blague ou bien ?&lt;/p&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;628&quot; data-original-width=&quot;1494&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpWCzENmg35urrmsFHtHsVn24Y8VqwaT-LVOwu9ZpEGntw5H1hPiTO1j9liLpvZya0hCB6WSwG0GAP0-4Qcp7QXuNujfvSImJkF6YYZ5dIQ_uYDCes0fo_eeXFxD1XkIeiC6NxPQ/w640-h270/Er9FA-CW4AAj10v.jpeg&quot; /&gt;

&lt;p&gt;[Bonus 3] &quot;deployJava.js&quot; quand même Oracle s&#39;y met. La factorisation ? Pour quoi faire ?&lt;p&gt;


&lt;img alt=&quot;&quot; data-original-height=&quot;1050&quot; data-original-width=&quot;1134&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhrLpdXGKdwhoHg2zNpNsp5g5w7AibVj7Et-fEYmyOSQ1JSav6DreRXwpF7AUReRbmDllU7mm78R2sNyH1JJVxJIypyIF7pclAb0ADURP8UyF8yTwlVFuOhYobrA98FcSKYSOHq_A/w640-h593/Er9GEP_XYAAnxFX.jpeg&quot; /&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;1518&quot; data-original-width=&quot;1854&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimADXFyHBGjXgZzP7mgBVXSyRLRO5fjQbLUhdVrWeeXPnFwWFCY87CLzbVRmPAYoq30-c2jNgjCByefe7hjxuu1mRbLUxlyshFIPMqLXv8TnAbxz8oCz1RMuPVrN9ATBYQhz4Yuw/w640-h524/Er9GA8BW8AE7hJh.jpeg&quot; /&gt;

&lt;p&gt;Après 1h30 de bataille.&amp;nbsp;&lt;p&gt;

&lt;ul style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;Brave (good point) ne permettant même pas de désactiver le mixed-content. Contrairement à Chrome/Firefox.&lt;/li&gt;
&lt;li&gt;Chrome/Firefox/Brave ne supportant plus Java.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;Je me décide à faire l&#39;impensable :&lt;p&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;1754&quot; data-original-width=&quot;2048&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhdJQvN7HS1JhbHV7mHB1RxuEvJ_g6J2q4dNJD8lTe2fbUq2TqT6SCl3TIfdGPQmPZLEQDCWmhLjjbbkGSrlF3ci3jbp9jjExVj1UAy2DCXrjgeWWe9_908ErbR4Bn1lzP_2wHEIg/w640-h549/Er9LhRvXAAUcJBH.jpeg&quot; /&gt;

&lt;p&gt;... sauf que c&#39;était trop demander à Microsoft:&lt;+p&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;680&quot; data-original-width=&quot;2108&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGj6moeCH-GtXrfEw2yYxWMwQZF6mOYBUlfRbaR0yGcOOghzxxHGVIuccSWq-MJ8zzyA0OgeGXu27tz4KHlA9OTmQ-WYYBv5kR9DVacEevl_VaXGoQp5Sxm8g1U0qm06ieAZ_JuA/w640-h206/Er9MFEEW4AEFMlU.jpeg&quot; /&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;90&quot; data-original-width=&quot;134&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgChH0Iyaxy0HiAre_7gOMyAypJcl8-hiQ_FsArfxWUPKZgfHdzg5Y9syUxKXQ7GZN7GGK2XkpRaWlCWyD9UiG0cBv62WmJ1aJrpWvsJg038pIYb81punZr40qegpEiQBR7cJzJWg/&quot; /&gt;
&lt;p&gt;C&#39;est partiiiiiiiiiiiiiiiii...&amp;nbsp;&lt;/p&gt;

&lt;p&gt;[3h plus tard. 3. Heures.] Et 1 VM et le vieux windows de ma femme. Toujours pas.&lt;/p&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;1470&quot; data-original-width=&quot;1800&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiAO_nF6BK2Do2YDBMRbQVpdpI5mMxt2JaNObsbrQmeR9cnpJWkdNHzMKbkBLivsBiHynzo3ZtGmkq22QWYyXWbR1IVptrWgBJ64sritUFZpA05goKkqWW0h661_D8eW6yCgKxR8w/w640-h522/Er9V35fWMAIMZOe.jpeg&quot; /&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;2174&quot; data-original-width=&quot;3010&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhfo4MitAjMvz5K1UO6-mB3Y5tYej936ZUDBP4D9rPumVjV6l8Q7UrYbg70h2BcJSe6wfj1M1Jj7BI7285Lk8bpyY2HMPDUWakxL-2O9RhK2kozbiikd7qWOZqBeMb7dVa3A-D6Q/w640-h462/Er9jn81XYAMdOtJ.jpeg&quot; /&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;1662&quot; data-original-width=&quot;3096&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdXaVdNSqPJTXotA8_r1wJ-83KpnZWEeRsuVvHfoyzD4d5WTVv6U2chsra4nuqDRlWKQEojWRt7NRsjLcmrJbXtdmuJhLdgbbeB2t9lnJunRBi9U8Cfeco0sX7z2HpWgV0SLX8Dg/w640-h344/Er9jn8tW4AEukPV.jpeg&quot; /&gt;

&lt;p&gt;MAIS NOOOOOOON ! Redirection http =&amp;gt; https =&amp;gt; homepage&lt;/p&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;1052&quot; data-original-width=&quot;1938&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaU6V0jawjEzd8LHWbeb-pAAymX4Hu6zj1wMZKsvK7wP0Kv0VPY1QMjp-NYdh4YafTlf9zVNcKIDCqZdDyF-BFQnN9C4rhYmgLvucBgg7jjBUE3Fcr-D63NJBQyBF8T9g_1ltHUw/w640-h348/Er9kuFCXYAA6PGe.jpeg&quot; /&gt;
&lt;img alt=&quot;&quot; data-original-height=&quot;1726&quot; data-original-width=&quot;1826&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUX6UB47ByuwLeo_yDieJk11eEvH8OQmh45nZTlymNl0Tur6O5X3mvI4NT2Anw3qllFEVWJNJEDazfWFxfqbk_9hGO5NyFy6D1fGdbZqP1bAYpG5OCfleMuYj7STXdc2owASmBYA/w640-h605/Er9k-5PXEAIHw53.jpeg&quot; /&gt;

&lt;p&gt;Les plus assidus remarquerons que la redirection depuis https va vers une adresse en ... http
&lt;br /&gt;
A votre avis, le système Sylaé a:&amp;nbsp
&lt;/p&gt;
&lt;ol style=&quot;text-align: left;&quot;&gt;
&lt;li&gt;enregistré mon mot de passe auto-généré MAIS en enlevant des caractères spéciaux SANS RIEN DIRE&lt;/li&gt;
&lt;li&gt;truncate mon mot de passe auto-généré de 22 caractères SANS RIEN DIRE&lt;/li&gt;
&lt;/ol&gt;


&lt;img alt=&quot;&quot; data-original-height=&quot;948&quot; data-original-width=&quot;2086&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKaHJhNRX4C3cJ5USuEp13DH9iuS5QrgBHKPYRENrUKUBFHLKHMm8fesgjK2RgBcP3yEV6gfJNNEVqxSsjmQ-b56h3IwrIhQE2NM9TWgXrLfkjCha6zaqgbtkbdU6M8m13j1UF4Q/w640-h290/Er9lo_rXcAInYOz.jpeg&quot; /&gt;

&lt;p&gt;Et c&#39;étaiiiiiiit ....... 🥁🥁🥁🥁 un truncate du mot de passe silencieux à 20 caractères&lt;/p&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;640&quot; data-original-width=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiiNA5uW7bb5GRIh2bgfLOkkH6GekTfRZ6VXMfvOJQeC_IAvw8qT4kmRSKk3YssscFuV6pXibUnp56QMiAs3ypEv7oKy3SXN2k4iRLY1ClNWG5AI4TN6YXeiNZqVN2LR0mpcERPQQ/w640-h640/leonardo-di-caprio-wolf-of-wall-street.gif&quot; /&gt;

&lt;p&gt;Maintenant retour à la case départ&amp;nbsp; 😭&lt;/p&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;692&quot; data-original-width=&quot;1336&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRI5tzKYwuydy_J1yUsJVdFUM85r5P6SA8wHkDe4fRFir_pOh6HoKVYhfbDZSRnUa4fTZmDnKVG3WTwgq6N-xnZF52hOyDvMdnhV0UtcDQP8QiSgdK3ksBrg8aJwf3wRJsg4_vXA/w640-h332/Er9mmQDXMAA7f3L.jpeg&quot; /&gt;

&lt;p&gt;On notera le ©2014 en footer.&amp;nbsp Mais QUI voudrait vous copier ??&lt;/p&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;1688&quot; data-original-width=&quot;2124&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi185FSXvWdpj5_FxCbOpmFsUP3XCOV83ALsXrp_AVqVlVtvIZ4RK6-oruzL7PCzNxbn2xzomcyYxnSArPOOxIw-YKaI0ES1DcX2a3IfmuiO626lZGC3XrlbuAGxuODiJjZEOIr_Q/w640-h509/Er9m3c9XAAMEyur.jpeg&quot; /&gt;


&lt;img alt=&quot;&quot; data-original-height=&quot;1106&quot; data-original-width=&quot;2088&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEho-53Xmuf2taIC_N8l4XPJ7KslwyezLFfQ0HwWZGlqdIkkX5ZzBQR3A9wiFys5jIZrrGAQnr4OXbSp8zXACdGejNXSOYNR5gy-viwOo6DMGR8qSH2zC9iWkyft5AZerZcvNAtT-g/w640-h340/Er9npAjXMAc2oGm.jpeg&quot; /&gt;

&lt;p&gt;+ 3 heures et 6 minutes&lt;/p&gt;

&lt;img alt=&quot;&quot; data-original-height=&quot;186&quot; data-original-width=&quot;250&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjLctFXaK2a1Fkrm-O_-kG3Ms1vqnBfb9-6KDSERikC7fm4esgZG_2I19IN0SuG2gRvTmdZYsRP7gTPd74Ypb3ILtaRx8Pce0DQT-8jJHRFgOSeviBWEMYKoldYrgkfG2TdjNznA/&quot; /&gt;

&lt;p&gt;[Bonus je-sais-plus-combien] Le code d&#39;erreur qui intègre l&#39;adresse IP (privée) du serveur qui a traité la requête..
&lt;img alt=&quot;&quot; data-original-height=&quot;1100&quot; data-original-width=&quot;2554&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMbPwOpX6w32FxoakpxQG-MGflOQsHmFrf82mlAUNL4kOYWt6T2vnEnPtBq7HQ9evhCZ27iyR8KINv_GkQ7eU9R5Q3RTBQx3h2N_G5BWY0_FkMGxr2Nz0Pmx0d9ZnnaRT2iLTs6g/w640-h276/Er9p7FPXIAAhp5Z.jpeg&quot; /&gt;

&lt;p&gt;Début d&#39;une 4ème tentative (+3h31). Si IE9+Win7 est trop récent, peut-être que WinXP fera l&#39;affaire &lt;/p&gt;


&lt;img alt=&quot;&quot; data-original-height=&quot;1152&quot; data-original-width=&quot;1804&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirkusRhKg4Ral4ZXyjdYDQA3vOBuBitxYsgLs42iSTzDGnvoabrgLopdYzKJj4xMoR5yZ44JTloDoEEF1ZHCMe1NOg9Oi0mKTLtHycTXDTR_gTUijFKDwrhL64gjNj9h_9336Weg/w640-h408/Er9tXbQXUAEJiSQ.jpeg&quot; /&gt;

&lt;p&gt;La réponse est non&lt;/p&gt;
&lt;p&gt;Fin.&lt;p&gt;
</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/4399493685225030694/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/4399493685225030694?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/4399493685225030694'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/4399493685225030694'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2021/12/fr-la-signature-numerique-de-sylae_0953050691.html' title='[Fr] La signature numérique de Sylae ? Bonjour Sisyphe, bienvenue en enfer'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEh6ZOdTAOijWdOstPNzz05pfLpY5K9quI-q4FLVhlrzE-1vQVFmua61QlksrPOVtxhwcpg4xmzUTdWr5tXwJQS7D2l42Gdi8xUkpVHVWEJoRhxXZTsZBMWV9uolOZ8GKHvdG1pbDJFDGGjDSGaSz2rSXZwXX8QbPxBB8ys9uP03N_S7Y2kNt2E=s72-w640-h366-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-9006280460077752202</id><published>2020-12-06T13:30:00.004+01:00</published><updated>2020-12-06T13:30:53.173+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="google drive"/><category scheme="http://www.blogger.com/atom/ns#" term="nocode"/><category scheme="http://www.blogger.com/atom/ns#" term="shopify"/><category scheme="http://www.blogger.com/atom/ns#" term="zapier"/><title type='text'>How to upload Shopify PDF invoices to Google Drive with Zapier</title><content type='html'>&lt;p&gt;Yesterday I discovered that &lt;a href=&quot;https://tatabulle.com/&quot; rel=&quot;external noopener&quot; target=&quot;_blank&quot;&gt;my fiancée&lt;/a&gt; was manually downloading every invoice (generated with &lt;a href=&quot;https://apps.shopify.com/order-printer-pro&quot; rel=&quot;nofollow noopener&quot; target=&quot;_blank&quot;&gt;Order Printer Pro&lt;/a&gt; app) from Shopify so then she could upload them to her Google Drive &quot;invoices&quot; folder. It&#39;s more than a hundred manual operations per month and that number is growing. FAST.&lt;/p&gt;

&lt;img alt=&quot;&quot; border=&quot;0&quot; width=&quot;400&quot; data-original-height=&quot;210&quot; data-original-width=&quot;480&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBLrPa6MY6J_UBTdjvisasI-eZyNApm_eDK0w7X9WV68otoEhXrKxudaoVkSw9udSfFg3avUodhZwPTXGSx7CFO8lQdy8mUA-KtDIIMyyuHffSP_0ecr6h4AHe9zBPa6vXsmhEkQ/s400/output.gif&quot;/&gt;

&lt;p&gt;Let&#39;s automate that with Zapier 😇.&lt;/p&gt;

&lt;h2&gt;Step 1: Trigger Zap on new Shopify paid order&lt;/h2&gt;

&lt;p&gt;First thing first, create a new zap with a Shopify trigger &quot;New Paid Order&quot;.&lt;/p&gt;

&lt;img alt=&quot;&quot; border=&quot;0&quot; data-original-height=&quot;2048&quot; data-original-width=&quot;1625&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPZ0yMOkLDAo9qlBHgxvuRcqBOlRUCsO5QDqNYoIxfj5R6w7npvQBMP05I1NElt9VhGA2ZWFSxjV8pPgwFQUTbJ_RtrNo0tcOuWuBwB2EShSoJaibUAoGvdHbdJRijGRRP_tXIhw/s0/Capture+d%25E2%2580%2599e%25CC%2581cran+2020-12-06+a%25CC%2580+12.56.23.png&quot;/&gt;

&lt;h2&gt;Step 2: Add some glue code&lt;/h2&gt;

&lt;p&gt;Sadly the Shopify connector does not expose metadata from apps like Order Printer Pro. We won&#39;t get the pdf invoice link for free. Hopefully we can download the receipt page html source code and extract the &quot;Download Invoice&quot; link from there.&lt;/p&gt;

&lt;p&gt;Add a &quot;Code by Zapier&quot; step along in order to run JavaScript code. Setup the 3 inputs variables below:&lt;/p&gt;

&lt;img alt=&quot;&quot; border=&quot;0&quot; data-original-height=&quot;2048&quot; data-original-width=&quot;1636&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_GsjrvhzMrQC4JqiEVGXxixcXRVknWLAnt9xKiBikgOJvvZDUO8AGs4de66eNyqvqXY6KNl068g9UGbXa9zKcBOHx_rT0eXin8la2-KmgkAvYzPKVslqac4L6y4PHI8gdfCKGRQ/s0/Capture+d%25E2%2580%2599e%25CC%2581cran+2020-12-06+a%25CC%2580+13.17.31.png&quot;/&gt;

&lt;p&gt;Copy/paste the -good-enough- code below:&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;
const order_name_as_file_name = inputData.order_name.replace(&#39;#&#39;, &#39;-&#39;);

fetch(inputData.order_url)
.then((res) =&gt; res.text())
.then((body) =&gt; {
  const url_regexp = /href=&quot;(.*?)&quot;/ig;
  let match;
  while(match = url_regexp.exec(body)){
    if(match[1].includes(&#39;.pdf&#39;)){
      return callback(null, {
        order_name: order_name_as_file_name, 
        invoice_url: match[1].replace(&#39;/.pdf&#39;, `/${inputData.order_number}.pdf`)
      });
    }
  }
  
  // we could not find the PDF invoice, fail loudly
 callback(`Could not find pdf URL in: ${body}`);
})
.catch((err) =&gt; callback(err));
&lt;/pre&gt;&lt;/code&gt;

&lt;h2&gt;Step 3: Upload PDF Invoice to Google Drive&lt;/h2&gt;

&lt;p&gt;One last thing, add a &quot;Upload File in Google Drive&quot; action, select your drive and folder. Select the &quot;Invoice URL&quot; we got from our previous step as the &lt;code&gt;file&lt;/code&gt; parameter, customize the filename as needed and you are good to go!&lt;/p&gt;

&lt;img alt=&quot;&quot; border=&quot;0&quot; data-original-height=&quot;2048&quot; data-original-width=&quot;1620&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEherbb7oF_OQFYOZPqeV0ZCTegtzwutp_eT45ePQ8LXGOiYut7Ug6guEOVSSkAXbVy-JmUN-P4Gqi7Bm3Ps0HQk-dxt8Zv_RfW89ZrzgE7gDFBWVU7wu4PFhkcKd6hhmInPGEaNww/s0/Capture+d%25E2%2580%2599e%25CC%2581cran+2020-12-06+a%25CC%2580+13.22.51.png&quot;/&gt;

&lt;p&gt;Job done, you now get full tracability and alerting capabilities from Zapier, enjoy!&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/9006280460077752202/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/9006280460077752202?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/9006280460077752202'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/9006280460077752202'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2020/12/how-to-upload-shopify-pdf-invoices-to-google-drive-with-zapier.html' title='How to upload Shopify PDF invoices to Google Drive with Zapier'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBLrPa6MY6J_UBTdjvisasI-eZyNApm_eDK0w7X9WV68otoEhXrKxudaoVkSw9udSfFg3avUodhZwPTXGSx7CFO8lQdy8mUA-KtDIIMyyuHffSP_0ecr6h4AHe9zBPa6vXsmhEkQ/s72-c/output.gif" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-666926182009229975</id><published>2020-11-20T10:02:00.001+01:00</published><updated>2020-11-20T10:02:23.594+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="error"/><category scheme="http://www.blogger.com/atom/ns#" term="rust"/><category scheme="http://www.blogger.com/atom/ns#" term="tokio"/><title type='text'>How to fix &quot;there is no timer running, must be called from the context of Tokio runtime&quot;</title><content type='html'>&lt;h2&gt;Some context&lt;/h2&gt;

&lt;p&gt;You just tried to compile your Rust project and got the &lt;code&gt;there is no timer running, must be called from the context of Tokio runtime&lt;/code&gt; error?&lt;p&gt;

&lt;p&gt;You are desesperatly trying to understand what is going on?&lt;/p&gt;
&lt;p&gt;So did I!&lt;/p&gt;

&lt;p&gt;For future reference, my project relied on tokio v0.3. I added a dependency (&lt;a href=&quot;https://crates.io/crates/bollard&quot; rel=&quot;nofollow&quot;&gt;bollard&lt;/a&gt;) that was relying on tokio v0.2.&lt;/p&gt;

&lt;p&gt;Even when my code was in the execution context of a tokio runtime (v0.3) calling bollard code triggered at runtime the &lt;code&gt;there is no timer running, must be called from the context of Tokio runtime&lt;/code&gt; error.&lt;/p&gt;


&lt;h2&gt;How do I fix this?&lt;/h2&gt;

&lt;h3&gt;Option 1: start a new runtime in tokio v0.2&lt;/h3&gt;

&lt;p&gt;Your first option would be to start another runtime in the same tokio version that your dependency requires, something like:&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;
use tokio::runtime::Runtime;

// Create the runtime
let rt = Runtime::new().unwrap();

// Spawn a future onto the runtime
rt.block_on(async {
    // call your dependency
});
&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;That would result in two threadpools so it might be an issue in some case. I did not go down this path&lt;/p&gt;

&lt;h3&gt;Option 2: use the same version that your dependency&lt;/h3&gt;

&lt;p&gt;It&#39;s the easiest option. Downgrade your tokio project dependency to v0.2 so it can be the same as your dependency. But yep, it sucks.&lt;/p&gt;

&lt;h3&gt;Option 3: send a patch to the dependency&lt;/h3&gt;

&lt;p&gt;Last option (and the best one) is to create and send a patch to your dependency project to upgrade its tokio version.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/666926182009229975/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/666926182009229975?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/666926182009229975'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/666926182009229975'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2020/11/how-to-fix-there-is-no-timer-running.html' title='How to fix &quot;there is no timer running, must be called from the context of Tokio runtime&quot;'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-1418947669490610626</id><published>2020-11-02T11:55:00.005+01:00</published><updated>2020-11-02T12:00:30.624+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="api"/><category scheme="http://www.blogger.com/atom/ns#" term="postgresql"/><category scheme="http://www.blogger.com/atom/ns#" term="postgrest"/><category scheme="http://www.blogger.com/atom/ns#" term="rest"/><category scheme="http://www.blogger.com/atom/ns#" term="trigger"/><title type='text'>How PostgreSQL triggers works when called with a PostgREST PATCH HTTP request</title><content type='html'>&lt;p&gt;Wonder what values are set or not inside your &lt;code&gt;new.column_name&lt;/code&gt; and &lt;code&gt;old.column_name&lt;/code&gt; when you are calling &lt;a href=&quot;https://postgrest.org/&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;PostgREST&lt;/a&gt; with a PATCH request? This article is for you!&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;private&lt;/code&gt; schema for our sample app, we will expose the &lt;code&gt;public&lt;/code&gt; schema for our PostgREST API:&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;
create schema private;
&lt;/pre&gt;&lt;/code&gt;


&lt;p&gt;A &lt;code&gt;city&lt;/code&gt; table:&lt;/p&gt;
  
&lt;code&gt;&lt;pre&gt;
create table private.city (
    city__id integer not null primary key ,
    name text not null,
    countrycode character(3) not null,
    district text not null,
    population integer not null
);
&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;with some data:&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;
copy private.city (city__id, name, countrycode, district, population) FROM stdin;
1	Kabul	AFG	Kabol	1780000
2	Qandahar	AFG	Qandahar	237500
3	Herat	AFG	Herat	186800
\.
&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;Now let&#39;s expose this &lt;code&gt;city&lt;/code&gt; private table through PostgREST as a public API (&lt;code&gt;public&lt;/code&gt; schema) so we stay clean regarding the &lt;a rel=&quot;nofollow&quot; href=&quot;https://en.wikipedia.org/wiki/Separation_of_concerns&quot;&gt;Separation of Concerns&lt;/a&gt; principle:&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;
create view public.cities as 
	select city__id as id, name, countrycode, district, population 
    from private.city;
&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;Let&#39;s add support for the &lt;code&gt;PATCH&lt;/code&gt; HTTP verb on our newly created &lt;code&gt;/cities&lt;/code&gt; REST endpoint.&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;
create or replace function private.update_city() returns trigger as
$$
begin
    raise exception &#39;new.name = %, old.name=%, new.countrycode = %, old.countrycode = %&#39;, new.name, old.name, new.countrycode, old.countrycode;

    return new;
end;
$$ security definer language plpgsql;


create trigger city_update
    instead of update
    on public.cities
    for each row
execute procedure private.update_city();
&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;Now our function &lt;code&gt;private.update_city()&lt;/code&gt; will be called for each rows submitted through &lt;code&gt;PATCH /cities&lt;/code&gt; HTTP request. As you can see from &lt;code&gt;update_city&lt;/code&gt; function body we print the before/after values of &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;countrycode&lt;/code&gt; columns in PATCH requests.&lt;/p&gt;

&lt;h2&gt;What&#39;s the value of &lt;code&gt;new.countrycode&lt;/code&gt; if I don&#39;t specify &lt;code&gt;countrycode&lt;/code&gt; property in PATCH request body?&lt;/h2&gt;

&lt;code&gt;&lt;pre&gt;
# PATCH /cities?id=eq.1 &#39;{&quot;name&quot;: &quot;new_value&quot;}&#39;
curl -H &quot;content-type: application/json&quot; \
	--request PATCH \
    --data &#39;{&quot;name&quot;: &quot;new_value&quot;}&#39; http://localhost:3000/cities?id=eq.1 | jq &#39;.message&#39;
&lt;/pre&gt;&lt;/code&gt;

&lt;code&gt;&lt;pre&gt;
new.name = new_value, old.name=Kabul,
new.countrycode = AFG, old.countrycode = AFG
&lt;/pre&gt;&lt;/code&gt;

As you can see, the property (column in fact) &lt;code&gt;countrycode&lt;/code&gt; was not specified in the PATCH request body but it still defined with its current value in &lt;code&gt;new.countrycode&lt;/code&gt; and &lt;code&gt;old.countrycode&lt;/code&gt; just like we expected it to be.

&lt;h2&gt;What&#39;s the value of &lt;code&gt;new.name&lt;/code&gt; if I set &lt;code&gt;name&lt;/code&gt; as a &lt;code&gt;null&lt;/code&gt; value in PATCH request body?&lt;/h2&gt;

&lt;code&gt;&lt;pre&gt;
# PATCH /cities?id=eq.1 &#39;{&quot;name&quot;: null}&#39;

curl -H &quot;content-type: application/json&quot; \
	--request PATCH \
	--data &#39;{&quot;name&quot;: null}&#39; http://localhost:3000/cities?id=eq.1 | jq &#39;.message&#39;
&lt;/pre&gt;&lt;/code&gt;

&lt;code&gt;&lt;pre&gt;
new.name = &amp;lt;NULL&amp;gt;, old.name=Kabul,
new.countrycode = AFG, old.countrycode = AFG
&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;Perfect! Just like we expected. We set &lt;code&gt;name&lt;/code&gt; property to &lt;code&gt;null&lt;/code&gt; in our PATCH request body thus &lt;code&gt;new.name&lt;/code&gt; is set to &lt;code&gt;NULL&lt;/code&gt; in our trigger function.&lt;/p&gt;

&lt;p&gt;Clone &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/FGRibreau/postgrest-patch-request-trigger&quot;&gt;this github repository&lt;/a&gt; to try all of this locally. Wonder what SQL conventions you should use? Check out &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/FGRibreau/sql-convention/&quot;&gt;these SQL conventions&lt;/a&gt;.&lt;/p&gt;

</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/1418947669490610626/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/1418947669490610626?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/1418947669490610626'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/1418947669490610626'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2020/11/how-postgresql-triggers-works-when.html' title='How PostgreSQL triggers works when called with a PostgREST PATCH HTTP request'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-4661453201460813251</id><published>2020-09-01T08:58:00.002+02:00</published><updated>2020-09-01T11:38:38.075+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="CTO"/><category scheme="http://www.blogger.com/atom/ns#" term="IndieHacker"/><category scheme="http://www.blogger.com/atom/ns#" term="Passive-income"/><category scheme="http://www.blogger.com/atom/ns#" term="SaaS"/><title type='text'>🤝 14 étapes pour vendre son SaaS en 3 mois (et pas 2 ans)</title><content type='html'>&lt;p&gt;J&#39;ai fait l&#39;erreur il y a 2 ans de penser que la vente de mon SaaS, &lt;a href=&quot;https://www.redsmin.com&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Redsmin.com&lt;/a&gt; (je parle de &lt;a href=&quot;https://www.youtube.com/watch?v=02WYzAP66y4&quot; target=&quot;_blank&quot;&gt;son histoire ici&lt;/a&gt;), serait naturelle. Malgré les demandes reçues au fil du temps, rien ne me convenait. Il y a 3 mois, j&#39;ai décidé de me prendre en main ce qui a eu pour résultat une vente de Redsmin jeudi dernier (20 août 2020). Cet article retrace les étapes que j&#39;ai suivies.&lt;/p&gt;

&lt;h2&gt;1 - Attendre un miracle&lt;/h2&gt;

&lt;p&gt;Cela faisait 2 ans (juillet 2018) que je laissais Redsmin en roue libre. Pas de mise à jour fonctionnelle, juste un peu de support à raison d&#39;un email par semaine environ. Le MRR variant de mois en mois entre &lt;a href=&quot;https://www.indiehackers.com/product/redsmin&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;1 700$ et 2 800$&lt;/a&gt;, cela me convenait et j&#39;espérais recevoir des propositions intéressantes. &lt;/p&gt;
&lt;p&gt;La bonne nouvelle est que j&#39;ai bien reçu des propositions, la mauvaise nouvelle est qu&#39;elles ne m&#39;intéressaient pas. Offre de partenariats, prise de participation, AcquiHire...&lt;/p&gt;
&lt;p&gt;Je n&#39;étais pas proactif quant à la communication et la gestion de la mise en vente de Redsmin et le projet stagnait.&lt;/p&gt;
&lt;p&gt;Bref, j&#39;attendais un miracle, tout comme on peut attendre d&#39;obtenir un travail en restant chez soi. Les chances de succès sont très limitées.&lt;/p&gt;

&lt;h2&gt;2 - Se prendre en main, publier l&#39;annonce sur des marketplaces&lt;/h2&gt;

&lt;p&gt;Le 11 mai 2020, je décide de rechercher les techniques pour vendre son SaaS. Je réalise qu&#39;il existe des marketplaces spécialisées pour cela.&lt;/p&gt;
&lt;p&gt;Je crée une fiche profil pour Redsmin.com sur indiemaker.co, la fiche est validée le jour même par le site.&lt;/p&gt;
&lt;p&gt;Deux semaines plus tard, le 22 mai, je découvre microacquire.com. Rebelote, création d&#39;une fiche profil qui sera validée 4 jours plus tard par le site.&lt;/p&gt;

&lt;h2&gt;3 - Attendre les propositions&lt;/h2&gt;

&lt;p&gt;12 jours plus tard, j&#39;ai pu recevoir la première demande de mise en contact via Indiemaker. Concernant MicroAcquire il a suffi de 5 jours d&#39;attente.&lt;/p&gt;
&lt;p&gt;Au total, c&#39;est 28 prises de contact qui ont eu lieu (19 via MicroAcquire, 9 via IndieMaker). Ces contacts ont découvert la fiche du SaaS majoritairement grâce à la newsletter dédiée de ces deux sites ainsi qu&#39;une mise en avant par les webmestres.&lt;/p&gt;

&lt;h2&gt;4 - Constituer un dossier de vente&lt;/h2&gt;


&lt;p&gt;Les potentiels acheteurs vont avoir besoin de plus que vos beaux yeux pour prendre la décision d&#39;aller plus loin.&lt;/p&gt;
&lt;p&gt;Il est donc nécessaire de créer et de partager un dossier qui contiendra un ensemble d&#39;informations communicable à l&#39;extérieur.&lt;/p&gt;
&lt;p&gt;Je suis parti sur un dossier par mois. Chaque dossier contenant des exports de données relatives au mois ou aux 6 derniers mois, suivant la métrique.&lt;/p&gt;

&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoLoSvkGADzPnS9WzBSJvcF6d68h2cu2qqSgX-GdLFWutqW-irD2fZxBQ_pZnYg0PHd9_Rr-_49rkuSt987UV73M9gEQ-tpl30B5EC_7BXcHGId206Lp74IhnPG3bMgikZEK41zQ/s462/Capture+d%25E2%2580%2599e%25CC%2581cran+2020-08-30+a%25CC%2580+16.02.17.png&quot;&gt;&lt;img alt=&quot;&quot; border=&quot;0&quot; width=&quot;400&quot; data-original-height=&quot;284&quot; data-original-width=&quot;462&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoLoSvkGADzPnS9WzBSJvcF6d68h2cu2qqSgX-GdLFWutqW-irD2fZxBQ_pZnYg0PHd9_Rr-_49rkuSt987UV73M9gEQ-tpl30B5EC_7BXcHGId206Lp74IhnPG3bMgikZEK41zQ/s400/Capture+d%25E2%2580%2599e%25CC%2581cran+2020-08-30+a%25CC%2580+16.02.17.png&quot;/&gt;&lt;/a&gt;

&lt;p&gt;Plut&amp;ocirc;t que de m&#39;emb&amp;ecirc;ter &amp;agrave; r&amp;eacute;aliser des exports partiels de la base de donn&amp;eacute;es et de Stripe. J&#39;ai pr&amp;eacute;f&amp;eacute;r&amp;eacute; utiliser les offres gratuites de &lt;a href=&quot;https://www.profitwell.com/&quot;&gt;ProfitWell&lt;/a&gt; et &lt;a href=&quot;https://chartmogul.com/&quot;&gt;ChartMogul&lt;/a&gt; pour agr&amp;eacute;ger et r&amp;eacute;aliser des exports.&lt;/p&gt;

&lt;p&gt;J&#39;obtenais ainsi tr&amp;egrave;s facilement les informations suivantes pour chaque mois :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MRR over the past 6 months&lt;/li&gt;
&lt;li&gt;MRR per plans&lt;/li&gt;
&lt;li&gt;MRR per countries&lt;/li&gt;
&lt;li&gt;MRR movements&lt;/li&gt;
&lt;li&gt;MRR breakdown (new business, expansion, contraction, churn, reactivation) over the past 6 months&lt;/li&gt;
&lt;li&gt;ARR&lt;/li&gt;
&lt;li&gt;Subscribers count over the past 6 months&lt;/li&gt;
&lt;li&gt;Churn/retention cohorts&lt;/li&gt;
&lt;li&gt;Cash-flow&lt;/li&gt;
&lt;li&gt;Freemium: free/paid ratio&lt;/li&gt;
&lt;li&gt;Customer Lifetime Value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&amp;Agrave; ceci il faut ajouter un export de votre comptabilit&amp;eacute; interne (profit &amp;amp; loss).&lt;/p&gt;

&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhev8ooCiFAifCWTbb6MP57aYpjo0evUCQ0o12wEcYPkauF_VNALcxupiF0WV1ah5cHBJcpskz_Frdgv7m_oQyRgWe6OI8T7CpR_TLvLDbUO8elNUHcqNj6jEiHXLa_NkW9mKnWWw/s0/Capture+d%25E2%2580%2599e%25CC%2581cran+2020-08-30+a%25CC%2580+22.53.24.png&quot;&gt;&lt;img alt=&quot;&quot; border=&quot;0&quot; data-original-height=&quot;844&quot; data-original-width=&quot;1206&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhev8ooCiFAifCWTbb6MP57aYpjo0evUCQ0o12wEcYPkauF_VNALcxupiF0WV1ah5cHBJcpskz_Frdgv7m_oQyRgWe6OI8T7CpR_TLvLDbUO8elNUHcqNj6jEiHXLa_NkW9mKnWWw/s0/Capture+d%25E2%2580%2599e%25CC%2581cran+2020-08-30+a%25CC%2580+22.53.24.png&quot;/&gt;&lt;/a&gt;

&lt;p&gt;Ce dossier &amp;eacute;tant dans dropbox il ne me restait plus qu&#39;&amp;agrave; partager le lien, suite aux mises en relation.&lt;/p&gt;

&lt;p&gt;R&amp;eacute;trospectivement, j&#39;aurais d&amp;ucirc; d&#39;abord constituer un dossier avant de soumettre les fiches. M&amp;ecirc;me s&#39;il ne faut pas plus de quelques heures pour cr&amp;eacute;er une premi&amp;egrave;re version.&lt;/p&gt;

&lt;h2&gt;5 - Maintenir un document de Q&amp;amp;A&lt;/h2&gt;

&lt;p&gt;Les questions des potentiels acheteurs sont souvent les m&amp;ecirc;mes. Je ne sais pas vous, mais je n&#39;aime pas trop me r&amp;eacute;p&amp;eacute;ter. J&#39;ai donc ajout&amp;eacute; un fichier Q_and_A.txt qui r&amp;eacute;pertorie l&#39;int&amp;eacute;gralit&amp;eacute; des questions que j&#39;avais pu recevoir ainsi que leurs r&amp;eacute;ponses associ&amp;eacute;es donc voici un extrait :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How much effort do you need to maintain this product?&lt;/li&gt;
&lt;li&gt;What are the unique values of your SaaS product, compared to your competitors, like XXX ?&lt;/li&gt;
&lt;li&gt;If you have time to make some dev effort, what functions will you add?&lt;/li&gt;
&lt;li&gt;What does the overall tech stack of the prod look like?&lt;/li&gt;
&lt;li&gt;It seems the maintenance cost is low, why do you still sell it?&lt;/li&gt;
&lt;li&gt;What is included in the sell and more importantly what is not included?&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sur les 28 mise en relations, j&#39;ai pu ainsi partager l&#39;acc&amp;egrave;s au dossier de vente &amp;agrave; 10 d&#39;entre eux.&lt;/p&gt;

&lt;h2&gt;6 - Filtrer les propositions&lt;/h2&gt;

&lt;p&gt;Suite au partage du dossier de vente, j&#39;ai pu recevoir 5 propositions (conversion : 17%).&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Je consid&amp;eacute;rais une proposition comme int&amp;eacute;ressante si elle respectait les crit&amp;egrave;res suivants :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;un maintien du service pour les clients et utilisateurs existants&lt;/li&gt;
&lt;li&gt;une vente int&amp;eacute;gralement en cash&lt;/li&gt;
&lt;li&gt;le montant correspondait &amp;agrave; mes attentes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pour ces raisons, j&#39;ai donc rejet&amp;eacute; les propositions suivantes :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;acquihire (acquisition + recrutement pour continuer &amp;agrave; maintenir Redsmin)&lt;/li&gt;
&lt;li&gt;apport + entr&amp;eacute;e au capital de Redsmin&lt;/li&gt;
&lt;li&gt;un peu de cash + une commission sur les nouvelles ventes&lt;/li&gt;
&lt;li&gt;un peu de cash + un second versement en fonction des r&amp;eacute;sultats apr&amp;egrave;s 1 an&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sur ces 5 propositions, en suivant mes crit&amp;egrave;res, deux ce sont av&amp;eacute;r&amp;eacute;es int&amp;eacute;ressantes. Vu que le premier y allait presque au chantage, j&#39;ai donc continu&amp;eacute; avec le second acheteur nous avons &amp;agrave; ce moment l&amp;agrave; &amp;eacute;chang&amp;eacute; sur le facteur multiplicateur.&lt;/p&gt;

&lt;h2&gt;7 - M&amp;eacute;thode EBITDA et facteur multiplicateur&lt;/h2&gt;

&lt;p&gt;Pour estimer la valeur d&#39;un SaaS plusieurs m&amp;eacute;thodes existent mais la plus connue et sans doute l&#39;EBITDA (Earnings Before Interest, Taxes, Depreciation and Amortization). En France nous parlons d&#39;EBE (Exc&amp;eacute;dent Brut d&#39;Exploitation).&amp;nbsp;&lt;/p&gt;

&lt;p&gt;EBITDA = Chiffre d&#39;affaires - Charges d&#39;exploitation&lt;/p&gt;

&lt;p&gt;Redsmin est un SaaS et son business model est un mod&amp;egrave;le de souscription majoritairement mensuel. Pour &amp;eacute;valuer le chiffre d&#39;affaires dans ces cas l&amp;agrave;, on se base sur un MRR m&amp;eacute;dian (par exemple sur les 6 derniers mois) report&amp;eacute; sur 1 an.&lt;/p&gt;

&lt;p&gt;Ensuite l&#39;EBITDA est multipli&amp;eacute; par le fameux &lt;strong&gt;facteur multiplicateur&lt;/strong&gt; afin d&#39;obtenir le montant final de la vente.&lt;/p&gt;

&lt;p&gt;L&#39;int&amp;eacute;r&amp;ecirc;t de la m&amp;eacute;thode EBITDA et facteur multiplicateur est qu&#39;elle donne un rep&amp;egrave;re &amp;agrave; l&#39;acheteur ainsi qu&#39;au vendeur. En r&amp;eacute;sum&amp;eacute; : on ne peut pas fake son EBE/EBITDA (cette affirmation est &amp;agrave; prendre avec des pincettes, je ne suis loin d&#39;&amp;ecirc;tre un expert).&amp;nbsp;&lt;/p&gt;

&lt;p&gt;Il ne reste plus alors qu&#39;&amp;agrave; jouer sur le facteur multiplicateur pour trouver un terrain d&#39;entente.&lt;/p&gt;

&lt;p&gt;Bref, connaissant le modeste MRR de Redsmin, je savais pertinemment que cette vente ne me rendrait pas millionnaire 😅.&lt;/p&gt;

&lt;h2&gt;8 - LOI - Letter Of Intent&lt;/h2&gt;

&lt;p&gt;La lettre d&#39;intention d&#39;achat (ou Letter Of Intent en anglais) a principalement deux int&amp;eacute;r&amp;ecirc;ts :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;une premi&amp;egrave;re d&amp;eacute;finition du prix d&#39;achat du SaaS&lt;/li&gt;
&lt;li&gt;la d&amp;eacute;finition d&#39;une p&amp;eacute;riode (e.g. 14 jours) d&#39;exclusivit&amp;eacute; o&amp;ugrave; le vendeur s&#39;engagent &amp;agrave; ne pas accepter d&#39;autres propositions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;N&amp;eacute;anmoins la LOI n&#39;engage habituellement pas l&#39;acheteur, il faut donc encore montrer patte blanche !&lt;/p&gt;

&lt;h2&gt;9 - Due Diligence&lt;/h2&gt;

&lt;p&gt;Pour r&amp;eacute;sumer, l&#39;objectif de cette &amp;eacute;tape (appel&amp;eacute;e Due Diligence) est de v&amp;eacute;rifier que le vendeur (moi) ne bullshit pas sur les chiffres. Dans notre cas la Due Diligence s&#39;est d&amp;eacute;roul&amp;eacute;e via un &amp;eacute;change Google Meet (l&#39;acheteur &amp;eacute;tait anglais et d&#39;un autre pays) et un partage d&#39;&amp;eacute;cran, en 1 heure c&#39;&amp;eacute;tait pli&amp;eacute;.&lt;/p&gt;

&lt;p&gt;Au programme :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Parcours du code et explication macro via GitLab et GitHub.&amp;nbsp;&lt;/li&gt;
&lt;ul&gt;
&lt;li&gt;Objectif : d&amp;eacute;montrer que le code n&#39;est pas d&amp;eacute;gueux, qu&#39;il y a des tests, une int&amp;eacute;gration continue (CI) et parfois m&amp;ecirc;me du d&amp;eacute;ploiement continu (CD), bref, que l&#39;application est toujours maintenable.&lt;/li&gt;
&lt;/ul&gt;
&lt;li&gt;Parcours des revenus via Stripe.&amp;nbsp;&lt;/li&gt;
&lt;ul&gt;
&lt;li&gt;Objectif : prouver que je ne bullshit pas sur les exports et sur les chiffres.&lt;/li&gt;
&lt;/ul&gt;
&lt;li&gt;Parcours des donn&amp;eacute;es de visites via Google Analytics.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;10 - Asset Purchase Agreement, Asset Transfer, Non Compete Agreement, NonDisclosure Agreement&lt;/h2&gt;

&lt;p&gt;&amp;Agrave; cette &amp;eacute;tape il faut d&amp;eacute;finir un inventaire des &lt;em&gt;assets&lt;/em&gt; &amp;agrave; transf&amp;eacute;rer (comptes Stripe, Analytics, Google Apps, GitLab, GitHub, npm, Tumblr, Netlify, OVH, Clever Cloud, MongoDB Atlas, RedisLabs, Uservoice, &amp;hellip;), sous quelle modalit&amp;eacute;, avec quel pr&amp;eacute;-requis...&lt;/p&gt;

&lt;p&gt;L&#39;&amp;eacute;change se concentre en parall&amp;egrave;le sur le contrat d&#39;Asset Purchase Agreement. Dans mon cas nous avons it&amp;eacute;r&amp;eacute; 7 fois afin de bien sp&amp;eacute;cifier les clauses l&amp;eacute;gales.&lt;/p&gt;
&lt;p&gt;En parall&amp;egrave;le nous avons chacun ouvert un compte sur &lt;a href=&quot;https://escrow.com&quot;&gt;Escrow.com&lt;/a&gt; (oui, la compr&amp;eacute;hension VF du nom ne rassure pas !). Escrow est une plateforme agissant en tant que tiers de confiance pour assurer la transaction.&lt;/p&gt;

&lt;h2&gt;11 - N&amp;eacute;gocier l&#39;accompagnement&amp;nbsp;&lt;/h2&gt;

&lt;p&gt;Nous sommes partis sur un accompagnement de 30 jours int&amp;eacute;gr&amp;eacute; au contrat de vente. Puis une facturation &amp;agrave; l&#39;heure pour du conseil apr&amp;egrave;s ces 30 jours.&amp;nbsp;&lt;/p&gt;

&lt;p&gt;R&amp;eacute;trospectivement, j&#39;aurais peut-&amp;ecirc;tre d&amp;ucirc; n&amp;eacute;gocier l&#39;accompagnement sur une dur&amp;eacute;e de 15 jours. La bonne nouvelle cependant est que pour cet accompagnement &amp;agrave; la transition il n&#39;y a qu&#39;un engagement de moyen (en mode best-effort) et pas de r&amp;eacute;sultat. No stress.&lt;/p&gt;

&lt;h2&gt;12 - Signature&lt;/h2&gt;

&lt;p&gt;Apr&amp;egrave;s plus de 60 &amp;eacute;changes emails qui ont eu lieu avec l&#39;acheteur sur 2 mois pour r&amp;eacute;pondre aux questions et de nous aligner sur le contenu du contrat de vente, nous &amp;eacute;tions fin pr&amp;ecirc;t &amp;agrave; signer.&lt;/p&gt;

&lt;img src=&quot;https://media.giphy.com/media/14tCGgXf4Z8C0o/giphy.gif&quot;/&gt;

&lt;p&gt;Parce que dans notre cas les documents de travails &amp;eacute;taient tous des PDF, j&#39;ai d&amp;ucirc; faire un suivi des changements... &amp;agrave; l&#39;ancienne :&lt;/p&gt;

&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-Ke_bzjru09oJuhxjwIK5DJWt0wVi8g7Xhn-Nt2nWtZS0-UZRfqiqwjpvrZ3nefQT-a24Qwep6P-UggEK0_5kXb0yGed5-pVkedMSiHYC_zwuB3GKV2qVH7T11XYTe10hpmTixQ/s1040/Capture+d%25E2%2580%2599e%25CC%2581cran+2020-08-31+a%25CC%2580+08.50.07.png&quot;&gt;&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj-Ke_bzjru09oJuhxjwIK5DJWt0wVi8g7Xhn-Nt2nWtZS0-UZRfqiqwjpvrZ3nefQT-a24Qwep6P-UggEK0_5kXb0yGed5-pVkedMSiHYC_zwuB3GKV2qVH7T11XYTe10hpmTixQ/s600/Capture+d%25E2%2580%2599e%25CC%2581cran+2020-08-31+a%25CC%2580+08.50.07.png&quot;/&gt;&lt;/a&gt;

&lt;h2&gt;13 - R&amp;eacute;aliser le transfert des assets&lt;/h2&gt;

&lt;p&gt;Le transfert de tous les assets, la migration des applications et des acc&amp;egrave;s ont &amp;eacute;t&amp;eacute; r&amp;eacute;alis&amp;eacute; en quelques heures, sans downtime pour les utilisateurs.&lt;/p&gt;

&lt;h2&gt;14 - Attendre l&#39;argent (rends l&#39;argent !) et payer ses imp&amp;ocirc;ts&lt;/h2&gt;

&lt;p&gt;Pas la peine de faire un dessin sur cette dernière partie :).&lt;/p&gt;

&lt;p&gt;Ainsi se termine ces 3 mois. &quot;Qui ose gagne. Là où se trouve une volonté, il existe un chemin.&quot; disait Churchil.&lt;/p&gt;

&lt;p&gt;Si tu lis jusqu&#39;ici c&#39;est sans doute que les SaaS, l&#39;indiehacking, ou simplement le développement en général te passionne. J&#39;ai une bonne nouvelle pour toi, il y a un &lt;a href=&quot;https://join.slack.com/t/fgribreau/shared_invite/zt-edpjwt2t-Zh39mDUMNQ0QOr9qOj~jrg&quot; target=&quot;_blank&quot;&gt;slack&lt;/a&gt; pour ça, rejoins-nous ! Il est temps de mon côté de te laisser, les autres chapitres du livre &lt;a href=&quot;https://www.getnobullshit.com/&quot; rel=&quot;me&quot;&gt;NoBullshit Tech-Lead&lt;/a&gt; ne sortirons pas tout seul 😅 !&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/4661453201460813251/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/4661453201460813251?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/4661453201460813251'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/4661453201460813251'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2020/08/14-etapes-pour-vendre-son-saas-en-3.html' title='🤝 14 étapes pour vendre son SaaS en 3 mois (et pas 2 ans)'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoLoSvkGADzPnS9WzBSJvcF6d68h2cu2qqSgX-GdLFWutqW-irD2fZxBQ_pZnYg0PHd9_Rr-_49rkuSt987UV73M9gEQ-tpl30B5EC_7BXcHGId206Lp74IhnPG3bMgikZEK41zQ/s72-c/Capture+d%25E2%2580%2599e%25CC%2581cran+2020-08-30+a%25CC%2580+16.02.17.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-4339838754381646077</id><published>2020-06-03T14:05:00.001+02:00</published><updated>2020-06-05T12:59:02.644+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="FinOps"/><category scheme="http://www.blogger.com/atom/ns#" term="google cloud platform"/><category scheme="http://www.blogger.com/atom/ns#" term="google cloud storage"/><category scheme="http://www.blogger.com/atom/ns#" term="image-charts"/><title type='text'>FinOps - Reducing Google Cloud Storage costs</title><content type='html'>&lt;p&gt;🤯 Inter-region network transfer can be a real PITA.&lt;/p&gt;
&lt;p&gt;My latest Google-Cloud Invoice for my &lt;a href=&quot;https://www.image-charts.com/&quot;&gt;Image-Charts Saas&lt;/a&gt; was ~40% related with inter-region transfer. 💸💸&lt;/p&gt;
&lt;p&gt;&amp;nbsp;- Why ? 🧐&lt;/p&gt;
&lt;p&gt;&amp;nbsp;- I&#39;m glad you asked 😍!&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ_oddXZeRhokul9qCOi5C11EGy3mzX5irFyZD5B3kk8hzCPHnvsxVOsuKc7bxdvNRF4Nr9WhvDYStEERAZsbtFIMR54dUQSevktX_p9gYkOHgkE8TqIeaGlJg88M_SkR71auIVA/w2028/EZkhpURXgAEr2PS.jpeg&quot;&gt;
  &lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ_oddXZeRhokul9qCOi5C11EGy3mzX5irFyZD5B3kk8hzCPHnvsxVOsuKc7bxdvNRF4Nr9WhvDYStEERAZsbtFIMR54dUQSevktX_p9gYkOHgkE8TqIeaGlJg88M_SkR71auIVA/w2028/EZkhpURXgAEr2PS.jpeg&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GCP billing report confirms that 40% came from Google Cloud Storage.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9hFDZkyEe1zlVXBEB1F9m_FuF1yNB7xxeK6o8A9UWrx9HrWzg6eHfrMuysTf72Md7AQ4wyyVVMqNBFF26qVOd5Vpgm8ME_xcfHJqmapMkxaYVkxPV8-Io_Ur2U5Edhyphenhyphenj7P2HZeg/w2028/EZkjwbMWsAIjB8k.jpeg&quot;&gt;
  &lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9hFDZkyEe1zlVXBEB1F9m_FuF1yNB7xxeK6o8A9UWrx9HrWzg6eHfrMuysTf72Md7AQ4wyyVVMqNBFF26qVOd5Vpgm8ME_xcfHJqmapMkxaYVkxPV8-Io_Ur2U5Edhyphenhyphenj7P2HZeg/w2028/EZkjwbMWsAIjB8k.jpeg&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Drilling down I saw that the main costs were related with GCP &quot;Storage egress between NA and EU&quot; and &quot;GCP Storage egress between EU and APAC&quot;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYblmMEKK8SIwENgmdZMaCnLkDBq0ipREKFvSrJK-zAa5lvJYYI7CXz9BI8Wd8Nz4Ejg91UaLfI-Ga5tr4ZXPvMcFkjLyWGq43u265RVt_1-PJG37PTqaRFtWeKuDVY0jwUrUP3g/w2028/EZkkTc6WAAUrLpd.jpeg&quot;&gt;
  &lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYblmMEKK8SIwENgmdZMaCnLkDBq0ipREKFvSrJK-zAa5lvJYYI7CXz9BI8Wd8Nz4Ejg91UaLfI-Ga5tr4ZXPvMcFkjLyWGq43u265RVt_1-PJG37PTqaRFtWeKuDVY0jwUrUP3g/w2028/EZkkTc6WAAUrLpd.jpeg&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Storage/sent bytes per location graph confirms it, I&#39;ve sent more than 3TB of data from EU to Asia (APAC) &amp;amp; USA (NA) clusters. &lt;/p&gt;
&lt;p&gt;WHYYYY 😭?&lt;/p&gt;
&lt;p&gt;Because docker images 🐳 are stored on GCP EU (eu.gcr.io). And are downloaded nearly at every Kubernetes node auto-scaling-up steps. And Image-Charts scales. Like a lot.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7IO2PNyheSrDsV9JhvuJGHH5icCMdY1baGY_UHmqAJmLB8699uEp-jXLLEtpPYngJX7qwDzJJtcfqpO7vlWaQX3PeBNInkZ1wE8fzmSe0SK37oXXfq8OTtxxgL92rPRz6JmOSNQ/w2028/EZklw4BWsAAG4D6.jpeg&quot;&gt;
  &lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7IO2PNyheSrDsV9JhvuJGHH5icCMdY1baGY_UHmqAJmLB8699uEp-jXLLEtpPYngJX7qwDzJJtcfqpO7vlWaQX3PeBNInkZ1wE8fzmSe0SK37oXXfq8OTtxxgL92rPRz6JmOSNQ/w2028/EZklw4BWsAAG4D6.jpeg&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&#39;ve updated &lt;a href=&quot;https://twitter.com/imagecharts&quot;&gt;@imagecharts&lt;/a&gt; continuous delivery pipeline yesterday to push images to the 3 locations (EU+Asia+USA) instead of one (EU) and I already see improvements 👍🔥&lt;/p&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg54UUsjG_1Sjcx8IeZ77I-PIZka4i03rYh2zPe4WiR9hyphenhyphencLNb-crS7wUmATZhu8OHoYc8A6yAPv7oEN7Dqop18dyvLFpNXZ9AClM7s8xe1qhkzHP0h_qbAGbZ-JJj8idcc_ZJjgw/w2028/EZkmlX4XgAIdI9j+%25281%2529.jpeg&quot;&gt;
  &lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg54UUsjG_1Sjcx8IeZ77I-PIZka4i03rYh2zPe4WiR9hyphenhyphencLNb-crS7wUmATZhu8OHoYc8A6yAPv7oEN7Dqop18dyvLFpNXZ9AClM7s8xe1qhkzHP0h_qbAGbZ-JJj8idcc_ZJjgw/w2028/EZkmlX4XgAIdI9j+%25281%2529.jpeg&quot; /&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Conclusion: 20x Google Cloud Storage cost reduction!&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/4339838754381646077/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/4339838754381646077?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/4339838754381646077'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/4339838754381646077'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2020/06/finops-reducing-google-cloud-storage.html' title='FinOps - Reducing Google Cloud Storage costs'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ_oddXZeRhokul9qCOi5C11EGy3mzX5irFyZD5B3kk8hzCPHnvsxVOsuKc7bxdvNRF4Nr9WhvDYStEERAZsbtFIMR54dUQSevktX_p9gYkOHgkE8TqIeaGlJg88M_SkR71auIVA/s72-w2028-c/EZkhpURXgAEr2PS.jpeg" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-4259537864211720261</id><published>2020-05-19T18:17:00.004+02:00</published><updated>2020-05-19T19:26:18.956+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="postgresql"/><category scheme="http://www.blogger.com/atom/ns#" term="postgrest"/><category scheme="http://www.blogger.com/atom/ns#" term="row-level-security"/><title type='text'>How to automatically activate PostgreSQL Row Level Security on tables with at least one policy attached</title><content type='html'>&lt;p&gt;&lt;a href=&quot;https://www.postgresql.org/docs/current/ddl-rowsecurity.html&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;Row level security&lt;/a&gt; is an awesome feature that let you control how your database (PostgreSQL in my case) manage access to each row of a table based on some policies declared upfront. It&#39;s also really useful when you expose your database through a REST API with a gateway like &lt;a href=&quot;http://postgrest.org/&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;PostgREST&lt;/a&gt;. I already &lt;a href=&quot;https://www.slideshare.net/FGRibreau/choisir-entre-une-api-rpc-soap-rest-graphql-et-si-le-problme-tait-ailleurs&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;talked enough about that&lt;/a&gt; &lt;code&gt;:)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I often forgot to add the &lt;code&gt;ALTER TABLE schema.table ENABLE ROW LEVEL SECURITY;&lt;/code&gt; statement when I declare row level security policies. Do you?&lt;/p&gt;

&lt;p&gt;Let&#39;s use our database awesome introspection feature to &lt;strong&gt;list tables for which we attached access policies&lt;/strong&gt; and then automatically activate row level security. The SQL request below list tables and display whether or not row level security is activated.&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;select pg_class.oid,
       pg_namespace.nspname || &#39;.&#39; || pg_class.relname as schema_table,
       pg_policy.polname as policy_name,
       pg_class.relrowsecurity as has_row_level_security_enabled
from pg_catalog.pg_policy
       inner join pg_catalog.pg_class on pg_class.oid = pg_policy.polrelid
       inner join pg_catalog.pg_namespace on pg_class.relnamespace = pg_namespace.oid;
&lt;/pre&gt;&lt;/code&gt;

&lt;table class=&quot;table&quot;&gt;
&lt;tbody&gt;&lt;tr&gt;
  &lt;th&gt;oid&lt;/th&gt;
  &lt;th&gt;schema_table&lt;/th&gt;
  &lt;th&gt;policy_name&lt;/th&gt;
  &lt;th&gt;has_row_level_security_enabled&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;369657&lt;/td&gt;
  &lt;td&gt;fsm.machine&lt;/td&gt;
  &lt;td&gt;fsm_machines_access_policy&lt;/td&gt;
  &lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;369745&lt;/td&gt;
  &lt;td&gt;iam.user&lt;/td&gt;
  &lt;td&gt;user_access_policy&lt;/td&gt;
  &lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;369803&lt;/td&gt;
  &lt;td&gt;actor.company&lt;/td&gt;
  &lt;td&gt;company_access_policy&lt;/td&gt;
  &lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;369803&lt;/td&gt;
  &lt;td&gt;actor.company&lt;/td&gt;
  &lt;td&gt;company_access_policy_for_update&lt;/td&gt;
  &lt;td&gt;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;369842&lt;/td&gt;
  &lt;td&gt;contract_manager.contract&lt;/td&gt;
  &lt;td&gt;contract_access_policy&lt;/td&gt;
  &lt;td&gt;false&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;p&gt;From the query output we observe that &lt;code&gt;contract_manager.contract&lt;/code&gt; table does have an associated access policy called &lt;code&gt;contract_access_policy&lt;/code&gt; but without row level security enabled on the table.&lt;/p&gt;

&lt;p&gt;Let&#39;s now enable row level security for each table where at least one policy was defined:&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;update pg_catalog.pg_class
set relrowsecurity = true
where pg_class.oid in (select pg_class.oid
      from pg_catalog.pg_policy
      inner join pg_catalog.pg_class on pg_class.oid = pg_policy.polrelid
      where pg_class.relrowsecurity = false);
&lt;/pre&gt;&lt;/code&gt;


&lt;p&gt;This is it! We&#39;ve activated row level security (not in &lt;a href=&quot;https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=491c029dbc4206779cf659aa0ff986af7831d2ff&quot;&gt;FORCE mode&lt;/a&gt;) to every table with attached policies. No more mistakes. No more boilerplate &lt;code&gt;\o/&lt;/code&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/4259537864211720261/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/4259537864211720261?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/4259537864211720261'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/4259537864211720261'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2020/05/how-to-automatically-activate.html' title='How to automatically activate PostgreSQL Row Level Security on tables with at least one policy attached'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-6049664808580821918</id><published>2020-04-09T23:31:00.003+02:00</published><updated>2020-04-09T23:32:04.063+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="api"/><category scheme="http://www.blogger.com/atom/ns#" term="postgresql"/><category scheme="http://www.blogger.com/atom/ns#" term="postgrest"/><category scheme="http://www.blogger.com/atom/ns#" term="sql"/><title type='text'>PostgREST &quot;response.headers guc must be a JSON array composed of objects with a single key and a string value&quot; error</title><content type='html'>&lt;p&gt;Yep. You are wondering why it&#39;s working locally and not in production right? Or maybe why PostgREST login feature is not working at all?&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;response.headers guc must be a JSON array composed of objects with a single key and a string value&lt;/code&gt; is often related to the fact that some call were made to set a header field but the value to be set was empty. Take a look at your &lt;code&gt;settings.secrets&lt;/code&gt; table. If it&#39;s empty, that&#39;s the problem.&lt;/p&gt;

&lt;p&gt;At least on the &lt;a href=&quot;https://github.com/subzerocloud/postgrest-starter-kit&quot; rel=&quot;nofollow&quot;&gt;PostgREST-starter-kit&lt;/a&gt;, the &lt;code&gt;settings.secrets&lt;/code&gt; table should contains at least 2 rows:&lt;/p&gt;

&lt;table&gt;
&lt;tr&gt;&lt;td&gt;jwt_secret&lt;/td&gt;&lt;td&gt;your_secret_here&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt; jwt_lifetime &lt;/td&gt;&lt;td&gt; 3600 &lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;

&lt;p&gt;Looking for some guidance on PostgREST? How to setup a CI/CD with it? What test strategy to use? As a CTO I&#39;ve built multiple SaaS with it underneath and now help tech teams build fast, reliable and safer API with PostgREST and PostgreSQL. Hire me on &lt;a href=&quot;https://www.codementor.io/@francois-guillaume-ribreau&quot;&gt;CodeMentor&lt;/a&gt; or &lt;a href=&quot;https://www.malt.fr/profile/francoisguillaumeribreau&quot;&gt;Malt&lt;/a&gt;!&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/6049664808580821918/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/6049664808580821918?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/6049664808580821918'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/6049664808580821918'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2020/04/postgrest-responseheaders-guc-must-be.html' title='PostgREST &quot;response.headers guc must be a JSON array composed of objects with a single key and a string value&quot; error'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-9074250463042710144</id><published>2020-03-03T23:23:00.001+01:00</published><updated>2020-03-03T23:23:37.885+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="postgresql"/><category scheme="http://www.blogger.com/atom/ns#" term="postgrest"/><category scheme="http://www.blogger.com/atom/ns#" term="subzero"/><title type='text'>How to expose all public stored function in PostgREST/SubZero</title><content type='html'>&lt;code&gt;&lt;pre&gt;
DO $$
    DECLARE
        schema_name INFORMATION_SCHEMA.routines.routine_schema%TYPE = &#39;api&#39;;
        fns CURSOR FOR
            select routine_name from INFORMATION_SCHEMA.routines WHERE routine_schema = schema_name;
    BEGIN
        FOR fn_record IN fns LOOP
                EXECUTE &#39;grant execute on function &#39; || schema_name || &#39;.&#39; || fn_record.routine_name || &#39; to anonymous;&#39;;
            END LOOP;
    END$$;
&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;Will expose all store functions from the &lt;code&gt;api&lt;/code&gt; public schema in the generated swagger/openapi specification from PostgREST/SubZero. Of course, ensure that all underneath private tables have row-level-security enabled to stay secure.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/9074250463042710144/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/9074250463042710144?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/9074250463042710144'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/9074250463042710144'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2020/03/how-to-expose-all-public-stored.html' title='How to expose all public stored function in PostgREST/SubZero'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-4463190654947865280</id><published>2020-03-03T23:15:00.001+01:00</published><updated>2020-03-03T23:15:13.672+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="postgresql"/><category scheme="http://www.blogger.com/atom/ns#" term="postgrest"/><category scheme="http://www.blogger.com/atom/ns#" term="subzero"/><title type='text'>How to expose all public views in PostgREST/SubZero</title><content type='html'>&lt;code&gt;&lt;pre&gt;
DO $$
    DECLARE
        schema_name INFORMATION_SCHEMA.views.table_schema%TYPE = &#39;api&#39;;
        views CURSOR FOR select table_name from INFORMATION_SCHEMA.views WHERE table_schema = schema_name;
    BEGIN
        FOR view_record IN views LOOP
                EXECUTE &#39;grant select, insert, update, delete on &#39; || schema_name || &#39;.&#39; || view_record.table_name || &#39; to anonymous;&#39;;
            END LOOP;
    END$$;
&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;Will expose all views from the api public schema in the generated swagger/openapi specification from PostgREST/SubZero. Of course, ensure that all underneath private tables have row-level-security enabled.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/4463190654947865280/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/4463190654947865280?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/4463190654947865280'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/4463190654947865280'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2020/03/how-to-expose-all-public-views-in.html' title='How to expose all public views in PostgREST/SubZero'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-8392711228591327795</id><published>2020-02-26T22:55:00.000+01:00</published><updated>2020-02-26T23:08:17.317+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ci"/><category scheme="http://www.blogger.com/atom/ns#" term="gitlab-ci"/><category scheme="http://www.blogger.com/atom/ns#" term="nodejs"/><category scheme="http://www.blogger.com/atom/ns#" term="openapi"/><category scheme="http://www.blogger.com/atom/ns#" term="swagger"/><title type='text'>Validate an openapi or swagger API definition from a Gitlab-CI test step</title><content type='html'>&lt;p&gt;Lets say you&#39;ve built &lt;code&gt;$BUILD_IMAGE&lt;/code&gt; container image at the build step. I did it on a NodeJS based project but it will work with other technology as well.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;check-openapi-contract:
  stage: test
  retry: 1
  timeout: 15m
  script:
    - docker run --name=my-container -d -i -p 8080:8080 --rm $BUILD_IMAGE npm start
    - bash -c &#39;while [[ &quot;$(curl -s -o /dev/null -w &#39;&#39;%{http_code}&#39;&#39; localhost:8080/swagger.json)&quot; != &quot;200&quot; ]]; do sleep 5; done&#39;
    - docker exec -i my-container curl http://localhost:8080/swagger.json -o ./swagger.json
    - docker exec -i my-container npx swagger-cli validate ./swagger.json
&lt;/code&gt;&lt;/pre&gt;

&lt;img src=&quot;https://live.staticflickr.com/65535/49589561046_65f0782d2e_h_d.jpg&quot; /&gt;

&lt;p&gt;So what do we do? We start the server, then retrieve the &lt;code&gt;swagger.json&lt;/code&gt; or &lt;code&gt;openapi.json&lt;/code&gt; and leverage &lt;code&gt;swagger-cli validate&lt;/code&gt; command to ensure our definition is valid and be notified if it is not. Nothing. More.&lt;/p&gt;
</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/8392711228591327795/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/8392711228591327795?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/8392711228591327795'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/8392711228591327795'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2020/02/validate-openapi-or-swagger-api.html' title='Validate an openapi or swagger API definition from a Gitlab-CI test step'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8128983.post-8416829629441021303</id><published>2019-11-14T14:13:00.002+01:00</published><updated>2019-11-14T14:48:05.276+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="google cloud run"/><category scheme="http://www.blogger.com/atom/ns#" term="knative"/><category scheme="http://www.blogger.com/atom/ns#" term="kubernetes"/><title type='text'>Google Cloud Run - how to fix &quot;ERROR: (gcloud.beta.run.deploy) Resource readiness deadline exceeded.&quot;</title><content type='html'>&lt;p&gt;As a full-stack developer you might want to deploy your containers on &lt;a href=&quot;https://cloud.google.com/run/&quot; target=&quot;_blank&quot;&gt;Google Cloud Run&lt;/a&gt; instead of managing a full-blown Kubernetes cluster with &lt;a href=&quot;https://github.com/knative&quot; target=&quot;_blank&quot;&gt;knative&lt;/a&gt; on top of it. However please note that Google Cloud Run is currently still in beta so your experience may vary.&lt;/p&gt;

&lt;p&gt;Yesterday I saw increased build failure related to a soon-to-be-merge branch from a co-worker, the CI &lt;code&gt;gcloud run deploy&lt;/code&gt; command yields:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;
ERROR: (gcloud.beta.run.deploy) Resource readiness deadline exceeded.
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Unlike in a traditional Kubernetes cluster, Readiness probes &lt;a href=&quot;https://cloud.google.com/run/docs/reference/rest/v1/RevisionSpec#Container&quot; target=&quot;_blank&quot;&gt;are not currently configurable in Cloud Run full managed mode&lt;/a&gt; and looking inside Cloud Run logs everything seemed to be fine, the app was listening to &lt;code&gt;8080&lt;/code&gt; after some time.&lt;/p&gt;

&lt;p&gt;After a deep dive into the code changes I found that some new code was delaying for up to 12 seconds the app socket listening step. Worst, those 12 seconds delay was a measure I took on my MacBook and this new code was highly Disk I/O bound so it explained why Cloud Run readiness probe timed out.&lt;/p&gt;

&lt;p&gt;The fix was then to remove this delay and load what was needed from the filesystem on-demand (instead of loading everything — with a lot of never-used stuff — somewhere in the app life-cycle). I hope this post will help someone because I&#39;ve found nothing on the web on that matter :) !&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.fgribreau.com/feeds/8416829629441021303/comments/default' title='Publier les commentaires'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/8128983/8416829629441021303?isPopup=true' title='0 commentaires'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/8416829629441021303'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8128983/posts/default/8416829629441021303'/><link rel='alternate' type='text/html' href='http://blog.fgribreau.com/2019/11/google-cloud-run-how-to-fix-error.html' title='Google Cloud Run - how to fix &quot;ERROR: (gcloud.beta.run.deploy) Resource readiness deadline exceeded.&quot;'/><author><name>Unknown</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>