<?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: thbe</title>
    <description>The latest articles on DEV Community by thbe (@thbe).</description>
    <link>https://dev.to/thbe</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%2F262334%2F3dfef449-cd48-4632-8b39-64bbf7a4ef1e.jpeg</url>
      <title>DEV Community: thbe</title>
      <link>https://dev.to/thbe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/thbe"/>
    <language>en</language>
    <item>
      <title>Show off on Apple M1 with assembly</title>
      <dc:creator>thbe</dc:creator>
      <pubDate>Sun, 19 Nov 2023 18:46:21 +0000</pubDate>
      <link>https://dev.to/thbe/show-off-on-apple-m1-with-assembly-2dc3</link>
      <guid>https://dev.to/thbe/show-off-on-apple-m1-with-assembly-2dc3</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published at &lt;a href="https://thbe.org/posts/2023/04/08/Show_off_on_Apple_M1_with_assembly.html"&gt;thbe.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Motivation
&lt;/h3&gt;

&lt;p&gt;Recently, I was a little bit bored on a grey and rainy November weekend without MotoGP or the like on television. So, I zapped through tons of YouTube videos and, out of a sudden, I stumbled over a video of a nerdy guy with glasses at whatever university talking about assembly and how cool and useful it is. Instantly I started to swallow in memories when I was a kid at the age of 15 or so, doing my first steps in "something with a computer". It was a time when no one cared about computers and when self-learning was the only way. Although it was a very small group, at least a few other guys were showing some interest in this new, futuristic thing. And what should I say, most of them looked like the nerds everyone was thinking of at this time. But there was one exception, a friend of mine, 2/ 3 years older, 1.90m, bodybuilder, professional swimmer and, what a shame, on top extremely smart. He was the first one with a 32-bit computer and he was a developer who programmed graphical stuff ... in assembly. It was a mixture of being jealous and impressed at the same point in time but I had to admit, he was far better than me. I tried to understand what he was programming but I was simply failing. So, I focused on what I was good at and forgot about that mysterious assembly.&lt;/p&gt;

&lt;p&gt;But as said in the introduction, I then saw the video of this nerdy guy and I thought, for god sake, why not? Second try so to speak, 35 years later. But better late than never. First I read through some tutorials and the first thing I recognized was, that assembly is so low-level, that it even depends on the processor. This already brought some challenges, I have a bunch of different machines at home, so what to use for my first steps? My decision in the end was my MacBook Air with the M1 CPU. The primary reason was that this is my development workstation and, as this exercise is only for educational reasons, I can compile and run my code directly on my laptop. And let's be frank, how can you show off more than programming in assembly on a modern MacBook? How cool is this to cite the nerdy guy ... &lt;em&gt;smile&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The first program
&lt;/h3&gt;

&lt;p&gt;Ok, it's time to start coding. As usual, let's start with the classical "Hello World!" or in this case "Hello ARM64!". Some remarks first, comments are written with two slashes at the beginning of a line. The program starting point, in this case _main needs to be defined in the linker section as well (see Makefile). So if you would like to derive from this naming convention, you need to adjust the Makefile as well. Having this mentioned, let's have a look at the complete program first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="nf"&gt;HelloWorld.s&lt;/span&gt;
&lt;span class="err"&gt;//&lt;/span&gt;
&lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="nl"&gt;Author:&lt;/span&gt;      &lt;span class="nf"&gt;Thomas&lt;/span&gt; &lt;span class="nv"&gt;Bendler&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;code@thbe.org&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="nl"&gt;Date:&lt;/span&gt;        &lt;span class="nf"&gt;Sun&lt;/span&gt; &lt;span class="nv"&gt;Nov&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;08&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;01&lt;/span&gt; &lt;span class="nv"&gt;CET&lt;/span&gt; &lt;span class="mi"&gt;2023&lt;/span&gt;
&lt;span class="err"&gt;//&lt;/span&gt;
&lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="nl"&gt;Description:&lt;/span&gt; &lt;span class="nf"&gt;Assembler&lt;/span&gt; &lt;span class="nv"&gt;program&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;print&lt;/span&gt; &lt;span class="s"&gt;"Hello World!"&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;stdout&lt;/span&gt;
&lt;span class="err"&gt;//&lt;/span&gt;
&lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="nl"&gt;Note:&lt;/span&gt;        &lt;span class="nf"&gt;X0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;X2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;Parameters&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;UNIX&lt;/span&gt; &lt;span class="nv"&gt;system&lt;/span&gt; &lt;span class="nv"&gt;calls&lt;/span&gt;
&lt;span class="err"&gt;//&lt;/span&gt;              &lt;span class="nf"&gt;X16&lt;/span&gt;   &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;Mach&lt;/span&gt; &lt;span class="nv"&gt;system&lt;/span&gt; &lt;span class="nv"&gt;call&lt;/span&gt; &lt;span class="nv"&gt;function&lt;/span&gt; &lt;span class="nv"&gt;number&lt;/span&gt;
&lt;span class="err"&gt;//&lt;/span&gt;

&lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="nf"&gt;Provide&lt;/span&gt; &lt;span class="nv"&gt;program&lt;/span&gt; &lt;span class="nv"&gt;starting&lt;/span&gt; &lt;span class="nv"&gt;address&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;linker&lt;/span&gt;
&lt;span class="nf"&gt;.global&lt;/span&gt; &lt;span class="nv"&gt;_main&lt;/span&gt;
&lt;span class="nf"&gt;.align&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

&lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="nf"&gt;Setup&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;parameters&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;hello&lt;/span&gt; &lt;span class="nv"&gt;world&lt;/span&gt; &lt;span class="nv"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;execute&lt;/span&gt; &lt;span class="nv"&gt;it&lt;/span&gt;
&lt;span class="nl"&gt;_main:&lt;/span&gt;  &lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="nv"&gt;X0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;          &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nv"&gt;Use&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;StdOut&lt;/span&gt;
        &lt;span class="nf"&gt;adr&lt;/span&gt; &lt;span class="nv"&gt;X1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="nv"&gt;msg&lt;/span&gt;         &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nv"&gt;Print&lt;/span&gt; &lt;span class="nv"&gt;Hello&lt;/span&gt; &lt;span class="nv"&gt;World&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;
        &lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="nv"&gt;X2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="nv"&gt;msg_len&lt;/span&gt;     &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nv"&gt;Length&lt;/span&gt; &lt;span class="nv"&gt;of&lt;/span&gt; &lt;span class="nv"&gt;Hello&lt;/span&gt; &lt;span class="nv"&gt;World&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;
        &lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="nv"&gt;X16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;          &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nv"&gt;MacOS&lt;/span&gt; &lt;span class="nv"&gt;write&lt;/span&gt; &lt;span class="nv"&gt;system&lt;/span&gt; &lt;span class="nv"&gt;call&lt;/span&gt;
        &lt;span class="nf"&gt;svc&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;               &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nv"&gt;Call&lt;/span&gt; &lt;span class="nv"&gt;MacOS&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;output&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;

&lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="nf"&gt;Setup&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;parameters&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;program&lt;/span&gt; &lt;span class="nv"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;execute&lt;/span&gt; &lt;span class="nv"&gt;it&lt;/span&gt;
        &lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="nv"&gt;X0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;          &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nv"&gt;Use&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;code&lt;/span&gt;
        &lt;span class="nf"&gt;mov&lt;/span&gt; &lt;span class="nv"&gt;X16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;          &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nv"&gt;MacOS&lt;/span&gt; &lt;span class="nv"&gt;terminate&lt;/span&gt; &lt;span class="nv"&gt;program&lt;/span&gt;
        &lt;span class="nf"&gt;svc&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;               &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="nv"&gt;Call&lt;/span&gt; &lt;span class="nv"&gt;MacOS&lt;/span&gt; &lt;span class="nv"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;terminate&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;program&lt;/span&gt;

&lt;span class="err"&gt;//&lt;/span&gt; &lt;span class="nf"&gt;Define&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;Hello&lt;/span&gt; &lt;span class="nv"&gt;World&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;calculate&lt;/span&gt; &lt;span class="nv"&gt;length&lt;/span&gt;
&lt;span class="nl"&gt;msg:&lt;/span&gt;    &lt;span class="nf"&gt;.ascii&lt;/span&gt;  &lt;span class="err"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ARM64&lt;/span&gt;&lt;span class="err"&gt;!\&lt;/span&gt;&lt;span class="nv"&gt;n&lt;/span&gt;&lt;span class="err"&gt;"&lt;/span&gt;
        &lt;span class="nf"&gt;msg_len&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;.&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;msg&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As this code significantly differs from other programming languages like, for example, Python, let's dig deeper into this example and analyze it step by step. The first directive ".global _main" defines the starting point of the assembly program. The second directive is an optimization directive. This is ".align X" where X is a number that must be a power of 2. For example 2, 4, 8, 16, and so on. The directive allows you to enforce alignment of the instruction or data immediately after the directive, on a memory address that is a multiple of the value X. The reason is that some instructions are simply executed faster if they are aligned on a byte boundary.&lt;/p&gt;

&lt;p&gt;As said, the real program starts at "_main:". First, we move the number 1 into the first parameter/ result register named X0. There are 8 of them in total (X0 till X7), but we only use X0, X1 and X2 for the example program. The 1 in the first register tells the machine to output the following registers. In the second register, the text string defined in msg is addressed whereas in the third register, the length of the text string is stored. The output text and the length of the test are defined in the section "msg:". The first line is the text, the second line is the calculation of the text length. The last register that is used, is X16. This is a temporary intra-procedure-call register which contains the action that should be executed. The number 16 that is written to the X16 register means, to write a system call to stdout as defined in X0. With the information in the four registers, the registers are then executed with the "svc" command.&lt;/p&gt;

&lt;p&gt;The second block terminates the program. First, the return code of the program after termination is written into X0. The 1 written to X16 is the number that terminates the program. As already seen in block one, the "svc" command executes the program block.&lt;/p&gt;

&lt;p&gt;So, that's effectively it. This is the program that outputs "Hello, ARM64!" using assembly and the way, you can show off ... &lt;em&gt;smile&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The build
&lt;/h3&gt;

&lt;p&gt;Although one could enter the compiler and linker commands manually every time, it's much more handy to create a Makefile that does the required steps for you. To build the HelloWorld example in the chapter above, the following Makefile do the trick:&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="c"&gt;# Makefile
#
# Author:      Thomas Bendler &amp;lt;code@thbe.org&amp;gt;
# Date:        Sun Nov 12 12:08:01 CET 2023
#
# Description: GNU Makefile that compiles and link the HelloWorld Assembler program
#
# Note:        You need to use [tab], space is not allowed
#
&lt;/span&gt;
&lt;span class="nl"&gt;HelloWorld&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;HelloWorld.o&lt;/span&gt;
    ld &lt;span class="nt"&gt;-o&lt;/span&gt; HelloWorld HelloWorld.o &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-macos_version_min&lt;/span&gt; 14.0.0 &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-lSystem&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-syslibroot&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;xcrun &lt;span class="nt"&gt;-sdk&lt;/span&gt; macosx &lt;span class="nt"&gt;--show-sdk-path&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-e&lt;/span&gt; _main &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="nt"&gt;-arch&lt;/span&gt; arm64

&lt;span class="nl"&gt;HelloWorld.o&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;HelloWorld.s&lt;/span&gt;
    as &lt;span class="nt"&gt;-o&lt;/span&gt; HelloWorld.o HelloWorld.s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Remark:&lt;/strong&gt; Within the Makefile, it is necessary to use tabs for any kind of indention. Using spaces will cause "make" to fail.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To use the Makefile, the make command is required 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;make &lt;span class="nt"&gt;-B&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>assembly</category>
      <category>m1</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Howto write enterprise-grade shell scripts</title>
      <dc:creator>thbe</dc:creator>
      <pubDate>Mon, 20 Apr 2020 20:09:59 +0000</pubDate>
      <link>https://dev.to/thbe/howto-write-enterprise-grade-shell-scripts-1f17</link>
      <guid>https://dev.to/thbe/howto-write-enterprise-grade-shell-scripts-1f17</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published at &lt;a href="https://thbe.org/posts/2020/04/17/Howto_write_enterprise_grade_shell_scripts.html"&gt;thbe.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One of the things I've done most in the last two decades in the IT business was writing shell scripts. Was it because to combine and condense different data sources or was it to get a custom reporting or to encrypt/ decrypt data. In the end, there was a wide range of different use cases where a small script helped to achieve the outcome I was seeking for. Often these scripts were used to support parts of global processes with high financial or legal impact on the business. There is nothing wrong with this as long as everything is working. Unfortunately, the only constant in the IT business is the change and those scripts that have done their job for years silently in the background stop out of the sudden to work. In a well managed IT you know where those scripts are being used, you know the purpose of each script and you can easily adapt them to the new environment. At least, this is the theory, let's have a look at a real-life example:&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/sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt;
