<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jean-Yves Pellé</title>
    <description>The latest articles on DEV Community by Jean-Yves Pellé (@jypelle).</description>
    <link>https://dev.to/jypelle</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1051473%2F007062f9-2b85-4ac6-bf2e-54b517fe4d04.png</url>
      <title>DEV Community: Jean-Yves Pellé</title>
      <link>https://dev.to/jypelle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jypelle"/>
    <language>en</language>
    <item>
      <title>Update thousands of servers</title>
      <dc:creator>Jean-Yves Pellé</dc:creator>
      <pubDate>Tue, 03 Feb 2026 12:58:53 +0000</pubDate>
      <link>https://dev.to/jypelle/update-thousands-of-servers-33i7</link>
      <guid>https://dev.to/jypelle/update-thousands-of-servers-33i7</guid>
      <description>&lt;p&gt;Keeping a fleet of servers up to date is one of those tasks that sounds simple until you have a thousand of them. Updating one machine is trivial; updating a thousand, concurrently, with proper error handling, is an infrastructure challenge, whether you're managing Linux, macOS, or Windows hosts. In this tutorial, we'll set up CTFreak to do exactly that: deploy and execute an update script across your entire fleet on a recurring schedule, with a clear view of what succeeded and what didn't. We'll illustrate with Debian-based Linux servers, but the same approach applies to any OS with minimal adaptation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we'll build
&lt;/h2&gt;

