<?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>Forem: Vedran Vidovic</title>
    <description>The latest articles on Forem by Vedran Vidovic (@vvidovic).</description>
    <link>https://forem.com/vvidovic</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%2F304969%2F3af4d633-d06f-4dbd-90ce-81db4b6c762b.jpeg</url>
      <title>Forem: Vedran Vidovic</title>
      <link>https://forem.com/vvidovic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/vvidovic"/>
    <language>en</language>
    <item>
      <title>How to run SoapUI mockservicerunner in a Docker container</title>
      <dc:creator>Vedran Vidovic</dc:creator>
      <pubDate>Tue, 15 Jun 2021 06:27:13 +0000</pubDate>
      <link>https://forem.com/vvidovic/how-to-run-soapui-mockservicerunner-in-a-docker-container-f50</link>
      <guid>https://forem.com/vvidovic/how-to-run-soapui-mockservicerunner-in-a-docker-container-f50</guid>
      <description>&lt;p&gt;On one of our integration projects using &lt;a href="https://www.ibm.com/cloud/api-connect"&gt;the IBM API Connect&lt;/a&gt;, we wanted to create a quick mock of our Customer's backend SOAP service. We used quite effectively a SpringBoot a few times in the past but I wondered if it would be faster (or at least more interesting) to use the mocking capabilities of &lt;a href="https://www.soapui.org/tools/soapui/"&gt;the SoapUI&lt;/a&gt; for this purpose 🤔&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR;
&lt;/h2&gt;

&lt;p&gt;You can use the prepared Docker image to run mock services created by the SoapUI in the &lt;a href="https://hub.docker.com/r/vvidovic/soapui-mockservicerunner"&gt;soapui-mockservicerunner image&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm -p 8080:8080 -v $PWD/soapui:/home/soapui/project vvidovic/soapui-mockservicerunner:latest -a "/" -p "8080" /home/soapui/project/my-soapui-project.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating mock service in the SoapUI
&lt;/h2&gt;

&lt;p&gt;SoapUI is a great tool with an intuitive GUI that you can use to call and mock&lt;br&gt;
SOAP web services. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a new project based on the WSDL&lt;/li&gt;
&lt;li&gt;generate a Mock SOAP service (right-click on the interface in a projects navigator view)&lt;/li&gt;
&lt;li&gt;edit generated responses and/or create new responses&lt;/li&gt;
&lt;li&gt;open and edit the MockOperation

&lt;ul&gt;
&lt;li&gt;select the appropriate Dispatch method (XPATH, SCRIPT, RANDOM, SEQUENCE, QUERY_MATCH)&lt;/li&gt;
&lt;li&gt;for example, you can select the response based on the XPath result of the request received&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more details about the SoapUI mocking features, please check &lt;a href="https://www.soapui.org/docs/soap-mocking/service-mocking-overview/"&gt;SOAP Service Mocking Overview&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5fb6yvng--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iyaue8vyes5bc51g5nih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5fb6yvng--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iyaue8vyes5bc51g5nih.png" alt="SoapUI mock operation response selection using the XPath"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Running the created mock service from the command line
&lt;/h2&gt;

&lt;p&gt;SoapUI doesn't stop with the GUI for your mocking needs. It have a command-line &lt;code&gt;mockservicerunner.sh&lt;/code&gt; script which can be used to run your SOAP mock service without the UI.&lt;/p&gt;

&lt;p&gt;The process to use it is quite simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a mock service using the GUI&lt;/li&gt;
&lt;li&gt;run a mock service using the provided script &lt;code&gt;mockservicerunner.sh&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./mockservicerunner.sh -a '/' -p 8080 my-soapui-project.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-a&lt;/code&gt;: the local path to listen on&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-p&lt;/code&gt;: the local port to listen on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more information about how to run your mock services from the command line, please check &lt;a href="https://www.soapui.org/docs/test-automation/running-from-command-line/soap-mock/"&gt;the SoapUI Test Automation documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2S4kIAuy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2g4pcb97zeaxhsjlnc4d.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2S4kIAuy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2g4pcb97zeaxhsjlnc4d.jpg" alt="And now,.. creating a mock service from a Docker container"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the created mock service as a Docker container
&lt;/h2&gt;

&lt;p&gt;I tried to find out if there is a SoapUI mockservicerunner image provided on the &lt;a href="https://hub.docker.com/"&gt;Docker Hub&lt;/a&gt;. I was able to find few images that bundle the SoapUI mockservicerunner but none of them provided the script as an Docker &lt;code&gt;ENTRYPOINT&lt;/code&gt;. That means I could not trivially migrate my logic used to start mock services on local machine to a Docker container.&lt;/p&gt;

&lt;p&gt;Well, how hard it can be to prepare a Docker image using the SoapUI binary?&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the &lt;code&gt;soapui-mockservicerunner&lt;/code&gt; image
&lt;/h3&gt;

&lt;p&gt;Not hard but there were few obstacles on the road, the first I encountered was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the latest &lt;a href="https://www.soapui.org/downloads/soapui/"&gt;SoapUI Open Source&lt;/a&gt; provided is version 5.6.0 but &lt;a href="https://community.smartbear.com/t5/SoapUI-Open-Source/Soap-UI-5-6-0-tgz-on-Linux-is-broken-FIX-INSIDE/td-p/204960"&gt;the version for the Linux is broken&lt;/a&gt; and it requires some work-around

&lt;ul&gt;
&lt;li&gt;I used &lt;a href="https://dl.eviware.com/soapuios/5.5.0/SoapUI-5.5.0-linux-bin.tar.gz"&gt;the version 5.5.0&lt;/a&gt; available from the &lt;a href="https://www.soapui.org/downloads/soapui/soapui-os-older-versions/"&gt;SoapUI Open Source Older Versions&lt;/a&gt; to simplify the process&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that, I had to prepare a &lt;code&gt;Dockerfile&lt;/code&gt; based on the OpenJDK JRE in which I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use JRE 11 or 8&lt;/li&gt;
&lt;li&gt;copy the SoapUI archive to the container&lt;/li&gt;
&lt;li&gt;unpack the archive, delete archive and move the resulting folder to the &lt;code&gt;/opt&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;declare the &lt;code&gt;mockservicerunner.sh&lt;/code&gt; as a Docker &lt;code&gt;ENTRYPOINT&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;add a non-root user to the image&lt;/li&gt;
&lt;li&gt;set a Docker &lt;code&gt;WORKDIR&lt;/code&gt; to this user's home directory&lt;/li&gt;
&lt;li&gt;set a newly create user as a user to use when running this Docker image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full &lt;code&gt;Dockerfile&lt;/code&gt; is here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM openjdk:11-jre-slim
#FROM openjdk:8-jre-alpine