&lt;span class="nv"&gt;TC8&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ps &lt;span class="nt"&gt;-ef&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;tomcat&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TC8&lt;/span&gt;&lt;span class="s2"&gt;"&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;
    /home/portal/tc8/bin/catalina.sh start &amp;amp;
    &lt;span class="nb"&gt;exit
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time to talk about enterprise-grade shell scripts. I'm pretty sure the script has done its job when it was deployed to the production machine, but, does anyone know what the purpose of this script is? With some experience in this area, you will recognize that the script is used to do something with a Tomcat instance. Based on the path I would assume it's a JAVA portal running on a Tomcat in the namespace of the user "portal". But why does it use the ampersand and the exit command and why is it only sometimes working? As you already noticed, this script is an example of how enterprise-grade scripts should not look like.&lt;/p&gt;

&lt;h3&gt;
  
  
  Header
&lt;/h3&gt;

&lt;p&gt;This brings us to the question, how should an enterprise-grade shell script look like? First and most obvious, some additional context would be helpful. What is the purpose of that script, who wrote it and when? A typical header that I use in my scripts looks 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;&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Author:       Thomas Bendler &amp;lt;code@thbe.org&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;# Date:         Fri Apr 17 22:48:34 CEST 2020&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Note:         To debug the script change the shebang to: /usr/bin/env bash -vx&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Prerequisite: This release needs a shell that could handle functions.&lt;/span&gt;
&lt;span class="c"&gt;#               If shell is not able to handle functions, remove the&lt;/span&gt;
&lt;span class="c"&gt;#               error section.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Release:      1.0.0&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# ChangeLog:    v0.1.0 - Initial release&lt;/span&gt;
&lt;span class="c"&gt;#               v0.9.0 - Prepare go-live&lt;/span&gt;
&lt;span class="c"&gt;#               v1.0.0 - Production go-live&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Purpose:      Watchdog for a Tomact 8 instance&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without reading a single line of code, you know who has written the script and when the script was written. You know which version of the script is deployed, you know the version history for the script, the purpose and so on and so forth. Depending on the processes in the company it could be handy to add additional information like the URL to the GIT repository or which deployment pipeline has been used or approvals or other information. Whatever is used, it should be used in every script and the structure of the information should be equal in every script.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shebang
&lt;/h3&gt;

&lt;p&gt;A script could contain different technologies. It could contain a shell script, a Python script, a Ruby script or something else. To tell the operating system which kind of script it is, the so-called "Shebang" is used. The "Shebang" is the hashtag plus an exclamation mark followed by the interpreter that should be used to execute the script. In the real-life, you'll find lots of "Shebang" like 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;#!/usr/bin/local/ksh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we see that someone installed manually a Korn-Shell on that box (because it's in /usr/local/bin/) and that the script uses this shell. This works in principle but it's not the way how an enterprise-grade shell script should look like. Imagine that at some point in time we want to deploy the script on another box. On this box, the Korn-Shell was installed through the package manager. This usually means that the path to the shell is in this case /usr/bin/ksh. Before we can deploy the script we need to change the "Shebang". The better approach to tackle this is to use env binary instead. You'll find env on every box under /usr/bin/env. As a parameter env will use the command that should be used for the Shebang. So assuming I would like to use the Korn-Shell, the Shebang would be:&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;#!/usr/bin/env ksh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Env knows the path to ksh and will call the correct binary for the "Shebang". This will also work for Perl/ Python or other script interpreters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Script behavior
&lt;/h3&gt;

&lt;p&gt;Regardless of the programming language, it's always beneficial if you know how a script behaves. When it stops, when it aborts, what is allowed, whatnot, and so on and so forth. This is controlled by the set command in the script and should be equally set in all scripts:&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;### General script behavior ###&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the end, this is the combination of three arguments which are explained here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-e           stops the script after the first command has failed
-u           stops the script after the first unset variable has been found
-o pipefail  stops the script after the first piped command has failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Error handling
&lt;/h3&gt;

&lt;p&gt;A central element of an enterprise-grade shell script is a proper error handling. Errors can happen and they happen unfortunately more often than anybody want so it's key to deal with them in a predictable way. I use a function called error_handling() to achieve this:&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;### Error handling ###&lt;/span&gt;
error_handling&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RETURN_CODE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &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;echo_verbose &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SCRIPT_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; successfull!"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;echo_error &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SCRIPT_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; aborted, reason: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXIT_REASON&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; script_usage
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RETURN_CODE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s2"&gt;"error_handling"&lt;/span&gt; EXIT HUP INT QUIT TERM
&lt;span class="nv"&gt;RETURN_CODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nv"&gt;EXIT_REASON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Finished!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The way of working for this function is quite simple. Whenever one of the signals is raised that you see at the end of the trap line, the function error_handling() is called. You also see an important rule for writing enterprise-grade ready scripts, the use of meaningful names for functions, variables, constants, and other script components. The purpose of the variable ${SCRIPT_NAME} is much easier to understand compared to ${0}.&lt;/p&gt;

&lt;h3&gt;
  
  
  Output
&lt;/h3&gt;

&lt;p&gt;You might have also noticed, that I use additional functions in the error handling function:&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;### Print out information if in verbose mode ###&lt;/span&gt;
echo_verbose&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;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;ARGUMENT_VERBOSE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 1 &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;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;### Print out information on error channel ###&lt;/span&gt;
echo_error&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good enterprise-grade scripts run silent by default and start being verbose if called with the respective option set. The second function takes care that the output in case of an error is redirected to STDERR instead of STDOUT. This enables calling programs to separate the normal script output from the error messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dry run
&lt;/h3&gt;

&lt;p&gt;Another good practice is to offer the possibility of a dry 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="c"&gt;### Don't execute commands if in dry run mode ###&lt;/span&gt;
execute_command&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;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;ARGUMENT_DRYRUN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 1 &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;"Command to execute: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;COMMAND_RETURN_CODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;COMMAND_RETURN_CODE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nv"&gt;COMMAND_RETURN_CODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the execute command function is used and the script was called with the dry run flag, the command is only displayed and not executed. Unfortunately, this function isn't that straight forward and requires some more thinking before widely use it especially when using pipes or redirects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;Now where we have covered the execution helpers, we need to provide a function for the script usage:&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;### Print out usage information ###&lt;/span&gt;
script_usage&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOT&lt;/span&gt;&lt;span class="sh"&gt;
usage:   &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SCRIPT_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt; [-n] [-v] [-h]
example: ./&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SCRIPT_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt; -v

arguments (optional):
-n:      Dry run
-v:      Be verbose
-h:      Print this help
&lt;/span&gt;&lt;span class="no"&gt;EOT
&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Defaults
&lt;/h3&gt;

&lt;p&gt;With all the functions in place, we can start with the script code itself. The first thing that needs to be done is to initialize the variables because otherwise, the script could fail because of uninitialized variables:&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;### Default script variables ###&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LANG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C
&lt;span class="nv"&gt;ARGUMENT_DRYRUN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;ARGUMENT_VERBOSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nv"&gt;SCRIPT_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting the language variables to an explicit value is as well a common good shell scriptwriting practice. This makes the output of called programs much more predictable which is beneficial if the output is used for other actions as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Arguments
&lt;/h3&gt;

&lt;p&gt;The next step takes care of the arguments that might have been passed to the script during execution:&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;### Get the arguments used at script execution ###&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="k"&gt;${#}&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="nt"&gt;-n&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;ARGUMENT_VERBOSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;ARGUMENT_DRYRUN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="nt"&gt;-v&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nt"&gt;--verbose&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;ARGUMENT_VERBOSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="nt"&gt;-h&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nt"&gt;--help&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    script_usage&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;esac&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;shift
&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code snippet is pretty straight forward, it checks if arguments exist and process each argument passed to the script.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;The last part before the script code starts is the check if the prerequisites has been matched:&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;### Check script prerequisite ###&lt;/span&gt;
&lt;span class="nv"&gt;TOMCAT_CATALINA_SCRIPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/portal/tc8/bin/catalina.sh
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOMCAT_CATALINA_SCRIPT&lt;/span&gt;&lt;span class="k"&gt;}&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="nv"&gt;RETURN_CODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
  &lt;span class="nv"&gt;EXIT_REASON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"The Tomcat catalina script (&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOMCAT_CATALINA_SCRIPT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) is not executable, aborting!"&lt;/span&gt;
  &lt;span class="nb"&gt;exit
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Main script logic
&lt;/h3&gt;

&lt;p&gt;Now as we have everything covered and in place it's time to implement the script logic:&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;### Check if Tomcat is running and start Tomcat if stopped ###&lt;/span&gt;
&lt;span class="nv"&gt;TOMCAT_STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ps &lt;span class="nt"&gt;-ef&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"[t]omcat "&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"no"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
echo_verbose &lt;span class="s2"&gt;"Is Tomcat running: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOMCAT_STATUS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;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;TOMCAT_STATUS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"no"&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;echo_verbose &lt;span class="s2"&gt;"Try to start Tomcat ..."&lt;/span&gt;
  execute_command &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOMCAT_CATALINA_SCRIPT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; start
  &lt;span class="k"&gt;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;COMMAND_RETURN_CODE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &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="nv"&gt;RETURN_CODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;COMMAND_RETURN_CODE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
    &lt;span class="nv"&gt;EXIT_REASON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Could not analyze &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOGFILE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, aborting!"&lt;/span&gt;
    &lt;span class="nb"&gt;exit
  &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;As I mentioned at the beginning of the post, one of the questions was, why does the script sometimes work and sometimes not. The reason for this is the way the script checks if a Tomcat process exists or not. If you do a simple grep on tomcat, grep will find occasionally his own process in the process list that seeks for tomcat. If you instead use regular expressions with grep to match the Tomcat process name, the grep command will be excluded from the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  The enterprise-grade shell script
&lt;/h3&gt;

&lt;p&gt;Now we can put everything together and deploy the enterprise-grade shell script in a productive environment with high financial impact without having the fear that we operate hidden timebombs that create severe risks in case of failures, especially if developers had left the company meanwhile:&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;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Author:       Thomas Bendler &amp;lt;code@thbe.org&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;# Date:         Fri Apr 17 22:48:34 CEST 2020&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Note:         To debug the script change the shebang to: /usr/bin/env bash -vx&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Prerequisite: This release needs a shell that could handle functions.&lt;/span&gt;
&lt;span class="c"&gt;#               If shell is not able to handle functions, remove the&lt;/span&gt;
&lt;span class="c"&gt;#               error section.&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Release:      1.0.0&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# ChangeLog:    v0.1.0 - Initial release&lt;/span&gt;
&lt;span class="c"&gt;#               v0.9.0 - Prepare go-live&lt;/span&gt;
&lt;span class="c"&gt;#               v1.0.0 - Production go-live&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Purpose:      Watchdog for a Tomact 8 instance&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;

&lt;span class="c"&gt;### General script behavior ###&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="c"&gt;### Error handling ###&lt;/span&gt;
error_handling&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RETURN_CODE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &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;echo_verbose &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SCRIPT_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; successfull!"&lt;/span&gt;
  &lt;span class="k"&gt;else
    &lt;/span&gt;echo_error &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SCRIPT_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; aborted, reason: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;EXIT_REASON&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; script_usage
  &lt;span class="k"&gt;fi
  &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;RETURN_CODE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s2"&gt;"error_handling"&lt;/span&gt; EXIT HUP INT QUIT TERM
&lt;span class="nv"&gt;RETURN_CODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nv"&gt;EXIT_REASON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Finished!"&lt;/span&gt;

&lt;span class="c"&gt;### Print out information if in verbose mode ###&lt;/span&gt;
echo_verbose&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;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;ARGUMENT_VERBOSE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 1 &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;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;### Print out information on error channel ###&lt;/span&gt;
echo_error&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;### Don't execute commands if in dry run mode ###&lt;/span&gt;
execute_command&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;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;ARGUMENT_DRYRUN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 1 &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;"Command to execute: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;COMMAND_RETURN_CODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;COMMAND_RETURN_CODE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nv"&gt;COMMAND_RETURN_CODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

&lt;span class="c"&gt;### Print out usage information ###&lt;/span&gt;
script_usage&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOT&lt;/span&gt;&lt;span class="sh"&gt;
usage:   &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SCRIPT_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt; [-n] [-v] [-h]
example: ./&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SCRIPT_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt; -v