&lt;p&gt;By the end of this tutorial, you'll have a fully automated update pipeline that handles your entire server fleet. Here's the plan:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Writing a non-interactive update script for Debian-based servers (Debian, Ubuntu, or similar)&lt;/li&gt;
&lt;li&gt;Adding an SSH credential to CTFreak&lt;/li&gt;
&lt;li&gt;Importing all your nodes from a single YAML file&lt;/li&gt;
&lt;li&gt;Creating a project and a scheduled task that targets nodes by tag&lt;/li&gt;
&lt;li&gt;Running the task and re-executing on failed nodes&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we start, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A fleet of Linux servers&lt;/strong&gt; to update (we'll use 1,000 in this example). Each server has a hostname following the pattern &lt;code&gt;serverXXXXX.local&lt;/code&gt; (from &lt;code&gt;server00001&lt;/code&gt; to &lt;code&gt;server01000&lt;/code&gt;), runs a Debian-based distribution, and is accessible via SSH on port 22 with a shared private key and an &lt;code&gt;adminuser&lt;/code&gt; account that has passwordless &lt;code&gt;sudo&lt;/code&gt; privileges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A CTFreak instance&lt;/strong&gt; with an administrator account. CTFreak has no external dependencies, so &lt;a href="https://ctfreak.com/docs/install" rel="noopener noreferrer"&gt;installation&lt;/a&gt; only takes a moment. The Free Edition is sufficient for everything in this tutorial.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Writing the update script
&lt;/h2&gt;

&lt;p&gt;The bash script we are going to run on each server is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DEBIAN_FRONTEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;noninteractive

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get upgrade &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; Dpkg::Options::&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--force-confdef"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-o&lt;/span&gt; Dpkg::Options::&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--force-confold"&lt;/span&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get autoremove &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get autoclean &lt;span class="nt"&gt;-q&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/run/reboot-required &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CTP_AUTO_REBOOT&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"THE SERVER IS GOING TO REBOOT"&lt;/span&gt;
        &lt;span class="nb"&gt;nohup &lt;/span&gt;sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'sleep 5 &amp;amp;&amp;amp; reboot'&lt;/span&gt; &amp;amp;&amp;gt;/dev/null &amp;amp;
        &lt;span class="nb"&gt;disown
    &lt;/span&gt;&lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"REBOOT REQUIRED"&lt;/span&gt;
    &lt;span class="k"&gt;fi
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DEBIAN_FRONTEND=noninteractive&lt;/code&gt; prevents &lt;code&gt;apt-get&lt;/code&gt; from opening interactive prompts that would block an unattended session.&lt;/li&gt;
&lt;li&gt;The two &lt;code&gt;Dpkg::Options&lt;/code&gt; flags handle configuration file conflicts automatically: keep the currently installed version (&lt;code&gt;--force-confold&lt;/code&gt;) unless the package ships a new file that didn't previously exist (&lt;code&gt;--force-confdef&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Finally, the script checks for &lt;code&gt;/var/run/reboot-required&lt;/code&gt; to detect whether a reboot is needed after the upgrade. If the &lt;code&gt;CTP_AUTO_REBOOT&lt;/code&gt; environment variable is set to &lt;code&gt;true&lt;/code&gt;, the server reboots itself automatically; otherwise it simply logs &lt;code&gt;REBOOT REQUIRED&lt;/code&gt; so you can schedule reboots at your convenience. We will define the content of &lt;code&gt;CTP_AUTO_REBOOT&lt;/code&gt; later via a dedicated &lt;em&gt;task parameter&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your fleet includes servers running RPM-based distributions (Rocky Linux, AlmaLinux, RHEL, or similar), replace the &lt;code&gt;apt-get&lt;/code&gt; commands with their &lt;code&gt;dnf&lt;/code&gt; equivalents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Adding the SSH credential
&lt;/h2&gt;

&lt;p&gt;CTFreak needs a credential to authenticate against your servers over SSH.&lt;/p&gt;

&lt;p&gt;Navigate to &lt;strong&gt;Credentials&lt;/strong&gt; and click the &lt;strong&gt;New Credential&lt;/strong&gt; button. Paste your private key, give it a recognizable name like &lt;em&gt;MySSHKey&lt;/em&gt;, and save.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6kabdlueq6nluusye882.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6kabdlueq6nluusye882.png" alt="SSH credential" width="800" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can also use password-based authentication instead of a private key. Both options are supported when creating a credential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Importing nodes from a YAML file
&lt;/h2&gt;

&lt;p&gt;In CTFreak, a &lt;em&gt;node&lt;/em&gt; represents a remote server that tasks can target. Nodes are organized into &lt;em&gt;node sources&lt;/em&gt;. You could add nodes one by one through the web interface, but with 1,000 servers the practical approach is to create an &lt;em&gt;external node source&lt;/em&gt; backed by a YAML file.&lt;/p&gt;

&lt;p&gt;Create the file &lt;code&gt;/home/adminuser/ctfreak-nodes.yaml&lt;/code&gt; (it must be readable by the OS user running the CTFreak process):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;server0001&lt;/span&gt;
  &lt;span class="na"&gt;tagNames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deb_server&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;linux_server&lt;/span&gt;
  &lt;span class="na"&gt;osFamily&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UNIX&lt;/span&gt;
  &lt;span class="na"&gt;connectionProtocolType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SSH&lt;/span&gt;
  &lt;span class="na"&gt;sshConnectionProtocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;adminuser&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;server0001.local&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;server0002&lt;/span&gt;
  &lt;span class="na"&gt;tagNames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deb_server&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;linux_server&lt;/span&gt;
  &lt;span class="na"&gt;osFamily&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UNIX&lt;/span&gt;
  &lt;span class="na"&gt;connectionProtocolType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SSH&lt;/span&gt;
  &lt;span class="na"&gt;sshConnectionProtocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;adminuser&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;server0002.local&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;

&lt;span class="c1"&gt;## ...&lt;/span&gt;
&lt;span class="c1"&gt;## Complete with nodes 3 to 1000&lt;/span&gt;
&lt;span class="c1"&gt;## ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;tagNames&lt;/code&gt; field. Tags let you organize nodes into logical groups and, more importantly, target them from tasks. We'll use the &lt;code&gt;deb_server&lt;/code&gt; tag in the next step to tell CTFreak which nodes should receive the update.&lt;/p&gt;

&lt;p&gt;Navigate to &lt;strong&gt;Nodes&lt;/strong&gt; and click the &lt;strong&gt;New External Node Source&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50ko9ox4jmit15dj84n6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50ko9ox4jmit15dj84n6.png" alt="Node source" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;em&gt;MySSHKey&lt;/em&gt; as the &lt;strong&gt;Credential&lt;/strong&gt; (CTFreak will use it when connecting to these nodes via SSH), then save. The new node source, named &lt;em&gt;Linux Servers&lt;/em&gt; in this example, will automatically resynchronize its nodes from the YAML file every two hours.&lt;/p&gt;

&lt;p&gt;If the file was processed successfully, you should see all your nodes listed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9denw8haxzh0yvx22nzo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9denw8haxzh0yvx22nzo.png" alt="Nodes" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Creating the project and task
&lt;/h2&gt;

&lt;p&gt;Navigate to &lt;strong&gt;Projects&lt;/strong&gt; and click the &lt;strong&gt;New Project&lt;/strong&gt; button. Name your project &lt;em&gt;Sysadmin&lt;/em&gt; (or any name that fits your organization), then save.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7lh113m9mbkj2bizjh9x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7lh113m9mbkj2bizjh9x.png" alt="Project" width="800" height="966"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you are on the project page, click the &lt;strong&gt;New Task&lt;/strong&gt; button, choose &lt;strong&gt;Bash Script&lt;/strong&gt; as the task type, and configure the task with the following settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;: &lt;code&gt;Upgrade debian servers&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parameters&lt;/strong&gt;: add a checkbox parameter named &lt;code&gt;AUTO_REBOOT&lt;/code&gt; (default unchecked)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bash script&lt;/strong&gt;: paste the update script from Step 1&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node Set filter&lt;/strong&gt;: &lt;code&gt;#deb_server&lt;/code&gt; (targets every node carrying this tag)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedule&lt;/strong&gt;: a cron expression such as &lt;code&gt;0 4 1 * *&lt;/code&gt; (4:00 AM on the 1st of every month)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F86yq33pog4uqaqgizfz0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F86yq33pog4uqaqgizfz0.png" alt="Task" width="800" height="1446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save the task.&lt;/p&gt;

&lt;p&gt;Because we used a tag-based filter rather than an explicit list of hostnames, any new node you add to the YAML file with the &lt;code&gt;deb_server&lt;/code&gt; tag will automatically be included in future executions, with no task reconfiguration needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Running the task and handling failures
&lt;/h2&gt;

&lt;p&gt;Rather than waiting for the first of the month, let's trigger a manual run. Click the &lt;strong&gt;Execute&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodkd9bwe8zs41poc9z0f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fodkd9bwe8zs41poc9z0f.png" alt="Launch execution" width="800" height="581"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, you can specify whether or not you want to automatically reboot servers that need it after the update (in the case of a scheduled launch, the default value &lt;code&gt;AUTO_REBOOT&lt;/code&gt; = false will be applied).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6yo33un96qhmor53rnx1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6yo33un96qhmor53rnx1.png" alt="Running execution" width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The execution starts and CTFreak connects to the matching nodes concurrently (sequentially in the Free Edition). You can watch progress on a per-node basis.&lt;/p&gt;

&lt;p&gt;When the execution completes, some nodes will almost certainly have failed. This is expected at scale: an SSH connection may time out, a package mirror might be temporarily unreachable, or a DNS resolution could fail. The important thing is that you have clear visibility into which nodes failed and why.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwpnkzmeg61haqvitu4ue.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwpnkzmeg61haqvitu4ue.png" alt="Failed node executions" width="800" height="593"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check the &lt;strong&gt;Failed&lt;/strong&gt; filter to isolate the problematic nodes and inspect their logs. Once you've addressed the underlying issue (or simply want to retry transient failures), click &lt;strong&gt;Re-execute failed nodes&lt;/strong&gt;. CTFreak will run the same task again, but only against the nodes that failed in the previous execution. This saves time and avoids redundant work on the nodes that already succeeded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going further
&lt;/h2&gt;

&lt;p&gt;Once the basic pipeline is running, there are several ways to extend it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Notifications&lt;/strong&gt;: attach an email or Slack notifier to the task so your team is alerted immediately when an update run finishes with failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mixed-OS workflows&lt;/strong&gt;: add RPM-based Linux nodes (Rocky Linux, AlmaLinux, RHEL) with a &lt;code&gt;rpm_server&lt;/code&gt; tag and Windows nodes with a &lt;code&gt;windows_server&lt;/code&gt; tag. Create a dedicated task for each OS (using Bash for Unix, PowerShell for Windows) and tie them all together in a &lt;strong&gt;workflow&lt;/strong&gt; task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ansible playbooks&lt;/strong&gt;: replace the Bash or PowerShell script with an Ansible playbook to leverage built-in idempotency. Ansible modules like &lt;code&gt;apt&lt;/code&gt; and &lt;code&gt;dnf&lt;/code&gt; handle package state declaratively, so you don't have to manage idempotence yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency tuning&lt;/strong&gt;: adjust the number of workers (aka &lt;em&gt;Max number of concurrent node executions&lt;/em&gt;) in the task configuration to control how many nodes are updated in parallel. This is useful if your network or package mirrors can't handle a thousand simultaneous connections.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;With a single YAML file, one SSH credential, and a scheduled task, CTFreak gives you a repeatable, observable process for keeping your entire server fleet up to date. Failed nodes are clearly surfaced and can be retried with one click, which turns a potentially chaotic maintenance window into a routine operation.&lt;/p&gt;

&lt;p&gt;The same pattern applies well beyond OS updates: configuration changes, log collection, security audits, or any script you need to run across many machines. Define the nodes, write the script, schedule the task, and let CTFreak handle the concurrency and error tracking.&lt;/p&gt;

&lt;p&gt;Happy automating!&lt;/p&gt;

</description>
      <category>scheduling</category>
      <category>sysadmin</category>
      <category>devops</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>Run a shell script from a webhook call</title>
      <dc:creator>Jean-Yves Pellé</dc:creator>
      <pubDate>Wed, 12 Nov 2025 09:55:33 +0000</pubDate>
      <link>https://dev.to/jypelle/run-a-shell-script-from-a-webhook-call-4bc4</link>
      <guid>https://dev.to/jypelle/run-a-shell-script-from-a-webhook-call-4bc4</guid>
      <description>&lt;p&gt;If you've ever found yourself thinking &lt;em&gt;I wish I could just run this script via curl&lt;/em&gt;, then webhooks are about to become your new best friend. In this tutorial, we'll explore how to transform CTFreak into a powerful webhook server that can trigger your automation tasks with nothing more than an HTTP request.&lt;/p&gt;

&lt;p&gt;The beauty of this approach? You can integrate your scripts and playbooks into virtually any workflow, whether it's responding to GitHub events, processing form submissions, or reacting to monitoring alerts. Let's dive in and build something practical.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we'll build
&lt;/h2&gt;

&lt;p&gt;By the end of this tutorial, you'll have a fully functional webhook endpoint that executes a Bash script with parameters passed directly through the URL. We'll then show you how this same pattern works seamlessly with Powershell scripts and Ansible playbooks.&lt;/p&gt;

&lt;p&gt;Here's what we'll cover:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Adding a node to CTFreak with SSH authentication&lt;/li&gt;
&lt;li&gt;Creating a parameterized Bash script task&lt;/li&gt;
&lt;li&gt;Setting up an incoming webhook&lt;/li&gt;
&lt;li&gt;Triggering the script via HTTP with query parameters&lt;/li&gt;
&lt;li&gt;Verifying execution through the logs&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we start, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://ctfreak.com" rel="noopener noreferrer"&gt;CTFreak&lt;/a&gt; instance up and running&lt;/li&gt;
&lt;li&gt;A Unix server accessible via SSH where your scripts will run&lt;/li&gt;
&lt;li&gt;An SSH key for connecting to the server &lt;/li&gt;
&lt;li&gt;Admin access to your CTFreak instance&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Adding your node
&lt;/h2&gt;

&lt;p&gt;First things first, we need to tell CTFreak about the server where your scripts will execute.&lt;/p&gt;

&lt;p&gt;Navigate to &lt;strong&gt;Credentials → New Credential&lt;/strong&gt; and add your SSH key. Make sure to paste your private key in the appropriate field and give it a memorable name, something like &lt;em&gt;Production server key&lt;/em&gt; rather than &lt;em&gt;That key I found in my downloads folder&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft2sp3gf8t1k3t027bf8s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft2sp3gf8t1k3t027bf8s.png" alt="SSH Credential creation form" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once your credential is saved, head over to &lt;strong&gt;Nodes → Internal nodes → New node&lt;/strong&gt; to register your server. Fill in the following details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt;: A descriptive name for your server (e.g., &lt;em&gt;webhook-runner&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OS Family&lt;/strong&gt;: Choose &lt;em&gt;Unix&lt;/em&gt; for Linux servers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Username&lt;/strong&gt;: The SSH user CTFreak will use to connect&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hostname&lt;/strong&gt;: Your server's IP address or hostname&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port&lt;/strong&gt;: SSH port, typically 22&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credential&lt;/strong&gt;: Select the SSH key you just created&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fildvmyu6i6k2ngo20wx5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fildvmyu6i6k2ngo20wx5.png" alt="Node creation form" width="800" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Create&lt;/strong&gt;, then verify that the connection to your node is working by attempting to open the file explorer (via the &lt;strong&gt;Browse&lt;/strong&gt; button). If you see the contents of a directory, congratulations, you're ready to move forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Creating a parameterized Bash script task
&lt;/h2&gt;

&lt;p&gt;Now comes the fun part. We're going to create a task that accepts parameters, which means the same webhook can behave differently based on the values you pass to it. This is where the real magic happens.&lt;/p&gt;

&lt;p&gt;Navigate to &lt;strong&gt;Projects → New project&lt;/strong&gt; if you don't already have a suitable project, or use an existing one. Let's call our project &lt;em&gt;Webhook Automation&lt;/em&gt; for clarity.&lt;/p&gt;

&lt;p&gt;Inside your project, click &lt;strong&gt;New task&lt;/strong&gt; and select &lt;strong&gt;Bash Script&lt;/strong&gt; as the task type. You'll see a form with several fields to fill out:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name&lt;/strong&gt;: Give your task a descriptive name like &lt;em&gt;Process deployment webhook&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Script&lt;/strong&gt;: This is where you write your Bash script. For this example, we'll create a simple script that uses two parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Webhook triggered with parameters:"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Environment: &lt;/span&gt;&lt;span class="nv"&gt;$CTP_ENVIRONMENT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Version: &lt;/span&gt;&lt;span class="nv"&gt;$CTP_VERSION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Your actual deployment logic would go here&lt;/span&gt;
&lt;span class="c"&gt;# For example:&lt;/span&gt;
&lt;span class="c"&gt;# cd /var/www/app&lt;/span&gt;
&lt;span class="c"&gt;# git pull origin $CTP_VERSION&lt;/span&gt;
&lt;span class="c"&gt;# systemctl restart myapp-$CTP_ENVIRONMENT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how we're using environment variables prefixed with &lt;code&gt;CTP_&lt;/code&gt;. This is CTFreak's convention for injecting parameters into your scripts, and it works beautifully once you get used to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node Set filter&lt;/strong&gt;: Enter the node name we created earlier.&lt;/p&gt;

&lt;p&gt;Now, let's add our parameters. Click the &lt;strong&gt;Add parameter&lt;/strong&gt; button twice to create two parameters:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First parameter&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: &lt;code&gt;ENVIRONMENT&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Parameter type: Selector&lt;/li&gt;
&lt;li&gt;Options: Add values like "staging", "production"&lt;/li&gt;
&lt;li&gt;Default value: "staging"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Second parameter&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name: &lt;code&gt;VERSION&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Parameter type: Text&lt;/li&gt;
&lt;li&gt;Default value: "main"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The beauty of this setup is that you can make the parameters as restrictive or flexible as you need. Selector types limit choices to prevent mishaps (nobody wants to accidentally deploy to production when they meant staging), while text types give you complete freedom.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjrlnv4qmhlchq0a3h70r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjrlnv4qmhlchq0a3h70r.png" alt="Task creation form" width="800" height="1906"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save your task, and let's move on to exposing it to the world via a webhook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Creating the incoming webhook
&lt;/h2&gt;

&lt;p&gt;This is where CTFreak transforms from a task automation tool into a full-fledged webhook server. From the task we just created, head to &lt;strong&gt;Incoming webhooks → New webhook → Generic&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You'll need to configure a few things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name&lt;/strong&gt;: Something descriptive like "Deployment trigger"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enabled&lt;/strong&gt;: Make sure this toggle is on, or you'll be wondering why nothing happens when you call it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcgr2zwkttvtgqjb36i7x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcgr2zwkttvtgqjb36i7x.png" alt="Incoming webhook form" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save the webhook, and CTFreak will display your webhook URL along with the authentication details. It'll look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://your-ctfreak-instance/wh/VEihowf6DHiDj1C2iZtXjTI7alFxFXzVoSareJeD7EskYzx2t1InyBEB6Wuq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy this URL, you're about to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Triggering the webhook
&lt;/h2&gt;

&lt;p&gt;Now for the moment of truth. You can trigger your webhook from anywhere that can make an HTTP request: a CI/CD pipeline, a monitoring tool, another script, or just your terminal for testing.&lt;/p&gt;

&lt;p&gt;Here's how to call it with curl, passing our two parameters as query strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://your-ctfreak-instance/wh/VEihowf6DHiDj1C2iZtXjTI7alFxFXzVoSareJeD7EskYzx2t1InyBEB6Wuq?ENVIRONMENT=production&amp;amp;VERSION=v2.1.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how we're passing &lt;code&gt;ENVIRONMENT&lt;/code&gt; and &lt;code&gt;VERSION&lt;/code&gt; as query parameters? CTFreak automatically maps these to the corresponding task parameters and injects them as environment variables (prefixed with &lt;code&gt;CTP-&lt;/code&gt;) into your script.&lt;/p&gt;

&lt;p&gt;You can also pass parameters as URL-encoded form submission if you prefer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://your-ctfreak-instance/wh/VEihowf6DHiDj1C2iZtXjTI7alFxFXzVoSareJeD7EskYzx2t1InyBEB6Wuq"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"ENVIRONMENT=production"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"VERSION=v2.1.0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both methods work equally well, so use whichever fits your workflow better.&lt;/p&gt;

&lt;p&gt;The webhook will return a response immediately, including an execution ID that you can use to check the status and logs later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"executionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"01K9STQAFNQ8MKB47F4DZ32RH7"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"I got the payload!"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is perfect for asynchronous workflows where you fire off a task and check back later to see how it went.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Verifying the execution
&lt;/h2&gt;

&lt;p&gt;Let's make sure everything worked as expected. Navigate to &lt;strong&gt;Projects → Webhook Automation → Process deployment webhook&lt;/strong&gt;. You should see your webhook-triggered execution at the top of the execution list.&lt;/p&gt;

&lt;p&gt;Click on the execution ID, and on the eye icon to view the execution logs. You'll see the output from your script, including the parameter values that were passed in:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7hgl6yxuoq6124ljj3v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7hgl6yxuoq6124ljj3v.png" alt="Execution logs" width="500" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Beautiful. Your parameters made it through the HTTP request, into CTFreak, and into your script exactly as intended. This is the foundation you can build upon for much more complex workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Bash: Powershell and Ansible
&lt;/h2&gt;

&lt;p&gt;Here's where things get even better. The webhook pattern we just built works identically with other task types. Let me show you how.&lt;/p&gt;

&lt;h3&gt;
  
  
  Powershell scripts
&lt;/h3&gt;

&lt;p&gt;If you're working with Windows servers, simply create a &lt;strong&gt;Powershell Script&lt;/strong&gt; task instead of Bash Script task. When adding your node, set the OS Family to &lt;em&gt;Windows&lt;/em&gt; and adjust your credentials according to the chosen connection protocol (SSH or WinRM).&lt;/p&gt;

&lt;p&gt;Your script parameters work exactly the same way, just with Powershell syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Webhook triggered with parameters:"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Environment: &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;CTP_ENVIRONMENT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Version: &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;CTP_VERSION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Your Powershell deployment logic here&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The webhook setup? Identical. The curl command? Identical. The only thing that changes is the language of your script itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ansible playbooks
&lt;/h3&gt;

&lt;p&gt;Running Ansible playbooks via webhooks is just as straightforward. Configure a task of type &lt;strong&gt;Ansible Playbook&lt;/strong&gt; and add your parameters.&lt;/p&gt;

&lt;p&gt;CTFreak will inject the parameters as extra variables, which you can access in your playbook like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Display received parameters&lt;/span&gt;
      &lt;span class="na"&gt;debug&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Deploying&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CTP_VERSION&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;CTP_ENVIRONMENT&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Your deployment workflow here&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same webhook URL, same query parameters. The power of this approach is that once you understand the pattern, you can apply it to any task type CTFreak supports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world use cases
&lt;/h2&gt;

&lt;p&gt;Now that you've got the basics down, let's talk about what you can actually do with this. Here are a few ideas to spark your imagination:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CI/CD integration&lt;/strong&gt;: Trigger deployments from GitHub Actions, GitLab CI, or Jenkins after successful builds. Pass the branch name, commit hash, and environment as parameters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure automation&lt;/strong&gt;: Respond to monitoring alerts by automatically scaling resources, restarting services, or running diagnostic scripts. Your monitoring tool hits a webhook, CTFreak handles the remediation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatOps&lt;/strong&gt;: Integrate with Slack or Microsoft Teams to let your team trigger common operations through chat commands. Someone types "/deploy staging v2.0" and behind the scenes, it's just a webhook call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IoT and edge computing&lt;/strong&gt;: Have your devices report status by hitting a webhook that triggers data processing scripts or updates dashboards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Business process automation&lt;/strong&gt;: Connect forms, CRM systems, or e-commerce platforms to trigger backend processes when specific events occur.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;You've now got a powerful webhook server that can execute arbitrary scripts with parameters, and you didn't have to write a single line of server code to make it happen. CTFreak handles the infrastructure, logging, and parameter injection, so you can focus on writing the scripts that actually do useful work.&lt;/p&gt;

&lt;p&gt;The pattern we've explored here is deceptively simple, but it opens up endless possibilities for automation and integration. Whether you're orchestrating deployments, automating infrastructure tasks, or building custom integrations, webhooks give you a clean, HTTP-based interface to trigger your operations from anywhere.&lt;/p&gt;

&lt;p&gt;Happy automating!&lt;/p&gt;

</description>
      <category>webhook</category>
      <category>bash</category>
      <category>powershell</category>
      <category>ansible</category>
    </item>
    <item>
      <title>Deploy a lightweight BI solution with your first dashboard in 5 steps</title>
      <dc:creator>Jean-Yves Pellé</dc:creator>
      <pubDate>Sun, 01 Sep 2024 09:19:08 +0000</pubDate>
      <link>https://dev.to/jypelle/deploy-a-lightweight-bi-solution-with-your-first-dashboard-in-5-steps-5gi9</link>
      <guid>https://dev.to/jypelle/deploy-a-lightweight-bi-solution-with-your-first-dashboard-in-5-steps-5gi9</guid>
      <description>&lt;p&gt;This article is aimed at those who know SQL and want to quickly set up a minimalist reporting solution (without going through the heavy artillery of PowerBI, Tableau, Looker Studio, etc.).&lt;/p&gt;

&lt;p&gt;Let's start with a database (of the Mysql, Mariadb or Postgresql type) including a &lt;code&gt;monthly_sales&lt;/code&gt; table containing monthly sales amounts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;month&lt;/th&gt;
&lt;th&gt;sales_amount_eur&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2024-01-01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;8730&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2024-02-01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;9620&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2024-03-01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4210&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2024-04-01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;6732&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2024-05-01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;9921&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2024-06-01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;8176&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2024-07-01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;7623&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Our aim is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate a report showing this information in a graph.&lt;/li&gt;
&lt;li&gt;Refresh it every 1st of the month.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1 - Installation
&lt;/h2&gt;

&lt;p&gt;Let's start by installing &lt;a href="https://ctfreak.com?ref=dev.to"&gt;CTFreak&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Under Ubuntu, open your terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;install &lt;/span&gt;ctfreak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;For alternative installations (Docker, Windows, Freebsd, ...), see &lt;a href="https://ctfreak.com/docs/install" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2 - Login
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="http://localhost:6700" rel="noopener noreferrer"&gt;http://localhost:6700&lt;/a&gt; and log in via &lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;ctfreak&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4r192lfrv8roj3seq47.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4r192lfrv8roj3seq47.png" alt="Login" width="420" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3 - Adding a database
&lt;/h2&gt;

&lt;p&gt;Go to &lt;em&gt;Databases&lt;/em&gt; → &lt;em&gt;New Database&lt;/em&gt;, select its type (e.g. &lt;em&gt;Postgresql&lt;/em&gt;), then fill in its connection parameters:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Furikwagwqyetxujsufdr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Furikwagwqyetxujsufdr.png" alt="Add database" width="420" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validate to add the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  4 - Creating a project
&lt;/h2&gt;

&lt;p&gt;A &lt;em&gt;project&lt;/em&gt; will group together all our reporting tasks.&lt;/p&gt;

&lt;p&gt;Go to &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;New Project&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F72s4j6ol9cuq62rka6qo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F72s4j6ol9cuq62rka6qo.png" alt="Create project" width="420" height="629"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validate to create the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  5 - Creating a &lt;em&gt;SQL Report&lt;/em&gt; task
&lt;/h2&gt;

&lt;p&gt;Our report will be generated via a &lt;em&gt;SQL Report&lt;/em&gt; task associated with our project.&lt;/p&gt;

&lt;p&gt;To do this, go to &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;Reporting&lt;/em&gt; → &lt;em&gt;New Task&lt;/em&gt;, select &lt;em&gt;SQL Report&lt;/em&gt; as the task type and then fill in the following information:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrc5621xaf3eaxk6a4yf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsrc5621xaf3eaxk6a4yf.png" alt="Create SQL Report task" width="420" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the SQL query, the suffix &lt;code&gt;_c_month&lt;/code&gt; allows you to &lt;a href="https://ctfreak.com/docs/tasks/sql-report#column-format" rel="noopener noreferrer"&gt;indicate that date values should be formatted as &lt;em&gt;months&lt;/em&gt;&lt;/a&gt; in the graph.&lt;/p&gt;

&lt;p&gt;Validate this form to create a task which, based on an SQL query, will generate a monthly sales report every 1st of the month at 9 a.m., with a 2 year (730 days) retention period for the reports generated.&lt;/p&gt;

&lt;p&gt;Let's execute this task right now (via &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;Reporting&lt;/em&gt; → &lt;em&gt;Sales report&lt;/em&gt; -&amp;gt; &lt;em&gt;Execute&lt;/em&gt;) to get the expected report:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fripuf3qmme0e0hse8vyu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fripuf3qmme0e0hse8vyu.png" alt="Execute task" width="420" height="780"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As you can see, this report meets our initial requirements.&lt;/p&gt;

&lt;p&gt;Feel free to use CTFreak's other features to make improvements, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add more charts.&lt;/li&gt;
&lt;li&gt;Add &lt;em&gt;task parameters&lt;/em&gt; to define the sales period to be taken into account.&lt;/li&gt;
&lt;li&gt;Add read-only access for a given user (via the &lt;em&gt;Viewer&lt;/em&gt; role).&lt;/li&gt;
&lt;li&gt;Send an e-mail each time a report is generated, with a link to open it (via a &lt;em&gt;Notifier&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Synchronize the generation of multiple reports (via &lt;em&gt;Workflow&lt;/em&gt; tasks).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dashboard</category>
      <category>reporting</category>
      <category>sql</category>
      <category>businessintelligence</category>
    </item>
    <item>
      <title>Déployer une solution de BI légère avec son premier dashboard en 5 étapes</title>
      <dc:creator>Jean-Yves Pellé</dc:creator>
      <pubDate>Sat, 31 Aug 2024 14:04:37 +0000</pubDate>
      <link>https://dev.to/jypelle/deployer-une-solution-de-bi-legere-avec-son-premier-dashboard-en-5-etapes-2k10</link>
      <guid>https://dev.to/jypelle/deployer-une-solution-de-bi-legere-avec-son-premier-dashboard-en-5-etapes-2k10</guid>
      <description>&lt;p&gt;Cet article s'adresse à ceux qui connaissent le SQL et souhaitent mettre en place rapidement une solution de reporting minimaliste (sans passer par l'artillerie lourde comme PowerBI, Tableau, Looker Studio, et consorts).&lt;/p&gt;

&lt;p&gt;Partons d'une base de données (de type Mysql, Mariadb ou Postgresql) comprenant une table &lt;code&gt;monthly_sales&lt;/code&gt; contenant des montants mensuels de vente:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;month&lt;/th&gt;
&lt;th&gt;sales_amount_eur&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2024-01-01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;8730&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2024-02-01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;9620&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2024-03-01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4210&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2024-04-01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;6732&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2024-05-01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;9921&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2024-06-01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;8176&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2024-07-01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;7623&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notre but est de:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Générer un rapport reprenant ces informations dans un graphique.&lt;/li&gt;
&lt;li&gt;Le rafraîchir tous les 1er du mois.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1 - Installation
&lt;/h2&gt;

&lt;p&gt;Commençons par installer &lt;a href="https://ctfreak.com?ref=dev.to"&gt;CTFreak&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Sous Ubuntu, ouvrez votre terminal et lancez:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;install &lt;/span&gt;ctfreak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Pour les installations alternatives (Docker, Windows, Freebsd, ...), ça se passe &lt;a href="https://ctfreak.com/docs/install" rel="noopener noreferrer"&gt;ici&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2 - Connexion
&lt;/h2&gt;

&lt;p&gt;Rendez vous sur &lt;a href="http://localhost:6700" rel="noopener noreferrer"&gt;http://localhost:6700&lt;/a&gt; et connectez-vous via &lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;ctfreak&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tsnihv2u4xv5a70pfbp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2tsnihv2u4xv5a70pfbp.png" alt="Login" width="420" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3 - Ajout d'une base de données
&lt;/h2&gt;

&lt;p&gt;Allez dans &lt;em&gt;Databases&lt;/em&gt; → &lt;em&gt;New Database&lt;/em&gt;, sélectionnez son type (par exemple &lt;em&gt;Postgresql&lt;/em&gt;), puis renseignez ses paramètres de connexion:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5tojz3uadtjox186m2l9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5tojz3uadtjox186m2l9.png" alt="Ajouter une base de données" width="420" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validez pour ajouter la base de données.&lt;/p&gt;

&lt;h2&gt;
  
  
  4 - Création d'un projet
&lt;/h2&gt;

&lt;p&gt;Un &lt;em&gt;projet&lt;/em&gt; va permettre de regrouper toutes nos tâches de reporting.&lt;/p&gt;

&lt;p&gt;Allez dans &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;New Project&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj80388erm8vtwuxfpm9r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj80388erm8vtwuxfpm9r.png" alt="Création d'un projet" width="420" height="629"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validez pour créer le projet.&lt;/p&gt;

&lt;h2&gt;
  
  
  5 - Création d'une tâche &lt;em&gt;SQL Report&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;Notre rapport sera généré via une tâche de type &lt;em&gt;SQL Report&lt;/em&gt; associé à notre projet.&lt;/p&gt;

&lt;p&gt;Pour ce faire, allez dans &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;Reporting&lt;/em&gt; → &lt;em&gt;New Task&lt;/em&gt;, sélectionnez &lt;em&gt;SQL Report&lt;/em&gt; comme type de tâche puis renseignez les informations suivantes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fheci1kzxn4lldpkmhmuk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fheci1kzxn4lldpkmhmuk.png" alt="Création d'une tâche SQL Report" width="420" height="1600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dans la requête SQL, le suffixe &lt;code&gt;_c_month&lt;/code&gt; permet d'&lt;a href="https://ctfreak.com/docs/tasks/sql-report#column-format" rel="noopener noreferrer"&gt;indiquer que les valeurs de date doivent être formatées comme des &lt;em&gt;mois&lt;/em&gt;&lt;/a&gt; dans le graphique.&lt;/p&gt;

&lt;p&gt;Validez ce formulaire pour créer une tâche qui à partir d'une requête SQL générera un rapport des ventes mensuelles tous les 1er du mois à 9h, avec une rétention de 2 ans (730 jours) des rapports générés.&lt;/p&gt;

&lt;p&gt;Exécutons cette tâche dès maintenant (via &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;Reporting&lt;/em&gt; → &lt;em&gt;Sales report&lt;/em&gt; -&amp;gt; &lt;em&gt;Execute&lt;/em&gt;) pour obtenir le rapport attendu:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5v4hkukskt4q7w78oncf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5v4hkukskt4q7w78oncf.png" alt="Exécution de la tâche" width="420" height="780"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Comme vous pouvez le constater, ce rapport répond bien à notre besoin initial.&lt;/p&gt;

&lt;p&gt;N'hésitez pas à exploiter les autres fonctionnalités de CTFreak pour y apporter quelques améliorations comme:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ajouter d'autres graphiques.&lt;/li&gt;
&lt;li&gt;Ajouter des paramètres de &lt;em&gt;tâche&lt;/em&gt; pour définir la période des ventes à prendre en compte.&lt;/li&gt;
&lt;li&gt;Ajouter un accès en lecture seule pour un utilisateur donné (via le rôle &lt;em&gt;Viewer&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Envoyer à chaque génération du rapport un mail avec un lien pour l'ouvrir (via un &lt;em&gt;Notifier&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Synchroniser la génération de plusieurs rapports (via les tâches de type &lt;em&gt;Workflow&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>businessintelligence</category>
      <category>sql</category>
      <category>reporting</category>
      <category>dashboard</category>
    </item>
    <item>
      <title>A web form to launch your bash and powershell scripts</title>
      <dc:creator>Jean-Yves Pellé</dc:creator>
      <pubDate>Mon, 18 Mar 2024 11:23:41 +0000</pubDate>
      <link>https://dev.to/jypelle/a-web-form-to-launch-your-bash-and-powershell-scripts-28k6</link>
      <guid>https://dev.to/jypelle/a-web-form-to-launch-your-bash-and-powershell-scripts-28k6</guid>
      <description>&lt;p&gt;What's the point of using a web interface to run scripts when you're a terminal master?&lt;/p&gt;

&lt;p&gt;Well, among other things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;To delegate the execution of certain scripts to business users securely, by limiting the available options to prevent any unpleasant surprises and keeping a record of executions:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk62h3wrdzbfcnoky5ww9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk62h3wrdzbfcnoky5ww9.png" alt="Generate and send sales report" width="420" height="386"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Exemple: Generate a report for a given date range and send it by email&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Or to easily execute predefined tasks in case of emergency (a terminal is not always at hand, unlike a smartphone):&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffm6xu461elmm94csns35.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffm6xu461elmm94csns35.png" alt="Service status" width="420" height="224"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Exemple: Check the status or restart/stop a Linux service&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I propose addressing these two use cases using the newly added &lt;em&gt;parameterizable tasks&lt;/em&gt; in the &lt;a href="https://ctfreak.com/docs/changelog#v1-14-0" rel="noopener noreferrer"&gt;latest release of Ctfreak&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;We need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A Unix server: Linux, Mac OS X, FreeBSD, ... (see end of article to adapt to a Windows server) accessible via SSH where your scripts will be executed. We'll call it &lt;em&gt;scriptsrv&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An instance of &lt;a href="https://ctfreak.com" rel="noopener noreferrer"&gt;Ctfreak&lt;/a&gt; with an admin account (the &lt;em&gt;Free Edition&lt;/em&gt; will suffice if you adapt the examples in this article by limiting to one parameter per task).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration of the &lt;em&gt;scriptsrv&lt;/em&gt; server
&lt;/h2&gt;

&lt;p&gt;Connect to your Ctfreak instance with an admin account.&lt;/p&gt;

&lt;p&gt;Start by adding the SSH key to connect to the server via &lt;em&gt;SSH Credential&lt;/em&gt; → &lt;em&gt;New SSH Credential&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgr2muvwlc1mv2pu6d1pe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgr2muvwlc1mv2pu6d1pe.png" alt="Adding SSH key" width="420" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then add the server itself via &lt;em&gt;Nodes&lt;/em&gt; → &lt;em&gt;Internal Nodes&lt;/em&gt; → &lt;em&gt;New node&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2fx4p90do7l38k2niwk6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2fx4p90do7l38k2niwk6.png" alt="Adding the server" width="420" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure that the &lt;em&gt;SSH Key for scriptsrv&lt;/em&gt; is selected as the &lt;em&gt;Credential&lt;/em&gt;, then validate to create the &lt;em&gt;scriptsrv&lt;/em&gt; node.&lt;/p&gt;

&lt;p&gt;Ctfreak can now connect to the server with the specified SSH key to execute various tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  1st case: Generate a report for a given date range and send it by email
&lt;/h2&gt;

&lt;p&gt;For our example, let's assume that you have an executable &lt;code&gt;generatereport&lt;/code&gt; on the &lt;em&gt;scriptsrv&lt;/em&gt; server, allowing you to generate and send a sales report by email for a given &lt;em&gt;period&lt;/em&gt;, &lt;em&gt;recipient&lt;/em&gt;, and &lt;em&gt;format&lt;/em&gt;. If we were to run this executable directly in a terminal, it might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;generatereport &lt;span class="nt"&gt;--from&lt;/span&gt; 2024-03-04 &lt;span class="nt"&gt;--to&lt;/span&gt; 2024-03-10 &lt;span class="nt"&gt;--recipient&lt;/span&gt; nigel@mycompany.zzz &lt;span class="nt"&gt;--format&lt;/span&gt; pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, we'll create a task in Ctfreak that, when executed, will dynamically generate a web form to enter these 4 parameters before launching the &lt;code&gt;generatereport&lt;/code&gt; executable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the &lt;em&gt;Reporting&lt;/em&gt; project
&lt;/h3&gt;

&lt;p&gt;Let's start by creating a project dedicated to reporting tasks.&lt;/p&gt;

&lt;p&gt;Go to &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;New Project&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fll6pwszysmp5gtk2jv1z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fll6pwszysmp5gtk2jv1z.png" alt="Creating the project" width="420" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validate to create the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the task &lt;em&gt;Generate and send sales report&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Go to &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;Reporting&lt;/em&gt; → &lt;em&gt;New task&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbw7prwe7x2oej803pio4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbw7prwe7x2oej803pio4.png" alt="Selecting task type" width="420" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose &lt;em&gt;Bash Script&lt;/em&gt; as the task type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqd8qhkibzq2clymteiem.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqd8qhkibzq2clymteiem.png" alt="New bash script" width="420" height="1120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill in the &lt;em&gt;Name&lt;/em&gt;, &lt;em&gt;Script&lt;/em&gt;, and &lt;em&gt;Node filter&lt;/em&gt; fields as indicated.&lt;/p&gt;

&lt;p&gt;In the content of our script, the 4 expected values are provided through environment variables, all prefixed by &lt;code&gt;CTP_&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
generatereport &lt;span class="nt"&gt;--from&lt;/span&gt; &lt;span class="nv"&gt;$CTP_FROM&lt;/span&gt; &lt;span class="nt"&gt;--to&lt;/span&gt; &lt;span class="nv"&gt;$CTP_TO&lt;/span&gt; &lt;span class="nt"&gt;--recipient&lt;/span&gt; &lt;span class="nv"&gt;$CTP_RECIPIENT&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="nv"&gt;$CTP_FORMAT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These variables correspond to the parameters that we will create by clicking the &lt;em&gt;Add parameter&lt;/em&gt; button.&lt;/p&gt;

&lt;p&gt;Let's start with the start and end dates of our period:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ppzi5iipgcfgzx8xqq1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ppzi5iipgcfgzx8xqq1.png" alt="Start and end dates" width="354" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The parameter type &lt;em&gt;Date&lt;/em&gt; ensures that a valid date will be entered in our execution form.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;em&gt;Default date type&lt;/em&gt; field allows us to take the previous week as the default period.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;em&gt;Name&lt;/em&gt; field will be used to define the name of the environment variable (&lt;code&gt;CTP_FROM&lt;/code&gt; in our script thus refers to the parameter named &lt;code&gt;FROM&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When the &lt;em&gt;Label&lt;/em&gt; field is filled, it replaces the &lt;em&gt;Name&lt;/em&gt; field to reference the variable during the execution form generation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;During task execution, the entered date will be converted to the &lt;code&gt;YYYY-MM-DD&lt;/code&gt; format to populate the corresponding environment variable.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regarding the emails of authorized recipients to receive the report, we will limit the possible choices to 2 emails via the &lt;em&gt;Selector&lt;/em&gt; parameter type:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9i55r4j3654e5xkptprm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9i55r4j3654e5xkptprm.png" alt="Recipient" width="354" height="762"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each option, when the &lt;em&gt;Label&lt;/em&gt; field is filled, it substitutes &lt;em&gt;Value&lt;/em&gt; during the execution form generation. We could have used it here to display &lt;code&gt;Nigel&lt;/code&gt; and &lt;code&gt;John&lt;/code&gt; in the form instead of their respective emails.&lt;/p&gt;

&lt;p&gt;Finally, another &lt;em&gt;Selector&lt;/em&gt; parameter type will allow choosing the format of the generated file (here PDF or Excel, but it's &lt;code&gt;pdf&lt;/code&gt; or &lt;code&gt;xlsx&lt;/code&gt; that will be passed to our script):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8bax6cglxr21f98h9lu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft8bax6cglxr21f98h9lu.png" alt="Format" width="354" height="762"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With all the necessary information filled in, all that's left is to validate to create the task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Executing the task
&lt;/h3&gt;

&lt;p&gt;Go to &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;Reporting&lt;/em&gt; → &lt;em&gt;Generate and send sales report&lt;/em&gt; → &lt;em&gt;Execute&lt;/em&gt; to bring up the execution form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk62h3wrdzbfcnoky5ww9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk62h3wrdzbfcnoky5ww9.png" alt="Generate and send a sales report" width="420" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, our 4 parameters are well taken into account in the form generation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a business user
&lt;/h3&gt;

&lt;p&gt;With our task operational, the next step is to hand it over to a business user for execution.&lt;/p&gt;

&lt;p&gt;Go to &lt;em&gt;Users&lt;/em&gt; → &lt;em&gt;New User&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzs4odugx26j6dod1x4jv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzs4odugx26j6dod1x4jv.png" alt="Creating a user" width="420" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validate to create the user.&lt;/p&gt;

&lt;p&gt;By default, a non-admin user has no access to anything (except for logging in), so you need to give them the necessary rights.&lt;/p&gt;

&lt;p&gt;Go to &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;Reporting&lt;/em&gt; → &lt;em&gt;Access&lt;/em&gt; → &lt;em&gt;Edit&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Add Jack by giving him the &lt;em&gt;Executor&lt;/em&gt; role.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn78fwlzqiyota4u6d41b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn78fwlzqiyota4u6d41b.png" alt="Project access" width="420" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Jack now has access to the &lt;em&gt;Reporting&lt;/em&gt; project, but &lt;strong&gt;only&lt;/strong&gt; to execute the tasks it contains.&lt;/p&gt;

&lt;h2&gt;
  
  
  2nd case: Check the status or restart / stop a Linux service
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Creating the &lt;em&gt;Sysadmin&lt;/em&gt; project
&lt;/h3&gt;

&lt;p&gt;Let's create a project dedicated to system administration tasks.&lt;/p&gt;

&lt;p&gt;Go to &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;New Project&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnenhb30v30p85ghg8y2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnenhb30v30p85ghg8y2w.png" alt="Creating the project" width="420" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validate to create the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the task &lt;em&gt;Systemctl&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Go to &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;Sysadmin&lt;/em&gt; → &lt;em&gt;New task&lt;/em&gt; and like our previous case, choose &lt;em&gt;Bash Script&lt;/em&gt; as the task type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0naby9bu1lvqz20wvco.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0naby9bu1lvqz20wvco.png" alt="New bash script" width="420" height="1100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fill in the &lt;em&gt;Name&lt;/em&gt;, &lt;em&gt;Script&lt;/em&gt;, and &lt;em&gt;Node filter&lt;/em&gt; fields as indicated.&lt;/p&gt;

&lt;p&gt;Click the &lt;em&gt;Add parameter&lt;/em&gt; button to add the list of services concerned (here Postgresql and Apache)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ml9midm3zy96pio4p8p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ml9midm3zy96pio4p8p.png" alt="Service" width="356" height="762"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As well as the commands to check the status, restart, or stop the service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fum31d57ze4io7mym9adx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fum31d57ze4io7mym9adx.png" alt="Command" width="356" height="991"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validate to create the task.&lt;/p&gt;

&lt;h3&gt;
  
  
  Executing the task
&lt;/h3&gt;

&lt;p&gt;Go to &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;Sysadmin&lt;/em&gt; → &lt;em&gt;Systemctl&lt;/em&gt; → &lt;em&gt;Execute&lt;/em&gt; to bring up the execution form.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffm6xu461elmm94csns35.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffm6xu461elmm94csns35.png" alt="Service status" width="420" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Execute the task and check the logs by clicking on the eye icon.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Furk44wmzv7ue4v6lvmog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Furk44wmzv7ue4v6lvmog.png" alt="Logs" width="540" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Powershell scripts instead of Bash
&lt;/h2&gt;

&lt;p&gt;If your &lt;em&gt;scriptsrv&lt;/em&gt; server is running Windows, you just need to make a few adjustments to run &lt;em&gt;Powershell&lt;/em&gt; scripts instead of &lt;em&gt;Bash&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ctfreak.com/docs/nodes/windows" rel="noopener noreferrer"&gt;Activate the internal SSH server&lt;/a&gt; in Windows.&lt;/li&gt;
&lt;li&gt;When creating the &lt;em&gt;scriptsrv&lt;/em&gt; node, set the &lt;em&gt;OS Family&lt;/em&gt; property to &lt;code&gt;Windows&lt;/code&gt; instead of &lt;code&gt;Unix&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create tasks of type &lt;em&gt;Powershell Script&lt;/em&gt; instead of &lt;em&gt;Bash Script&lt;/em&gt; and write the content of your scripts in Powershell instead of Bash.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The principle of injecting parameters as environment variables remains exactly the same. If we adapted the script from our first case, it would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;generatereport&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;CTP_FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;CTP_TO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--recipient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;CTP_RECIPIENT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;CTP_FORMAT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As you have noticed, simply adding a web interface to a script offers many possibilities.&lt;/p&gt;

&lt;p&gt;Start by adapting the examples presented in this article to your needs; they will already allow you to offer greater autonomy to your users.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>powershell</category>
      <category>devops</category>
      <category>sysadmin</category>
    </item>
    <item>
      <title>Un formulaire web pour lancer vos scripts bash et powershell</title>
      <dc:creator>Jean-Yves Pellé</dc:creator>
      <pubDate>Sun, 17 Mar 2024 11:03:33 +0000</pubDate>
      <link>https://dev.to/jypelle/un-formulaire-web-pour-lancer-vos-scripts-bash-et-powershell-1ppd</link>
      <guid>https://dev.to/jypelle/un-formulaire-web-pour-lancer-vos-scripts-bash-et-powershell-1ppd</guid>
      <description>&lt;p&gt;A quoi bon passer par une interface web pour lancer des scripts lorsque vous êtes un pro du terminal ?&lt;/p&gt;

&lt;p&gt;Et bien entre autres:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Pour déléguer aux utilisateurs métiers l'exécution de certains scripts de manière sécurisée, en limitant les options disponibles afin de prévenir toute mauvaise surprise et en gardant l'historique des exécutions:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74uayoefvr1ls6ed0lzr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74uayoefvr1ls6ed0lzr.png" alt="Générer et envoyer un rapport des ventes" width="420" height="386"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Exemple: Générer un rapport pour une plage de dates donnée et l'envoyer par mail&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ou pour exécuter facilement des tâches prédéfinies en cas d'urgence (un terminal n'est pas toujours à portée de main, contrairement à un smartphone):&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffkvvi2cqut3jy15qvuq5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffkvvi2cqut3jy15qvuq5.png" alt="Etat d'un service" width="420" height="224"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Exemple: Connaitre l’état ou redémarrer / arrêter un service linux&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Je vous propose de répondre à ces 2 cas d'usage via l'utilisation des &lt;em&gt;tâches paramétrables&lt;/em&gt; qui viennent d'être ajoutées dans la &lt;a href="https://ctfreak.com/docs/changelog#v1-14-0" rel="noopener noreferrer"&gt;dernière release de Ctfreak&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prérequis
&lt;/h2&gt;

&lt;p&gt;Il nous faut:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Un serveur Unix: Linux, Mac OS X, FreeBSD, ... (voir en fin d'article pour adapter à un serveur Windows) accessible via SSH où seront exécutés vos scripts. Nous le nommerons &lt;em&gt;scriptsrv&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Une instance de &lt;a href="https://ctfreak.com" rel="noopener noreferrer"&gt;Ctfreak&lt;/a&gt; avec un compte administrateur (la &lt;em&gt;Free Edition&lt;/em&gt; suffira si vous adaptez les exemples de cet article en vous limitant à un paramètre par tâche).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration du serveur &lt;em&gt;scriptsrv&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;Connectez vous à votre instance Ctfreak avec un compte administrateur.&lt;/p&gt;

&lt;p&gt;Commencez par ajouter la clef SSH permettant de se connecter au serveur via &lt;em&gt;SSH Credential&lt;/em&gt; → &lt;em&gt;New SSH Credential&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzlss12zvyrxygpmmmtlj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzlss12zvyrxygpmmmtlj.png" alt="Ajout de la clef SSH" width="420" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Puis ajoutez le serveur en lui même via &lt;em&gt;Nodes&lt;/em&gt; → &lt;em&gt;Internal Nodes&lt;/em&gt; → &lt;em&gt;New node&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8ajn1mr9n08hfz5xcmp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo8ajn1mr9n08hfz5xcmp.png" alt="Ajout du serveur" width="420" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vérifiez que la clef &lt;em&gt;SSH Key for scriptsrv&lt;/em&gt; est bien sélectionnée comme &lt;em&gt;Credential&lt;/em&gt;, puis validez pour créer le node &lt;em&gt;scriptsrv&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Ctfreak peut désormais de connecter au serveur avec la clef SSH spécifiée pour y exécuter différentes tâches.&lt;/p&gt;

&lt;h2&gt;
  
  
  1er cas: Générer un rapport pour une plage de dates donnée et l'envoyer par mail
&lt;/h2&gt;

&lt;p&gt;Pour notre exemple, nous allons partir du principe que vous disposez sur le serveur &lt;em&gt;scriptsrv&lt;/em&gt; d'un exécutable &lt;code&gt;generatereport&lt;/code&gt; permettant de générer et d'envoyer par mail un rapport des ventes pour une &lt;em&gt;période&lt;/em&gt;, un &lt;em&gt;destinataire&lt;/em&gt; et un &lt;em&gt;format&lt;/em&gt; donné. Si nous devions lancer cet exécutable directement dans un terminal, cela pourrait donner quelque chose comme:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;generatereport &lt;span class="nt"&gt;--from&lt;/span&gt; 2024-03-04 &lt;span class="nt"&gt;--to&lt;/span&gt; 2024-03-10 &lt;span class="nt"&gt;--recipient&lt;/span&gt; nigel@mycompany.zzz &lt;span class="nt"&gt;--format&lt;/span&gt; pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nous allons donc créer dans Ctfreak une tâche qui lors de son exécution génèrera à la volée un formulaire web permettant de renseigner ces 4 paramètres avant de lancer l'exécutable &lt;code&gt;generatereport&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Création du projet &lt;em&gt;Reporting&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Pour commencer, créons un projet dédié aux tâches de reporting.&lt;/p&gt;

&lt;p&gt;Allez dans &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;New Project&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcm3endqo4s6xxrzet4yy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcm3endqo4s6xxrzet4yy.png" alt="Création du projet" width="420" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validez pour créer le projet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Création de la tâche &lt;em&gt;Generate and send sales report&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Allez dans &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;Reporting&lt;/em&gt; → &lt;em&gt;New task&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7k5m4l297wmsmnls9ds.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft7k5m4l297wmsmnls9ds.png" alt="Sélection du type de tâche" width="420" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choisissez &lt;em&gt;Bash Script&lt;/em&gt; comme type de tâche.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0p2ozwt0hh4hyee0i9td.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0p2ozwt0hh4hyee0i9td.png" alt="Nouveau script bash" width="420" height="1120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Renseignez comme indiqué les champs &lt;em&gt;Name&lt;/em&gt;, &lt;em&gt;Script&lt;/em&gt; et &lt;em&gt;Node filter&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Dans le contenu de notre script, les 4 valeurs attendues sont renseignées via des variables d'environnement, toutes préfixées par &lt;code&gt;CTP_&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
generatereport &lt;span class="nt"&gt;--from&lt;/span&gt; &lt;span class="nv"&gt;$CTP_FROM&lt;/span&gt; &lt;span class="nt"&gt;--to&lt;/span&gt; &lt;span class="nv"&gt;$CTP_TO&lt;/span&gt; &lt;span class="nt"&gt;--recipient&lt;/span&gt; &lt;span class="nv"&gt;$CTP_RECIPIENT&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="nv"&gt;$CTP_FORMAT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ces variables correspondent aux paramètres que nous allons créer en cliquant sur le bouton &lt;em&gt;Add parameter&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Commençons par les dates de début et de fin de notre période:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv736vuep5lrk4t3pqd7k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv736vuep5lrk4t3pqd7k.png" alt="Dates de début et de fin" width="354" height="590"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Le type de paramètre &lt;em&gt;Date&lt;/em&gt; permet de s'assurer qu'une date valide sera saisie dans notre formulaire d'exécution.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Le champ &lt;em&gt;Default date type&lt;/em&gt; permet de prendre la semaine précédente comme période par défaut.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Le champs &lt;em&gt;Name&lt;/em&gt; sera utilisé pour définir le nom de la variable d'environnement (&lt;code&gt;CTP_FROM&lt;/code&gt; dans notre script fait donc référence au paramètre nommé &lt;code&gt;FROM&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lorsque le champ &lt;em&gt;Label&lt;/em&gt; est renseigné, il remplace le champ &lt;em&gt;Name&lt;/em&gt; pour faire référence à la variable lors de la génération du formulaire d'exécution.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lors de l'exécution de la tâche, la date saisie sera convertie au format &lt;code&gt;YYYY-MM-DD&lt;/code&gt; pour alimenter la variable d'environnement correspondante.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En ce qui concerne les mails des destinataires autorisés à recevoir le rapport, nous limiterons les choix possibles à 2 mails via le type de paramètre &lt;em&gt;Selector&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22r8mhgyq55ecme6ddpy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22r8mhgyq55ecme6ddpy.png" alt="Destinataire" width="354" height="762"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pour chaque option, le champs &lt;em&gt;Label&lt;/em&gt; lorsqu'il est renseigné se substitue à &lt;em&gt;Value&lt;/em&gt; lors de la génération du formulaire d'exécution. Nous aurions pu l'utiliser ici pour afficher &lt;code&gt;Nigel&lt;/code&gt; et &lt;code&gt;John&lt;/code&gt; dans le formulaire au lieu de leurs emails respectifs.&lt;/p&gt;

&lt;p&gt;Pour terminer, un dernier paramètre de type &lt;em&gt;Selector&lt;/em&gt; permettra de choisir le format du fichier généré (ici PDF ou Excel, mais c'est bien &lt;code&gt;pdf&lt;/code&gt; ou &lt;code&gt;xlsx&lt;/code&gt; qui sera transmis à notre script):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcek7cyzh90xrpzs7pw29.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcek7cyzh90xrpzs7pw29.png" alt="Format" width="354" height="762"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Toutes les informations nécessaires étant renseignées, il ne reste plus qu'à valider pour créer la tâche.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exécution de la tâche
&lt;/h3&gt;

&lt;p&gt;Allez dans &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;Reporting&lt;/em&gt; → &lt;em&gt;Generate and send sales report&lt;/em&gt; → &lt;em&gt;Execute&lt;/em&gt; pour faire apparaitre le formulaire d'exécution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74uayoefvr1ls6ed0lzr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74uayoefvr1ls6ed0lzr.png" alt="Générer et envoyer un rapport des ventes" width="420" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Comme vous pouvez le constater, nos 4 paramètres sont bien pris en compte dans la génération du formulaire.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ajout d'un utilisateur métier
&lt;/h3&gt;

&lt;p&gt;Notre tâche étant opérationnelle, reste à donner la main à un utilisateur métier afin qu'il puisse l'exécuter.&lt;/p&gt;

&lt;p&gt;Allez dans &lt;em&gt;Users&lt;/em&gt; → &lt;em&gt;New User&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frn8hchxtortsshvh0175.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frn8hchxtortsshvh0175.png" alt="Création d'un utilisateur" width="420" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validez pour créer l'utilisateur.&lt;/p&gt;

&lt;p&gt;De base, un utilisateur non-admin n'a accès à rien (mis à part se connecter), il faut donc lui ajouter les droits nécessaires.&lt;/p&gt;

&lt;p&gt;Allez dans &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;Reporting&lt;/em&gt; → &lt;em&gt;Access&lt;/em&gt; → &lt;em&gt;Edit&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ajouter Jack en lui donnant le rôle &lt;em&gt;Executor&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4yi96gvkabf5qwmjyjnx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4yi96gvkabf5qwmjyjnx.png" alt="Accès au projet" width="420" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Jack a désormais accès au projet &lt;em&gt;Reporting&lt;/em&gt;, mais &lt;strong&gt;uniquement&lt;/strong&gt; pour exécuter les tâches qu'il contient.&lt;/p&gt;

&lt;h2&gt;
  
  
  2ème cas: Connaitre l’état ou redémarrer / arrêter un service linux
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Création du projet &lt;em&gt;Sysadmin&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Créons un projet dédié aux tâches d'administration système.&lt;/p&gt;

&lt;p&gt;Allez dans &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;New Project&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fan2whqe1b5oo9im9st7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fan2whqe1b5oo9im9st7r.png" alt="Création du projet" width="420" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validez pour créer le projet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Création de la tâche &lt;em&gt;Systemctl&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Allez dans &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;Sysadmin&lt;/em&gt; → &lt;em&gt;New task&lt;/em&gt; et comme pour notre cas précédent choisissez &lt;em&gt;Bash Script&lt;/em&gt; comme type de tâche.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yzzdw3ksgmjezb9qnes.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yzzdw3ksgmjezb9qnes.png" alt="Nouveau script bash" width="420" height="1100"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Renseignez comme indiqué les champs &lt;em&gt;Name&lt;/em&gt;, &lt;em&gt;Script&lt;/em&gt; et &lt;em&gt;Node filter&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Cliquez sur le bouton &lt;em&gt;Add parameter&lt;/em&gt; pour ajouter la liste des services concernés (ici Postgresql et Apache)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2j670cbg52jer5ajvqgn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2j670cbg52jer5ajvqgn.png" alt="Service" width="356" height="762"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ainsi que les commandes pour connaitre l’état, redémarrer ou arrêter le service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxql960wux1n0fr5d2j4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxql960wux1n0fr5d2j4.png" alt="Commande" width="356" height="991"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validez pour créer la tâche.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exécution de la tâche
&lt;/h3&gt;

&lt;p&gt;Allez dans &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;Sysadmin&lt;/em&gt; → &lt;em&gt;Systemctl&lt;/em&gt; → &lt;em&gt;Execute&lt;/em&gt; pour faire apparaitre le formulaire d'exécution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffkvvi2cqut3jy15qvuq5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffkvvi2cqut3jy15qvuq5.png" alt="Etat d'un service" width="420" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exécutez la tâche et consultez les logs en cliquant sur l’icône en forme d’œil.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqoqjbdsqitqryc0n3p3d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqoqjbdsqitqryc0n3p3d.png" alt="Logs" width="540" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Lancer des scripts Powershell plutôt que Bash
&lt;/h2&gt;

&lt;p&gt;Si votre serveur &lt;em&gt;scriptsrv&lt;/em&gt; est sous Windows, il suffit de faire quelques ajustements pour lancer des scripts &lt;em&gt;Powershell&lt;/em&gt; plutôt que &lt;em&gt;Bash&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://ctfreak.com/docs/nodes/windows" rel="noopener noreferrer"&gt;Activez le serveur SSH interne&lt;/a&gt; de Windows.&lt;/li&gt;
&lt;li&gt;Lors de la création du node &lt;em&gt;scriptsrv&lt;/em&gt;, définissez la propriété &lt;em&gt;OS Family&lt;/em&gt; à &lt;code&gt;Windows&lt;/code&gt; plutôt que &lt;code&gt;Unix&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Créez des tâches de type &lt;em&gt;Powershell Script&lt;/em&gt; au lieu de &lt;em&gt;Bash Script&lt;/em&gt; et écrivez le contenu de vos scripts en Powershell plutôt que Bash.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le principe d'injection des paramètres sous forme de variables d'environnement reste exactement le même. Si nous adaptions le script de notre premier cas, cela donnerait:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;generatereport&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;CTP_FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;CTP_TO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--recipient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;CTP_RECIPIENT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;CTP_FORMAT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pour finir
&lt;/h2&gt;

&lt;p&gt;Comme vous l'aurez constaté, le simple fait d'apposer une interface web sur un script offre beaucoup de possibilités.&lt;/p&gt;

&lt;p&gt;Commencez par adapter les exemples présentés dans cet article à vos besoins, ils vous permettrons déjà d'offrir une plus grande autonomie à vos utilisateurs.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>powershell</category>
      <category>devops</category>
      <category>sysadmin</category>
    </item>
    <item>
      <title>Configuring Apache Superset 3 in a production environment</title>
      <dc:creator>Jean-Yves Pellé</dc:creator>
      <pubDate>Wed, 11 Oct 2023 10:20:19 +0000</pubDate>
      <link>https://dev.to/jypelle/configuring-apache-superset-3-in-a-production-environment-409f</link>
      <guid>https://dev.to/jypelle/configuring-apache-superset-3-in-a-production-environment-409f</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/apache/superset" rel="noopener noreferrer"&gt;&lt;em&gt;Apache Superset&lt;/em&gt;&lt;/a&gt; proves to be a fantastic Business Intelligence tool: open-source, feature-rich (with a multitude of charts, integration with multiple database management systems, etc.), and it has nothing to envy in comparison to well-known commercial alternatives (Tableau, PowerBI, BusinessObjects, and the like).&lt;/p&gt;

&lt;p&gt;Unfortunately, being &lt;em&gt;fantastic&lt;/em&gt; doesn't mean it's without flaws, particularly in terms of documentation, which may seem a bit &lt;em&gt;sparse&lt;/em&gt; to some.&lt;/p&gt;

&lt;p&gt;It is precisely to address this shortcoming that I propose we look at how to quickly set up a Superset instance tailored for production together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Have a production server running Linux (regardless of the distribution) with:

&lt;ul&gt;
&lt;li&gt;docker&lt;/li&gt;
&lt;li&gt;git&lt;/li&gt;
&lt;li&gt;caddy (or another reverse proxy of your choice)&lt;/li&gt;
&lt;li&gt;ports 443 and 80 open at the firewall level&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;A domain name (e.g., &lt;code&gt;bi.myawesomecompany.com&lt;/code&gt;) that points to your server&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cloning the superset repo
&lt;/h3&gt;

&lt;p&gt;Start by cloning the repository directly on your production server (yes, this is not common):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;
git clone https://github.com/apache/superset.git
&lt;span class="nb"&gt;cd &lt;/span&gt;superset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then select the desired version via its tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout tags/3.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Editing configuration files
&lt;/h3&gt;

&lt;p&gt;Now comes the step of customizing certain configuration files. Since you are on a git tag, you won't be able to commit your modifications as is, so you have the choice of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;documenting all your modifications to reproduce them exactly in case of reinstallation&lt;/li&gt;
&lt;li&gt;or forking the project and creating a branch to commit your modifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's up to you.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;docker/.env-non-dev&lt;/code&gt; file (understand here: &lt;code&gt;non-dev&lt;/code&gt; = &lt;code&gt;prod&lt;/code&gt;) allows you to define a set of environment variables that will be used by the docker containers we will start later.&lt;/p&gt;

&lt;p&gt;Add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# We don't want demo data in production&lt;/span&gt;
&lt;span class="nv"&gt;SUPERSET_LOAD_EXAMPLES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no
&lt;span class="c"&gt;# A random string to encode session cookies&lt;/span&gt;
&lt;span class="nv"&gt;SUPERSET_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4Sido8BkIjs54Vz2XyVD5GJIvANVIAT399dRESjdmr4vm92n
&lt;span class="c"&gt;# To prevent XSS attacks (among other things)&lt;/span&gt;
&lt;span class="nv"&gt;TALISMAN_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes&lt;/span&gt;
&lt;span class="c"&gt;# Number of workers: the higher the value, the fewer intermittent chart refresh failures you will have in your dashboards (adjust according to your server's power).&lt;/span&gt;
&lt;span class="nv"&gt;SERVER_WORKER_AMOUNT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, make some adjustments in &lt;code&gt;docker/pythonpath_dev/superset_config.py&lt;/code&gt; to enable &lt;em&gt;alerting&lt;/em&gt; and the &lt;em&gt;template engine&lt;/em&gt; (necessary for creating datasets with dynamic filtering):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;FEATURE_FLAGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALERT_REPORTS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ENABLE_TEMPLATE_PROCESSING&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Disable telemetry by replacing in the &lt;code&gt;docker-compose-non-dev.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;x-superset-image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;superset-image&lt;/span&gt; &lt;span class="s"&gt;apachesuperset.docker.scarf.sh/apache/superset:${TAG:-latest-dev}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;x-superset-image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;superset-image&lt;/span&gt; &lt;span class="s"&gt;apache/superset:${TAG:-latest-dev}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And switch to the latest stable version of postgreSQL by replacing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;     &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:14&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;     &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Startup
&lt;/h3&gt;

&lt;p&gt;Instantiate and start the docker containers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose-non-dev.yml up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Superset is accessible on your production server via &lt;code&gt;http://127.0.0.1:8088&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reverse proxy
&lt;/h3&gt;

&lt;p&gt;Configure a reverse proxy to secure the connection, for example, using caddy:&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;/etc/caddy/Caddyfile&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bi.myawesomecompany.com {
        reverse_proxy http://127.0.0.1:8088
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then restart caddy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;service caddy restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  First Login
&lt;/h3&gt;

&lt;p&gt;Log in at &lt;a href="https://bi.myawesomecompany.com" rel="noopener noreferrer"&gt;https://bi.myawesomecompany.com&lt;/a&gt; with the &lt;em&gt;username&lt;/em&gt; / &lt;em&gt;password&lt;/em&gt;: admin / admin&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqnqrxae37i440t3p2dr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuqnqrxae37i440t3p2dr.png" alt="Login page" width="480" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Change your password.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backup and Restoration
&lt;/h2&gt;

&lt;p&gt;When editing the &lt;code&gt;docker-compose-non-dev.yml&lt;/code&gt; configuration file, you may have noticed that a postgresql database is being instantiated.&lt;/p&gt;

&lt;p&gt;Therefore, backup and restoration for superset only need to consider this database.&lt;/p&gt;

&lt;p&gt;You can perform a hot backup with a simple command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; superset_db pg_dump superset &lt;span class="nt"&gt;-U&lt;/span&gt; superset | xz &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; backup.sql.xz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For restoration, start &lt;em&gt;only&lt;/em&gt; the postgresql container in advance, avoiding having a superset instance connected to a database being restored:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose down
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose-non-dev.yml up db &lt;span class="nt"&gt;-d&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; superset_db dropdb &lt;span class="nt"&gt;-U&lt;/span&gt; superset superset
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; superset_db createdb &lt;span class="nt"&gt;-U&lt;/span&gt; superset superset
xz &lt;span class="nt"&gt;-dc&lt;/span&gt; backup.sql.xz | docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; superset_db psql &lt;span class="nt"&gt;-U&lt;/span&gt; superset &lt;span class="nt"&gt;-d&lt;/span&gt; superset
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose-non-dev.yml up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>superset</category>
      <category>analytics</category>
      <category>bi</category>
    </item>
    <item>
      <title>Configurer Apache Superset 3 dans un environnement de production</title>
      <dc:creator>Jean-Yves Pellé</dc:creator>
      <pubDate>Mon, 09 Oct 2023 12:04:52 +0000</pubDate>
      <link>https://dev.to/jypelle/configurer-apache-superset-3-dans-un-environnement-de-production-2ban</link>
      <guid>https://dev.to/jypelle/configurer-apache-superset-3-dans-un-environnement-de-production-2ban</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/apache/superset" rel="noopener noreferrer"&gt;&lt;em&gt;Apache Superset&lt;/em&gt;&lt;/a&gt; se révèle être un formidable outil de Business Intelligence: open source, riche en fonctionnalités (multitude de charts, intégration avec plusieurs SGBD, ...) et qui n'a rien à envier aux alternatives commerciales les plus connues (Tableau, PowerBI, BusinessObjects et consorts).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Formidable&lt;/em&gt; ne veut malheureusement pas dire qu'il est exempt de défauts, notamment sa documentation qui pourra paraître un peu &lt;em&gt;frugale&lt;/em&gt; à certains.&lt;/p&gt;

&lt;p&gt;C'est justement pour palier à ce manque que je vous propose de voir ensemble comment mettre en place rapidement une instance de Superset taillée pour la production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prérequis
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Disposer d'un serveur de production sous linux (peu importe la distribution) avec:

&lt;ul&gt;
&lt;li&gt;docker&lt;/li&gt;
&lt;li&gt;git&lt;/li&gt;
&lt;li&gt;caddy (ou autre reverse proxy de votre choix)&lt;/li&gt;
&lt;li&gt;les ports 443 et 80 ouverts au niveau du firewall&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Un nom de domaine (par exemple &lt;code&gt;bi.myawesomecompany.com&lt;/code&gt;) qui pointe vers votre serveur&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Clonage du repo superset
&lt;/h3&gt;

&lt;p&gt;Commencez par faire un git clone du repo directement sur votre serveur de prod (oui ça n'est pas commun):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$HOME&lt;/span&gt;
git clone https://github.com/apache/superset.git
&lt;span class="nb"&gt;cd &lt;/span&gt;superset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Puis sélectionnez la version souhaitée via son tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout tags/3.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Édition des fichiers de configuration
&lt;/h3&gt;

&lt;p&gt;Arrive l’étape de customisation de certains fichiers de configuration. Comme vous êtes sur un tag git, vous ne pourrez pas commiter vos modifications en l'état, vous aurez donc le choix de:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;documenter toutes vos modifications pour les reproduire à l'identique en cas de réinstallation&lt;/li&gt;
&lt;li&gt;ou forker le projet et créer une branche pour commiter vos modifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A vous de voir.&lt;/p&gt;

&lt;p&gt;Le fichier &lt;code&gt;docker/.env-non-dev&lt;/code&gt; (comprendre ici: &lt;code&gt;non-dev&lt;/code&gt; = &lt;code&gt;prod&lt;/code&gt;) permet de définir un ensemble de variables d'environnement qui seront utilisées par les containers dockers que nous démarrerons par la suite.&lt;/p&gt;

&lt;p&gt;Ajoutez-y ceci:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# On ne veut pas de données de démonstration en prod&lt;/span&gt;
&lt;span class="nv"&gt;SUPERSET_LOAD_EXAMPLES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no
&lt;span class="c"&gt;# Une chaîne de caractère aléatoire pour encoder les cookies de session&lt;/span&gt;
&lt;span class="nv"&gt;SUPERSET_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4Sido8BkIjs54Vz2XyVD5GJIvANVIAT399dRESjdmr4vm92n
&lt;span class="c"&gt;# Pour prévenir les attaques de type XSS (entre autre)&lt;/span&gt;
&lt;span class="nv"&gt;TALISMAN_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes&lt;/span&gt;
&lt;span class="c"&gt;# Nombre de workers: plus la valeur est élevée, moins vous aurez d'échecs intermittents de refresh de charts dans vos dashboards (à ajuster selon la puissance de votre serveur).&lt;/span&gt;
&lt;span class="nv"&gt;SERVER_WORKER_AMOUNT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Effectuez aussi quelques retouches dans &lt;code&gt;docker/pythonpath_dev/superset_config.py&lt;/code&gt; pour activer l'&lt;em&gt;alerting&lt;/em&gt; et le &lt;em&gt;moteur de template&lt;/em&gt; (nécessaire pour créer des datasets avec filtrage dynamique):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;FEATURE_FLAGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALERT_REPORTS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ENABLE_TEMPLATE_PROCESSING&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Désactivez la télémétrie en remplaçant dans le fichier &lt;code&gt;docker-compose-non-dev.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;x-superset-image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;superset-image&lt;/span&gt; &lt;span class="s"&gt;apachesuperset.docker.scarf.sh/apache/superset:${TAG:-latest-dev}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;par&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;x-superset-image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;superset-image&lt;/span&gt; &lt;span class="s"&gt;apache/superset:${TAG:-latest-dev}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Et passez sur la dernière version stable de postgresql en remplaçant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;     &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:14&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;par&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;     &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Démarrage
&lt;/h3&gt;

&lt;p&gt;Instanciez et démarrez les conteneurs docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose-non-dev.yml up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Superset est donc accessible sur votre serveur de prod via &lt;code&gt;http://127.0.0.1:8088&lt;/code&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Reverse proxy
&lt;/h3&gt;

&lt;p&gt;Configurez un reverse proxy pour sécuriser la connexion, exemple avec caddy:&lt;/p&gt;

&lt;p&gt;Editer &lt;code&gt;/etc/caddy/Caddyfile&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bi.myawesomecompany.com {
        reverse_proxy http://127.0.0.1:8088
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Puis redémarrez caddy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;service caddy restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Première connexion
&lt;/h3&gt;

&lt;p&gt;Connectez vous à &lt;a href="https://bi.myawesomecompany.com" rel="noopener noreferrer"&gt;https://bi.myawesomecompany.com&lt;/a&gt; avec le &lt;em&gt;nom d'utilisateur&lt;/em&gt; / &lt;em&gt;mot de passe&lt;/em&gt;: admin / admin&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxeqpbtqatkg75kge30gp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxeqpbtqatkg75kge30gp.png" alt="Page de connexion" width="480" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Changez votre mot de passe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sauvegarde et restauration
&lt;/h2&gt;

&lt;p&gt;Qui dit prod, dit sauvegarde !&lt;/p&gt;

&lt;p&gt;Lors de l'édition du fichier de configuration &lt;code&gt;docker-compose-non-dev.yml&lt;/code&gt;, vous aurez sans-doute remarqué qu'on instancie une base de données postgresql.&lt;/p&gt;

&lt;p&gt;Pour faire la sauvegarde/restauration de superset il n'y a que cette base à prendre en compte.&lt;/p&gt;

&lt;p&gt;La sauvegarde peut donc se faire à chaud via un simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; superset_db pg_dump superset &lt;span class="nt"&gt;-U&lt;/span&gt; superset | xz &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; backup.sql.xz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pour la restauration, on ne démarrera au préalable QUE le conteneur postgresql, évitant ainsi d'avoir une instance superset branchée sur une base de données en cours de restauration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose down
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose-non-dev.yml up db &lt;span class="nt"&gt;-d&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; superset_db dropdb &lt;span class="nt"&gt;-U&lt;/span&gt; superset superset
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; superset_db createdb &lt;span class="nt"&gt;-U&lt;/span&gt; superset superset
xz &lt;span class="nt"&gt;-dc&lt;/span&gt; backup.sql.xz | docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; superset_db psql &lt;span class="nt"&gt;-U&lt;/span&gt; superset &lt;span class="nt"&gt;-d&lt;/span&gt; superset
docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose-non-dev.yml up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>superset</category>
      <category>french</category>
      <category>bi</category>
    </item>
    <item>
      <title>Mettre à jour des milliers de servers linux en un clin d'oeil avec Ctfreak</title>
      <dc:creator>Jean-Yves Pellé</dc:creator>
      <pubDate>Mon, 27 Mar 2023 15:04:06 +0000</pubDate>
      <link>https://dev.to/jypelle/mettre-a-jour-des-milliers-de-servers-linux-en-un-clin-doeil-avec-ctfreak-g1g</link>
      <guid>https://dev.to/jypelle/mettre-a-jour-des-milliers-de-servers-linux-en-un-clin-doeil-avec-ctfreak-g1g</guid>
      <description>&lt;p&gt;Vous cherchez à automatiser la mise à jour de vos serveurs sous linux ? &lt;/p&gt;

&lt;p&gt;En lançant les mises à jour en parallèle ?&lt;/p&gt;

&lt;p&gt;Sans devoir mettre en place une usine à gaz ?&lt;/p&gt;

&lt;p&gt;Ne cherchez plus ce tuto est fait pour vous 😉.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prérequis
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;1000 serveurs à mettre à jour (par exemple) :

&lt;ul&gt;
&lt;li&gt;Ayant pour hostname &lt;code&gt;serverXXXXX.local&lt;/code&gt; avec &lt;code&gt;XXXXX&lt;/code&gt; allant de 00001 à 01000&lt;/li&gt;
&lt;li&gt;Utilisant une distribution linux basée sur Debian (Debian, Ubuntu, Linux mint, ...)&lt;/li&gt;
&lt;li&gt;Accessibles via SSH sur le port 22 avec la même clef privée et un compte utilisateur &lt;code&gt;adminuser&lt;/code&gt; pouvant lancer des commandes &lt;code&gt;sudo&lt;/code&gt; sans demande de mot de passe&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Une instance de &lt;a href="https://ctfreak.com" rel="noopener noreferrer"&gt;Ctfreak&lt;/a&gt; (vous verrez ça s'&lt;a href="https://ctfreak.com/docs/install" rel="noopener noreferrer"&gt;installe&lt;/a&gt; très bien : pas de dépendance, pas de bdd à configurer) avec un compte administrateur, la &lt;em&gt;Free Edition&lt;/em&gt; suffira.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Le script
&lt;/h2&gt;

&lt;p&gt;Sur chaque serveur, ce script shell permettra d'effectuer la mise à jour :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DEBIAN_FRONTEND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;noninteractive
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update 
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; Dpkg::Options::&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--force-confdef"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; Dpkg::Options::&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"--force-confold"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On retrouve le classique &lt;code&gt;apt update/upgrade&lt;/code&gt; enrichi de quelques éléments permettant de répondre automatiquement à toute question susceptible d'interrompre le script.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NB : Si vos serveurs sont sous Redhat (ou tout autre distribution basé sur RPM), je vous invite à adapter le script en conséquence&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ctfreak
&lt;/h2&gt;

&lt;p&gt;L'étape suivante sera d'utiliser &lt;a href="https://ctfreak.com" rel="noopener noreferrer"&gt;ctfreak&lt;/a&gt; pour déployer et exécuter via SSH le script sur nos 1000 serveurs.&lt;/p&gt;

&lt;p&gt;Connectez-vous à ctfreak avec votre compte administrateur.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UMV6-At2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/connect.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UMV6-At2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/connect.webp" alt="Connection" width="600" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Ajout de la clef SSH
&lt;/h3&gt;

&lt;p&gt;Allez dans &lt;em&gt;SSH Credential&lt;/em&gt; -&amp;gt; Bouton &lt;em&gt;New SSH Credential&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uZutiPu7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/sshkey.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uZutiPu7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/sshkey.webp" alt="SSH credential" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ajoutez la clef privée SSH &lt;strong&gt;MySSHKey&lt;/strong&gt; qui permet de se connecter aux serveurs et validez.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NB : Vous pouvez aussi vous connecter avec un mot de passe plutôt qu'une clef privée&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Ajout des nodes
&lt;/h3&gt;

&lt;p&gt;Un &lt;em&gt;node&lt;/em&gt; désigne les paramètres de connexion à un serveur via SSH, les nodes sont regroupés en &lt;em&gt;node sources&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Pour rappel, il y a 1000 serveurs, donc 1000 nodes à ajouter.&lt;/p&gt;

&lt;p&gt;Il est bien sûr possible d'ajouter les nodes un par un via l'interface web, mais dans ce cas de figure, vous allez plutôt partir sur la création d'un &lt;em&gt;node source&lt;/em&gt; dédié qui sera alimenté par un fichier yaml décrivant nos 1000 nodes.  &lt;/p&gt;

&lt;p&gt;Soit le fichier &lt;code&gt;/home/adminuser/ctfreak-nodes.yaml&lt;/code&gt; (accessible en lecture par le user faisant tourner l'instance de ctfreak) :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;server0001&lt;/span&gt;
  &lt;span class="na"&gt;tagNames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;debian_server&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;linux_server&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;adminuser&lt;/span&gt;
  &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;server0001.local&lt;/span&gt;
  &lt;span class="na"&gt;osFamily&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UNIX&lt;/span&gt;
  &lt;span class="na"&gt;sshPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;server0002&lt;/span&gt;
  &lt;span class="na"&gt;tagNames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;debian_server&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;linux_server&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;adminuser&lt;/span&gt;
  &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;server0002.local&lt;/span&gt;
  &lt;span class="na"&gt;osFamily&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UNIX&lt;/span&gt;
  &lt;span class="na"&gt;sshPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;

&lt;span class="c1"&gt;## ...  &lt;/span&gt;
&lt;span class="c1"&gt;## Complétez avec les nodes 3 à 1000&lt;/span&gt;
&lt;span class="c1"&gt;## ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Allez dans &lt;em&gt;Nodes&lt;/em&gt; -&amp;gt; Bouton &lt;em&gt;New External Node Source&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y7FKpoUq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/node-source.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y7FKpoUq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/node-source.webp" alt="Connection" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vérifiez que la clef SSH &lt;strong&gt;MySSHKey&lt;/strong&gt; est bien sélectionnée comme &lt;em&gt;Credential&lt;/em&gt; (ctfreak s'en servira pour se connecter aux nodes via SSH),&lt;br&gt;
puis validez pour créer le &lt;em&gt;node source&lt;/em&gt; &lt;strong&gt;Linux Servers&lt;/strong&gt; (ce dernier resynchronisera ses nodes à partir du fichier yaml toutes les 2 heures).&lt;/p&gt;

&lt;p&gt;Si le fichier yaml a bien été reconnu, vous devriez voir la liste suivante :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UE1Eb3ZG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/nodes.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UE1Eb3ZG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/nodes.webp" alt="Nodes" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Ajout de la tâche
&lt;/h3&gt;

&lt;p&gt;Allez dans &lt;em&gt;Projects&lt;/em&gt; -&amp;gt; Bouton &lt;em&gt;New Project&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_0E56_W5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/project.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_0E56_W5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/project.webp" alt="Project" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validez pour créer le projet &lt;strong&gt;Sysadmin&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Cliquez sur le bouton &lt;em&gt;New Task&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0u3yfwj---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/task.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0u3yfwj---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/task.webp" alt="Task" width="800" height="1094"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validez pour créer la tâche &lt;strong&gt;Upgrade debian servers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Cette tâche déploiera et exécutera le script shell :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sur tous les &lt;em&gt;nodes&lt;/em&gt; correspondants au tag &lt;strong&gt;#debian_server&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;à chaque 1er jour du mois à 4h du matin
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Exécution de la tâche
&lt;/h3&gt;

&lt;p&gt;Pour éviter d'attendre le 1er du mois pour voir ce que ça donne 😉, exécutez la tâche en cliquant sur le bouton &lt;em&gt;Execute&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7eHCMsaW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/execution.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7eHCMsaW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/execution.webp" alt="Execution" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;L'exécution est lancée, cliquez alors sur l'identifiant d'exécution &lt;strong&gt;#T0ZJX&lt;/strong&gt; pour consulter son avancement pour chaque node.&lt;/p&gt;

&lt;p&gt;Une fois l'exécution terminée, il se peut que quelques nodes soient en échecs (timeout sur l'ouverture de connexion SSH, repo des packages indispo au lancement du apt-update, problème de DNS, ...) :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--evXtTIX9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/execution-nodes.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--evXtTIX9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mettre-a-jour-des-milliers-de-serveurs-avec-ctfreak/execution-nodes.webp" alt="Execution nodes" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Après avoir coché le filtre &lt;em&gt;Failed only&lt;/em&gt; pour n'afficher que les nodes en échecs et consulter les logs pour fixer le souci (ici un problème de DNS),&lt;br&gt;
vous pouvez lancer une nouvelle exécution pour &lt;strong&gt;retraiter uniquement les nodes en échecs&lt;/strong&gt; via un simple clic sur &lt;em&gt;Re-execute failed nodes&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Vous voilà avec un process fiable de mise à jour de vos serveurs.&lt;/p&gt;

&lt;p&gt;Pour ceux qui voudraient aller plus loin, voici quelques suggestions d'amélioration :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ajouter une notification par mail en cas d'échec de mise à jour.&lt;/li&gt;
&lt;li&gt;Ajouter des serveurs Redhat:

&lt;ul&gt;
&lt;li&gt;Ajouter 1000 nouveaux nodes avec les tags &lt;strong&gt;redhat_server&lt;/strong&gt;, &lt;strong&gt;linux_server&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Dupliquer la tâche &lt;strong&gt;Upgrade debian servers&lt;/strong&gt; en &lt;strong&gt;Upgrade redhat servers&lt;/strong&gt; et l'adapter en conséquence.&lt;/li&gt;
&lt;li&gt;Créer une tâche &lt;strong&gt;Upgrade all servers&lt;/strong&gt; de type &lt;strong&gt;workflow&lt;/strong&gt; pour lancer en parallèle (PRO Edition) ou séquentiellement (FREE Edition) les tâches &lt;strong&gt;Upgrade debian servers&lt;/strong&gt; et &lt;strong&gt;Upgrade redhat servers&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>french</category>
      <category>scheduling</category>
      <category>sysadmin</category>
    </item>
    <item>
      <title>Mise en place d'un pipeline CI/CD self-hosted</title>
      <dc:creator>Jean-Yves Pellé</dc:creator>
      <pubDate>Mon, 27 Mar 2023 14:32:30 +0000</pubDate>
      <link>https://dev.to/jypelle/mise-en-place-dun-pipeline-cicd-self-hosted-34lb</link>
      <guid>https://dev.to/jypelle/mise-en-place-dun-pipeline-cicd-self-hosted-34lb</guid>
      <description>&lt;p&gt;Pour rappel, un pipeline CI/CD (ou pipeline d'&lt;em&gt;intégration et de déploiement continus&lt;/em&gt;) est une série d'actions à effectuer en vue de distribuer de manière cohérente et fiable une nouvelle version d'un logiciel.&lt;/p&gt;

&lt;p&gt;Dans cet article, nous allons voir comment mettre en place un pipeline CI/CD minimaliste (lancer automatiquement les builds et déploiements lors des événements &lt;em&gt;push&lt;/em&gt; sur un dépôt Git) et auto-hébergé que vous pourrez par la suite adapter selon vos besoins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prérequis
&lt;/h2&gt;

&lt;p&gt;Il nous faut:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Un serveur de build que nous nommerons &lt;em&gt;buildsrv&lt;/em&gt;:

&lt;ul&gt;
&lt;li&gt;Sous linux&lt;/li&gt;
&lt;li&gt;Accessible via SSH&lt;/li&gt;
&lt;li&gt;Disposant des outils &lt;em&gt;make&lt;/em&gt; et &lt;em&gt;git&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Une forge git hébergeant les sources de notre &lt;em&gt;application&lt;/em&gt;. Nous utiliserons ici &lt;a href="https://github.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; mais toute autre forge capable d'appeler des webhooks lors de la réception d'évènements &lt;em&gt;push&lt;/em&gt; sur ses dépôts fera aussi très bien l'affaire.&lt;/li&gt;

&lt;li&gt;Une instance de &lt;a href="https://ctfreak.com" rel="noopener noreferrer"&gt;Ctfreak&lt;/a&gt; avec un compte administrateur (la &lt;em&gt;Free Edition&lt;/em&gt; suffira). Nous utiliserons ici &lt;a href="https://demo.ctfreak.com" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://demo.ctfreak.com" rel="noopener noreferrer"&gt;https://demo.ctfreak.com&lt;/a&gt;
&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Les sources
&lt;/h2&gt;

&lt;p&gt;Les sources de notre &lt;em&gt;application&lt;/em&gt; peuvent se résumer à ce &lt;em&gt;Makefile&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;build&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Build my awesome app"&lt;/span&gt;
&lt;span class="nl"&gt;deploy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Deploy my awesome app"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vous pouvez les retrouver dans &lt;a href="https://github.com/jypsoftware/myawesomeapp" rel="noopener noreferrer"&gt;ce dépôt git&lt;/a&gt;, avec la branche &lt;em&gt;master&lt;/em&gt; correspondant à la dernière version déployée et la branche &lt;em&gt;dev&lt;/em&gt; à la version en cours de développement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Objectifs
&lt;/h2&gt;

&lt;p&gt;Passons en revue les objectifs à atteindre par la mise en place de notre pipeline CI/CD:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pour la partie &lt;strong&gt;I&lt;/strong&gt;ntégration &lt;strong&gt;C&lt;/strong&gt;ontinue:

&lt;ul&gt;
&lt;li&gt;Compiler les sources (via la commande &lt;code&gt;make&lt;/code&gt;) à chaque fois qu'un développeur push sur la branche &lt;em&gt;dev&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Recevoir une notification en cas d'échec.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Pour la partie &lt;strong&gt;D&lt;/strong&gt;éploiement &lt;strong&gt;C&lt;/strong&gt;ontinue:

&lt;ul&gt;
&lt;li&gt;Compiler les sources et déployer (via la commande &lt;code&gt;make deploy&lt;/code&gt;) à chaque fois qu'un développeur push sur la branche &lt;em&gt;master&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Recevoir une notification en cas de succès ou d'échec.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Pour ce faire &lt;a href="https://ctfreak.com" rel="noopener noreferrer"&gt;Ctfreak&lt;/a&gt; va jouer le rôle de passerelle entre la forge git et le serveur de build.&lt;/p&gt;

&lt;p&gt;C'est à dire qu'à chaque push reçu par la forge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Celle-ci va appeler un webhook défini dans Ctfreak.&lt;/li&gt;
&lt;li&gt;Ctfreak va alors se connecter via SSH au serveur de build pour exécuter la commande &lt;code&gt;make&lt;/code&gt; adéquate, puis envoyer une notification.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Configuration du serveur de build
&lt;/h2&gt;

&lt;p&gt;Connectez-vous à votre serveur de build et lancez les commandes suivantes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ctfreakdemo@buildsrv:~&lt;span class="nv"&gt;$ &lt;/span&gt;git clone https://github.com/jypsoftware/myawesomeapp.git myawesomeapp.master
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Cloning into &lt;span class="s1"&gt;'myawesomeapp.master'&lt;/span&gt;...
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Enumerating objects: 15, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Counting objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;15/15&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Compressing objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;13/13&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Total 15 &lt;span class="o"&gt;(&lt;/span&gt;delta 2&lt;span class="o"&gt;)&lt;/span&gt;, reused 6 &lt;span class="o"&gt;(&lt;/span&gt;delta 1&lt;span class="o"&gt;)&lt;/span&gt;, pack-reused 0
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Receiving objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;15/15&lt;span class="o"&gt;)&lt;/span&gt;, 14.38 KiB | 3.59 MiB/s, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Resolving deltas: 100% &lt;span class="o"&gt;(&lt;/span&gt;2/2&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

ctfreakdemo@buildsrv:~&lt;span class="nv"&gt;$ &lt;/span&gt;git clone https://github.com/jypsoftware/myawesomeapp.git myawesomeapp.dev
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Cloning into &lt;span class="s1"&gt;'myawesomeapp.dev'&lt;/span&gt;...
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Enumerating objects: 15, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Counting objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;15/15&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Compressing objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;13/13&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Total 15 &lt;span class="o"&gt;(&lt;/span&gt;delta 2&lt;span class="o"&gt;)&lt;/span&gt;, reused 6 &lt;span class="o"&gt;(&lt;/span&gt;delta 1&lt;span class="o"&gt;)&lt;/span&gt;, pack-reused 0
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Receiving objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;15/15&lt;span class="o"&gt;)&lt;/span&gt;, 14.38 KiB | 7.19 MiB/s, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Resolving deltas: 100% &lt;span class="o"&gt;(&lt;/span&gt;2/2&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

ctfreakdemo@buildsrv:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;myawesomeapp.dev

ctfreakdemo@buildsrv:~/myawesomeapp.dev&lt;span class="nv"&gt;$ &lt;/span&gt;git checkout dev
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Branch &lt;span class="s1"&gt;'dev'&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;up to track remote branch &lt;span class="s1"&gt;'dev'&lt;/span&gt; from &lt;span class="s1"&gt;'origin'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Switched to a new branch &lt;span class="s1"&gt;'dev'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Comme vous pouvez le constater, nous avons cloné le repo git 2 fois (1 par branche), ce qui nous permettra d'éviter d'éventuels conflits si les builds des branches &lt;em&gt;master&lt;/em&gt; et &lt;em&gt;dev&lt;/em&gt; devaient s'exécuter de manière concurrente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration de Ctfreak
&lt;/h2&gt;

&lt;p&gt;Connectez vous à votre instance Ctfreak avec un compte administrateur.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ajout du serveur de build
&lt;/h3&gt;

&lt;p&gt;Commencez par ajouter la clef SSH permettant de se connecter au serveur de build via &lt;em&gt;SSH Credential&lt;/em&gt; → &lt;em&gt;New SSH Credential&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yiOiwvr7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/ssh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yiOiwvr7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/ssh.webp" alt="Ajout de la clef SSH" width="420" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Puis ajoutez le serveur en lui même via &lt;em&gt;Nodes&lt;/em&gt; → &lt;em&gt;Internal Nodes&lt;/em&gt; → &lt;em&gt;New node&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---uls8fei--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/node.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---uls8fei--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/node.webp" alt="Ajout du serveur" width="420" height="726"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Vérifiez que la clef &lt;em&gt;SSH Key for buildsrv&lt;/em&gt; est bien sélectionnée comme &lt;em&gt;Credential&lt;/em&gt; (Ctfreak s’en servira pour se connecter au serveur), puis validez pour créer le node &lt;em&gt;buildsrv&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Création du projet CI/CD
&lt;/h3&gt;

&lt;p&gt;Nous allons créer un projet dédié pour rassembler toutes les tâches en rapport avec notre pipeline CI/CD.&lt;/p&gt;

&lt;p&gt;Allez dans &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;New Project&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RThKWPCy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/project.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RThKWPCy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/project.webp" alt="Création du projet" width="360" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validez pour créer le projet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ajout d'un notifieur
&lt;/h3&gt;

&lt;p&gt;Allez dans &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;CI/CD&lt;/em&gt; → &lt;em&gt;Notifiers&lt;/em&gt; → &lt;em&gt;New notifier&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vGtoEjJ6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/notifier1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vGtoEjJ6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/notifier1.webp" alt="Sélection du type de notifieur" width="420" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choisissez &lt;em&gt;Discord&lt;/em&gt; comme type de notifieur (par exemple).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yc-LQW1_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/notifier2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yc-LQW1_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/notifier2.webp" alt="Edition du notifieur" width="420" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Précisez l'URL du webhook discord à appeler et validez.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WMHBKQhx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/notifier3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WMHBKQhx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/notifier3.webp" alt="Liste des notifieurs" width="420" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Une notification de test peut être envoyée en cliquant sur le bouton d'envoi (celui en forme d'avion en papier).&lt;/p&gt;

&lt;h3&gt;
  
  
  Création de la tâche de build
&lt;/h3&gt;

&lt;p&gt;Allez dans &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;CI/CD&lt;/em&gt; → &lt;em&gt;New task&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---JpeMPm5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/task-type.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---JpeMPm5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/task-type.webp" alt="Sélection du type de tâche" width="420" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choisissez &lt;em&gt;Bash Script&lt;/em&gt; comme type de tâche.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3xCL9_MW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/task-dev.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3xCL9_MW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/task-dev.webp" alt="Tâche d'intégration continue" width="420" height="1340"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Comme vous l'aurez deviné, cette tâche va permettre de compiler la branche &lt;em&gt;dev&lt;/em&gt; sur le serveur de build et envoyer une notification en cas d'échec.&lt;/p&gt;

&lt;p&gt;La &lt;em&gt;stratégie d'exécution multiple&lt;/em&gt; (&lt;em&gt;multiple execution policy&lt;/em&gt;): &lt;em&gt;chaînage intelligent&lt;/em&gt; (&lt;em&gt;smart chaining&lt;/em&gt;) choisie ici va permettre d'éviter de lancer des builds superflus.&lt;/p&gt;

&lt;p&gt;Prenons un exemple: vous avez un build qui dure une demi-heure et 3 pushs sont envoyés sur le dépôt git à 5 minutes d'intervalle.&lt;/p&gt;

&lt;p&gt;Au premier push, le build est lancé immédiatement.&lt;/p&gt;

&lt;p&gt;Au 2ème push, un 2ème build est mis en attente vu que le premier n'est pas encore terminé.&lt;/p&gt;

&lt;p&gt;Au 3ème push, on ne prévoit pas de faire de 3ème build car le 2ème est toujours en attente.&lt;/p&gt;

&lt;h3&gt;
  
  
  Création de la tâche de déploiement
&lt;/h3&gt;

&lt;p&gt;Pour gagner du temps, créez cette tâche à partir de celle de build via &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;CI/CD&lt;/em&gt; → &lt;em&gt;Buid myawesomeapp / dev&lt;/em&gt; → &lt;em&gt;Duplicate&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1WLV-VfY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/task-master.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1WLV-VfY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/task-master.webp" alt="Tâche de deploiement continue" width="420" height="1338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En y apportant les modifications suivantes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nous souhaitons recevoir une notification en fin de traitement dans tous les cas.&lt;/li&gt;
&lt;li&gt;Le contenu du script est mis à jour pour déployer à partir de la branche &lt;em&gt;master&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Ajout des webhooks
&lt;/h3&gt;

&lt;p&gt;Maintenant que nos tâches de build et de déploiement sont créées, nous allons ajouter pour chacune d'elle un webhook entrant qui pourra être appelé par notre forge git.&lt;/p&gt;

&lt;p&gt;Commençons avec la tâche de déploiement en allant dans &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;CI/CD&lt;/em&gt; → &lt;em&gt;Buid myawesomeapp / dev&lt;/em&gt; → &lt;em&gt;Incoming webhooks&lt;/em&gt; → &lt;em&gt;New webhook&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kI2BayEo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/webhook-type.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kI2BayEo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/webhook-type.webp" alt="Sélection du type de webhook" width="420" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choisissez &lt;em&gt;GitHub&lt;/em&gt; comme type de webhook.&lt;/p&gt;

&lt;p&gt;Lorsque GitHub appelera notre webhook suite à un push, il le fera quelque soit la branche concernée or notre tâche ne doit être exécuté que lorsque ce push concerne la branche &lt;em&gt;dev&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Pour résoudre ce problème, nous ajoutons une &lt;em&gt;condition d'exécution&lt;/em&gt; qui s'appliquera au payload JSON envoyé par GitHub. La tâche ne sera exécutée que si la condition est remplie (c-a-d le payload fait bien référence à la branche &lt;em&gt;dev&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xz16j3gx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/webhook-dev1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xz16j3gx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/webhook-dev1.webp" alt="Création du webhook dev 1" width="420" height="663"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Définissez au besoin un &lt;em&gt;secret&lt;/em&gt; qui permettra à Ctfreak de valider que les appels au webhook proviennent bien de GitHub et validez.&lt;/p&gt;

&lt;p&gt;Une fois le webhook créé, nous récupérons son URL pour le paramétrage de GitHub.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lFoSB2Al--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/webhook-dev2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lFoSB2Al--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/webhook-dev2.webp" alt="Création du webhook dev 2" width="420" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Au niveau de la configuration de votre dépôt GitHub, ajoutez le webhook que nous venons de créer en précisant bien:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Son URL et son &lt;em&gt;secret&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Que c'est le format JSON qui est attendu&lt;/li&gt;
&lt;li&gt;Que le webhook ne doit être appelé QUE pour les évènements push&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DB8UfL4H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/webhook-dev-github.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DB8UfL4H--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/webhook-dev-github.webp" alt="Paramétrage du webhook dev dans github" width="444" height="830"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Procédez de la même façon (création du webhook, référencement dans GitHub) pour la tâche de déploiement (pensez à bien faire référence à la branche &lt;em&gt;master&lt;/em&gt; dans la &lt;em&gt;condition d'exécution&lt;/em&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X85SZSaw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/webhook-master.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X85SZSaw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/webhook-master.webp" alt="Création du webhook master" width="420" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Test du pipeline
&lt;/h3&gt;

&lt;p&gt;Maintenant que tout est en place, voyons ce que provoque un git push sur la branche &lt;em&gt;dev&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q2BGDyAE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-dev.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q2BGDyAE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-dev.webp" width="420" height="629"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Suite à l'appel du webhook &lt;em&gt;GitPushDev&lt;/em&gt; la tâche de build a bien été exécutée.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jne8A6wl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-dev-execution.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jne8A6wl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-dev-execution.webp" width="420" height="587"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En cliquant sur l'icône en forme d'œil, nous avons les logs de l'exécution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YF2YCbRu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-dev-log.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YF2YCbRu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-dev-log.webp" width="500" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Même test avec un git push sur la branche &lt;em&gt;master&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IN1UJ4S4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-master.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IN1UJ4S4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-master.webp" width="420" height="608"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ici c'est le webhook &lt;em&gt;GitPushMaster&lt;/em&gt; qui a été appelé comme prévu.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bslwUX2p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-master-execution.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bslwUX2p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-master-execution.webp" width="420" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Lr7V4ctB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-master-log.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Lr7V4ctB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-master-log.webp" width="500" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Et le petit plus ici, c'est la réception d'une notification Discord pour confirmer que le déploiement s'est bien passé.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3C7O5Qz0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-master-notifier.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3C7O5Qz0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://pelle.link/fr/mise-en-place-pipeline-ci-cd-self-hosted/gitpush-master-notifier.webp" width="541" height="142"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Vous voilà avec un pipeline CI/CD opérationnel et suffisamment souple pour prendre en charge les workflows de build les plus complexes.&lt;/p&gt;

&lt;p&gt;En optant pour cette solution self-hosted et en plaçant le cœur du processus de build dans un script shell plutôt que dans un logiciel d'intégration continue comme Jenkins, GitHub Actions, ..., vous bénéficierez de plusieurs avantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vous ne serez pas contraint par les limites de ce dernier (version de git pas la plus récente, non prise en charge de certaines spécificités de votre build, limite du nombre de build journalier souscrit atteinte, ...).&lt;/li&gt;
&lt;li&gt;Si votre serveur de build est indisponible, vous pourrez encore effectuer un déploiement en urgence à partir d'un poste de développeur (croyez-moi, ça peut servir).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cicd</category>
      <category>devops</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>Setting up a self-hosted CI/CD pipeline</title>
      <dc:creator>Jean-Yves Pellé</dc:creator>
      <pubDate>Wed, 22 Mar 2023 23:27:49 +0000</pubDate>
      <link>https://dev.to/jypelle/setting-up-a-self-hosted-cicd-pipeline-14l6</link>
      <guid>https://dev.to/jypelle/setting-up-a-self-hosted-cicd-pipeline-14l6</guid>
      <description>&lt;p&gt;As a reminder, a CI/CD pipeline (or &lt;em&gt;Continuous Integration/Continuous Deployment&lt;/em&gt; pipeline) is a series of actions to be performed in order to consistently and reliably distribute a new version of a software.&lt;/p&gt;

&lt;p&gt;In this article, we will see how to set up a minimalistic CI/CD pipeline (automatically triggering builds and deployments upon &lt;em&gt;push&lt;/em&gt; events to a git repository) that is self-hosted and can be adapted according to your needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;We will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A build server that we will name &lt;em&gt;buildsrv&lt;/em&gt;:

&lt;ul&gt;
&lt;li&gt;Running Linux&lt;/li&gt;
&lt;li&gt;Accessible via SSH&lt;/li&gt;
&lt;li&gt;With &lt;em&gt;make&lt;/em&gt; and &lt;em&gt;git&lt;/em&gt; tools&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;A git forge hosting the sources of our &lt;em&gt;application&lt;/em&gt;. We will use &lt;a href="https://github.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; here, but any other forge capable of calling webhooks upon receiving push events on its repositories will also do the job.&lt;/li&gt;

&lt;li&gt;An instance of &lt;a href="https://ctfreak.com" rel="noopener noreferrer"&gt;Ctfreak&lt;/a&gt; with an administrator account (the &lt;em&gt;Free Edition&lt;/em&gt; will suffice). We will use &lt;a href="https://demo.ctfreak.com" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;a href="https://demo.ctfreak.com" rel="noopener noreferrer"&gt;https://demo.ctfreak.com&lt;/a&gt; here.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  The sources
&lt;/h2&gt;

&lt;p&gt;The sources of our application can be summarized in this &lt;em&gt;Makefile&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;build&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Build my awesome app"&lt;/span&gt;
&lt;span class="nl"&gt;deploy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Deploy my awesome app"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find them in &lt;a href="https://github.com/jypsoftware/myawesomeapp" rel="noopener noreferrer"&gt;this git repository&lt;/a&gt;, with the &lt;em&gt;master&lt;/em&gt; branch corresponding to the latest deployed version and the &lt;em&gt;dev&lt;/em&gt; branch to the version currently under development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Objectives
&lt;/h2&gt;

&lt;p&gt;Let’s review the objectives to be achieved by setting up our CI/CD pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For the &lt;strong&gt;C&lt;/strong&gt;ontinuous &lt;strong&gt;I&lt;/strong&gt;ntegration part:

&lt;ul&gt;
&lt;li&gt;Compile the sources (via the &lt;code&gt;make&lt;/code&gt; command) every time a developer pushes to the &lt;em&gt;dev&lt;/em&gt; branch.&lt;/li&gt;
&lt;li&gt;Receive a notification in case of failure.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;For the &lt;strong&gt;C&lt;/strong&gt;ontinuous &lt;strong&gt;D&lt;/strong&gt;eployment part:

&lt;ul&gt;
&lt;li&gt;Compile the sources and deploy (via the &lt;code&gt;make deploy&lt;/code&gt; command) every time a developer pushes to the &lt;em&gt;master&lt;/em&gt; branch.&lt;/li&gt;
&lt;li&gt;Receive a notification in case of success or failure.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;To do this, &lt;a href="https://ctfreak.com" rel="noopener noreferrer"&gt;Ctfreak&lt;/a&gt; will act as a gateway between the git forge and the build server.&lt;/p&gt;

&lt;p&gt;That is, every time a push is received by the forge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It will call a webhook defined in Ctfreak.&lt;/li&gt;
&lt;li&gt;Ctfreak will then connect via SSH to the build server to execute the appropriate &lt;code&gt;make&lt;/code&gt; command, then send a notification.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Build server configuration
&lt;/h2&gt;

&lt;p&gt;Connect to your build server and run the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ctfreakdemo@buildsrv:~&lt;span class="nv"&gt;$ &lt;/span&gt;git clone https://github.com/jypsoftware/myawesomeapp.git myawesomeapp.master
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Cloning into &lt;span class="s1"&gt;'myawesomeapp.master'&lt;/span&gt;...
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Enumerating objects: 15, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Counting objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;15/15&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Compressing objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;13/13&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Total 15 &lt;span class="o"&gt;(&lt;/span&gt;delta 2&lt;span class="o"&gt;)&lt;/span&gt;, reused 6 &lt;span class="o"&gt;(&lt;/span&gt;delta 1&lt;span class="o"&gt;)&lt;/span&gt;, pack-reused 0
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Receiving objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;15/15&lt;span class="o"&gt;)&lt;/span&gt;, 14.38 KiB | 3.59 MiB/s, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Resolving deltas: 100% &lt;span class="o"&gt;(&lt;/span&gt;2/2&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

ctfreakdemo@buildsrv:~&lt;span class="nv"&gt;$ &lt;/span&gt;git clone https://github.com/jypsoftware/myawesomeapp.git myawesomeapp.dev
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Cloning into &lt;span class="s1"&gt;'myawesomeapp.dev'&lt;/span&gt;...
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Enumerating objects: 15, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Counting objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;15/15&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Compressing objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;13/13&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; remote: Total 15 &lt;span class="o"&gt;(&lt;/span&gt;delta 2&lt;span class="o"&gt;)&lt;/span&gt;, reused 6 &lt;span class="o"&gt;(&lt;/span&gt;delta 1&lt;span class="o"&gt;)&lt;/span&gt;, pack-reused 0
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Receiving objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;15/15&lt;span class="o"&gt;)&lt;/span&gt;, 14.38 KiB | 7.19 MiB/s, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Resolving deltas: 100% &lt;span class="o"&gt;(&lt;/span&gt;2/2&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

ctfreakdemo@buildsrv:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;myawesomeapp.dev

ctfreakdemo@buildsrv:~/myawesomeapp.dev&lt;span class="nv"&gt;$ &lt;/span&gt;git checkout dev
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Branch &lt;span class="s1"&gt;'dev'&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;up to track remote branch &lt;span class="s1"&gt;'dev'&lt;/span&gt; from &lt;span class="s1"&gt;'origin'&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Switched to a new branch &lt;span class="s1"&gt;'dev'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, we have cloned the git repo 2 times (1 per branch), which will allow us to avoid potential conflicts if the builds of the &lt;em&gt;master&lt;/em&gt; and &lt;em&gt;dev&lt;/em&gt; branches were to run concurrently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ctfreak Configuration
&lt;/h2&gt;

&lt;p&gt;Log in to your Ctfreak instance with an admin account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding the build server
&lt;/h3&gt;

&lt;p&gt;Start by adding the SSH key to connect to the build server via &lt;em&gt;SSH Credential&lt;/em&gt; → &lt;em&gt;New SSH Credential&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fssh.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fssh.webp" alt="Adding the SSH key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then add the server itself via &lt;em&gt;Nodes&lt;/em&gt; → &lt;em&gt;Internal Nodes&lt;/em&gt; → &lt;em&gt;New node&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fnode.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fnode.webp" alt="Adding the server"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure that the &lt;em&gt;SSH Key for buildsrv&lt;/em&gt; key is selected as the &lt;em&gt;Credential&lt;/em&gt; (Ctfreak will use it to connect to the server), then validate to create the &lt;em&gt;buildsrv&lt;/em&gt; node.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the CI/CD project
&lt;/h3&gt;

&lt;p&gt;We are going to create a dedicated project to gather all the tasks related to our CI/CD pipeline.&lt;/p&gt;

&lt;p&gt;Go to &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;New Project&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fproject.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fproject.webp" alt="Project creation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Validate to create the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding a notifier
&lt;/h3&gt;

&lt;p&gt;Go to &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;CI/CD&lt;/em&gt; → &lt;em&gt;Notifiers&lt;/em&gt; → &lt;em&gt;New notifier&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fnotifier1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fnotifier1.webp" alt="Selecting the notifier type"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose &lt;em&gt;Discord&lt;/em&gt; as the notifier type (for example).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fnotifier2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fnotifier2.webp" alt="Editing the notifier"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Specify the discord webhook URL to call and validate.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fnotifier3.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fnotifier3.webp" alt="List of notifiers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A test notification can be sent by clicking on the send button (the one shaped like a paper airplane).&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the build task
&lt;/h3&gt;

&lt;p&gt;Go to &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;CI/CD&lt;/em&gt; → &lt;em&gt;New task&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Ftask-type.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Ftask-type.webp" alt="Selecting the task type"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose &lt;em&gt;Bash Script&lt;/em&gt; as the task type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Ftask-dev.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Ftask-dev.webp" alt="Continuous integration task"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you may have guessed, this task will compile the &lt;em&gt;dev&lt;/em&gt; branch on the build server and send a notification in case of failure.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;smart chaining&lt;/em&gt; multiple execution policy selected here will avoid launching unnecessary builds.&lt;/p&gt;

&lt;p&gt;Let’s take an example: you have a build that lasts half an hour and 3 pushes are sent to the git repository at 5-minute intervals.&lt;/p&gt;

&lt;p&gt;At the first push, the build is launched immediately.&lt;/p&gt;

&lt;p&gt;At the 2nd push, a 2nd build is put on hold since the first one is not yet finished.&lt;/p&gt;

&lt;p&gt;At the 3rd push, we don’t plan to do a 3rd build because the 2nd one is still on hold.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the deployment task
&lt;/h3&gt;

&lt;p&gt;To save time, create this task from the build task via &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;CI/CD&lt;/em&gt; → &lt;em&gt;Buid myawesomeapp / dev&lt;/em&gt; → &lt;em&gt;Duplicate&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Ftask-master.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Ftask-master.webp" alt="Continuous deployment task"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Making the following modifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We want to receive a notification in case of success or failure.&lt;/li&gt;
&lt;li&gt;The script content is updated to deploy from the &lt;em&gt;master&lt;/em&gt; branch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Adding webhooks
&lt;/h3&gt;

&lt;p&gt;Now that our build and deployment tasks are created, we will add an incoming webhook for each one that can be called by our git forge.&lt;/p&gt;

&lt;p&gt;Let’s start with the deployment task by going to &lt;em&gt;Projects&lt;/em&gt; → &lt;em&gt;CI/CD&lt;/em&gt; → &lt;em&gt;Buid myawesomeapp / dev&lt;/em&gt; → &lt;em&gt;Incoming webhooks&lt;/em&gt; → &lt;em&gt;New webhook&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fwebhook-type.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fwebhook-type.webp" alt="Selecting webhook type"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose &lt;em&gt;GitHub&lt;/em&gt; as the webhook type.&lt;/p&gt;

&lt;p&gt;When GitHub calls our webhook following a push, it will do so regardless of the branch involved, but our task should only be executed when this push concerns the &lt;em&gt;dev&lt;/em&gt; branch.&lt;/p&gt;

&lt;p&gt;To solve this issue, we add an &lt;em&gt;execution condition&lt;/em&gt; that will apply to the JSON payload sent by GitHub. The task will only be executed if the condition is met (i.e. the payload does indeed refer to the &lt;em&gt;dev&lt;/em&gt; branch).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fwebhook-dev1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fwebhook-dev1.webp" alt="Creating the dev webhook 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Define a &lt;em&gt;secret&lt;/em&gt; if necessary, which will allow Ctfreak to validate that calls to the webhook come from GitHub, and validate.&lt;/p&gt;

&lt;p&gt;Once the webhook is created, we retrieve its URL for GitHub configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fwebhook-dev2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fwebhook-dev2.webp" alt="Creating the dev webhook 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In your GitHub repository settings, add the webhook we just created, specifying:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its URL and &lt;em&gt;secret&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;That the expected format is JSON&lt;/li&gt;
&lt;li&gt;That the webhook should be called ONLY for push events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fwebhook-dev-github.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fwebhook-dev-github.webp" alt="Configuring the dev webhook in github"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Proceed in the same way (create the webhook, reference it in GitHub) for the deployment task (be sure to reference the &lt;em&gt;master&lt;/em&gt; branch in the &lt;em&gt;execution condition&lt;/em&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fwebhook-master.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fwebhook-master.webp" alt="Creating the master webhook"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pipeline testing
&lt;/h3&gt;

&lt;p&gt;Now that everything is set up, let’s see what happens when we do a git push on the &lt;em&gt;dev&lt;/em&gt; branch:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fgitpush-dev.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fgitpush-dev.webp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After calling the &lt;em&gt;GitPushDev&lt;/em&gt; webhook, the build task was executed successfully.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fgitpush-dev-execution.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fgitpush-dev-execution.webp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By clicking on the eye-shaped icon, we can see the execution logs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fgitpush-dev-log.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fgitpush-dev-log.webp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s do the same test with a git push on the &lt;em&gt;master&lt;/em&gt; branch:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fgitpush-master.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fgitpush-master.webp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This time, the GitPushMaster webhook was called as expected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fgitpush-master-execution.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fgitpush-master-execution.webp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fgitpush-master-log.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fgitpush-master-log.webp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the added bonus here is the receipt of a Discord notification confirming that the deployment was successful.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fgitpush-master-notifier.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpelle.link%2Fen%2Fsetting-up-self-hosted-ci-cd-pipeline%2Fgitpush-master-notifier.webp"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;You now have an operational CI/CD pipeline that is flexible enough to handle even the most complex build workflows.&lt;/p&gt;

&lt;p&gt;By opting for this self-hosted solution and placing the core of the build process in a shell script rather than in a continuous integration software like Jenkins, GitHub Actions, etc., you will benefit from several advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You will not be constrained by the limitations of the latter (older version of Git, certain specificities of your build not supported, limit of the number of daily builds reached, etc.).&lt;/li&gt;
&lt;li&gt;If your build server is unavailable, you can still perform an emergency deployment from a developer’s workstation (trust me, it can be useful).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cicd</category>
      <category>ctfreak</category>
      <category>devops</category>
      <category>selfhosted</category>
    </item>
    <item>
      <title>Introduction to web scraping with goquery</title>
      <dc:creator>Jean-Yves Pellé</dc:creator>
      <pubDate>Sat, 17 Apr 2021 16:27:40 +0000</pubDate>
      <link>https://dev.to/jypelle/introduction-to-web-scraping-with-goquery-4l9b</link>
      <guid>https://dev.to/jypelle/introduction-to-web-scraping-with-goquery-4l9b</guid>
      <description>&lt;p&gt;The use of an API is the preferred solution to retrieve data, but when it does not exist, is incomplete, or is subject to limitations that are too restrictive, &lt;em&gt;scraping&lt;/em&gt; the associated website can be a good alternative.&lt;/p&gt;

&lt;p&gt;What we call &lt;em&gt;web scraping&lt;/em&gt; is the collection (via the writing of bots called &lt;em&gt;scrapers&lt;/em&gt;) of targeted data present on a set of web pages. A &lt;em&gt;scraper&lt;/em&gt; is designed specifically for a given site, unlike a &lt;em&gt;crawler&lt;/em&gt; which will be more generalist (&lt;em&gt;Google Shopping&lt;/em&gt; uses &lt;em&gt;scrapers&lt;/em&gt; when &lt;em&gt;Google&lt;/em&gt; (aka the search engine) uses &lt;em&gt;crawlers&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;The uses are multiple (aggregation of ads from several job boards, competitive intelligence, …), and are not necessarily reserved to the professional world.&lt;/p&gt;

&lt;p&gt;We can indeed find several situations where writing a personal &lt;em&gt;scraper&lt;/em&gt; can have its interest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sending an email as soon as the homework has been updated on the children’s school blog&lt;/li&gt;
&lt;li&gt;Automatically setting the alarm clock 15 minutes earlier when a weather site forecasts ice the next morning&lt;/li&gt;
&lt;li&gt;Receive a notification when the price of an item on an e-commerce site falls below a certain threshold&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s illustrate this last use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking for a cheap Raspberry Pi 4
&lt;/h2&gt;

&lt;p&gt;Let’s take this &lt;a href="https://www.kubii.fr/cartes-raspberry-pi/2955-raspberry-pi-4-modele-b-8gb-0765756931199.html" rel="noopener noreferrer"&gt;Raspberry Pi 4B&lt;/a&gt; on &lt;a href="https://www.kubii.fr" rel="noopener noreferrer"&gt;www.kubii.fr&lt;/a&gt;, it is on sale for 83.99€ but we would like to wait until it drops below 75€ before ordering. As a price drop can quickly lead to a stockout, we might as well be warned quickly to avoid missing a good deal.&lt;/p&gt;

&lt;p&gt;The information we are looking for here is the price of the item:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9ytuauelzweg0uwpvkp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl9ytuauelzweg0uwpvkp.png" alt="raspberry-pi-4b-price" width="455" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s see how it looks in the HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"our_price_display"&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"offers"&lt;/span&gt; &lt;span class="na"&gt;itemscope=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="na"&gt;itemtype=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/Offer"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"availability"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://schema.org/InStock"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"our_price_display"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"price"&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"price"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"83.99"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;83,99 €&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt; TTC
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;itemprop=&lt;/span&gt;&lt;span class="s"&gt;"priceCurrency"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"EUR"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, we need to find an &lt;em&gt;anchor point&lt;/em&gt;, that is to say a criterion that will allow us to find the price (i.e. the attribute &lt;code&gt;content&lt;/code&gt; of the &lt;code&gt;span&lt;/code&gt; tag) for sure and without duplication.&lt;/p&gt;

&lt;p&gt;After analyzing the complete page, it turns out that the &lt;code&gt;itemprop="price"&lt;/code&gt; attribute is a good candidate (we only find one &lt;code&gt;span&lt;/code&gt; tag meeting this criterion).&lt;/p&gt;

&lt;p&gt;Our &lt;em&gt;scraper&lt;/em&gt; (in go) can now take shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/PuerkitoBio/goquery"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"strconv"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;goodPrice&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;

    &lt;span class="c"&gt;// Stay in the loop as long as we have not passed below our threshold&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;goodPrice&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c"&gt;// Loading of the product page&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://www.kubii.fr/cartes-raspberry-pi/2955-raspberry-pi-4-modele-b-8gb-0765756931199.html"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unable to load the product page, we will try again later: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Minute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unable to load the product page, we will try again later, status code: %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hour&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// HTML Parsing through goquery&lt;/span&gt;
        &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;goquery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDocumentFromReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Panicf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unable to parse HTML content: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Price retrieval&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;priceStr&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;priceStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"span[itemprop='price']"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Attr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Panicf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Price not found, scraper should be updated"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Price conversion&lt;/span&gt;
        &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;priceStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Panicf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unable to read the price, scraper should be updated"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;75&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%0.2f € it's still too expensive!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Only %0.2f €, go go go!!!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c"&gt;// TODO: Sending an email, an SMS, calling a discord webhook, triggering the alarm, flashing the connected bulbs, etc ...&lt;/span&gt;
            &lt;span class="n"&gt;goodPrice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// We try again in 30 minutes&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Minute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Done!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logic is quite simple&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every 30 minutes, we retrieve the price until it falls below 75€, if necessary we raise an alert.&lt;/li&gt;
&lt;li&gt;A basic (and perfectible!) management of the unavailability of the product page is applied, whether they are

&lt;ul&gt;
&lt;li&gt;due to us (network failure, … ) =&amp;gt; new attempt after 30 minutes&lt;/li&gt;
&lt;li&gt;or due to the site (maintenance, anti-bot defense, …) =&amp;gt; new attempt after 3 hours (no need to be stubborn)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;If the price is no longer retrievable, we assume that the site must have been updated and that our &lt;em&gt;anchor point&lt;/em&gt; must have jumped at the same time, so we must find a new one and update the &lt;em&gt;scraper&lt;/em&gt; accordingly.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Note that the search for the famous &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; tag containing our price is easily done via the excellent &lt;a href="https://github.com/PuerkitoBio/goquery" rel="noopener noreferrer"&gt;goquery&lt;/a&gt; library, which allows, as in javascript, to use CSS selectors to locate DOM elements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finally
&lt;/h2&gt;

&lt;p&gt;A small VPS, a NAS, an old Raspberry PI (hence the purchase of a new one!) or simply your PC (if you’re the kind of person who keeps it on 24 hours a day), and you’re ready to deploy your first &lt;em&gt;scraper&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Feel free to adapt this example to your needs but, word to the wise, be sure to stay reasonable on the frequency of requests, some sites do not like bots that monopolize too many resources on their server to the detriment of &lt;em&gt;real&lt;/em&gt; visitors. So you can understand them if they end up blacklisting your IP because you try to retrieve a price every 2 seconds instead of 30 minutes…&lt;/p&gt;

</description>
      <category>go</category>
      <category>scraping</category>
    </item>
  </channel>
</rss>