COPY ./soapui/*.tar.gz /opt/

RUN cd /opt &amp;amp;&amp;amp; tar -xvf /opt/*.tar.gz &amp;amp;&amp;amp; rm /opt/*.tar.gz &amp;amp;&amp;amp; mv * SoapUI

ENTRYPOINT ["/opt/SoapUI/bin/mockservicerunner.sh"]

RUN adduser --uid 1000 --disabled-password soapui

WORKDIR /home/soapui

USER soapui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before building this image I had to put SoapUI 5.5.0 archive to the &lt;code&gt;./soapui&lt;/code&gt; directory and then just execute the build command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build . -t vvidovic/soapui-mockservicerunner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make the usage of this image more simple for everyone else, I published it to a new Docker Hub repository under my user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker login
docker push vvidovic/soapui-mockservicerunner:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using the &lt;code&gt;soapui-mockservicerunner&lt;/code&gt; image
&lt;/h3&gt;

&lt;p&gt;The end result is available in the published &lt;a href="https://hub.docker.com/r/vvidovic/soapui-mockservicerunner"&gt;soapui-mockservicerunner Docker image&lt;/a&gt; which can be run using the following command (using your own SoapUI project):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm -p 8080:8080 -v $PWD/soapui:/home/soapui/project vvidovic/soapui-mockservicerunner:latest -a "/" -p "8080" /home/soapui/project/my-soapui-project.xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;If you want to create a mock SOAP service for your project, an approach described here can help you to do it faster and easier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;using SoapUI GUI application to create mock operations&lt;/li&gt;
&lt;li&gt;using &lt;a href="https://hub.docker.com/r/vvidovic/soapui-mockservicerunner"&gt;soapui-mockservicerunner Docker image&lt;/a&gt; to run those service on your local machine or in your cloud&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Some of the images used in this post are provided by &lt;a href="https://pixabay.com"&gt;Pixabay&lt;/a&gt; users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pixabay.com/users/alexas_fotos-686414/"&gt;Alexas_Fotos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pixabay.com/users/qimono-1962238"&gt;qimono&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>mock</category>
      <category>soapui</category>
    </item>
    <item>
      <title>AqiSDS011 - Android AQI app</title>
      <dc:creator>Vedran Vidovic</dc:creator>
      <pubDate>Sun, 21 Mar 2021 20:38:38 +0000</pubDate>
      <link>https://forem.com/vvidovic/aqisds011-android-aqi-app-498o</link>
      <guid>https://forem.com/vvidovic/aqisds011-android-aqi-app-498o</guid>
      <description>&lt;p&gt;In the previous two posts in this series, you could read a bit more about out-of-the-box ways to use the SDS011 air quality sensor. At first, I connected the sensor to my laptop but then I realized that my mobile phone could be a more portable appliance accomplishing the same purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the existing sds011_android app
&lt;/h2&gt;

&lt;p&gt;After some time spent using a sensor with my mobile phone and the &lt;a href="https://github.com/milosevic81/sds011_android"&gt;sds011_android application found on the GitHub&lt;/a&gt;, I realized there are few features that would make this application even more usable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UI should reflect &lt;a href="https://uk-air.defra.gov.uk/air-pollution/daqi?view=more-info&amp;amp;pollutant=pm25#pollutant"&gt;proper colours for air quality according to the PM2.5 or PM10 levels&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;save/view a history of measurements

&lt;ul&gt;
&lt;li&gt;date, time &amp;amp; location included&lt;/li&gt;
&lt;li&gt;show location for each measurement on the map&lt;/li&gt;
&lt;li&gt;this way we can take a ride or walk around the town and later analyze data to see if air quality differs between different locations&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;pause/continue measurements

&lt;ul&gt;
&lt;li&gt;this would save the sensor laser &amp;amp; my phone battery&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;configure periodic measurements

&lt;ul&gt;
&lt;li&gt;this would save the sensor laser &amp;amp; my phone battery&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I didn't touch Android development for quite a long time but I felt it shouldn't be too hard to implement at least some of the above-mentioned features. Since I wanted to have such an application for my own usage and I hoped something like that could be useful to other people I decided to spend some time on the project I named &lt;a href="https://github.com/vvidovic/aqi-sds011"&gt;the Mobile Air Quality Monitor application for the SDS011 Sensor&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  AqiSDS011 - the android app
&lt;/h2&gt;

&lt;p&gt;The current result is available on GitHub as &lt;a href="https://github.com/vvidovic/aqi-sds011"&gt;the AqiSDS011 repository&lt;/a&gt;. I will need some more time to prepare and publish it as an application on the &lt;a href="https://play.google.com/store/apps"&gt;Google Play Android app store&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before I do that, anyone wishing to use the application will (unfortunately) need to build and run the application herself/himself which is not an easy task for a non-developer. This task can be accomplished using the Android Studio, &lt;a href="https://developer.android.com/training/basics/firstapp/running-app"&gt;as described in the official tutorial&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I am thankful to the developers of &lt;a href="https://github.com/milosevic81/sds011_android"&gt;the sds011_android project&lt;/a&gt; and the developers of &lt;a href="https://github.com/zefanja/aqi"&gt;the aqi project&lt;/a&gt; - without these two projects, it would be much harder to implement the AqiSDS011 project. I copied parts of the logic for interaction with the SDS011 sensor from the sds011_android application and logic for calculating the AQI indices for PM2.5 and PM10 from the aqi Python logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  The modes of operation of the SDS011 sensor
&lt;/h3&gt;

&lt;p&gt;The SDS011 sensor has two data reporting modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Active reporting (used by AqiSDS011 and the sds011_android projects)

&lt;ul&gt;
&lt;li&gt;when this mode is enabled, the sensor sends measuring data without additional queries&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Query reporting (used by aqi project)

&lt;ul&gt;
&lt;li&gt;when this mode is enabled, the sensor waits for query before measuring and sending the measuring data back to the connected appliance&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the active data reporting is used, the sensor can work in one of two modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sending data continuously (each second)&lt;/li&gt;
&lt;li&gt;sending data each n minutes (depending on settings sent to the sensor)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AqiSDS011 supports both modes of active data reporting.&lt;/p&gt;

&lt;h3&gt;
  
  
  The current status of the AqiSDS011
&lt;/h3&gt;

&lt;p&gt;Currently, the project finally implemented the list of basic requirements through 3 screens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cOB1pZmm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bt7tq2eibnsotflfp4i9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cOB1pZmm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bt7tq2eibnsotflfp4i9.png" alt="The main screen - the current info"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main measurements screen shows the current AQI information and enables you to start or stop the measuring process. This is a screen where you can choose between the continuous measuring mode and the periodic measuring mode.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z5tYVfSG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ubtms3gi3omchm5ejh3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z5tYVfSG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ubtms3gi3omchm5ejh3.png" alt="The history screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The history screen shows all saved measurements but also enables you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;save the history information&lt;/li&gt;
&lt;li&gt;clear the history&lt;/li&gt;
&lt;li&gt;open the map application to show you the location for the saved measurement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XDf5-gur--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6b8oesf98gyjuydyo4od.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XDf5-gur--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6b8oesf98gyjuydyo4od.png" alt="The settings screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The settings screen enables you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enter the length of the work period (applied when you use the application in the periodic mode).&lt;/li&gt;
&lt;li&gt;enter the number of measurements (equals the number of seconds) for averaging before saving to history&lt;/li&gt;
&lt;li&gt;select the location data gatherings setting&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The road ahead
&lt;/h3&gt;

&lt;p&gt;While AqiSDS011 became an application with all initially envisioned features, there are still a few issues I would like to do or at least investigate. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;prepare a description and publish AqiSDS011 to the Google Play app store&lt;/li&gt;
&lt;li&gt;check if converting a part of the application to the service would make AqiSDS011 more reliable for the long-running measurements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cover image by &lt;a href="https://pixabay.com/users/8385-8385/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=2462053"&gt;Reimund Bertrams&lt;/a&gt; from &lt;a href="https://pixabay.com/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=2462053"&gt;Pixabay&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>sds011</category>
      <category>android</category>
      <category>airquality</category>
    </item>
    <item>
      <title>Using SDS011 particle sensor with my Android phone</title>
      <dc:creator>Vedran Vidovic</dc:creator>
      <pubDate>Sat, 06 Feb 2021 09:04:12 +0000</pubDate>
      <link>https://forem.com/vvidovic/using-sds011-particle-sensor-with-my-android-phone-2475</link>
      <guid>https://forem.com/vvidovic/using-sds011-particle-sensor-with-my-android-phone-2475</guid>
      <description>&lt;p&gt;After some testing of SDS011 particle sensor with my laptop (s described in the first post of this series) I realized it is not the most practical way to check for the air quality. I needed to take a laptop with me in a backpack, get it out of it, resume it, connect the sensor using the USB and start &lt;a href="https://github.com/vvidovic/aqi"&gt;the Python3 script&lt;/a&gt; (while keeping the lighttpd server running during suspends).&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZciJsAIf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wvw89tafzl8b6vodjxnq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZciJsAIf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/wvw89tafzl8b6vodjxnq.jpg" alt="taking the laptop outside"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At some point, I realized that I have a quite powerful and much smaller portable computer with me at all times - Android mobile phone. Fortunately, a few months ago I bought &lt;a href="https://en.wikipedia.org/wiki/USB_On-The-Go"&gt;OTG cable&lt;/a&gt; for my phone.&lt;/p&gt;

&lt;p&gt;I found a &lt;a href="https://play.google.com/store/apps/details?id=de.kai_morich.serial_usb_terminal"&gt;Serial USB Terminal&lt;/a&gt; for an Android phone, installed it and connected the SDS011 sensor to my phone. An application was started automatically and after I started a connection I saw some garbage characters periodically appearing in the terminal output - so, I guessed my phone can read the output from a sensor :)&lt;/p&gt;

&lt;p&gt;First tests showed this guess was correct. Initial calculations proved that data fetched from the phone represent correct measurements. Hand-made calculations were made based on the &lt;a href="https://joy-it.net/en/products/SEN-SDS011"&gt;Arduino C code from the joy-it sensor manual&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
#define LEN 9
unsigned char incomingByte = 0;
unsigned char buf[LEN];
int PM2_5Val = 0;
int PM10Val = 0;
...
if (incomingByte == 0xAA) {
  Serial.readBytes(buf, LEN);
  if ((buf[0] == 0xC0) &amp;amp;&amp;amp; (buf[8] == 0xAB)) {
    for (i=1; i&amp;lt;=6; i++) {
      checksum = checksum + buf[i];
    }
    if (checksum != buf[7]) {
      PM2_5Val=((buf[2]&amp;lt;&amp;lt;8) + buf[1])/10;
      PM10Val=((buf[4]&amp;lt;&amp;lt;8) + buf[3])/10;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which means that for the following sequence of bytes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hex: aa  c0  50  00  29  01  7f  bc  b5  ab
dec:         80  00  41  01 127 188 181
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...I did the following math which shows same values I was able to measure using the above mentioned Python3 script on my laptop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;checksum (hex) = 50+00+29+01+7f+bc = 1b5 (which overflows to b5)
PM25 (dec) = (00*256 + 80) / 10 =  8.0
PM10 (dec) = (01*256 + 41) / 10 = 29.7 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After some thinking, I realized this is a good use-case for a new Android application.&lt;/p&gt;

&lt;p&gt;"Well, maybe I am not the first one thinking about that.."&lt;/p&gt;

&lt;p&gt;I searched for such application on the &lt;a href="https://play.google.com/store/apps"&gt;Google Play&lt;/a&gt;. Unfortunately - no such application existed (at least at the time of writing this post).&lt;/p&gt;

&lt;p&gt;I searched for Android application on the Internet and I found &lt;a href="https://github.com/milosevic81/sds011_android"&gt;a GitHub repo for SDS011 Android application&lt;/a&gt; - much better. Unfortunately, this application is not published to the Google Play so I needed to build and install it "by hand".&lt;/p&gt;

&lt;p&gt;I built and installed this application to my phone using &lt;a href="https://developer.android.com/studio/run/device"&gt;the Android Developers instructions&lt;/a&gt;. This is not for the casual phone user, it requires a download and configuration of the &lt;a href="https://developer.android.com/studio"&gt;Android Studio&lt;/a&gt;. However, the end result is nice - thanks to the &lt;a href="https://github.com/milosevic81/sds011_android/graphs/contributors"&gt;developers of the SD011 Android app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n8AKin1M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a365o93ugghw8se93p16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n8AKin1M--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/a365o93ugghw8se93p16.png" alt="SDS011 Android application"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After some playing with this app, I found there are some downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gauges' ranges are not showing &lt;a href="https://uk-air.defra.gov.uk/air-pollution/daqi?view=more-info&amp;amp;pollutant=pm25#pollutant"&gt;proper colours for air quality according to the PM2.5 or PM10 levels&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;app doesn't record measurements history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If I find a time, I would like to create an Android application with the following functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;save/view a history of measurements

&lt;ul&gt;
&lt;li&gt;date-time &amp;amp; GPS included&lt;/li&gt;
&lt;li&gt;show location for each measurement on the map&lt;/li&gt;
&lt;li&gt;this way we can take a ride or walk around the town and later analyze data to see if air quality differs between different locations&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;pause/continue measurements

&lt;ul&gt;
&lt;li&gt;this would save the sensor laser &amp;amp; my phone battery&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;configure periodic measurements

&lt;ul&gt;
&lt;li&gt;this would save the sensor laser &amp;amp; my phone battery&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cover image &amp;amp; laptop image are created by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pixabay.com/users/computergottyt-2511322/"&gt;computergottyt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pixabay.com/users/goumbik-3752482/"&gt;Goumbik&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>sds011</category>
      <category>android</category>
      <category>airquality</category>
    </item>
    <item>
      <title>Using SDS011 particle sensor from my laptop (python3)</title>
      <dc:creator>Vedran Vidovic</dc:creator>
      <pubDate>Fri, 29 Jan 2021 07:11:24 +0000</pubDate>
      <link>https://forem.com/vvidovic/using-sds011-particle-sensor-from-my-laptop-python3-4lc7</link>
      <guid>https://forem.com/vvidovic/using-sds011-particle-sensor-from-my-laptop-python3-4lc7</guid>
      <description>&lt;p&gt;Recently we started to check the quality of the air in our town using the &lt;a href="https://www.iqair.com/"&gt;IQAir&lt;/a&gt; / &lt;a href="https://play.google.com/store/apps/details?id=com.airvisual"&gt;Air Visual&lt;/a&gt; and results were sometimes quite disturbing. That is why we decided to buy an air quality measuring device. Unfortunately, I was not able to find it in any of our local stores so I ordered it from the &lt;a href="https://www.amazon.de/"&gt;Amazon.de&lt;/a&gt; but we needed to wait for around 1 month (if not more) for this monitor to arrive.&lt;/p&gt;

&lt;p&gt;I realized there is &lt;a href="https://joy-it.net/en/products/SEN-SDS011"&gt;SDS011 Air Quality sensor&lt;/a&gt; available from our local electronics components store so we went there and bought it the same day.&lt;/p&gt;

&lt;p&gt;It seems that I would only need a laptop with the USB port and working Python installation to use it. Of course, it was not 100% straight-forward to use it - instructions were made for usage with &lt;a href="https://www.raspberrypi.org/"&gt;the Raspberry Pi&lt;/a&gt; machine and older Python2 version. It didn't take a long to adjust &lt;a href="https://github.com/zefanja/aqi"&gt;the open-source code cloned from the GitHub&lt;/a&gt; for my specific needs.&lt;/p&gt;

&lt;p&gt;The forked project with changes (in case you need to use the sensor from your machine with the Python3 instead of &lt;a href="https://www.python.org/doc/sunset-python-2/"&gt;no longer supported Python2&lt;/a&gt; environment) is available on &lt;a href="https://github.com/vvidovic/aqi"&gt;my GitHub fork of the original project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A nice byproduct is that I realized I can add a measurements history to the sensor web page and I created &lt;a href="https://github.com/zefanja/aqi/pull/15"&gt;a pull request to the original project&lt;/a&gt; which is now merged and available for everyone using this code with the Raspberry Pi :)&lt;/p&gt;

&lt;p&gt;The cover image is created by the &lt;a href="https://pixabay.com/users/foto-rabe-715168/"&gt;Foto-Rabe&lt;/a&gt;&lt;/p&gt;

</description>
      <category>sds011</category>
      <category>python</category>
      <category>rasspberypi</category>
      <category>airquality</category>
    </item>
    <item>
      <title>Using datefudge to fake Docker date &amp; time for testing</title>
      <dc:creator>Vedran Vidovic</dc:creator>
      <pubDate>Wed, 04 Nov 2020 11:01:10 +0000</pubDate>
      <link>https://forem.com/vvidovic/using-datefudge-to-fake-docker-date-time-for-testing-3eee</link>
      <guid>https://forem.com/vvidovic/using-datefudge-to-fake-docker-date-time-for-testing-3eee</guid>
      <description>&lt;p&gt;On one project we are starting docker container with our services running on &lt;a href="https://hub.docker.com/r/ibmcom/websphere-liberty"&gt;the IBM WebSphere Liberty server&lt;/a&gt; and run tests which make sure our services are behaving according to our expectations.&lt;/p&gt;

&lt;p&gt;Problem is that some of the scenarios we need to test include specific dates and times. For example, you can have a system which behaves differently if it is called during the working day vs. during a weekend.&lt;br&gt;
Service can behave differently if it is called during working hours or during the night.&lt;/p&gt;

&lt;p&gt;Current date and time in the docker container are connected to time on the host machine it is running on. We wanted to find a way to force our service to behave as it was called on any specific date and time - not only the current time of test execution.&lt;/p&gt;

&lt;p&gt;Of course, we could change our service for the purpose of testing but I thought it would be nicer to find another way to accomplish the same task, without any changes to our services and our docker images.&lt;/p&gt;
&lt;h2&gt;
  
  
  Modifying the Linux system time without modifying the Linux system time
&lt;/h2&gt;

&lt;p&gt;Fortunately, we did find some solutions to our problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wolfcw/libfaketime"&gt;libfaketime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://manpages.ubuntu.com/manpages/trusty/man1/datefudge.1.html"&gt;datefudge&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both of those libraries intercept various system calls that programs use to retrieve the current date and time and change results according to library configuration.&lt;/p&gt;

&lt;p&gt;Here are examples of typical libfaketime &amp;amp; datefudge usage applied to a &lt;code&gt;date&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# LD_PRELOAD=/usr/lib64/faketime/libfaketime.so.1 FAKETIME="2020-10-22 12:13:14" date -R
Thu, 22 Oct 2020 12:13:14 +0200

# datefudge "2020-10-22 12:13:14" date -R
Thu, 22 Oct 2020 12:13:14 +0200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After an initial investigation, libfaketime seemed like a great solution for our task. Its configuration is quite flexible and it supports configuring it statically through the environment variable &lt;code&gt;FAKETIME&lt;/code&gt; or dynamically through a local file.&lt;/p&gt;

&lt;p&gt;For example, if we set the libfaketime library to be loaded by&lt;br&gt;
&lt;code&gt;LD_PRELOAD&lt;/code&gt; environment variable it will check &lt;code&gt;FAKETIME&lt;/code&gt; environment variable for date &amp;amp; time configuration to use and if the environment variable is not found it will check the local file &lt;code&gt;~/.faketimerc&lt;/code&gt; for its configuration. This way we can change contents of this file to change date &amp;amp; time in a running program.&lt;/p&gt;

&lt;p&gt;Unfortunately, libfaketime slowed down our service considerably (almost by order of magnitude) so I decided to investigate the datefudge library.&lt;/p&gt;

&lt;p&gt;Datefudge configuration was unfortunately much less flexible - it supported only the configuration during the initial start of our service. However, initial tests showed that our service didn't show visible slow down so I checked how hard would it be to make it a bit more configurable.&lt;/p&gt;
&lt;h3&gt;
  
  
  Customizing datefudge
&lt;/h3&gt;

&lt;p&gt;Source code of &lt;a href="https://github.com/vvidovic/datefudge/tree/initial-commit-v1.24"&gt;datefudge version 1.24&lt;/a&gt; is ingeniously simple, it combines &lt;a href="https://github.com/vvidovic/datefudge/blob/initial-commit-v1.24/datefudge.sh"&gt;a single shell script&lt;/a&gt; and &lt;a href="https://github.com/vvidovic/datefudge/blob/initial-commit-v1.24/datefudge.c"&gt;less than 100 lines of C code&lt;/a&gt;.&lt;br&gt;
Datefudge uses shell &lt;code&gt;date&lt;/code&gt; command to parse date string and to get the current time which makes C code quite straight-forward - C code just uses prepared values passed to it through environment variables.&lt;/p&gt;

&lt;p&gt;Unfortunately this approach is not possible if date is configured dynamically - if configuration is changed after datefudge is initialized. In that case our date/time configuration string must be parsed by C code. GNU &lt;code&gt;time.h&lt;/code&gt; library fortunately implements &lt;a href="https://man7.org/linux/man-pages/man3/strptime.3.html"&gt;strptime&lt;/a&gt;&lt;br&gt;
function which makes parsing quite straight-forward. With just a few tweaks partial date-time string parsing &amp;amp; correct daylight saving handling is possible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Initialize tm struct to 1900-01-01 00:00:00 &amp;amp; tells mktime() to
// determine whether daylight saving time is in effect
struct tm tm = (struct tm){.tm_mday = 1, .tm_isdst = -1};
strptime(datetime_str, "%Y-%m-%d %H:%M:%S", &amp;amp;tm);
time_t config_time = mktime(&amp;amp;tm);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/vvidovic/datefudge"&gt;Customized datefudge&lt;/a&gt; library adds the following feature to the datefudge v1.24 version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;date &amp;amp; time can be configured using a local file

&lt;ul&gt;
&lt;li&gt;when this file is changed datefudge will load new configuration, however, the datefudge configuration file is checked for changes at most each 100 ms (this time can be configured using &lt;code&gt;-s&lt;/code&gt; flag) to lower the performance costs of a file access operation&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Datefudging a docker container with our service
&lt;/h3&gt;

&lt;p&gt;We have multiple test scenarios containing multiple test steps. We can execute a single test scenario or all test scenarios using a single Docker container started automatically before our tests start to run.&lt;/p&gt;

&lt;p&gt;For example we can have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;test scenario 1

&lt;ul&gt;
&lt;li&gt;test step 1&lt;/li&gt;
&lt;li&gt;test step 2&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;test scenario 2

&lt;ul&gt;
&lt;li&gt;test step 1&lt;/li&gt;
&lt;li&gt;test step 2&lt;/li&gt;
&lt;li&gt;test step 3&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For our tests, we wanted to be able to set our service date &amp;amp; time for each of our test steps and every call to service during that step (and steps following it) will use new date &amp;amp; time.&lt;/p&gt;

&lt;p&gt;(We prepared automated tests using &lt;a href="https://www.testcontainers.org/"&gt;Testcontainers project&lt;/a&gt; but that is a different story which doesn't belong to this post)&lt;/p&gt;

&lt;p&gt;The final solution consists of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;copy customized datefudge command (shell script) &amp;amp; datefudge shared library to the container (we are using
&lt;a href="https://github.com/testcontainers/testcontainers-java/blob/eeecf94f7c42b628599de13bbc5dbeaa5296f6ef/core/src/main/java/org/testcontainers/containers/GenericContainer.java#L1249"&gt;Testcontainers GenericContainer.withCopyFileToContainer&lt;/a&gt; to achieve that but this could be done using different strategies otherwise)&lt;/li&gt;
&lt;li&gt;change the CMD (default docker command from the image)

&lt;ul&gt;
&lt;li&gt;for our WebSphere Liberty image CMD is by default set to &lt;code&gt;CMD ["/opt/ibm/wlp/bin/server" "run" "defaultServer"]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;on container run we set it's CMD to &lt;code&gt;CMD ["datefudge" "-f" "/home/default/date.txt" "/opt/ibm/wlp/bin/server" "run" "defaultServer"]&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;for each test scenario step where we need to set custom date &amp;amp; time we copy a new file with specific date &amp;amp; time to the running container as a datefudge configuration file &lt;code&gt;/home/default/date.txt&lt;/code&gt; before running tests (and wait (configurable) 100 ms to make sure datefudge will apply new date &amp;amp; time configuration before tests are run)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Running tests with customized system times
&lt;/h3&gt;

&lt;p&gt;For above given example if we add custom datefudge configuration for scenario 1 / step 2 (date-time A) and additional configuratno for scenario 2 / step 1 (date-time B) tests are executed like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scenario 1

&lt;ul&gt;
&lt;li&gt;test step 1 (container uses "normal" date &amp;amp; time)&lt;/li&gt;
&lt;li&gt;configure date-time A&lt;/li&gt;
&lt;li&gt;test step 2 (container uses date-time A date &amp;amp; time)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;test scenario 2

&lt;ul&gt;
&lt;li&gt;configure date-time B&lt;/li&gt;
&lt;li&gt;test step 1 (container uses date-time B date &amp;amp; time)&lt;/li&gt;
&lt;li&gt;test step 2 (container uses date-time B date &amp;amp; time)&lt;/li&gt;
&lt;li&gt;test step 3 (container uses date-time B date &amp;amp; time)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;If you need to test the behaviour of your application at specific date &amp;amp; time (on Linux, Docker or otherwise) it is quite straight-forward to use datefudge and/or libfaketime to achieve this without ever touching your application code.&lt;/p&gt;

&lt;p&gt;The great news is that both of those libraries are opensource under GPL-2.0 so if you need some additional features you could add them without any problems. If you do that, please let the original authors know you did those changes so they include them in the next releases of libraries. Open-source community will be thankful to you :)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The cover image is created by &lt;a href="https://www.pexels.com/@kaboompics"&gt;Kaboompics .com&lt;/a&gt; from Pexels.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>datefudge</category>
      <category>testing</category>
      <category>docker</category>
    </item>
    <item>
      <title>Set up your new machine in a blink of an eye</title>
      <dc:creator>Vedran Vidovic</dc:creator>
      <pubDate>Thu, 13 Aug 2020 13:39:21 +0000</pubDate>
      <link>https://forem.com/vvidovic/set-up-your-new-machine-in-a-blink-of-an-eye-43j7</link>
      <guid>https://forem.com/vvidovic/set-up-your-new-machine-in-a-blink-of-an-eye-43j7</guid>
      <description>&lt;p&gt;When my &lt;a href="https://croz.net"&gt;company&lt;/a&gt; internal IT informed me that my new laptop arrived I was glad to finally set up a new &lt;a href="https://ubuntu.com/"&gt;Ubuntu LTS release&lt;/a&gt;. While Ubuntu comes with a lot of great applications installed out-of-the-box there are many applications which I have to install after OS installation to make my laptop useful for everyday work. Of course, there is the third part of the laptop setup equation which makes my work possible - my data :)&lt;/p&gt;

&lt;p&gt;My laptop is set up using these scripts in a repeatable way in minutes. I just keep them in a private GitHub repo and make sure that I don't install applications manually without adding them to those scripts.&lt;/p&gt;

&lt;p&gt;No sensitive data like passwords or similar are uploaded there but I still don't want to make this repo public because some of our company client names and our company project names are visible in some of the rules for the backup (exclude/include).&lt;/p&gt;

&lt;p&gt;Restore of the most valuable data is ensured with backups to both, external drive &amp;amp; Cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR;
&lt;/h2&gt;

&lt;p&gt;Using a collection of Bash scripts and the &lt;a href="https://www.gnu.org/software/stow/"&gt;GNU Stow&lt;/a&gt; you can set up your machine quickly in an easily repeatable way. Make all of the operations you perform idempotent so you can run all scripts as many times as you want (on a new machine, on a partially set up machine or if you just want to make sure all apps are installed).&lt;/p&gt;

&lt;p&gt;Add a proper backup application to that and you won't need to spend much time on setting up your new laptop - you will be able to start to use it almost immediately after it arrives.&lt;/p&gt;

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

&lt;p&gt;[Ubuntu installation[(&lt;a href="https://ubuntu.com/tutorials/install-ubuntu-desktop"&gt;https://ubuntu.com/tutorials/install-ubuntu-desktop&lt;/a&gt;) is quite straight-forward and there is no need to describe it here in more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding applications &amp;amp; OS customization
&lt;/h2&gt;

&lt;p&gt;I have read a few articles about &lt;a href="https://www.theguild.nl/how-to-manage-dotfiles-with-gnu-stow/"&gt;configuring&lt;/a&gt; and &lt;a href="https://dev.to/victoria/how-to-set-up-a-fresh-ubuntu-desktop-using-only-dotfiles-and-bash-scripts-31m6"&gt;setting up&lt;/a&gt; a machine using &lt;a href="https://www.gnu.org/software/stow/"&gt;GNU Stow&lt;/a&gt; and a bunch of shell scripts and I wanted to check how good can it work for me.&lt;/p&gt;

&lt;p&gt;What I came up with was a set of scripts with where I need to call only &lt;code&gt;install.sh&lt;/code&gt; script (to be more precise, &lt;code&gt;sudo install.sh&lt;/code&gt;) which in turn, calls all the other scripts:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/install-from-repos.sh
&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/install-custom.sh
&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/install-github-releases.sh
&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/install-custom-opt.sh

&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/install-config.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  install-from-repos.sh
&lt;/h3&gt;

&lt;p&gt;This script first adds custom Ubuntu repositories (using another script) and after that executes a bunch of &lt;code&gt;apt install&lt;/code&gt; &amp;amp; &lt;code&gt;snap install&lt;/code&gt; commands, something like:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/add-custom-repos.sh

apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; git
apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; stow
apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; source-highlight
apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; vim
...
snap &lt;span class="nb"&gt;install &lt;/span&gt;xmlstarlet &lt;span class="nt"&gt;--classic&lt;/span&gt;
snap &lt;span class="nb"&gt;install &lt;/span&gt;go &lt;span class="nt"&gt;--classic&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  install-custom.sh
&lt;/h3&gt;

&lt;p&gt;Custom installation is for now very simple - it just &lt;a href="https://dev.to/vvidovic/viewing-more-with-less-color-syntax-hhp"&gt;enables syntax colouring&lt;/a&gt; in the &lt;code&gt;less&lt;/code&gt; pager command:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'#!/bin/bash\ndiff -u -r "$1" "$2" | cdiff | less -R'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /usr/local/bin/ldiff
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/local/bin/ldiff
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"installed ldiff script"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  install-github-releases.sh
&lt;/h3&gt;

&lt;p&gt;Installation of GitHub releases is more complex but it boils down to fetching releases list using &lt;a href="https://docs.github.com/en/rest"&gt;GitHub REST API&lt;/a&gt; and installing the latest version (if it is not already installed). There are more of it for many different applications but this will give you enough info to make your version of a script:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Starting to install custom releases from GitHub."&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;

&lt;span class="c"&gt;# Fetches available versions from GitHub and finds the latest version satisfying&lt;/span&gt;
&lt;span class="c"&gt;# grep constraints&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;find_github_latest_release_url&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;release_repo_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;release_grep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;release_list_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/repos/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;release_repo_path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/releases"&lt;/span&gt;

  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;asset_download_url_list&lt;/span&gt;&lt;span class="o"&gt;=&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;-s&lt;/span&gt; &lt;span class="se"&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;release_list_url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.[].assets[].browser_download_url'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;asset_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&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="nv"&gt;asset_download_url_list&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;release_grep&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n1&lt;/span&gt;&lt;span class="si"&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="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"asset_url"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Latest relase for '&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;release_repo_path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' with grep '&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;release_grep&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' not found, exiting."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
  &lt;span class="k"&gt;fi

  &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="nv"&gt;asset_url&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="c"&gt;# Get version from GitHub release download URL eg:&lt;/span&gt;
&lt;span class="c"&gt;# https://github.com/croz-ltd/dpcmder/releases/download/v0.6.0/dpcmder-linux-amd64&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;get_github_release_version&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;asset_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&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="nv"&gt;asset_url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s|.*/download/||; s|/.*||'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function &lt;/span&gt;install_asset&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;local_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;release_repo_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;release_grep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;installer_function_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$4&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;asset_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find_github_latest_release_url &lt;span class="se"&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;release_repo_path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&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;release_grep&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;github_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;get_github_release_version &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;asset_url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Local version: '&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;local_version&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;', version on the GitHub: '&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;github_version&lt;/span&gt;&lt;span class="k"&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;release_repo_path&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="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;local_version&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="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;github_version&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="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;asset_file&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;asset_url&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;
    wget &lt;span class="nt"&gt;-nv&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$asset_url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;asset_file&lt;/span&gt;&lt;span class="k"&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;installer_function_name&lt;/span&gt;&lt;span class="k"&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;asset_file&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&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;################################################&lt;/span&gt;