arguments (optional):
-n:      Dry run
-v:      Be verbose
-h:      Print this help
&lt;/span&gt;&lt;span class="no"&gt;EOT
&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;### Default script variables ###&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LANG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C
&lt;span class="nv"&gt;ARGUMENT_DRYRUN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;ARGUMENT_VERBOSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="nv"&gt;SCRIPT_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;### Get the arguments used at script execution ###&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="k"&gt;${#}&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="nt"&gt;-n&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;ARGUMENT_VERBOSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;ARGUMENT_DRYRUN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="nt"&gt;-v&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nt"&gt;--verbose&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nv"&gt;ARGUMENT_VERBOSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="nt"&gt;-h&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nt"&gt;--help&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    script_usage&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;esac&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;shift
&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;### Check script prerequisite ###&lt;/span&gt;
&lt;span class="nv"&gt;TOMCAT_CATALINA_SCRIPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/portal/tc8/bin/catalina.sh
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOMCAT_CATALINA_SCRIPT&lt;/span&gt;&lt;span class="k"&gt;}&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="nv"&gt;RETURN_CODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
  &lt;span class="nv"&gt;EXIT_REASON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"The Tomcat catalina script (&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOMCAT_CATALINA_SCRIPT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) is not executable, aborting!"&lt;/span&gt;
  &lt;span class="nb"&gt;exit
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;### Check if Tomcat is running and start Tomcat if stopped ###&lt;/span&gt;
&lt;span class="nv"&gt;TOMCAT_STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ps &lt;span class="nt"&gt;-ef&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"[t]omcat "&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"no"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
echo_verbose &lt;span class="s2"&gt;"Is Tomcat running: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOMCAT_STATUS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;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;TOMCAT_STATUS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"no"&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;echo_verbose &lt;span class="s2"&gt;"Try to start Tomcat ..."&lt;/span&gt;
  execute_command &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TOMCAT_CATALINA_SCRIPT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; start
  &lt;span class="k"&gt;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;COMMAND_RETURN_CODE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &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="nv"&gt;RETURN_CODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;COMMAND_RETURN_CODE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
    &lt;span class="nv"&gt;EXIT_REASON&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Could not analyze &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOGFILE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, aborting!"&lt;/span&gt;
    &lt;span class="nb"&gt;exit
  &lt;/span&gt;&lt;span class="k"&gt;fi
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Final thoughts
&lt;/h3&gt;

&lt;p&gt;Let's conclude this exercise of writing enterprise-grade shell scripts with some thoughts. The first question I normally get, is this over-engineered? It pretty much depends, it's finally all about risk, financial impact, maintainability and so on and so forth. Templates and practices like this are used in environments where the flawless use of scripts to support processes is key. Where it's not acceptable to pause a business for a week because a script doesn't work anymore and no one is able to fix the script. These are the typical scenarios where it is worth the effort to standardized shell scripts as shown and that require those structures before something goes into production. In hobby environments, it's not necessarily required but even there it becomes handy once you have to change a script you've written years before. However you do it finally, enjoy coding and happy scripting!&lt;/p&gt;

</description>
      <category>linux</category>
      <category>shell</category>
      <category>howto</category>
    </item>
    <item>
      <title>Lean ZSH terminal</title>
      <dc:creator>thbe</dc:creator>
      <pubDate>Sat, 22 Feb 2020 12:50:34 +0000</pubDate>
      <link>https://dev.to/thbe/lean-zsh-terminal-1ll8</link>
      <guid>https://dev.to/thbe/lean-zsh-terminal-1ll8</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published at &lt;a href="https://www.thbe.org/posts/2020/01/02/Lean_zsh_terminal.html"&gt;thbe.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the past two terminal posts (&lt;a href="https://dev.to/thbe/enhance-your-macos-terminal-25o7"&gt;&lt;strong&gt;Enhance your macOS terminal&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://dev.to/thbe/enhance-your-macos-terminal-p10k-1g3m"&gt;&lt;strong&gt;Enhance your macOS terminal - p10k&lt;/strong&gt;&lt;/a&gt;) I showed how a fully-fledged terminal can look like. In this post, I'll concentrate on a lean terminal I'll for example use on my Raspberry PI boxes. This is how it should look like in the end:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yNCck8VO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/r5snk3i5p45t1yjjk5ow.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yNCck8VO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/r5snk3i5p45t1yjjk5ow.png" alt="Customized lean Terminal with oh-my-zsh" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My Raspberry PIs are normally equipped with &lt;a href="https://www.raspberrypi.org/downloads/raspbian/"&gt;&lt;strong&gt;Raspbian&lt;/strong&gt;&lt;/a&gt; in the minimal version. The standard shell on Debian is normally the &lt;em&gt;bash&lt;/em&gt; shell, so we need to change it to ZSH first:&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get clean all
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nb"&gt;install &lt;/span&gt;zsh zsh-autosuggestions zsh-syntax-highlighting
&lt;span class="nv"&gt;$ &lt;/span&gt;chsh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;which zsh&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt; /etc/passwd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last command should end with something like &lt;em&gt;:/usr/bin/zsh&lt;/em&gt; indicating that ZSH is now the primary shell of the account. As I'm looking for a lean configuration I will only install &lt;a href="https://github.com/ohmyzsh/ohmyzsh"&gt;&lt;strong&gt;oh-my-zsh&lt;/strong&gt;&lt;/a&gt; and a third party theme.&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="nv"&gt;$ &lt;/span&gt;sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, the third party theme needs to be installed. I used typewritten by &lt;a href="https://github.com/reobin/typewritten"&gt;&lt;strong&gt;reobin&lt;/strong&gt;&lt;/a&gt; for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/reobin/typewritten.git &lt;span class="nv"&gt;$ZSH_CUSTOM&lt;/span&gt;/themes/typewritten
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once everything is installed and in place, the components need to be configured. This is done primarily through the &lt;em&gt;.zshrc&lt;/em&gt; configuration file. Here is the one that creates the configuration from the screenshot above:&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;# zsh configuration file&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Author: Thomas Bendler &amp;lt;code@thbe.org&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;# Date:   Thu Jan  2 17:53:48 GMT 2020&lt;/span&gt;

&lt;span class="c"&gt;# Add local sbin to $PATH.&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/sbin:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Path to the oh-my-zsh installation.&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ZSH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.oh-my-zsh"&lt;/span&gt;

&lt;span class="c"&gt;# Use case-sensitive completion.&lt;/span&gt;
&lt;span class="nv"&gt;CASE_SENSITIVE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;

&lt;span class="c"&gt;# Define how often to auto-update (in days).&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;UPDATE_ZSH_DAYS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7

&lt;span class="c"&gt;# Enable command auto-correction.&lt;/span&gt;
&lt;span class="nv"&gt;ENABLE_CORRECTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;

&lt;span class="c"&gt;# Display red dots whilst waiting for completion.&lt;/span&gt;
&lt;span class="nv"&gt;COMPLETION_WAITING_DOTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;

&lt;span class="c"&gt;# Configure history stamp format&lt;/span&gt;
&lt;span class="nv"&gt;HIST_STAMPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"yyyy-mm-dd"&lt;/span&gt;

&lt;span class="c"&gt;# Plugin configuration&lt;/span&gt;
&lt;span class="c"&gt;# Standard plugins can be found in ~/.oh-my-zsh/plugins/*&lt;/span&gt;
&lt;span class="c"&gt;# Custom plugins may be added to ~/.oh-my-zsh/custom/plugins/&lt;/span&gt;
&lt;span class="c"&gt;# Add wisely, as too many plugins slow down shell startup.&lt;/span&gt;
&lt;span class="nv"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;
  ansible
  bundler
  colored-man-pages
  colorize
  common-aliases
  debian
  git
  nmap
  zsh-navigation-tools
  zsh_reload
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Use typewritten theme with multiline enabled&lt;/span&gt;
&lt;span class="nv"&gt;TYPEWRITTEN_MULTILINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="nv"&gt;ZSH_THEME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"typewritten/typewritten"&lt;/span&gt;

&lt;span class="c"&gt;# Load zsh extensions&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh

&lt;span class="c"&gt;# Load oh-my-zsh framework&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/.oh-my-zsh/oh-my-zsh.sh &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/.oh-my-zsh/oh-my-zsh.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>lean</category>
      <category>zsh</category>
      <category>ohmyzsh</category>
      <category>raspberrypi</category>
    </item>
    <item>
      <title>Harmonize basic set up with Ansible, part 3</title>
      <dc:creator>thbe</dc:creator>
      <pubDate>Tue, 14 Jan 2020 14:27:44 +0000</pubDate>
      <link>https://dev.to/thbe/harmonize-basic-set-up-with-ansible-part-3-4pao</link>
      <guid>https://dev.to/thbe/harmonize-basic-set-up-with-ansible-part-3-4pao</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published at &lt;a href="https://www.thbe.org/posts/2020/01/05/Harmonize_basic_set_up_with_Ansible-Part_3.html"&gt;thbe.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The last open part is, how does this look in action and where do I find the code. Before we start and in case you missed it, here are the previous two posts about this topic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/thbe/harmonize-basic-set-up-with-ansible-part-1-eb"&gt;&lt;strong&gt;Harmonize basic set up with Ansible, 1/3&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/thbe/harmonize-basic-set-up-with-ansible-part-2-27fg"&gt;&lt;strong&gt;Harmonize basic set up with Ansible 2/3&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, I set up some minimal virtual machines that should act as target nodes for this demonstration. I used different distributions to demonstrate how flexible Ansible could be configured:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wja9t6cY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/uqag0en8n6id0oz9s08b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wja9t6cY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/uqag0en8n6id0oz9s08b.png" alt="List of virtual target hosts" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next step I will execute the configuration 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="nv"&gt;$ &lt;/span&gt;ansible-playbook &lt;span class="nt"&gt;-i&lt;/span&gt; production.yml common.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ansible now connects to target nodes and start its magic. It will go through the list of defined tasks and execute them. When Ansible finishes, the screen will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uj2NEHNs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/1pn5jd50twl5t0qh4wjd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uj2NEHNs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/1pn5jd50twl5t0qh4wjd.png" alt="Result of Ansible run" width="800" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ansible in fact does, in the end, two different things, it set the desired state and it checks the desired state. This is quite important because it means if the Ansible detects a wrong state, it will correct it to the desired state. It protects the IT landscape against configuration drifts (small fixes or enhancements performed on one node but not the rest of the nodes).&lt;/p&gt;

&lt;p&gt;This finally means that all nodes look pretty much the same, even on different distributions (not everything is equal, Debian distributions still use &lt;code&gt;dpkg&lt;/code&gt; instead of &lt;code&gt;rpm&lt;/code&gt; etc., but most of the things will be the same):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LkZ5j2L9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/gla18sztxrvmiitck0jd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LkZ5j2L9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/gla18sztxrvmiitck0jd.png" alt="Harmonized target nodes" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is it for the moment, I've shown how the role-based approach for Ansible look like and what you can achieve. If we look forward we still need to extend the role-based approach with roles for specific applications like web-servers, but I hope the basic idea is clear. If you would like to see the full Ansible code that I had used for this demo, you need to check out my corresponding &lt;code&gt;Github&lt;/code&gt; project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/thbe/ansible-demo"&gt;https://github.com/thbe/ansible-demo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>automation</category>
      <category>ansible</category>
      <category>redhat</category>
    </item>
    <item>
      <title>Harmonize basic set up with Ansible, part 2</title>
      <dc:creator>thbe</dc:creator>
      <pubDate>Sun, 12 Jan 2020 17:46:53 +0000</pubDate>
      <link>https://dev.to/thbe/harmonize-basic-set-up-with-ansible-part-2-27fg</link>
      <guid>https://dev.to/thbe/harmonize-basic-set-up-with-ansible-part-2-27fg</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published at &lt;a href="https://www.thbe.org/posts/2020/01/04/Harmonize_basic_set_up_with_Ansible-Part_2.html"&gt;thbe.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As promised in the previous post &lt;a href="https://dev.to/thbe/harmonize-basic-set-up-with-ansible-part-1-eb"&gt;&lt;strong&gt;Harmonize basic set up with Ansible, part 1&lt;/strong&gt;&lt;/a&gt; I would like to dig deeper into the details about the common role. This is how it looks like in the filesystem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;roles
└── common
    ├── files
    │   └── user
    │       ├── alias.plugin.zsh
    │       ├── docker.plugin.zsh
    │       ├── functions.plugin.zsh
    │       ├── git.plugin.zsh
    │       ├── jekyll.plugin.zsh
    │       ├── nerd.plugin.zsh
    │       ├── puppet.plugin.zsh
    │       └── ruby.plugin.zsh
    ├── tasks
    │   ├── localtime.yml
    │   ├── main.yml
    │   ├── motd.yml
    │   ├── networking.yml
    │   ├── repositories.yml
    │   ├── tools.yml
    │   ├── upgrade.yml
    │   └── user.yml
    └── templates
        ├── ifcfg-interface.j2
        ├── motd.j2
        ├── network.j2
        └── route-interface.j2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important file is the main.yml file. This file defines which tasks will be executed in which order when the common role is called. So we need one file that calls the role (in my case common.yml):&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="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# Common playbook needs to executed first&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;Common configuration shared for all nodes&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;remote_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
  &lt;span class="na"&gt;gather_facts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;

  &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;common&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the role is called, it first calls the file main.yml:&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="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# Main playbook for common configuration&lt;/span&gt;
&lt;span class="c1"&gt;#- include: networking.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;motd.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localtime.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repositories.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;upgrade.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tools.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user.yml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Based on the fact that this is only a demonstration I've deactivated the network part. This part expects at least three network devices attached to different network segments like internal, backup, MPLS and so on and so forth. As all nodes of this demonstration only have one network interface, this configuration won't work anyway. So let's go through the tasks that are active and that will work. First of all, I'll set the message of the day:&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="nn"&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;Message of the day setup&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;motd.j2&lt;/span&gt;
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/motd&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0644&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a pretty simple task, it takes the &lt;code&gt;motd.j2&lt;/code&gt; template (all templates end with j2 because of all are Jinja2 templates) which is stored in the template directory of the common role. This template will be stored on the target node under &lt;code&gt;/etc/motd&lt;/code&gt; with the access rights &lt;code&gt;0644&lt;/code&gt;. Let's have a look at a slightly more complex example, the user configuration. The task itself looks like:&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="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# Create all users defined in local_user (group_vars/all.yml)&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;Create personal user account&lt;/span&gt;
  &lt;span class="na"&gt;user&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="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!"&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/usr/bin/zsh&lt;/span&gt;
    &lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wheel&lt;/span&gt;
    &lt;span class="na"&gt;generate_ssh_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
    &lt;span class="na"&gt;ssh_key_bits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2048&lt;/span&gt;
    &lt;span class="na"&gt;ssh_key_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.ssh/id_rsa&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;local_user&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;

&lt;span class="c1"&gt;# Add local public rsa key to authorized_keys for all users defined in local_user (group_vars/all.yml)&lt;/span&gt;
&lt;span class="c1"&gt;# This one needs to be reworked for multiple named users)&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;Add remote authorized key to allow future password-less logins&lt;/span&gt;
  &lt;span class="na"&gt;authorized_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;lookup('file',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'~/.ssh/id_rsa.pub')&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;local_user&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;

&lt;span class="c1"&gt;# Add all users defined in local_user (group_vars/all.yml) to sudo administration group&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;Add personal user to sudoers&lt;/span&gt;
  &lt;span class="na"&gt;lineinfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/sudoers&lt;/span&gt;
    &lt;span class="na"&gt;regexp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&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;ALL"&lt;/span&gt;
    &lt;span class="na"&gt;line&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&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;ALL=(ALL)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;NOPASSWD:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ALL"&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
    &lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/sbin/visudo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-cf&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%s"&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;local_user&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;

&lt;span class="c1"&gt;# Install oh-my-zsh for all users defined in local_user (group_vars/all.yml)&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;Download oh-my-zsh installer&lt;/span&gt;
  &lt;span class="na"&gt;get_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh&lt;/span&gt;
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/srv/oh-my-zsh-install.sh&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;Make oh-my-zsh-install.sh executable for all&lt;/span&gt;
  &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/srv/oh-my-zsh-install.sh&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0755&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;Execute the zsh-installer.sh&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/srv/oh-my-zsh-install.sh&lt;/span&gt;
    &lt;span class="na"&gt;creates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/{{ item }}/.oh-my-zsh&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
  &lt;span class="na"&gt;become_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;local_user&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;Remove oh-my-zsh-install.sh&lt;/span&gt;
  &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/srv/oh-my-zsh-install.sh&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;absent&lt;/span&gt;

&lt;span class="c1"&gt;# Install typewritten for all users defined in local_user (group_vars/all.yml)&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;Deploy typewritten theme to oh-my-zsh using git&lt;/span&gt;
  &lt;span class="na"&gt;git&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/thbe/typewritten.git&lt;/span&gt;
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/{{ item }}/.oh-my-zsh/custom/themes/typewritten&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;local_user&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;

&lt;span class="c1"&gt;# Deploy zsh configuration file to all users defined in local_user (group_vars/all.yml)&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;Deploy local zsh user configuration - .zshrc&lt;/span&gt;
  &lt;span class="na"&gt;copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user/.zshrc&lt;/span&gt;
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/{{ item }}/.zshrc&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0640&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;local_user&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;

&lt;span class="c1"&gt;# Deploy plugin configuration files to all users defined in local_user (group_vars/all.yml)&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;Create .profile.d directory&lt;/span&gt;
  &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/{{ item }}/.profile.d&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;directory&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0750&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;local_user&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;Deploy local zsh user configuration - alias.plugin.zsh&lt;/span&gt;
  &lt;span class="na"&gt;copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user/alias.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/{{ item }}/.profile.d/alias.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0640&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;local_user&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;Deploy local zsh user configuration - docker.plugin.zsh&lt;/span&gt;
  &lt;span class="na"&gt;copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user/docker.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/{{ item }}/.profile.d/docker.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0640&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;local_user&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;Deploy local zsh user configuration - functions.plugin.zsh&lt;/span&gt;
  &lt;span class="na"&gt;copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user/functions.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/{{ item }}/.profile.d/functions.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0640&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;local_user&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;Deploy local zsh user configuration - git.plugin.zsh&lt;/span&gt;
  &lt;span class="na"&gt;copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user/git.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/{{ item }}/.profile.d/git.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0640&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;local_user&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;Deploy local zsh user configuration - jekyll.plugin.zsh&lt;/span&gt;
  &lt;span class="na"&gt;copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user/jekyll.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/{{ item }}/.profile.d/jekyll.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0640&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;local_user&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;Deploy local zsh user configuration - nerd.plugin.zsh&lt;/span&gt;
  &lt;span class="na"&gt;copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user/nerd.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/{{ item }}/.profile.d/nerd.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0640&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;local_user&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;Deploy local zsh user configuration - puppet.plugin.zsh&lt;/span&gt;
  &lt;span class="na"&gt;copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user/puppet.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/{{ item }}/.profile.d/puppet.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0640&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;local_user&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;Deploy local zsh user configuration - ruby.plugin.zsh&lt;/span&gt;
  &lt;span class="na"&gt;copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user/ruby.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/{{ item }}/.profile.d/ruby.plugin.zsh&lt;/span&gt;
    &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0640&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&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;local_user&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What does this task do? First, it creates user IDs in a loop. Therefore it looks up &lt;code&gt;local_user&lt;/code&gt; which is defined in the group_vars &lt;code&gt;all.yml&lt;/code&gt; configuration 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="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# Users that should be available on all target nodes&lt;/span&gt;
&lt;span class="na"&gt;local_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ansible&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;thbe&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each user is transferred into the &lt;code&gt;item&lt;/code&gt; variable and process one by one. The user will be created, the password disabled (we'll use SSH keys instead), the primary shell is set to ZSH, the SSH keys for the users are created and the users are added to the administrator group.&lt;/p&gt;

&lt;p&gt;The next step is that my local public key will be added to the &lt;code&gt;authorized_keys&lt;/code&gt; of each user so I can log in without a password. The users will be included in the &lt;code&gt;/etc/sudoers&lt;/code&gt; configuration and finally, oh-my-zsh will be installed as well as my preferred theming and last but not least, my personal functions and aliases.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>automation</category>
      <category>ansible</category>
      <category>redhat</category>
    </item>
    <item>
      <title>Harmonize basic set up with Ansible, part 1</title>
      <dc:creator>thbe</dc:creator>
      <pubDate>Sat, 11 Jan 2020 12:21:57 +0000</pubDate>
      <link>https://dev.to/thbe/harmonize-basic-set-up-with-ansible-part-1-eb</link>
      <guid>https://dev.to/thbe/harmonize-basic-set-up-with-ansible-part-1-eb</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published at &lt;a href="https://www.thbe.org/posts/2020/01/04/Harmonize_basic_set_up_with_Ansible-Part_1.html"&gt;thbe.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Recently, ok, it's roughly one year ago, I posted about bootstrapping your box with Ansible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.thbe.org/posts/2019/01/02/Bootstrap_with_Ansible-Part_1.html"&gt;&lt;strong&gt;Bootstrap with Ansible 1/3&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.thbe.org/posts/2019/01/05/Bootstrap_with_Ansible-Part_2.html"&gt;&lt;strong&gt;Bootstrap with Ansible 2/3&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.thbe.org/posts/2019/01/06/Bootstrap_with_Ansible-Part_3.html"&gt;&lt;strong&gt;Bootstrap with Ansible 3/3&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The posts where about the automated setup of Linux machines to get rid of the traditional checklists. The posts where about the automated setup of Linux machines to get rid of the traditional checklists. This is already quite nice but unleashes just a small part of the power that Ansible is able to provide. This post is about the next step using Ansible as a configuration instance with some kind of a role concept to provision server. This means in short, all servers with the role web server, for example, get everything installed that is required to act as a web server. As I wrote already, Ansible doesn't require agents on the target hosts. So the only thing that is required is a controller node that has access to the target nodes (for details look at the first part of this blog series). In this example, the controller node is my MacBook and the target nodes are some virtual machines with minimal installations.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note 1:&lt;/strong&gt; This example should work well in private and semi-professional use cases where you don't have to rely on specific SLAs and where you still able to do the administration manually if required. If you plan to use this kind of automation in an enterprise scenario you should consider evaluating &lt;a href="https://www.redhat.com/en/technologies/management/ansible"&gt;&lt;strong&gt;Ansible Tower&lt;/strong&gt;&lt;/a&gt;. The Ansible core engine will stay more or less the same but Ansible Tower enriches the core engine with enterprise functionality like web-based dashboards, RBAC, SSO, audit trail, cloud connectors, support and so on and so forth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note 2:&lt;/strong&gt; Automation ease your life, it makes things predictable, it harmonize things, it let you administer big server farms, but, set up wrongly it can mess up your entire IT landscape (imagine you set a wrong default route pointing to nowhere on all servers). Therefore it is essential to test all automation before you deploy them in production.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ok, back to the example. On the controller node, we need to create a directory first that holds our ansible configuration. You can choose whatever you want, in this example I use &lt;code&gt;~/Development/ansible&lt;/code&gt; as the root for my configuration. The first thing that is required, is the inventory file. Till now this file was based on the INI-file syntax you often see in the Windows world but since Ansible 2.something it is also possible to use the YAML format for this file. Although I'm not the biggest YAML enthusiast, it makes sense from my point of view as the rest of the configuration in Ansible uses this format as well. The inventory file contains the target nodes, optional parameters for that target nodes and, if required, some custom grouping based on for example location, role or the like. This is the inventory I used in this example:&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="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# Production inventory&lt;/span&gt;
&lt;span class="na"&gt;all&lt;/span&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="na"&gt;bb-8.fritz.box&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ansible_ssh_host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10.1.1.1&lt;/span&gt;
      &lt;span class="na"&gt;ansible_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
    &lt;span class="na"&gt;r2-d2.fritz.box&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ansible_ssh_host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10.1.1.2&lt;/span&gt;
      &lt;span class="na"&gt;ansible_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
    &lt;span class="na"&gt;c-3po.fritz.box&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ansible_ssh_host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10.1.2.1&lt;/span&gt;
      &lt;span class="na"&gt;ansible_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
    &lt;span class="na"&gt;r4-p17.fritz.box&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ansible_ssh_host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10.1.2.2&lt;/span&gt;
      &lt;span class="na"&gt;ansible_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
  &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;webservers&lt;/span&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="na"&gt;bb-8.fritz.box&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;c-3po.fritz.box&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;dbservers&lt;/span&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="na"&gt;r2-d2.fritz.box&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;r4-p17.fritz.box&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;hamburg&lt;/span&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="na"&gt;bb-8.fritz.box&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;r2-d2.fritz.box&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;berlin&lt;/span&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="na"&gt;c-3po.fritz.box&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;r4-p17.fritz.box&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;prod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;hamburg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;berlin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can use more than one inventory to, for example, separate the production from the test environment. The next step is to create the required directories on the top-level. In this example, we will use four directories, group_vars, host_vars, playbooks, and roles. Group_vars and host_vars contain the variables that either belong to a group or to a host. An example would be a network configuration that belongs to a specific host like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;host_vars
├── bb-8.fritz.box
│   └── network.yml
├── c-3po.fritz.box
│   └── network.yml
├── r2-d2.fritz.box
│   └── network.yml
└── r4-p17.fritz.box
    └── network.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The group_vars contain a special file called all.yml which contains variables that belong to the whole landscape, for example, the user configuration for the administrators or packages that should be installed on all nodes.&lt;/p&gt;

&lt;p&gt;With the configuration files in place, we can now start to explore the two remaining directories. The first one is the playbooks. I have already introduced this directory in my previous posts about Ansible (see the beginning of the document). Long story short, here you find the one-off checklists as well as other one-off tasks like reboot a specific node set or all nodes.&lt;/p&gt;

&lt;p&gt;The second directory is the role directory. Here we group tasks, handlers, variables and so on and so forth around roles that can be assigned to single hosts or groups of hosts. By taking the example of the common role, I will explain this in the next part of this post.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>automation</category>
      <category>ansible</category>
      <category>redhat</category>
    </item>
    <item>
      <title>Enhance your macOS terminal - p10k</title>
      <dc:creator>thbe</dc:creator>
      <pubDate>Fri, 03 Jan 2020 02:20:48 +0000</pubDate>
      <link>https://dev.to/thbe/enhance-your-macos-terminal-p10k-1g3m</link>
      <guid>https://dev.to/thbe/enhance-your-macos-terminal-p10k-1g3m</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published at &lt;a href="https://www.thbe.org/posts/2020/01/01/Enhance_your_macOS_terminal_p10k.html"&gt;thbe.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In my previous blog post about &lt;a href="https://www.thbe.org/posts/2019/07/23/Enhance_your_macOS_terminal.html"&gt;&lt;strong&gt;enhancing the MacOS terminal&lt;/strong&gt;&lt;/a&gt; I used the &lt;a href="https://github.com/Powerlevel9k/powerlevel9k"&gt;&lt;strong&gt;powerlevel9k&lt;/strong&gt;&lt;/a&gt; theme. When I was publishing the article on &lt;a href="https://dev.to/thbe/enhance-your-macos-terminal-25o7"&gt;&lt;strong&gt;dev.to&lt;/strong&gt;&lt;/a&gt; as well, I was made aware there is a re-implementation of p9k available called &lt;a href="https://github.com/romkatv/powerlevel10k"&gt;&lt;strong&gt;powerlevel10k&lt;/strong&gt;&lt;/a&gt;. p10k is calling itself a fast drop-in replacement for Powerlevel9k. Fast sounds always good so I thought, give it a try.&lt;/p&gt;

&lt;p&gt;p10k is able to use the same configuration p9k is using, so if you only install p10k and change the theme in &lt;a href="https://github.com/ohmyzsh/ohmyzsh"&gt;&lt;strong&gt;oh-my-zsh&lt;/strong&gt;&lt;/a&gt; to p10k without further modifications, everything will look the same as it looked before. The only difference you might notice is that the terminal indeed behaves a little faster. This is, what I did first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &lt;span class="nt"&gt;--depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 https://github.com/romkatv/powerlevel10k.git &lt;span class="nv"&gt;$ZSH_CUSTOM&lt;/span&gt;/themes/powerlevel10k
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now where p10k is installed, it needs to be activated in the ZSH configuration:&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;# zsh configuration file&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Author: Thomas Bendler &amp;lt;code@thbe.org&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;# Date:   Tue Sep 24 20:28:27 UTC 2019&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;...]
&lt;span class="nv"&gt;ZSH_THEME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"powerlevel10k/powerlevel10k"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;...]
&lt;span class="c"&gt;# Configure the prompt content&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_LEFT_PROMPT_ELEMENTS&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;root_indicator context &lt;span class="nb"&gt;dir &lt;/span&gt;vcs&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;status command_execution_time ram disk_usage ip&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Local custom plugins&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;item &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/.profile.d/&lt;span class="k"&gt;*&lt;/span&gt;.plugin.zsh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After the restart of my terminal, literally, nothing has changed. It looks like the drop-in replacement part seems to be true. Let's check if we really use p10k now:&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"__p9k_root_dir="&lt;/span&gt;
&lt;span class="nv"&gt;__p9k_root_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/Users/thbe/.oh-my-zsh/custom/themes/powerlevel10k
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So far, so good. But p10k should not only be a drop-in replacement, but it also offers additional functionality that goes beyond the functionality of p9k. One thing I personally like is the fact that p10k separate the configuration from the .zshrc file per default. p10k also comes with a wizard that generates a sample configuration. I started with the lean configuration and adjusted it in a way that it fits my need. This is how my terminal now looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ldsuv5ZA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/2f7ertdh82j2jonpi3p5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ldsuv5ZA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/2f7ertdh82j2jonpi3p5.png" alt="Customized iTerm2 with p10k" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first thing I did was to clean up my .zshrc configuration file. I removed some of my oh-my-zsh plugins because I hadn't used them recently. I also added some and ended up with the following .zshrc configuration file:&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;# zsh configuration file&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Author: Thomas Bendler &amp;lt;code@thbe.org&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;# Date:   Fri Dec 27 23:48:31 CET 2019&lt;/span&gt;