&lt;span class="c"&gt;# Callback functions for apps installation BEGIN&lt;/span&gt;
&lt;span class="c"&gt;################################################&lt;/span&gt;

&lt;span class="c"&gt;#########&lt;/span&gt;
&lt;span class="c"&gt;# dpcmder&lt;/span&gt;
&lt;span class="c"&gt;#########&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;install_dpcmder&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;downloaded_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;chmod&lt;/span&gt; +x &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;downloaded_file&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;downloaded_file&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; /usr/local/bin/dpcmder

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"installed dpcmder: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpcmder &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;#########&lt;/span&gt;
&lt;span class="c"&gt;# oc&lt;/span&gt;
&lt;span class="c"&gt;#########&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;install_oc&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;downloaded_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;downloaded_file&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nv"&gt;unpack_dir&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;downloaded_file&lt;/span&gt;&lt;span class="p"&gt;%.tar.gz&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;unpack_dir&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/oc"&lt;/span&gt; /usr/local/bin/oc
  &lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;unpack_dir&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/kubectl"&lt;/span&gt; /usr/local/bin/kubectl
  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;unpack_dir&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;downloaded_file&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  oc completion bash &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/bash_completion.d/oc_bash_completion

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"installed oc: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;oc version &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;################################################&lt;/span&gt;
&lt;span class="c"&gt;# Callback functions for apps installation END&lt;/span&gt;
&lt;span class="c"&gt;################################################&lt;/span&gt;