&lt;span class="c"&gt;# Add local sbin to $PATH.&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/sbin:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Path to the oh-my-zsh installation.&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ZSH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.oh-my-zsh"&lt;/span&gt;

&lt;span class="c"&gt;# Use case-sensitive completion.&lt;/span&gt;
&lt;span class="nv"&gt;CASE_SENSITIVE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;

&lt;span class="c"&gt;# Define how often to auto-update (in days).&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;UPDATE_ZSH_DAYS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7

&lt;span class="c"&gt;# Enable command auto-correction.&lt;/span&gt;
&lt;span class="nv"&gt;ENABLE_CORRECTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;

&lt;span class="c"&gt;# Display red dots whilst waiting for completion.&lt;/span&gt;
&lt;span class="nv"&gt;COMPLETION_WAITING_DOTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;

&lt;span class="c"&gt;# Configure history stamp format&lt;/span&gt;
&lt;span class="nv"&gt;HIST_STAMPS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"yyyy-mm-dd"&lt;/span&gt;

&lt;span class="c"&gt;# Plugin configuration&lt;/span&gt;
&lt;span class="c"&gt;# Standard plugins can be found in ~/.oh-my-zsh/plugins/*&lt;/span&gt;
&lt;span class="c"&gt;# Custom plugins may be added to ~/.oh-my-zsh/custom/plugins/&lt;/span&gt;
&lt;span class="c"&gt;# Add wisely, as too many plugins slow down shell startup.&lt;/span&gt;
&lt;span class="nv"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;
  ansible
  brew
  bundler
  colored-man-pages
  colorize
  docker
  git
  nmap
  osx
  zsh-navigation-tools
  zsh_reload
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;ZSH_THEME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"powerlevel10k/powerlevel10k"&lt;/span&gt;

&lt;span class="c"&gt;# Load Zsh tools for syntax highlighting and autosuggestions&lt;/span&gt;
&lt;span class="nv"&gt;HOMEBREW_FOLDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/share"&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOMEBREW_FOLDER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/zsh-autosuggestions/zsh-autosuggestions.zsh"&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOMEBREW_FOLDER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"&lt;/span&gt;

&lt;span class="c"&gt;# Load oh-my-zsh framework&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ZSH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/oh-my-zsh.sh"&lt;/span&gt;

&lt;span class="c"&gt;# Powerlevel10k configuration&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/.p10k.zsh &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/.p10k.zsh

&lt;span class="c"&gt;# Local custom plugins&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;item &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/.profile.d/&lt;span class="k"&gt;*&lt;/span&gt;.plugin.zsh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The important line from the p10k perspective is:&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="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/.p10k.zsh &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/.p10k.zsh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This line source the p10k configuration that is stored in the .p10k.zsh file. The one that creates the layout shown in the screenshot above is the following:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Currently, the configuration file is quite big but I'm pretty sure not everything in this file is really required. It is more or less based on the generated configuration file using the lean set up. I haven't checked the default values for the configuration content yet but I could imagine a lot of configurations are default and as such, could be removed from the configuration file.&lt;/p&gt;

&lt;p&gt;For those who want to know what this part in the .zshrc configuration file is about:&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;# Local custom plugins&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;item &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/.profile.d/&lt;span class="k"&gt;*&lt;/span&gt;.plugin.zsh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This includes my personal zsh plugins that are currently not available in other frameworks like oh-my-zsh. It seems to be a little challenge to get additional plugins included there. You can see them here:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



</description>
      <category>iterm2</category>
      <category>zsh</category>
      <category>ohmyzsh</category>
      <category>powerlevel10k</category>
    </item>
    <item>
      <title>From zero to hero - Bootstrap with Ansible</title>
      <dc:creator>thbe</dc:creator>
      <pubDate>Sun, 22 Dec 2019 17:13:32 +0000</pubDate>
      <link>https://dev.to/thbe/from-zero-to-hero-bootstrap-with-ansible-2ohi</link>
      <guid>https://dev.to/thbe/from-zero-to-hero-bootstrap-with-ansible-2ohi</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published at &lt;a href="https://www.thbe.org/posts/2019/01/02/bootstrap-with-ansible-part-1.html"&gt;thbe.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In preparation for a meeting to discuss ways of working to optimize server operations in a mid-size data center, I was seeking a lightweight approach to automate server installations. In the past, I was primarily using &lt;a href="https://puppet.com/"&gt;Puppet&lt;/a&gt; for data center management. Although it’s still a good choice for an enterprise-level, full automation, it also has some disadvantages. First of all, it’s a client/server infrastructure that requires by nature more installation effort compared to clientless solutions. It also requires more resources on the client to permanently get the state of the client. Last but not least, it’s also a bit more complex when it comes to writing generic usable modules.&lt;/p&gt;

&lt;p&gt;Long story short, I decided to give the number one competitor of Puppet called &lt;a href="https://www.ansible.com/"&gt;Ansible&lt;/a&gt; a try. Ansible has a similar, slightly smaller scope and is designed to be more lightweight then Puppet. You don’t need to install agents on the clients and the whole architecture looks a little bit straighter forward.&lt;/p&gt;

&lt;p&gt;As proof of concept, I built a small automation procedure for my home office. As Ansible is owned by &lt;a href="https://www.redhat.com/en"&gt;RedHat&lt;/a&gt; I used &lt;a href="https://www.centos.org/"&gt;CentOS&lt;/a&gt; as the server operating system. CentOS can be automatically installed by using &lt;a href="https://pykickstart.readthedocs.io/en/latest/"&gt;kickstart&lt;/a&gt; and is as such, a good platform to test the general idea. As a first step, I’ve created a generic kickstart file that enables me to install CentOS 7 with a minimal package set (the kickstart for CentOS 8 should look quite similar). This is my preferred approach as I tend to use the automation tool for the setup work. In my proof of concept, I use the root user for the configuration management. In a real-world scenario, you should use a dedicated configuration management user in conjunction with &lt;strong&gt;sudo&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;#version=DEVEL
#
# Kickstart installation file with minimal package set
#
# Author:       Thomas Bendler &amp;lt;code@thbe.org&amp;gt;
# Date:         Wed Jan  2 17:25:26 CET 2019
# Revision:     1.0
#
# Distribution: CentOS
# Version:      7
# Processor:    x86_64
&lt;/span&gt;
&lt;span class="c"&gt;# Kickstart settings
&lt;/span&gt;&lt;span class="n"&gt;install&lt;/span&gt;
&lt;span class="n"&gt;cdrom&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt;
&lt;span class="n"&gt;reboot&lt;/span&gt;

&lt;span class="c"&gt;# System language settings
&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt; &lt;span class="n"&gt;en_US&lt;/span&gt;.&lt;span class="n"&gt;UTF&lt;/span&gt;-&lt;span class="m"&gt;8&lt;/span&gt;
&lt;span class="n"&gt;keyboard&lt;/span&gt; --&lt;span class="n"&gt;vckeymap&lt;/span&gt;=&lt;span class="n"&gt;de&lt;/span&gt;-&lt;span class="n"&gt;nodeadkeys&lt;/span&gt; --&lt;span class="n"&gt;xlayouts&lt;/span&gt;=&lt;span class="s1"&gt;'de (nodeadkeys)'&lt;/span&gt;
&lt;span class="n"&gt;timezone&lt;/span&gt; &lt;span class="n"&gt;Europe&lt;/span&gt;/&lt;span class="n"&gt;Berlin&lt;/span&gt; --&lt;span class="n"&gt;isUtc&lt;/span&gt;

&lt;span class="c"&gt;# System password for root (create with i.e. pwkickstart)
&lt;/span&gt;&lt;span class="n"&gt;rootpw&lt;/span&gt; --&lt;span class="n"&gt;iscrypted&lt;/span&gt; $&lt;span class="m"&gt;6&lt;/span&gt;$&lt;span class="n"&gt;aaa&lt;/span&gt;[]&lt;span class="n"&gt;zzz&lt;/span&gt;

&lt;span class="c"&gt;# System bootloader configuration
&lt;/span&gt;&lt;span class="n"&gt;bootloader&lt;/span&gt; --&lt;span class="n"&gt;append&lt;/span&gt;=&lt;span class="s2"&gt;" crashkernel=auto"&lt;/span&gt; --&lt;span class="n"&gt;location&lt;/span&gt;=&lt;span class="n"&gt;mbr&lt;/span&gt; --&lt;span class="n"&gt;boot&lt;/span&gt;-&lt;span class="n"&gt;drive&lt;/span&gt;=&lt;span class="n"&gt;sda&lt;/span&gt;
&lt;span class="n"&gt;ignoredisk&lt;/span&gt; --&lt;span class="n"&gt;only&lt;/span&gt;-&lt;span class="n"&gt;use&lt;/span&gt;=&lt;span class="n"&gt;sda&lt;/span&gt;

&lt;span class="c"&gt;# System partition information
&lt;/span&gt;&lt;span class="n"&gt;clearpart&lt;/span&gt; --&lt;span class="n"&gt;all&lt;/span&gt; --&lt;span class="n"&gt;initlabel&lt;/span&gt; --&lt;span class="n"&gt;drives&lt;/span&gt;=&lt;span class="n"&gt;sda&lt;/span&gt;
&lt;span class="n"&gt;autopart&lt;/span&gt; --&lt;span class="n"&gt;type&lt;/span&gt;=&lt;span class="n"&gt;lvm&lt;/span&gt;

&lt;span class="c"&gt;# System network settings
&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;  --&lt;span class="n"&gt;bootproto&lt;/span&gt;=&lt;span class="n"&gt;dhcp&lt;/span&gt; --&lt;span class="n"&gt;device&lt;/span&gt;=&lt;span class="n"&gt;link&lt;/span&gt; --&lt;span class="n"&gt;onboot&lt;/span&gt;=&lt;span class="n"&gt;on&lt;/span&gt; --&lt;span class="n"&gt;ipv6&lt;/span&gt;=&lt;span class="n"&gt;auto&lt;/span&gt; --&lt;span class="n"&gt;hostname&lt;/span&gt;=&lt;span class="n"&gt;node_1&lt;/span&gt;.&lt;span class="n"&gt;local&lt;/span&gt;.&lt;span class="n"&gt;domain&lt;/span&gt;

&lt;span class="c"&gt;# System security settings
&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt; --&lt;span class="n"&gt;enableshadow&lt;/span&gt; --&lt;span class="n"&gt;passalgo&lt;/span&gt;=&lt;span class="n"&gt;sha512&lt;/span&gt;
&lt;span class="n"&gt;selinux&lt;/span&gt; --&lt;span class="n"&gt;permissive&lt;/span&gt;
&lt;span class="n"&gt;firewall&lt;/span&gt; --&lt;span class="n"&gt;service&lt;/span&gt;=&lt;span class="n"&gt;ssh&lt;/span&gt;

&lt;span class="c"&gt;# System services
&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt; --&lt;span class="n"&gt;enabled&lt;/span&gt;=&lt;span class="s2"&gt;"chronyd"&lt;/span&gt;

&lt;span class="c"&gt;# System packages
&lt;/span&gt;%&lt;span class="n"&gt;packages&lt;/span&gt;
@^&lt;span class="n"&gt;minimal&lt;/span&gt;
@&lt;span class="n"&gt;core&lt;/span&gt;
&lt;span class="n"&gt;chrony&lt;/span&gt;
&lt;span class="n"&gt;kexec&lt;/span&gt;-&lt;span class="n"&gt;tools&lt;/span&gt;
%&lt;span class="n"&gt;end&lt;/span&gt;

&lt;span class="c"&gt;# System addons
&lt;/span&gt;%&lt;span class="n"&gt;addon&lt;/span&gt; &lt;span class="n"&gt;com_redhat_kdump&lt;/span&gt; --&lt;span class="n"&gt;enable&lt;/span&gt; --&lt;span class="n"&gt;reserve&lt;/span&gt;-&lt;span class="n"&gt;mb&lt;/span&gt;=&lt;span class="s1"&gt;'auto'&lt;/span&gt;
%&lt;span class="n"&gt;end&lt;/span&gt;

&lt;span class="c"&gt;# Post installation settings
&lt;/span&gt;%&lt;span class="n"&gt;post&lt;/span&gt; --&lt;span class="n"&gt;log&lt;/span&gt;=/&lt;span class="n"&gt;root&lt;/span&gt;/&lt;span class="n"&gt;kickstart&lt;/span&gt;-&lt;span class="n"&gt;post&lt;/span&gt;.&lt;span class="n"&gt;log&lt;/span&gt;
/&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="s2"&gt;"Starting anaconda postinstall"&lt;/span&gt;

/&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;mkdir&lt;/span&gt; /&lt;span class="n"&gt;root&lt;/span&gt;/.&lt;span class="n"&gt;ssh&lt;/span&gt;
/&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;chmod&lt;/span&gt; &lt;span class="m"&gt;700&lt;/span&gt; /&lt;span class="n"&gt;root&lt;/span&gt;/.&lt;span class="n"&gt;ssh&lt;/span&gt;
/&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ssh-rsa AAA[...]ZZZ admin1@management"&lt;/span&gt; &amp;gt; /&lt;span class="n"&gt;root&lt;/span&gt;/.&lt;span class="n"&gt;ssh&lt;/span&gt;/&lt;span class="n"&gt;authorized_keys&lt;/span&gt;
/&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;bin&lt;/span&gt;/&lt;span class="n"&gt;chmod&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt; /&lt;span class="n"&gt;root&lt;/span&gt;/.&lt;span class="n"&gt;ssh&lt;/span&gt;/&lt;span class="n"&gt;authorized_keys&lt;/span&gt;

&lt;span class="n"&gt;sync&lt;/span&gt;
&lt;span class="n"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
%&lt;span class="n"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last part of the kickstart file is used to distribute the authorized_keys file to enable passwordless login to the root account. Again, in a real-world scenario, you should use this section to create a dedicated management user as well as the required &lt;strong&gt;sudo&lt;/strong&gt; configuration. Alternatively, you can use Ansible to create individual administrative user accounts on the target hosts.&lt;/p&gt;

&lt;p&gt;The now still remaining part is to tell the installer that he should use the kickstart file instead of asking the user how and what to install. This is simply done by adding &lt;strong&gt;inst.ks=&lt;/strong&gt; to the kernel parameter. The installer can use different methods to get the file which is described in the kickstart documentation. In this proof of concept I simply use a web target:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;inst&lt;/span&gt;.&lt;span class="n"&gt;ks&lt;/span&gt;=&lt;span class="n"&gt;https&lt;/span&gt;://&lt;span class="n"&gt;server&lt;/span&gt;.&lt;span class="n"&gt;local&lt;/span&gt;.&lt;span class="n"&gt;domain&lt;/span&gt;/&lt;span class="n"&gt;kickstart&lt;/span&gt;.&lt;span class="n"&gt;cfg&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the Kickstart based installation is finished, the configuration management done with Ansible will take over. If you move the proof of concept to a real datacenter you should consider extending the automated Kickstart installation by tools like &lt;a href="https://cobbler.github.io/"&gt;Cobbler&lt;/a&gt;. This will enable fully-fledged automation by using PXE boot (network boot without a local operating system). Another option, depending on your security and update strategy, is to use templates to provision the operating system instead of doing a real installation. But this approach has, at least from my point of view, too many disadvantages (especially in the operating area).&lt;/p&gt;

&lt;p&gt;So far, I showed how to automate the installation process of the core operating system. When this process is done, we should have a minimum operating system installation with a bit of basic configuration (mainly access related). As already said, this will be the point were Ansible will perform the final configuration. I won't provide a manual on how to use Ansible in this post (you can find the complete documentation at &lt;a href="https://docs.ansible.com/ansible/latest/index.html"&gt;https://docs.ansible.com/ansible/latest/index.html&lt;/a&gt;), instead, I would show some basic concepts how the tool is used for its purpose. To get an idea of how things are organized in Ansible, let's first take a look at the folder structure of the proof of concept:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── group_vars
│   ├── all.yml
│   ├── datacenter1.yml
│   └── location1.yml
├── host_vars
│   └── server1.domain.local
│       └── network.yml
├── playbooks
│   ├── add_ssh_fingerprints.yml
│   └── reboot_hosts.yml
├── roles
│   ├── common
│   │   ├── tasks
│   │   │   ├── environment.yml
│   │   │   ├── localtime.yml
│   │   │   ├── main.yml
│   │   │   ├── motd.yml
│   │   │   ├── networking.yml
│   │   │   ├── repositories.yml
│   │   │   ├── tools.yml
│   │   │   ├── upgrade.yml
│   │   │   └── user.yml
│   │   └── templates
│   │       ├── custom_sh.j2
│   │       ├── ifcfg-interface.j2
│   │       ├── motd.j2
│   │       ├── network.j2
│   │       └── route-interface.j2
│   └── web
│       ├── handlers
│       │   └── main.yml
│       ├── tasks
│       │   └── main.yml
│       └── templates
│           ├── index_html.j2
│           └── nginx_conf.j2
├── common.yml
├── production
├── site.yml
├── staging
└── web.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, let's go through this tree step by step. The first two directories, &lt;strong&gt;group_vars&lt;/strong&gt; and &lt;strong&gt;host_vars&lt;/strong&gt; contain the variables used in the configuration scripts. The idea is to define the scope of the variables. They can apply to either all hosts, to a data center, a region/location or only to one specific host. Variables that apply to all managed hosts can be for example local administrative user IDs. Variables that apply to a region/location can be for example settings in which time-server should be used by the hosts in that region. Datacenter related variables can be related to the routing for example. And last but not least, host-specific variables can be related to IPs, hostnames and so on and so forth. This enables the administrator to easily set up global infrastructures without much effort.&lt;/p&gt;

&lt;p&gt;Before I move on to the playbooks I would like to highlight two files in the root directory. Those are staging and production. Both files contain the servers and groups (region, location, role) where the servers belong to. The distinction between staging and production reflects the role of those (here you can see the staging file):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[hamburg-hosts]&lt;/span&gt;
&lt;span class="err"&gt;node_1.local.domain&lt;/span&gt;  &lt;span class="py"&gt;ansible_ssh_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;172.20.0.4  ansible_user=root&lt;/span&gt;
&lt;span class="err"&gt;node_2.local.domain&lt;/span&gt;  &lt;span class="py"&gt;ansible_ssh_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;172.20.0.5  ansible_user=root&lt;/span&gt;
&lt;span class="err"&gt;node_3.local.domain&lt;/span&gt;  &lt;span class="py"&gt;ansible_ssh_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;172.20.0.6  ansible_user=root&lt;/span&gt;

&lt;span class="nn"&gt;[hamburg-web]&lt;/span&gt;
&lt;span class="err"&gt;node_4.local.domain&lt;/span&gt;  &lt;span class="py"&gt;ansible_ssh_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;172.20.0.7  ansible_user=root&lt;/span&gt;
&lt;span class="err"&gt;node_5.local.domain&lt;/span&gt;  &lt;span class="py"&gt;ansible_ssh_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;172.20.0.8  ansible_user=root&lt;/span&gt;
&lt;span class="err"&gt;node_6.local.domain&lt;/span&gt;  &lt;span class="py"&gt;ansible_ssh_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;172.20.0.9  ansible_user=root&lt;/span&gt;
&lt;span class="err"&gt;node_7.local.domain&lt;/span&gt;  &lt;span class="py"&gt;ansible_ssh_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;172.20.0.10 ansible_user=root&lt;/span&gt;

&lt;span class="nn"&gt;[berlin-web]&lt;/span&gt;
&lt;span class="err"&gt;node_8.local.domain&lt;/span&gt;  &lt;span class="py"&gt;ansible_ssh_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;172.20.0.11 ansible_user=root&lt;/span&gt;
&lt;span class="err"&gt;node_9.local.domain&lt;/span&gt;  &lt;span class="py"&gt;ansible_ssh_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;172.20.0.12 ansible_user=root&lt;/span&gt;
&lt;span class="err"&gt;node_10.local.domain&lt;/span&gt; &lt;span class="py"&gt;ansible_ssh_host&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;172.20.0.13 ansible_user=root&lt;/span&gt;

&lt;span class="nn"&gt;[web:children]&lt;/span&gt;
&lt;span class="err"&gt;hamburg-web&lt;/span&gt;
&lt;span class="err"&gt;berlin-web&lt;/span&gt;

&lt;span class="nn"&gt;[hamburg:children]&lt;/span&gt;
&lt;span class="err"&gt;hamburg-hosts&lt;/span&gt;
&lt;span class="err"&gt;hamburg-web&lt;/span&gt;

&lt;span class="nn"&gt;[berlin:children]&lt;/span&gt;
&lt;span class="err"&gt;berlin-web&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hosts in the staging file can be addressed by the names in the brackets. The exception is the colon entries. Those represent a collection of groups for dedicated hosts, for example, all groups located in Hamburg as shown with the entry [hamburg:children]. The hosts can be accessed with the keyword &lt;strong&gt;hamburg&lt;/strong&gt;. The group names are in a relationship with the variables and this, in the end, closes the circle. Now that we know how things are controlled, we can follow up with the answer to the question of how things are done.&lt;/p&gt;

&lt;p&gt;Before we start with the playbooks that define how things are done, some words on the ad-hoc administration. Ansible can also be used to do ad-hoc administration based on the inventory. This gives an administrator the ability to perform activities based on the region or the datacenter or on all hosts at once. Let's assume we've updated an alias (CNAME) on our central DNS server. Before we use the updated alias everywhere in our landscape we would like to know if every host is already linked to the updated alias. Instead of logging into each and every host, executing dig, we can do it in one rush with Ansible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible &lt;span class="nt"&gt;-i&lt;/span&gt; staging all &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"/bin/dig updated-cname.local.domain +short"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another task would be to check if all servers in the inventory are reachable or if a specific service is running on the server. This will more or less look 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;ansible &lt;span class="nt"&gt;-i&lt;/span&gt; staging all &lt;span class="nt"&gt;-m&lt;/span&gt; ping
ansible &lt;span class="nt"&gt;-i&lt;/span&gt; staging all &lt;span class="nt"&gt;-m&lt;/span&gt; shell &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"/bin/ps aux | /bin/grep chronyd"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The playbooks itself can be seen as a grouping for the ad-hoc tasks. Instead of doing action after action manually, playbooks gives you the ability to group and combine several activities and execute it step by step as a whole instruction set. It's a kind of automating the things the administrator did previously with checklists. A sample playbook that could be used to first install glances and then reboot a host one minute after the execution of the playbook looks 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="nn"&gt;---&lt;/span&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;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;       &lt;span class="s"&gt;yes&lt;/span&gt;
  &lt;span class="na"&gt;become_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;root&lt;/span&gt;
  &lt;span class="na"&gt;gather_facts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;

  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Install glances&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;Install glances&lt;/span&gt;
      &lt;span class="na"&gt;yum&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;name=glances state=latest&lt;/span&gt;
    &lt;span class="c1"&gt;# Reboot the target host&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;Reboot the target host&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/sbin/shutdown --no-wall -r +1 "Reboot was triggered by Ansible"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file is based on the YAML format and contains one or more activities based on a scope. It can be executed 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;ansible-playbook &lt;span class="nt"&gt;-i&lt;/span&gt; staging ./example_playbook.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the time being, this is already sufficient to transition the widely used checklist approach to an automated approach. But this is only the tip of the iceberg, automation, once it is in place, can do a lot more for you which brings us finally back to the folder structure you find in the middle of this post. When I find some time I will create another post and dive a little bit deeper into the automation possibilities, especially into the playbooks and roles.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>automation</category>
      <category>ansible</category>
      <category>redhat</category>
    </item>
    <item>
      <title>Enhance your macOS terminal</title>
      <dc:creator>thbe</dc:creator>
      <pubDate>Sun, 15 Dec 2019 19:07:58 +0000</pubDate>
      <link>https://dev.to/thbe/enhance-your-macos-terminal-25o7</link>
      <guid>https://dev.to/thbe/enhance-your-macos-terminal-25o7</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published at &lt;a href="https://www.thbe.org/posts/2019/07/23/Enhance_your_macOS_terminal.html"&gt;thbe.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Personally, I use quite often the terminal when I use my computer, laptop or whatever. As a result, I modified my terminal quite heavily to ease my work and to get the best out of the terminal. In the past, I did it mostly manually which requires a lot of attention from my side and regular upgrades when e.g. OS updates are performed. So I tried to reduce at least the effort I have to spent when enhancing the terminal.&lt;/p&gt;