&lt;span class="c"&gt;################################################&lt;/span&gt;
&lt;span class="c"&gt;# Install apps&lt;/span&gt;
&lt;span class="c"&gt;################################################&lt;/span&gt;

&lt;span class="c"&gt;################################################&lt;/span&gt;
&lt;span class="c"&gt;# dpcmder&lt;/span&gt;
&lt;span class="nv"&gt;dpcmder_version_full&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;dpcmder &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;dpcmder_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&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="nv"&gt;dpcmder_version_full&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/.* version //; s/ .*//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
install_asset &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;dpcmder_version&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"croz-ltd/dpcmder"&lt;/span&gt; &lt;span class="s2"&gt;"linux"&lt;/span&gt; install_dpcmder

&lt;span class="c"&gt;################################################&lt;/span&gt;
&lt;span class="c"&gt;# oc &amp;amp; kubectl&lt;/span&gt;
&lt;span class="nv"&gt;oc_version_full&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;oc version &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;oc_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&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="nv"&gt;oc_version_full&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;oc | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/.*v/v/; s/+.*//'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
install_asset &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;oc_version&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"openshift/origin"&lt;/span&gt; &lt;span class="s2"&gt;"v3.*client.*linux"&lt;/span&gt; install_oc

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Finished installing custom releases from GitHub."&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  install-custom-opt.sh
&lt;/h3&gt;