&lt;p&gt;The result was the combination of &lt;strong&gt;iTerm2&lt;/strong&gt; + &lt;strong&gt;zsh&lt;/strong&gt; + &lt;strong&gt;oh-my-zsh&lt;/strong&gt; + &lt;strong&gt;powerline&lt;/strong&gt; + &lt;strong&gt;powerlevel9k&lt;/strong&gt;. This combination covers roughly 95% of my requirements and reduced the effort I have to spend maintaining my terminal significantly. In this blog post, I’ll show you how you can get the same terminal that I use:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r5YU6Cp2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/stzo7jq8fmtmscuju8gc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r5YU6Cp2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/stzo7jq8fmtmscuju8gc.png" alt="Customized iTerm2" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, let’s start! I assume you have Homebrew installed on your macOS. If you don’t have Homebrew installed, I strongly recommend installing it, it’s a must-have when you work with macOS. You find the installation instructions on their homepage &lt;a href="https://brew.sh/"&gt;&lt;strong&gt;https://brew.sh/&lt;/strong&gt;&lt;/a&gt;. With Homebrew you can install most of the required packages. But before we do this, let’s download the current stable iTerm2 version:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.iterm2.com/downloads.html"&gt;&lt;strong&gt;https://www.iterm2.com/downloads.html&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Extract the ZIP file and move the app to your program folder. You can now start iTerm2. Once this is done you can install &lt;strong&gt;zsh&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;zsh zsh-autosuggestions zsh-syntax-highlighting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is to install &lt;strong&gt;oh-my-zsh&lt;/strong&gt;. This is fortunately also quite easy, just use this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of the actions are now already completed. Next, we need to install &lt;strong&gt;powerline&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;python3
pip3 &lt;span class="nb"&gt;install &lt;/span&gt;powerline-status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I use &lt;strong&gt;powerline&lt;/strong&gt; primarily for vim which needs to be configured in the .vimrc file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="err"&gt;"&lt;/span&gt; &lt;span class="n"&gt;powerline&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;rtp&lt;/span&gt;+=/&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;local&lt;/span&gt;/&lt;span class="n"&gt;lib&lt;/span&gt;/&lt;span class="n"&gt;python3&lt;/span&gt;.&lt;span class="m"&gt;6&lt;/span&gt;/&lt;span class="n"&gt;site&lt;/span&gt;-&lt;span class="n"&gt;packages&lt;/span&gt;/&lt;span class="n"&gt;powerline&lt;/span&gt;/&lt;span class="n"&gt;bindings&lt;/span&gt;/&lt;span class="n"&gt;vim&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;laststatus&lt;/span&gt;=&lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;set&lt;/span&gt; &lt;span class="n"&gt;t_Co&lt;/span&gt;=&lt;span class="m"&gt;256&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last step is to install &lt;strong&gt;powerlevel9k&lt;/strong&gt;. This can be done again with &lt;strong&gt;Homebrew&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew tap sambadevi/powerlevel9k
brew &lt;span class="nb"&gt;install &lt;/span&gt;powerlevel9k
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have all the required packages installed and we can start with the configuration. First things first, the built-in fonts do not fully support this configuration, you need to install an appropriate font first. I used the &lt;strong&gt;FiraCode&lt;/strong&gt; light font. To install the font, you need to download the font to the font library:&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;mkdir&lt;/span&gt; ~/Downloads/FiraCode &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~/Downloads/FiraCode
wget https://github.com/tonsky/FiraCode/releases/download/2/FiraCode_2.zip
unzip FiraCode_2.zip
&lt;span class="nb"&gt;cp &lt;/span&gt;ttf/&lt;span class="k"&gt;*&lt;/span&gt;.ttf ~/Library/Fonts/
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/Downloads &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; FiraCode/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is the iTerm2 color scheme. I use &lt;strong&gt;Cobalt2&lt;/strong&gt; provided by Wes Bos at &lt;a href="https://github.com/wesbos/Cobalt2-iterm"&gt;https://github.com/wesbos/Cobalt2-iterm&lt;/a&gt;. The color scheme needs to be downloaded and then imported into &lt;strong&gt;iTerm2&lt;/strong&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="nb"&gt;cd&lt;/span&gt; ~/Downloads
curl https://raw.githubusercontent.com/wesbos/Cobalt2-iterm/master/cobalt2.itermcolors &lt;span class="nt"&gt;--output&lt;/span&gt; cobalt2.itermcolors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The color scheme can now be imported when the preferences in iTerm2 are opened and then profiles -&amp;gt; colors -&amp;gt; color preset -&amp;gt; import is chosen.&lt;/p&gt;

&lt;p&gt;Last but not least, you need to modify the zsh configuration file to match your needs. My .zshrc looks 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;&lt;span class="c"&gt;# zsh configuration file&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Author: Thomas Bendler &amp;lt;code@thbe.org&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;# Date: Tue Sep 24 20:28:27 UTC 2019&lt;/span&gt;

&lt;span class="c"&gt;# Add powerline support&lt;/span&gt;
&lt;span class="nv"&gt;POWERLINE_ZSH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/lib/python3.7/site-packages/powerline/bindings/zsh/powerline.zsh"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POWERLINE_ZSH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;POWERLINE_ZSH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# If you come from bash you might have to change your $PATH.&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/sbin:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Path to your oh-my-zsh installation.&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ZSH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/.oh-my-zsh"&lt;/span&gt;

&lt;span class="c"&gt;# Uncomment the following line to use case-sensitive completion.&lt;/span&gt;
&lt;span class="nv"&gt;CASE_SENSITIVE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;

&lt;span class="c"&gt;# Uncomment the following line to change how often to auto-update (in days).&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;UPDATE_ZSH_DAYS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7

&lt;span class="c"&gt;# Uncomment the following line to enable command auto-correction.&lt;/span&gt;
&lt;span class="nv"&gt;ENABLE_CORRECTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;

&lt;span class="c"&gt;# Uncomment the following line to display red dots whilst waiting for completion.&lt;/span&gt;
&lt;span class="nv"&gt;COMPLETION_WAITING_DOTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;

&lt;span class="c"&gt;# Uncomment the following line if you want to change the command execution time&lt;/span&gt;
&lt;span class="c"&gt;# stamp shown in the history command output.&lt;/span&gt;
&lt;span class="c"&gt;# You can set one of the optional three formats:&lt;/span&gt;
&lt;span class="c"&gt;# "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd"&lt;/span&gt;
&lt;span class="c"&gt;# or set a custom format using the strftime function format specifications,&lt;/span&gt;
&lt;span class="c"&gt;# see 'man strftime' for details.&lt;/span&gt;
&lt;span class="c"&gt;# HIST_STAMPS="mm/dd/yyyy"&lt;/span&gt;

&lt;span class="c"&gt;# Which plugins would you like to load?&lt;/span&gt;
&lt;span class="c"&gt;# Standard plugins can be found in ~/.oh-my-zsh/plugins/*&lt;/span&gt;
&lt;span class="c"&gt;# Custom plugins may be added to ~/.oh-my-zsh/custom/plugins/&lt;/span&gt;
&lt;span class="c"&gt;# Add wisely, as too many plugins slow down shell startup.&lt;/span&gt;
&lt;span class="nv"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;
  ansible
  battery
  brew
  bundler
  colorize
  docker
  dotenv
  git
  git-flow-avh
  iterm2
  nmap
  osx
  rake
  ruby
  &lt;span class="nb"&gt;sudo
  &lt;/span&gt;zsh-navigation-tools
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;ZSH_THEME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"powerlevel9k"&lt;/span&gt;

&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ZSH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/oh-my-zsh.sh"&lt;/span&gt;

&lt;span class="c"&gt;# User configuration&lt;/span&gt;
&lt;span class="c"&gt;# Load Zsh tools for syntax highlighting and autosuggestions&lt;/span&gt;
&lt;span class="nv"&gt;HOMEBREW_FOLDER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/usr/local/share"&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOMEBREW_FOLDER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/zsh-autosuggestions/zsh-autosuggestions.zsh"&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOMEBREW_FOLDER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"&lt;/span&gt;

&lt;span class="c"&gt;# Powerlevel9k configuration&lt;/span&gt;
&lt;span class="c"&gt;#POWERLEVEL9K_MODE="compatible"&lt;/span&gt;

&lt;span class="c"&gt;# Left prompt - Configure indicator when working as root&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_ROOT_INDICATOR_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_ROOT_INDICATOR_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"red"&lt;/span&gt;

&lt;span class="c"&gt;# Left prompt - Configure context (user@hostname)&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_CONTEXT_DEFAULT_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_CONTEXT_DEFAULT_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"magenta"&lt;/span&gt;

&lt;span class="c"&gt;# Left prompt - Configure display of current directory&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DIR_HOME_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DIR_HOME_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"white"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DIR_HOME_SUBFOLDER_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DIR_HOME_SUBFOLDER_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"white"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DIR_ETC_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DIR_ETC_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"red"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DIR_WRITABLE_FORBIDDEN_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DIR_WRITABLE_FORBIDDEN_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"red"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DIR_DEFAULT_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DIR_DEFAULT_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"white"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_SHORTEN_DIR_LENGTH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"3"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_SHORTEN_STRATEGY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"truncate_middle"&lt;/span&gt;

&lt;span class="c"&gt;# Right prompt - Configure command execution status indicator&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_STATUS_OK_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_STATUS_OK_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"green"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_STATUS_ERROR_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_STATUS_ERROR_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"red"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_STATUS_CROSS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_STATUS_VERBOSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;

&lt;span class="c"&gt;# Right prompt - Configure command execution time measurement&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_COMMAND_EXECUTION_TIME_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"white"&lt;/span&gt;

&lt;span class="c"&gt;# Right prompt - Configure version control system&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_VCS_CLEAN_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_VCS_CLEAN_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"green"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_VCS_MODIFIED_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_VCS_MODIFIED_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"darkorange"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_VCS_UNTRACKED_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_VCS_UNTRACKED_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"red"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_SHOW_CHANGESET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_CHANGESET_HASH_LENGTH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"12"&lt;/span&gt;

&lt;span class="c"&gt;# Right prompt - Configure display of running background jobs&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_BACKGROUND_JOBS_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_BACKGROUND_JOBS_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"green"&lt;/span&gt;

&lt;span class="c"&gt;# Right prompt - Configure RAM settings&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_RAM_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_RAM_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"white"&lt;/span&gt;

&lt;span class="c"&gt;# Right prompt - Configure load settings&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_LOAD_CRITICAL_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_LOAD_WARNING_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_LOAD_NORMAL_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_LOAD_CRITICAL_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"red"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_LOAD_WARNING_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"darkorange"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_LOAD_NORMAL_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"green"&lt;/span&gt;

&lt;span class="c"&gt;# Right prompt - Configure battery status&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_BATTERY_CHARGING_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_BATTERY_CHARGING_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"white"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_BATTERY_CHARGED_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_BATTERY_CHARGED_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"green"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_BATTERY_DISCONNECTED_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_BATTERY_DISCONNECTED_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"darkorange"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_BATTERY_LOW_THRESHOLD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"10"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_BATTERY_LOW_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_BATTERY_LOW_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"red"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_BATTERY_VERBOSE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;

&lt;span class="c"&gt;# Right prompt - Configure disk usage&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DISK_USAGE_NORMAL_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DISK_USAGE_NORMAL_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"green"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DISK_USAGE_WARNING_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DISK_USAGE_WARNING_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"darkorange"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DISK_USAGE_CRITICAL_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_DISK_USAGE_CRITICAL_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"red"&lt;/span&gt;

&lt;span class="c"&gt;# Right prompt - Configure IP address&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_IP_BACKGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_IP_FOREGROUND&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"white"&lt;/span&gt;