&lt;p&gt;This script installs custom applications which I usually unpack to the &lt;code&gt;/opt&lt;/code&gt; directory, for example &lt;a href="https://www.eclipse.org/"&gt;Eclipse&lt;/a&gt;. Here I install specific version of the product and URL to each product is hardcoded in this script. There are not many of these so I find that this approach is the best fit for my requirements. For these apps following steps are performed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;check the current version installed

&lt;ul&gt;
&lt;li&gt;does directory &lt;code&gt;opt/{app_name}.{app_version}&lt;/code&gt; exist?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;if the target version is not found:

&lt;ul&gt;
&lt;li&gt;download an archive&lt;/li&gt;
&lt;li&gt;unpack an archive to &lt;code&gt;opt/{app_name}.{app_version}&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;make a symbolic link from &lt;code&gt;opt/{app_name}&lt;/code&gt; to &lt;code&gt;opt/{app_name}.{app_version}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  install-config.sh
&lt;/h3&gt;

&lt;p&gt;This is where the custom configuration (not installation) of my Ubuntu is performed. These are custom configuration tasks performed by this script:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;setting &lt;a href="https://www.vim.org/"&gt;the vim&lt;/a&gt; as my default editor&lt;/li&gt;
&lt;li&gt;stowing my dotfiles&lt;/li&gt;
&lt;li&gt;make docker run as a non-root user&lt;/li&gt;
&lt;li&gt;changing some Ubuntu key bindings&lt;/li&gt;
&lt;li&gt;configuring my &lt;a href="https://restic.net/"&gt;restic&lt;/a&gt; backups&lt;/li&gt;
&lt;li&gt;enabling &lt;a href="https://wiki.ubuntu.com/UncomplicatedFirewall"&gt;ufw&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a bit shortened version of this script:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"my_username"&lt;/span&gt;

update-alternatives &lt;span class="nt"&gt;--set&lt;/span&gt; editor /usr/bin/vim.basic

&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /home/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/.config/systemd/user

&lt;span class="c"&gt;# Get all directories except bin&lt;/span&gt;
&lt;span class="nv"&gt;STOW_DIRS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;/ &lt;span class="nt"&gt;-d&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-Ev&lt;/span&gt; &lt;span class="s1"&gt;'download|bin'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; stow &lt;span class="nt"&gt;--dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/dotfiles &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ignore&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;download &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--ignore&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;bin &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;STOW_DIRS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# run docker as a non-root user&lt;/span&gt;
groupadd docker &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;


&lt;span class="c"&gt;# Remove &amp;lt;Ctrl&amp;gt;&amp;lt;Alt&amp;gt;[&amp;lt;Shift&amp;gt;]ArrowKey mappings&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; dconf write /org/gnome/desktop/wm/keybindings/move-to-workspace-down &lt;span class="s2"&gt;"['&amp;lt;Super&amp;gt;&amp;lt;Shift&amp;gt;Page_Down']"&lt;/span&gt;

&lt;span class="c"&gt;# Configure restic-backup timer&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; loginctl enable-linger username
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nb"&gt;enable &lt;/span&gt;restic-backup.timer
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; start restic-backup.timer

ufw &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h4&gt;
  
  
  dotfiles &amp;amp; Stow - examples
&lt;/h4&gt;

&lt;p&gt;To give you the better overview of what can be configured using &lt;strong&gt;Stow&lt;/strong&gt; here are some example of files which I keep in the GIT repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# "classic" dotfile configurations
dotfiles/bash/.bashrc
dotfiles/bash/.profile

# scripts for the setup
dotfiles/bin/add-custom-repos.sh
dotfiles/bin/install-config.sh
dotfiles/bin/install-custom-opt.sh
dotfiles/bin/install-custom.sh
dotfiles/bin/install-from-repos.sh
dotfiles/bin/install.sh

# Restic backup scripts
dotfiles/bin/restic/.gitignore
dotfiles/bin/restic/restic-backup.sh
dotfiles/bin/restic/restic-exclude
dotfiles/bin/restic/restic-include
dotfiles/bin/restic/restic-password

# Gnome desktop launcher files
dotfiles/eclipse/.local/share/applications/eclipse.desktop

# Restic service &amp;amp; timer configuration
dotfiles/restic-backup/.config/systemd/user/restic-backup.service
dotfiles/restic-backup/.config/systemd/user/restic-backup.timer
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Restic backup setup
&lt;/h2&gt;

&lt;p&gt;For the backup, I use the &lt;a href="https://restic.net/"&gt;restic&lt;/a&gt; application written in Go. I won't write a lengthy description about it here but I do find it a great app with many configuration options to securely back up your encrypted data. If you want to get a good description of how to use Restic I would suggest you check it's &lt;a href="https://restic.readthedocs.io/"&gt;documentation on "Read the Docs" pages&lt;/a&gt;. It can also make a backup to &lt;a href="https://aws.amazon.com/s3/"&gt;the AWS S3&lt;/a&gt; which I use. In my case, this is just a few GB backup of the most valuable data. Costs of storing a few GB in AWS S3 is just around a 1$ per year and gives me a peace of mind for the worst-case scenario.&lt;/p&gt;

&lt;p&gt;Restic is run by systemd timer running systemd service and both of those are created using Stow + custom configuration (check &lt;code&gt;sytemctl enable&lt;/code&gt; &amp;amp; &lt;code&gt;systemctl start&lt;/code&gt; in the &lt;code&gt;install-config.sh&lt;/code&gt; script).&lt;/p&gt;

&lt;p&gt;My Restic configuration is one of dotfiles configuration directories. Restic script run by restic-backup.service is also present there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dotfiles/bin/restic/.gitignore
dotfiles/bin/restic/restic-backup.sh
dotfiles/bin/restic/restic-exclude
dotfiles/bin/restic/restic-include
dotfiles/bin/restic/restic-password

dotfiles/restic-backup/.config/systemd/user/restic-backup.timer
dotfiles/restic-backup/.config/systemd/user/restic-backup.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Just make sure that you don't commit your &lt;code&gt;restic-password&lt;/code&gt; file to GIT repository by mistake - add it to your .gitignore file :)&lt;/p&gt;

&lt;h3&gt;
  
  
  restic-backup.sh
&lt;/h3&gt;



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