&lt;span class="c"&gt;# Configure multiline prompt&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_PROMPT_ON_NEWLINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_SHOW_CHANGESET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"true"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_MULTILINE_FIRST_PROMPT_PREFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_MULTILINE_LAST_PROMPT_PREFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_LEFT_SEGMENT_SEPARATOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_RIGHT_SEGMENT_SEPARATOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_LEFT_SUBSEGMENT_SEPARATOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_RIGHT_SUBSEGMENT_SEPARATOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;

&lt;span class="c"&gt;# Configure the prompt content&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_LEFT_PROMPT_ELEMENTS&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;root_indicator context &lt;span class="nb"&gt;dir &lt;/span&gt;vcs&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;status command_execution_time ram disk_usage ip&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;#POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(status command_execution_time load ram disk_usage ip)&lt;/span&gt;

&lt;span class="c"&gt;# Local custom snippets&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;item &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/.profile.d/&lt;span class="k"&gt;*&lt;/span&gt;.profile&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;item&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these add-ons and configuration in place, your terminal should look like my one and should, if you don’t like it, at least be a good starting point to start your own configuration.&lt;/p&gt;

</description>
      <category>macos</category>
      <category>iterm2</category>
      <category>zsh</category>
      <category>ohmyzsh</category>
    </item>
    <item>
      <title>CI/ CD with Github Actions</title>
      <dc:creator>thbe</dc:creator>
      <pubDate>Wed, 11 Dec 2019 23:51:44 +0000</pubDate>
      <link>https://dev.to/thbe/ci-cd-with-github-actions-iji</link>
      <guid>https://dev.to/thbe/ci-cd-with-github-actions-iji</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published at &lt;a href="https://www.thbe.org/posts/2019/12/03/Github_actions_-_CI.html"&gt;thbe.org&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My traditional CI/CD pipeline I have used so far was primarily a combination of &lt;a href="https://github.com/"&gt;&lt;strong&gt;Github&lt;/strong&gt;&lt;/a&gt; in conjunction with &lt;a href="https://travis-ci.com/"&gt;&lt;strong&gt;TravisCI&lt;/strong&gt;&lt;/a&gt;. This combination worked pretty well for me but had some downsides. Based on the fact that I use a PRO account on &lt;a href="https://github.com/"&gt;&lt;strong&gt;Github&lt;/strong&gt;&lt;/a&gt; I have to deal with public and private repositories. Unfortunately, the pipeline works only with public Github repositories but not with private Github repositories. Also, the way I handled secrets/ passwords that are required in the CD pipeline to deploy code to a third-party platform was not really satisfying me as it was completely manual and required personal hardware. However, when I saw that Github introduced &lt;a href="https://github.com/features/actions"&gt;&lt;strong&gt;Actions&lt;/strong&gt;&lt;/a&gt; I thought it was worth a try to check it out and to play a little bit around with the new CI/CD pipeline. In the following article, I will share my experience with Github Actions as well as what is good and whatnot.&lt;/p&gt;

&lt;p&gt;According to Github, Actions should automate your workflow from idea to production. Sounds good and is something I was looking for. But describing something on an advertising page looks, in reality, most likely somewhat different. So I picked one of the more complicated repositories I use that didn't work before and tried to establish a CI/ CD pipeline based on Github Actions. The candidate I had chosen was &lt;a href="https://www.thbe.org/"&gt;&lt;strong&gt;my website&lt;/strong&gt;&lt;/a&gt;, more precise, the tools I use to build my website. As I already posted earlier I'm using &lt;a href="https://jekyllrb.com/"&gt;&lt;strong&gt;Jekyll&lt;/strong&gt;&lt;/a&gt; to build my site. All required actions to build and test my site are collected in a Ruby-based Rakefile. For example, when my site is compiled, I run &lt;a href="https://github.com/gjtorikian/html-proofer"&gt;&lt;strong&gt;HTML-Proofer&lt;/strong&gt;&lt;/a&gt; to check the HTML code. Additional complexity was added by the fact that my website repository is private due to some parts I need to refactor first before I can release the code.&lt;/p&gt;

&lt;p&gt;The first thing that is required is a workflow file. You need to create a directory called .github/workflows in your project root. Inside that directory, you have to create a YAML file. In my case, I named the first file development.yml but you can name it as you like as long as the file ends with ".yml". This file contains the job and the associated actions the build pipeline will execute to test and deploy the code. The following file contains the CI part of the story, this means, it builds the website and check it for errors:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Development Workflow&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&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;CI Pipeline&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&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;Checkout master&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&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;Set up Ruby &lt;/span&gt;&lt;span class="m"&gt;2.6&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-ruby@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2.6.x&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;Setup bundler and required gems&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;gem install bundler&lt;/span&gt;
          &lt;span class="s"&gt;bundle install --jobs 4 --retry 3&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;Build and test the website&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rake&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, what does exactly happens in the configuration file? The first keyword defines the name of the workflow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--onRjhWS0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/dmj67wpaawtyhejoh4vc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--onRjhWS0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/dmj67wpaawtyhejoh4vc.png" alt="Workflow definition" width="800" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second keyword "on" defines that the workflow should be executed every time I push code into my Github repository. The "jobs" keyword describes what should be executed in the workflow. You can define one or more jobs, depending on what you would like to execute.&lt;/p&gt;

&lt;p&gt;The first version of the development.yml file I've shown before starts a container based on ubuntu-latest. After the start of the container, the configuration file defines four actions that should be executed inside the Ubuntu container. The first action that takes place is the checkout of my current git repository. The second action is to set up Ruby with version 2.6. Action number three setup the required Ruby packages based on the Gemfile in my repository root. When these three actions have been performed, the fourth action can do the effective test of my web site based on the Rakefile that is also defined in my git repository root.&lt;/p&gt;

&lt;p&gt;Once the actions have been performed you can see the result in the Actions tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8PoxWcPf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/6klvivyug5g5xiihq24b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8PoxWcPf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/6klvivyug5g5xiihq24b.png" alt="CI job result" width="800" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can use the triangle to expand the performed steps to see the logs in detail.&lt;/p&gt;

&lt;p&gt;Once the CI scenario has been set up for testing the changes on the website, it's time to configure the CD part for the QA deployment. So, what are we trying to achieve? Easily spoken, when the git commit has passed all required tests I would like to upload the generated site into my test tenant. This is a little bit tricky. I already wrote in my first post about Github Actions that my website is not hosted somewhere in the Github space, instead, it is hosted by a third-party provider in my web-space area. The third-party provider offers an SFTP option to upload the site data. This completely fine when the upload part is done manually but not that easy anymore when a CD tool should do the upload. Fortunately, Github Actions has options for this as well. But before we start with this, let's have a look at how my development.yml looks now with the CD part included:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Development Workflow&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&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;CI Pipeline&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&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;Checkout master&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&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;Set up Ruby &lt;/span&gt;&lt;span class="m"&gt;2.6&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-ruby@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2.6.x&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;Setup bundler and required gems&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;gem install bundler&lt;/span&gt;
          &lt;span class="s"&gt;bundle install --jobs 4 --retry 3&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;Build and test the website&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rake&lt;/span&gt;

  &lt;span class="na"&gt;deploy&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;CD Pipeline QA&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&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;webfactory/ssh-agent&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webfactory/ssh-agent@v0.1.1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ssh-private-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_PRIVATE_KEY }}&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;Checkout master&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&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;Set up Ruby &lt;/span&gt;&lt;span class="m"&gt;2.6&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-ruby@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2.6.x&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;Set up lftp&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sudo apt-get install lftp -y&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;Setup bundler and required gems&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;gem install bundler&lt;/span&gt;
          &lt;span class="s"&gt;bundle install --jobs 4 --retry 3&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;Build and deploy the website to QA&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rake deploy:qa&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SSH_DEPLOY_SERVER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_DEPLOY_SERVER }}&lt;/span&gt;
          &lt;span class="na"&gt;SSH_DEPLOY_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_DEPLOY_USER }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compared to the initial YAML I've described in my former post, you might have noticed that I have added a second job called deploy. The actions in that job are pretty much the same as in the test job. This job should also create a new Ubuntu container, it should install Ruby and the Gems required to create my Jekyll site. Two things, however, differ from the test job, the first one is an action called "webfactory/ssh-agent", the second one is a deploy action that uses a special environment. Let's start with the second one, as mentioned, I use a special environment for this action. The rationale behind this is, that I need to pass the target and the credentials to deploy the generated site to this target. To achieve this I created three secrets in the Github secret area of the repository:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4tbgjQtz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/f6mnw1x8t2wkqkba4pq4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4tbgjQtz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/f6mnw1x8t2wkqkba4pq4.png" alt="Github secret area" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the end, these secrets are "only" variables that will be made available in the container that is started with Github Actions. Nonetheless, this is the missing feature to generate deploy scripts without storing secrets in the code that is pushed to Github. Instead, the secrets are kept outside of the container in a secure area at Github ready to be used at each deploy. In detail this means, I use environment variables in my Rakefile to define the target where I would like to deploy the generated site as well as the user that should be used for the deploy. In principle, it would be possible as well to deploy a password with that way but I made the decision to use a cryptographic key instead.&lt;/p&gt;

&lt;p&gt;To make things easier for me I used a predefined action called &lt;a href="https://github.com/webfactory/ssh-agent"&gt;&lt;strong&gt;ssh-agent&lt;/strong&gt;&lt;/a&gt;. This action reads the cryptographic key stored in SSH_PRIVATE_KEY and adds it to the ssh-agent installed in the container. With this mechanism in place, I'm able to deploy the generated site code with a passwordless login.&lt;/p&gt;

&lt;p&gt;Last but not least, I need to deploy the generated website code to my production environment, at least when I'm happy with the results from the QA deploy. Till now, the trigger to start the deployment was the push of a commit. When we talk about the productive deployment it makes sense to introduce another flag that must be set before something is deployed to production. Fortunately here comes git into the game. Git allows us to tag a specific commit. Tagging a specific commit in a defined way is the flag I use for the production deployment. Here you see the release.yml that I use for my production deployments:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Release Workflow&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v*"&lt;/span&gt; &lt;span class="c1"&gt;# Push events to matching v*, i.e. v1.0&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&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;CD Pipeline PRD&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&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;webfactory/ssh-agent&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webfactory/ssh-agent@v0.1.1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ssh-private-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_PRIVATE_KEY }}&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;Checkout master&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&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;Set up Ruby &lt;/span&gt;&lt;span class="m"&gt;2.6&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-ruby@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ruby-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2.6.x&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;Set up lftp&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sudo apt-get install lftp -y&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;Setup bundler and required gems&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;gem install bundler&lt;/span&gt;
          &lt;span class="s"&gt;bundle install --jobs 4 --retry 3&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;Build and deploy the website to PRD&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bundle exec rake deploy:production&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;SSH_DEPLOY_SERVER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_DEPLOY_SERVER }}&lt;/span&gt;
          &lt;span class="na"&gt;SSH_DEPLOY_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SSH_DEPLOY_USER }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most import thing is the "on -&amp;gt; push -&amp;gt; tags" part. I still deploy with the push event, but only if the commit is tagged starting with a "v". So when I tag my commit as "v1.0.2" it will be automatically pushed to production. The even better part of this solution is, I don't need a special development environment to push a commit to production, it could be completely done from the Github web interface. To do so, you need to go to the releases tab of your code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--O7tVtTGz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/z0q1uullunw2azgzok1d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--O7tVtTGz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/z0q1uullunw2azgzok1d.png" alt="Github release tab" width="800" height="193"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a release, e.g. v1.0.0:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YFusPx3F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/f8udxfs973xe577bjelq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YFusPx3F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/f8udxfs973xe577bjelq.png" alt="Create a release" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As soon as the version tag is pushed to the Github platform, the whole CI/ CD chain starts. This means, the development.yml and release.yml instructions run in parallel and, when everything went well and no errors were detected, the web-site goes straight into production:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Si7cYPGM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/vs8p9n61fgcax7dq7g3e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Si7cYPGM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/vs8p9n61fgcax7dq7g3e.png" alt="Create a release" width="800" height="745"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wWyB9RQQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/6ru9nnucd5sawk7rfjjz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wWyB9RQQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/6ru9nnucd5sawk7rfjjz.png" alt="Create a release" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The release automatically creates the corresponding tag:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---l-hfiHg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/64v4tbv7kbum67yvvh0s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---l-hfiHg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/64v4tbv7kbum67yvvh0s.png" alt="Create a release" width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This triggers finally the whole CI/ CD pipeline:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lhSz3gwc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/yvl730y1omrxuizbnvcy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lhSz3gwc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/yvl730y1omrxuizbnvcy.png" alt="Create a release" width="800" height="174"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And finally, everything is in production as planned:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tKLHsk1i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/k2ed3kxsfonu97c1l6c2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tKLHsk1i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/k2ed3kxsfonu97c1l6c2.png" alt="Create a release" width="800" height="314"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope you enjoyed my three posts on how to set up and how to use Github Actions. So far I'm quite surprised how flawless and good the CI/ CD pipeline already works and I didn't find any major caveats so far that prevents me from using Github Actions. I'm already quite eager to migrate some of my other repositories as well to get more inside into what is possible on Github Actions and whatnot, but for now, that's all I could tell. I'll keep you updated when I had migrated more projects.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>github</category>
      <category>devops</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