restic backup &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-r&lt;/span&gt; ~/restic-repo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude-caches&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--files-from&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/restic-include &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude-file&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/restic-exclude &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--password-file&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/restic-password
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  restic-backup.timer
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;###################################################
# restic-backup.timer - runs restic backup each day
# loginctl enable-linger vedran
# systemctl --user list-timers
# systemctl --user enable restic-backup.timer
# systemctl --user start restic-backup.timer

[Unit]
Description=Regular restic backup

[Timer]
Unit=restic-backup.service
OnCalendar= *-*-* 15:00:00
Persistent=true

[Install]
WantedBy=default.target
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  restic-backup.service
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;##########################################################################
# restic-backup.service - runs restic backup (used by restic-backup.timer)
# systemctl --user status restic-backup

[Unit]
Description=Restic backup

[Service]
Type=oneshot
ExecStart=/home/vedran/dotfiles/bin/restic/restic-backup.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Which data I do back up using this setup?
&lt;/h3&gt;

&lt;p&gt;I back up with this setup only the data I find hard to "reconstruct".&lt;/p&gt;

&lt;p&gt;I don't back up application installations available from the Internet - these can be easily fetched again from the Internet if required.&lt;/p&gt;

&lt;p&gt;I don't back up virtual machines using this setup - there is only one VM I do back up manually, periodically but I just save whole VirtualBox machine to the external drive.&lt;/p&gt;

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

&lt;p&gt;This approach is a &lt;strong&gt;DevOps&lt;/strong&gt; inspired way to set up and configure a new desktop machine (Ubuntu Linux OS). Simplicity and ease of this approach make a process of the migration to the next machine so fast you won't think twice about it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The cover image is created by &lt;a href="https://www.pexels.com/@pixabay"&gt;Pixabay&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>stow</category>
      <category>installation</category>
      <category>configuration</category>
    </item>
    <item>
      <title>Viewing more with less - color syntax</title>
      <dc:creator>Vedran Vidovic</dc:creator>
      <pubDate>Sat, 22 Feb 2020 09:50:49 +0000</pubDate>
      <link>https://forem.com/vvidovic/viewing-more-with-less-color-syntax-hhp</link>
      <guid>https://forem.com/vvidovic/viewing-more-with-less-color-syntax-hhp</guid>
      <description>&lt;p&gt;If you are using Linux command-line a lot (out of love or out of necessity) you probably use less command a lot too. When you need to check the contents of some file less is here to help you to do it quickly. Maybe you didn't know but less can do more than help you view your text files, for example, you can check the contents of your archive using less:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ less go1.13.7.linux-amd64.tar.gz&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fa11xzn1nil5i03sc2np8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fa11xzn1nil5i03sc2np8.png" alt="View archive contents"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Syntax highlighting for less - source-highlight
&lt;/h2&gt;

&lt;p&gt;The only issue I had with the default less configuration on my Linux machines (Ubuntu 16.04 &amp;amp; 18.04) was that all of my source files were shown without any syntax highlighting. For me it was not just an issue of form, it was an issue of function - syntax highlighting makes reading source files much easier.&lt;/p&gt;

&lt;p&gt;Fortunately, for Linux environments, you can use a &lt;a href="http://www.gnu.org/software/src-highlite/source-highlight.html" rel="noopener noreferrer"&gt;GNU Source-highlight&lt;/a&gt; project which can be &lt;a href="http://www.gnu.org/software/src-highlite/source-highlight.html#Using-source_002dhighlight-with-less" rel="noopener noreferrer"&gt;used by less command to add syntax highlighting&lt;/a&gt; to files opened using less command.&lt;/p&gt;

&lt;p&gt;Just install syntax-highlight for your favorite Linux distribution and configure two environment variables less uses to get syntax highlighting in your favorite pager command.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;source-highlight
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LESSOPEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"| /usr/share/source-highlight/src-hilite-lesspipe.sh %s"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;' -R'&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;If you want to have these settings applied permanently to your system add something like this to your ~/.bashrc or ~/.profile file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# less syntax highlighting + source-highlight installation
export LESSOPEN="| /usr/share/source-highlight/src-hilite-lesspipe.sh %s"
export LESS=' -R'


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

&lt;/div&gt;

&lt;p&gt;The result can be easily seen if you open for example JSON file and compare how less command shows it with highlighting vs. without highlighting:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Customizing source-highlight - adding new languages
&lt;/h2&gt;

&lt;p&gt;There are some languages that you won't find on the &lt;a href="http://www.gnu.org/software/src-highlite/source-highlight.html#Supported-languages" rel="noopener noreferrer"&gt;list of supported languages&lt;/a&gt; for the source-highlight project. Often, you can find a solution to this problem with some help from the development community on the Internet. For example, YAML is not supported by default but you can find &lt;a href="https://gist.github.com/tkfm-yamaguchi/c4952b355bb7a27552a5f23e0c53b65f" rel="noopener noreferrer"&gt;a solution created by user tkfm-yamaguchi and available as a GitHub gist&lt;/a&gt;. Basically you have to add make two small changes (one new configuration (language) file and small change of the existing configuration file) to enable syntax highlighting for your YAML files.&lt;/p&gt;

&lt;p&gt;The result is visible below:&lt;/p&gt;

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

</description>
      <category>linux</category>
      <category>less</category>
      <category>ubuntu</category>
      <category>sourcehighlight</category>
    </item>
    <item>
      <title>Trimming go binaries of dpcmder - trimming a compression time :)</title>
      <dc:creator>Vedran Vidovic</dc:creator>
      <pubDate>Wed, 12 Feb 2020 14:07:57 +0000</pubDate>
      <link>https://forem.com/vvidovic/trimming-go-binaries-of-dpcmder-trimming-a-compression-time-1k4e</link>
      <guid>https://forem.com/vvidovic/trimming-go-binaries-of-dpcmder-trimming-a-compression-time-1k4e</guid>
      <description>&lt;p&gt;Preparing for the next release of &lt;a href="https://github.com/croz-ltd/dpcmder"&gt;dpcmder&lt;/a&gt; I wanted to do some more testing of release binaries compression achievable using &lt;a href="https://upx.github.io/"&gt;UPX&lt;/a&gt; and find out I can achieve &lt;em&gt;40 times faster compression&lt;/em&gt; just by changing single upx compression flag.&lt;/p&gt;

&lt;p&gt;Note: this continues where the last post about &lt;a href="https://dev.to/vvidovic/trimming-go-binaries-of-dpcmder-1lhg"&gt;dpcmder binaries compression&lt;/a&gt; finished.&lt;/p&gt;

&lt;p&gt;I realized there are some upx flags I didn't test (for example "--ultra-brute" or "--best") and read a bit more about them. In the end, it seems (for dpcmder binaries at least) best compression results are achieved using the "--lzma" flag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ time upx --lzma release.lzma/*
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2013
UPX 3.91        Markus Oberhumer, Laszlo Molnar &amp;amp; John Reiser   Sep 30th 2013

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   9146368 -&amp;gt;   2812760   30.75%  linux/ElfAMD   dpcmder-linux-amd64           
   9697256 -&amp;gt;   2789376   28.76%   Mach/AMD64    dpcmder-mac-amd64             
   8932352 -&amp;gt;   2610688   29.23%    win64/pe     dpcmder-win-amd64.exe         
   --------------------   ------   -----------   -----------
  27775976 -&amp;gt;   8212824   29.57%                 [ 3 files ]

Packed 3 files.

real    0m23.254s
user    0m22.911s
sys 0m0.196s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Seems that the "--brute" flag makes upx tries many different algorithms and selects one which achieves the best result. The "--ultra-brute" flag tries even more compression algorithms so it takes even more time. In my case (dpcmder binaries built on my Linux machine) best result is achieved using the lzma compression algorithm. As you can see from outputs of both upx run timings, result compression is exactly the same while time spent for compression is drastically faster (more than &lt;em&gt;40 times&lt;/em&gt;) when using "--lzma" flag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ time upx --brute release.brute/*
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2013
UPX 3.91        Markus Oberhumer, Laszlo Molnar &amp;amp; John Reiser   Sep 30th 2013

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   9146368 -&amp;gt;   2812760   30.75%  linux/ElfAMD   dpcmder-linux-amd64           
upx: release.brute/dpcmder-linux-amd64.upx: UnknownExecutableFormatException
   9697256 -&amp;gt;   2789376   28.76%   Mach/AMD64    dpcmder-mac-amd64             
   8932352 -&amp;gt;   2610688   29.23%    win64/pe     dpcmder-win-amd64.exe         
   --------------------   ------   -----------   -----------
  27775976 -&amp;gt;   8212824   29.57%                 [ 3 files ]

Packed 3 files.

real    15m39.952s
user    15m37.567s
sys 0m1.408s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



</description>
      <category>go</category>
      <category>dpcmder</category>
      <category>upx</category>
      <category>compression</category>
    </item>
    <item>
      <title>Trimming go binaries of dpcmder</title>
      <dc:creator>Vedran Vidovic</dc:creator>
      <pubDate>Mon, 13 Jan 2020 11:43:16 +0000</pubDate>
      <link>https://forem.com/vvidovic/trimming-go-binaries-of-dpcmder-1lhg</link>
      <guid>https://forem.com/vvidovic/trimming-go-binaries-of-dpcmder-1lhg</guid>
      <description>&lt;p&gt;This weekend I spent some time thinking that (Golang built) binaries of &lt;a href="https://github.com/croz-ltd/dpcmder"&gt;dpcmder&lt;/a&gt; are a bit too huge for my taste. Some would say that ~10 megs is not so much for today's world but ~10 megabytes seemed to me to be too much so I set out on a mission to find out if I can trim dpcmder size a bit.&lt;/p&gt;

&lt;p&gt;After some googling, I found this nice &lt;a href="https://blog.filippo.io/shrink-your-go-binaries-with-this-one-weird-trick/"&gt;"shrink your go binaries" blog&lt;/a&gt; which explains exactly what I needed to do to achieve my goal.&lt;/p&gt;

&lt;p&gt;It boils down to one change in the current build process (remove debugging info &amp;amp; system table) and one additional step after the build (compress binaries).&lt;/p&gt;

&lt;h2&gt;
  
  
  Removing debugging info &amp;amp; system table
&lt;/h2&gt;

&lt;p&gt;Instead of using default go build command, for example:&lt;br&gt;
&lt;code&gt;GOOS=linux GOARCH=amd64 go build -o release/dpcmder-linux-amd64 dpcmder.go&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You have to add additional ldflags to instruct the linker to remove all debugging info (-w) &amp;amp; symbol table (-s):&lt;br&gt;
&lt;code&gt;GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o release/dpcmder-linux-amd64 dpcmder.go&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Actually, this research of ldflags brought me to one other issue on my dpcmder todo list - adding application version for each release. This caused me to use one more ldflag (-X) to change values of version variables:&lt;br&gt;
&lt;code&gt;GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X 'github.com/croz-ltd/dpcmder/help.Version=$(git tag | tail -n1)' -X 'github.com/croz-ltd/dpcmder/help.Platform=linux/amd64' -X 'github.com/croz-ltd/dpcmder/help.BuildTime=$(git tag | tail -n1).$(date -u -Iseconds)'" -o release/dpcmder-linux-amd64 dpcmder.go&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Compressing binaries
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://upx.github.io/"&gt;UPX&lt;/a&gt; is a free, portable, extendable, high-performance executable packer for several executable formats.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After installing UPX it is easy to use it and for the best compression results I added the flag "--brute" mentioned in the blog above:&lt;br&gt;
&lt;code&gt;upx --brute release/*&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ upx --brute release/*
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2013
UPX 3.91        Markus Oberhumer, Laszlo Molnar &amp;amp; John Reiser   Sep 30th 2013

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
   7925760 -&amp;gt;   2538980   32.03%  linux/elf386   dpcmder-linux-386             
   9105408 -&amp;gt;   2686192   29.50%  linux/ElfAMD   dpcmder-linux-amd64           
   8421068 -&amp;gt;   2772992   32.93%    Mach/i386    dpcmder-mac-386               
   9653400 -&amp;gt;   2785280   28.85%   Mach/AMD64    dpcmder-mac-amd64             
   7698432 -&amp;gt;   2449408   31.82%    win32/pe     dpcmder-win-386.exe           
   8865792 -&amp;gt;   2597376   29.30%    win64/pe     dpcmder-win-amd64.exe         
   --------------------   ------   -----------   -----------
  51669860 -&amp;gt;  15830228   30.64%                 [ 6 files ]

Packed 6 files.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This step took quite a long time to complete but results were worth waiting. As you can see below from binary files update times, some binaries were compressed faster and some were compressed slower (compression of win/386 binaries lasted almost 30 minutes while compression of Linux binaries lasted for around 3 minutes).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ stat -c "%.19z %n" release/*
2020-01-13 10:23:02 release/dpcmder-linux-386
2020-01-13 10:26:57 release/dpcmder-linux-amd64
2020-01-13 10:29:52 release/dpcmder-mac-386
2020-01-13 10:36:54 release/dpcmder-mac-amd64
2020-01-13 11:03:45 release/dpcmder-win-386.exe
2020-01-13 11:10:54 release/dpcmder-win-amd64.exe
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Note that these 6 binaries could be compressed in parallel to achieve much better times on the modern-day multi-core machines.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much did we trimmed?
&lt;/h2&gt;

&lt;p&gt;After the whole process, all 6 binaries are now much smaller. The total size of all binaries shrank from the original 65M to a more acceptable 16M. Sizes for all 6 binaries are given below.&lt;/p&gt;

&lt;h3&gt;
  
  
  default go build without any -ldflags
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls -sh1 release/
total 65M
11M dpcmder-linux-386
12M dpcmder-linux-amd64
11M dpcmder-mac-386
12M dpcmder-mac-amd64
11M dpcmder-win-386.exe
12M dpcmder-win-amd64.exe
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  go build with -ldflags="-s -w"
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls -sh1 release/
total 50M
7,6M dpcmder-linux-386
8,7M dpcmder-linux-amd64
8,1M dpcmder-mac-386
9,3M dpcmder-mac-amd64
7,4M dpcmder-win-386.exe
8,5M dpcmder-win-amd64.exe
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  result of running UPX on go build (with -ldflags="-s -w")
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls -sh1 release/
total 16M
2,5M dpcmder-linux-386
2,6M dpcmder-linux-amd64
2,7M dpcmder-mac-386
2,7M dpcmder-mac-amd64
2,4M dpcmder-win-386.exe
2,5M dpcmder-win-amd64.exe
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After comparing the &lt;a href="https://github.com/croz-ltd/dpcmder/releases/tag/v0.3.4"&gt;latest dpcmder release&lt;/a&gt; assets sizes to the &lt;a href="https://github.com/croz-ltd/dpcmder/releases/tag/v0.3.3"&gt;previous dpcmder release&lt;/a&gt; asset sizes I must conclude that my mission from the start of this blog is successfully accomplished :)&lt;/p&gt;

</description>
      <category>dpcmder</category>
      <category>datapower</category>
      <category>croz</category>
      <category>go</category>
    </item>
    <item>
      <title>DataPower Commander v0.3 - more useful than ever</title>
      <dc:creator>Vedran Vidovic</dc:creator>
      <pubDate>Fri, 10 Jan 2020 09:12:14 +0000</pubDate>
      <link>https://forem.com/vvidovic/datapower-commander-v0-3-more-useful-than-ever-36i7</link>
      <guid>https://forem.com/vvidovic/datapower-commander-v0-3-more-useful-than-ever-36i7</guid>
      <description>&lt;p&gt;DataPower Commander (&lt;strong&gt;dpcmder&lt;/strong&gt;) is a command line tool I created for easier maintenance of files on &lt;strong&gt;DataPower&lt;/strong&gt; appliances and easier development of DataPower appliance solutions. &lt;span&gt;You can use dpcmder too – it is an open source project available on &lt;a href="https://github.com/croz-ltd/dpcmder" rel="noopener"&gt;GitHub (croz-ltd/dpcmder)&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;It is developed using go (golang) which makes cross-platform releases much easier and cross-compilation is built in as a go tool feature :)&lt;/p&gt;

&lt;p&gt;Since last time I wrote about &lt;a href="https://croz.net/news/datapower-commander/" rel="noopener"&gt;dpcmder&lt;/a&gt; so many new features were added that I felt compelled to write a new blog and describe how can dpcmder make your day nicer and your work with DataPower easier. Let's be honest, it will make your whole life easier :)&lt;/p&gt;

&lt;p&gt;Most interesting new features are:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;DataPower appliance configuration list&lt;/li&gt;
    &lt;li&gt;domain export&lt;/li&gt;
    &lt;li&gt;object maintenance mode
&lt;ul&gt;
        &lt;li&gt;view object configuration&lt;/li&gt;
        &lt;li&gt;edit object configuration&lt;/li&gt;
        &lt;li&gt;delete object configuration&lt;/li&gt;
        &lt;li&gt;view non-saved changes in object configuration&lt;/li&gt;
        &lt;li&gt;save current domain changes&lt;/li&gt;
&lt;/ul&gt;




&lt;/li&gt;

    &lt;li&gt;nicer input/progress dialogs&lt;/li&gt;

    &lt;li&gt;status messages history&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;A bit more comprehensive list of new features will be given below but let's help you to start using dpcmder before we come to that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started with dpcmder
&lt;/h2&gt;

&lt;p&gt;DataPower Commander works under Linux, macOS and Windows (normal Windows cmd prompt) but since there are no default file viewer/editor/diff externals command-line programs available under Windows it is a bit "harder" to do the initial setup for Windows environment. I would suggest you to use WSL or cmder environment to make all features of dpcmder easier to use.&lt;/p&gt;

&lt;p&gt;The Best way to start using dpcmder is to run it with "-h" flag and see all flags available and then just call with appropriate flags to connect to your DataPower appliance, for example:&lt;br&gt;
&lt;code&gt;dpcmder -s &lt;a href="https://127.0.0.1:5550"&gt;https://127.0.0.1:5550&lt;/a&gt; -u admin -p admin -c LocalDp&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This command will connect to your local DataPower instance which exposes its SOMA interface at port 5550 using the default username and password combination and will save this configuration under given configuration name "LocalDp". Don't worry if you forgot to add "-c" flag - DataPower configuration just opened is saved as "&lt;em&gt;PreviousAppliance&lt;/em&gt;" in the DataPower appliance list where you can easily clone it to whatever configuration name you need using F9/9 key.&lt;/p&gt;

&lt;p&gt;You can even start without using any command line parameters when starting dpcmder for the first time and create a new DataPower configuration using the F8/8 key (when the [empty] list of appliances is shown). However, this option is not recommended if you plan to save your password to dpcmder configuration file (&lt;em&gt;~/.dpcmder/config.json&lt;/em&gt;) - passwords are saved in the configuration file as base32 encoded text so you would have to properly encode password value before saving it.&lt;/p&gt;

&lt;p&gt;If you need more information you can pass flag "-help" to dpcmder and it will write full dpcmder help to console so you can get more information about its features and their use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note on appliances configuration password safety&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If passwords are saved in a dpcmder configuration file (if they are passed to dpcmder using "-p" flag) they will be base32 encoded so anyone with access to your dpcmder configuration file would be able to decode it easily. If you cannot guarantee the safety of this file, please avoid using "-p" flag and enter a password when you connect to each appliance - this password will not be saved into the dpcmder configuration file.&lt;/p&gt;

&lt;h2&gt;
  
  
  DataPower appliance list
&lt;/h2&gt;

&lt;p&gt;When you open DataPower commander without any parameters a list of saved DataPower appliance configuration is shown (which can be empty if no appliances were added through command-line flags or by using F8/8 key).&lt;/p&gt;

&lt;p&gt;You can connect to DataPower using a given configuration (Enter key), view current configuration (F3/3 key), edit the current configuration (F4/4 key), clone current configuration (F9/9), create a new empty configuration (F8/8 key) or delete current configuration (Del/x key).&lt;/p&gt;

&lt;p&gt;If the configuration for DataPower appliance you want to access doesn't contain password information, the first time you want to connect to that appliance (in running dpcmder program) a dialog for password input will be shown.&lt;/p&gt;

&lt;p&gt;If you run dpcmder using proper SOMA or REST connection parameters on command line DataPower appliance list will not be shown - list of domains on DataPower you wanted to connect to (using command line parameters) will be shown instead. You can easily go back to the DataPower appliance list by going to the parent view (selecting ".." item).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MCYMloD4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/1b1iwcd72ojhuuw9n2hh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MCYMloD4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/1b1iwcd72ojhuuw9n2hh.png" alt="List of DataPower appliances"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  DataPower domain export
&lt;/h2&gt;

&lt;p&gt;One of the new features for which I felt to fit most naturally into dpcmder is exporting of the DataPower domains. Just as it is possible to copy files and/or directories from DataPower to the local filesystem it feels natural to copy the whole domain to a local filesystem. Actually, the same key is used for that purpose (F5/5 key) and the result of that operation is the DataPower export zip archive created on a local filesystem.&lt;/p&gt;

&lt;p&gt;The exported archive contains all objects and files from the selected DataPower domain.&lt;/p&gt;

&lt;p&gt;I am considering to add function to import domain (and even delete domain) but since these operations are quite powerful there is a bigger chance to make some "damage" to your appliance using those. However, these options are something that would nicely round out features of working with DataPower domains so these will probably be added in some future version of the dpcmder.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qeJZ5PuT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/pp65ly1kgnm8vrymbm97.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qeJZ5PuT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/pp65ly1kgnm8vrymbm97.gif" alt="Exporting DataPower domain"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  DataPower object maintenance mode
&lt;/h2&gt;

&lt;p&gt;Object maintenance mode is very useful for making small changes to existing DataPower object configuration or viewing configuration changes (SOMA only). For example, changing the port number used for XML Firewall Service. Or maybe you don't want to change anything but just want to quickly check the current value of some configuration option.&lt;/p&gt;

&lt;p&gt;In the new release of the dpcmder, 0 key is used to switch between (default) "file" mode and "object" mode. To use object mode we have to connect to the DataPower appliance and select one of the domains available.&lt;/p&gt;

&lt;p&gt;When we switch to object mode a list of all "class" of objects for which at least one object in the selected DataPower domain exists is shown. Instead of file size count of available objects is shown. In case we used the SOMA management interface to access DataPower classes we mark all modified classes with "*" (if at least one object is new, modified or deleted for that class).&lt;/p&gt;

&lt;p&gt;The same key (Enter key) used for the operation of "entering" domain/filestore/directory is used to show all available objects of the selected object class. In case we used the SOMA management interface we show if an object is new, deleted or modified (and can even view changes for a modified object using "d" key). That operation shows us names of all objects of the selected class and enables us to do more advanced operations on each of those objects:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;view object (JSON/XML)&lt;/li&gt;
    &lt;li&gt;edit object (JSON/XML)&lt;/li&gt;
    &lt;li&gt;delete object&lt;/li&gt;
    &lt;li&gt;view unsaved object changes (XML)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In case we used the REST management interface to connect to the DataPower appliance object will be accessible using JSON, in case we used SOMA management interface XML will be used. The best feature we get "for free" is that if our editor (or viewer) supports syntax highlighting for JSON/XML we will have nicely highlighted code making our viewing and/or editing of DataPower configuration easier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AQFO42yT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/0sm3oev8z25u8ry721ec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AQFO42yT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/0sm3oev8z25u8ry721ec.png" alt="DataPower list of object classes used"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wYERHaKM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/pksv2x3wrs31o6vmgwi7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wYERHaKM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/pksv2x3wrs31o6vmgwi7.png" alt="DataPower list of objects of selected class"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QwLTGIPg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/y7iiem9mxosbw7nvda5i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QwLTGIPg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/y7iiem9mxosbw7nvda5i.png" alt="Edit of DataPower object (REST / JSON example) using vim editor"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q0W_iTfn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/hw8re4esggvb5ugxlhjw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q0W_iTfn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/hw8re4esggvb5ugxlhjw.png" alt="View DataPower object unsaved changes (SOMA-only) using external diff command"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  List of interesing changes since last blog
&lt;/h2&gt;

&lt;p&gt;For a more comprehensive list of changes you can check &lt;a href="https://github.com/croz-ltd/dpcmder/releases"&gt;releases history&lt;/a&gt; but here is a summary of changes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;fixes&lt;/li&gt;
  &lt;li&gt;DataPower appliance configuration saving and loading&lt;/li&gt;
  &lt;li&gt;diff command (comparing files / directories)&lt;/li&gt;
  &lt;li&gt;better error handling in calls to external programs (+ hints how to fix those)&lt;/li&gt;
  &lt;li&gt;better in program help&lt;/li&gt;
  &lt;li&gt;shows program help in console ("-help" flag)&lt;/li&gt;
  &lt;li&gt;creation of new (empty) file&lt;/li&gt;
  &lt;li&gt;view status history ("m" key)&lt;/li&gt;
  &lt;li&gt;replaced library for terminal interaction (termbox-go -&amp;gt; tcell) for better cross platform support&lt;/li&gt;
  &lt;li&gt;added nicer terminal dialog and error messages&lt;/li&gt;
  &lt;li&gt;blinking status when sync mode is on&lt;/li&gt;
  &lt;li&gt;added object mode (edit and delete operations available)&lt;/li&gt;
  &lt;li&gt;editing DataPower files can use syntax highlighting available in external editor&lt;/li&gt;
  &lt;li&gt;workaround enabling to use diff command (normal, non-blocking) - custom blocking command is still recommended&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Features of dpcmder
&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;appliance configurations
  &lt;ul&gt;
    &lt;li&gt;saves and loads DataPower appliance configuration to enable fast connections to appliances&lt;/li&gt;
    &lt;li&gt;edit/clone/create/delete DataPower appliance configuration&lt;/li&gt;
  &lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;file maintenance
  &lt;ul&gt;
    &lt;li&gt;view, edit, copy and delete file hierarchies (DataPower and local file system)&lt;/li&gt;
    &lt;li&gt;filter and search files in current directory&lt;/li&gt;
    &lt;li&gt;comparing files / directories (DataPower vs. local file system)&lt;/li&gt;
  &lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;objects maintenance
  &lt;ul&gt;
    &lt;li&gt;switching between filestore mode and object mode (0 key)&lt;/li&gt;
    &lt;li&gt;existing objects can be quickly edited (as JSON (REST mgmt) or XML (SOMA mgmt) using current editor)&lt;/li&gt;
    &lt;li&gt;existing objects can be quickly deleted&lt;/li&gt;
  &lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;domain export
  &lt;ul&gt;
    &lt;li&gt;when applying copy operation to DataPower domain it is automatically exported as zip files (exporting all local files)&lt;/li&gt;
  &lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;sync mode
  &lt;ul&gt;
    &lt;li&gt;turn on to automatically upload new and changed files from local filesystem to DataPower&lt;/li&gt;
    &lt;li&gt;useful for development to automatically propagate your changes from any IDE/editor you are using to DataPower&lt;/li&gt;
  &lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  REST management vs. SOMA management
&lt;/h2&gt;

&lt;p&gt;Some of the later features added were available only if we use the SOMA management interface to connect to the DataPower appliance. While the &lt;strong&gt;REST management interface&lt;/strong&gt; seems a bit nicer some features are not available and/or documented. For example, Diff operation seems to be available through REST but I wasn't able to find any documentation about that action or find a way to use it. From &lt;strong&gt;154 actions available&lt;/strong&gt; through /mgmt/actionqueue/{domain} (IDG.2018.4.1.3) only 3 are documented in IBM's Knowledge Center:&lt;/p&gt;

&lt;ul&gt;
    &lt;li&gt;LoadConfiguration&lt;/li&gt;
    &lt;li&gt;Export&lt;/li&gt;
    &lt;li&gt;Import&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some functions of DataPower object mode work a bit differently in REST vs. SOMA mode - for example in the latest version available on the Git master branch, if the object's admin state is down we can't make changes to it using a REST management interface (REST call returns the HTTP error 409 Conflict).&lt;/p&gt;

&lt;p&gt;Issues mentioned above are reasons why I would suggest you to use the SOMA management interface for your DataPower management - REST is nice but it still doesn't offer everything SOMA does.&lt;/p&gt;

&lt;h2&gt;
  
  
  So Long, and Thanks for All the Fish
&lt;/h2&gt;

&lt;p&gt;There are still many &lt;strong&gt;new features we consider adding&lt;/strong&gt; to dpcmder so its development will continue further. For example - currently, dpcmder has a feature to edit or delete DataPower objects but doesn't have a feature to create a new object (it would be a bit hard to use). The next version (already on Git master) will be able to clone the existing one or even copy the object configuration to a local file or create/update the DataPower object from the local configuration file. Currently, dpcmder can create a new domain but can't delete the existing one, it can export the DataPower domain but can't import it - these are some of the areas we consider for dpcmder further development.&lt;/p&gt;

&lt;p&gt;The last version seems to be much nicer and contains a lot more functions compared to the first release but this doesn't mean DataPower Commander is complete so stay tuned, there will be new features for dpcmder in 2020. :-)&lt;/p&gt;

</description>
      <category>dpcmder</category>
      <category>datapower</category>
      <category>croz</category>
      <category>go</category>
    </item>
  </channel>
</rss>
