<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Caustic Blog]]></title><description><![CDATA[Technical mind / Wandering creativity]]></description><link>https://blog.causticlabz.net/</link><image><url>https://blog.causticlabz.net/favicon.png</url><title>Caustic Blog</title><link>https://blog.causticlabz.net/</link></image><generator>Ghost 5.89</generator><lastBuildDate>Sun, 12 Apr 2026 06:12:33 GMT</lastBuildDate><atom:link href="https://blog.causticlabz.net/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[New-MailboxExportRequest Content Filter is invalid - Exchange Server]]></title><description><![CDATA[This PowerShell Cmdlet is bugged and only works with US formatted date. Let's see how to work around that bug !]]></description><link>https://blog.causticlabz.net/new-mailboxexportrequest-content-filter-is-invalid-exchange-server/</link><guid isPermaLink="false">65377706e2c755019db7a4fb</guid><category><![CDATA[microsoft]]></category><category><![CDATA[exchange]]></category><category><![CDATA[server]]></category><category><![CDATA[powershell]]></category><dc:creator><![CDATA[Frédéric Gilson]]></dc:creator><pubDate>Tue, 24 Oct 2023 08:46:48 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1508769941802-a6ca19d19e08?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDQ2fHxzZXJ2ZXIlMjBtYWlsfGVufDB8fHx8MTY5ODEzNzE2NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1508769941802-a6ca19d19e08?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDQ2fHxzZXJ2ZXIlMjBtYWlsfGVufDB8fHx8MTY5ODEzNzE2NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="New-MailboxExportRequest Content Filter is invalid - Exchange Server"><p>When you are trying to export to PST with the New-MailboxExportRequest cmdlet, the following error occur :</p><span style="color:red;font-family:monospace;">The provided ContentFilter value is invalid. ContentFilter is invalid. The value &quot;31/05/2012&quot; could not be converted to type System.DateTime</span><p>It turns out this PowerShell Cmdlet only works with US formatted date (MM/dd/yyyy) !</p><p>You might be tempted to feed it a date with that format... At first, it will take it but the job will quickly fail. It will result in a <code>FailedOther</code> state. When investigating further with this command :</p><p><code>Get-MailboxExportRequest | Get-MailboxExportRequestStatistics -IncludeReport | Ft Report -Wrap</code></p><p>You will find this error :</p><span style="color:red;font-family:monospace;">Fatal error InvalidContentFilterPermanentException has occurred
The attempt to deserialize failed for type: &apos;System.UnitySerializationHolder&apos;</span><p>This error happened because, yes the cmdlet accepted the parameters, but the PowerShell session runs with the configured location (culture) date format which is most likely not in US format. So, PowerShell will give to Exchange what it thinks is a valid date in its current culture but it&apos;s actually <strong>not</strong> in his culture. Of course, Exchange can&apos;t process the date and fail.</p><p>This weird bug exists since Exchange 2010 and is still in existence in current Exchange 2019 !</p><p>Since we are forced to use US culture by this cmdlet, to work around that, we have 3 choice :</p><ul><li>Change the server culture to US... Thanks Microsoft but no !</li><li>Change current PowerShell thread culture to US with those commands :</li></ul><pre><code>[System.Reflection.Assembly]::LoadWithPartialName(&quot;System.Threading&quot;)
[System.Reflection.Assembly]::LoadWithPartialName(&quot;System.Globalization&quot;)
[System.Threading.Thread]::CurrentThread.CurrentCulture = [System.Globalization.CultureInfo]::CreateSpecificCulture(&quot;en-us&quot;)</code></pre><ul><li>Connect remotely with PowerShell to the Exchange Server while changing PSSessionOption to use &quot;en-US&quot; culture</li></ul><pre><code>$SessionOption = New-PSSessionOption -Culture &apos;en-US&apos;
$PSSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri &quot;https://&lt;ExchangeServer&gt;/PowerShell/&quot; -SessionOption $SessionOption</code></pre><p>That way, PowerShell works totally with the US culture so the Exchange Server will be available to recognize that culture and use it correctly.</p><blockquote class="kg-blockquote-alt">This article is a summary of these informations :</blockquote><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://www.mysysadmintips.com/windows/servers/282-exchanage-2010-the-provided-contentfilter-value-is-invalid?ref=blog.causticlabz.net"><div class="kg-bookmark-content"><div class="kg-bookmark-title">MS Exchange - The provided ContentFilter value is invalid</div><div class="kg-bookmark-description">Running New-MailboxExportRequest cmdlet on Exchange 2010 and getting error
&#x201C;The provided ContentFilter value is invalid. Could not be converted to type System.DateTime&#x201D;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://www.mysysadmintips.com/media/templates/site/cassiopeia/images/joomla-favicon.svg" alt="New-MailboxExportRequest Content Filter is invalid - Exchange Server"><span class="kg-bookmark-author">Sysadmin Tips</span><span class="kg-bookmark-publisher">TB</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://www.gravatar.com/avatar/814118faec13c9fc2ed07ae0363965fa?s=48&amp;r=G&amp;d=mp" alt="New-MailboxExportRequest Content Filter is invalid - Exchange Server"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="http://occasionalutility.blogspot.com/2014/03/everyday-powershell-part-17-using-new.html?_sm_au_=iHV1qHS1LVTWF6Rn&amp;ref=blog.causticlabz.net"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Everyday Powershell - Part 17 - Using new-mailboxexportrequest with NON-US dates</div><div class="kg-bookmark-description">Here&#x2019;s a crazy making problem! The new-mailboxexportrequest commandlet will only accept dates in US formats. Roland Paix has made this pos&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="http://occasionalutility.blogspot.com/favicon.ico" alt="New-MailboxExportRequest Content Filter is invalid - Exchange Server"><span class="kg-bookmark-author">Using new-mailboxexportrequest with NON-US dates</span><span class="kg-bookmark-publisher">Ben Haslett</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://resources.blogblog.com/img/icon18_edit_allbkg.gif" alt="New-MailboxExportRequest Content Filter is invalid - Exchange Server"></div></a></figure><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://serverfault.com/a/972213?ref=blog.causticlabz.net"><div class="kg-bookmark-content"><div class="kg-bookmark-title">ContentFilter is invalid. System.Datetime</div><div class="kg-bookmark-description">If i run the following $3MonthsBack = Get-Date (Get-Date).AddMonths(-3) -f MM/dd/yyyy
New-MailboxExportRequest -mailbox kundesenter@company.no -IncludeFolders akriv -ContentFilter{Received -lt &#x201D;$</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://cdn.sstatic.net/Sites/serverfault/Img/apple-touch-icon.png?v=6c3100d858bb" alt="New-MailboxExportRequest Content Filter is invalid - Exchange Server"><span class="kg-bookmark-author">Server Fault</span><span class="kg-bookmark-publisher">Jakodns</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://cdn.sstatic.net/Sites/serverfault/Img/apple-touch-icon@2.png?v=9b1f48ae296b" alt="New-MailboxExportRequest Content Filter is invalid - Exchange Server"></div></a></figure>]]></content:encoded></item><item><title><![CDATA[SSH won't work out of the box for a LXC container on Proxmox]]></title><description><![CDATA[If you're used to use LXC container on Proxmox, you may have realised that something is messed up out of the box. 
It looks like the SSH service does not read the config file, especially the "sshd_config" one. Let's see how to fix that !]]></description><link>https://blog.causticlabz.net/ssh-wont-work-out-of-the-box-for-a-lxc-container-on-proxmox/</link><guid isPermaLink="false">64ebec623a6d71019cbd06e2</guid><category><![CDATA[ssh]]></category><category><![CDATA[sshd]]></category><category><![CDATA[proxmox]]></category><category><![CDATA[lxc]]></category><category><![CDATA[container]]></category><dc:creator><![CDATA[Frédéric Gilson]]></dc:creator><pubDate>Mon, 28 Aug 2023 00:51:13 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1629654291663-b91ad427698f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDZ8fGNvbXB1dGVyJTIwdGVybWluYWx8ZW58MHx8fHwxNjkzMTgzMTczfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1629654291663-b91ad427698f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDZ8fGNvbXB1dGVyJTIwdGVybWluYWx8ZW58MHx8fHwxNjkzMTgzMTczfDA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="SSH won&apos;t work out of the box for a LXC container on Proxmox"><p>If you&apos;re used to use LXC container on Proxmox, you may have realised that something is messed up out of the box. Thankfully, the fix is quick and easy !</p><p>Out of the box, it looks like the SSH service does not read the config file, especially the &quot;sshd_config&quot; one. It appears that this functionnality is broken down by the ssh.socket configuration.</p><p>To resolve this issue, we have to basically hide the ssh socket service and disabling / reenabling the proper ssh service.</p><p>The fix hold in these four commands :</p><!--kg-card-begin: markdown--><pre><code class="language-bash">systemctl mask ssh.socket
systemctl mask sshd.socket

systemctl disable sshd
systemctl enable ssh
</code></pre>
<!--kg-card-end: markdown--><p>Now, restart your container and the problem is fixed !</p>]]></content:encoded></item><item><title><![CDATA[How to set up a self-hosted Gitlab Runner and publish files through SFTP]]></title><description><![CDATA[Sometimes you want a small setup to use self-hosted Gitlab with your side project but still using some CI functionality. Let's see how to publish those files through SFTP.]]></description><link>https://blog.causticlabz.net/how-to-set-up-a-self-hosted-gitlab-runner-and-publish-files-through-sftp/</link><guid isPermaLink="false">64ebeb40c7caff2ac2b74d86</guid><category><![CDATA[gitlab]]></category><category><![CDATA[gitlab runner]]></category><category><![CDATA[sftp]]></category><category><![CDATA[devops]]></category><category><![CDATA[docker]]></category><category><![CDATA[CI/CD]]></category><dc:creator><![CDATA[Frédéric Gilson]]></dc:creator><pubDate>Wed, 09 Feb 2022 03:17:12 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1531030874896-fdef6826f2f7?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGdpdGxhYnxlbnwwfHx8fDE2NDQyODE5OTQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1531030874896-fdef6826f2f7?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGdpdGxhYnxlbnwwfHx8fDE2NDQyODE5OTQ&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="How to set up a self-hosted Gitlab Runner and publish files through SFTP"><p>When I wanted to use some versioning functionality when developing my main website, I naturally used Gitlab. But Gitlab does not stop at simply offering that function, it&apos;s a complete DevOps platform. While using it, why not try to use a little automation even if you&apos;re alone to work on your project.</p><p>So, because my project is small and, as stated, I&apos;m alone on it, this article is to be taken as a light introduction to how Gitlab works with his Gitlab Runner. I&apos;m not going to show how to do compilation or even testing. We&apos;ll simply go through how Gitlab use a specific orchestration file which is automatically executed when you do a git push to a specific branch.</p><hr><h2 id="how-does-it-work">How does it work?</h2><p>Gitlab, like his name hint at, is a platform based around the Git protocol. But it&apos;s much more than that. It&apos;s a full-on collaborative development system for teams to work together on several projects. It shines by integrating CI/CD functionality. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://images.unsplash.com/photo-1617347454431-f49d7ff5c3b1?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGRlbGl2ZXJ5fGVufDB8fHx8MTY0NDMxNzgxNA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" class="kg-image" alt="How to set up a self-hosted Gitlab Runner and publish files through SFTP" loading="lazy" width="4294" height="3059" srcset="https://images.unsplash.com/photo-1617347454431-f49d7ff5c3b1?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGRlbGl2ZXJ5fGVufDB8fHx8MTY0NDMxNzgxNA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=600 600w, https://images.unsplash.com/photo-1617347454431-f49d7ff5c3b1?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGRlbGl2ZXJ5fGVufDB8fHx8MTY0NDMxNzgxNA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1000 1000w, https://images.unsplash.com/photo-1617347454431-f49d7ff5c3b1?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGRlbGl2ZXJ5fGVufDB8fHx8MTY0NDMxNzgxNA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=1600 1600w, https://images.unsplash.com/photo-1617347454431-f49d7ff5c3b1?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDF8fGRlbGl2ZXJ5fGVufDB8fHx8MTY0NDMxNzgxNA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2400 2400w" sizes="(min-width: 720px) 720px"><figcaption>Photo by <a href="https://unsplash.com/@rowanfreeman?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Rowan Freeman</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></figcaption></figure><h3 id="what-is-cicd">What is CI/CD?</h3><p>It stands for <strong>Continuous Integration</strong> and <strong>Continuous Delivery</strong>/<strong>Deployment </strong>(it&apos;s either delivery or deployment, not both because they are opposed as you&apos;re about to understand).</p><p><strong>Continuous Integration</strong> is the part you may already know. It&apos;s the organization in branches of codes where developers commit their changes to and merge them to proceed in a full commit to the main branch. It&apos;s really what Git brings on the table on top of versioning but automated.</p><p><strong>Continuous Delivery</strong> is the concept of automatically compiling and testing at every commit to the main branch. Once testing is complete and changes approved, the software is ready to be published. But, it doesn&apos;t mean that it is actually published, it&apos;s just <em>considered</em> as ready.</p><p><strong>Continuous Deployment </strong>is like Delivery but, as his name implies, has the software automatically deployed (which delivery does not). The old revision is removed and the new one put in place. That kind of organization really goes hand in hand with micro-services through container.</p><p>For what we want in this small project, it&apos;s merely a Continuous Deployment that we&apos;re going to put in place. Indeed, we just want to push our website files to the SFTP automatically when we commit changes. No testing, no compiling, no merge, etc...</p><h3 id="in-practice">In practice</h3><p>The way Gitlab executes all that automation is through a Gitlab Runner. That runner is just another machine where commands are executed on them. It could be a fully fledged computer or just a container, it doesn&apos;t really matter as long as the system has the Gitlab Runner software loaded on it and connected to the main Gitlab instance.</p><p>Runners come in three flavors:</p><ul><li>Specific</li><li>Group</li><li>Shared</li></ul><p>I think the names are obvious but let&apos;s explain them anyway. &#xA0;A <strong>specific runner</strong> will be dedicated to a <em>specific</em> project. It&apos;s great for an active and computationally intensive project. <strong>Group</strong> and <strong>Shared Runners</strong> are shared between projects, the difference between the two is that <strong>group runners </strong>will execute tasks for a <em>group</em> of projects where <strong>shared runners</strong> will execute tasks for <em>any</em> projects set up to allow use of them.</p><p>To send those commands to the runner, Gitlab run Jobs through Pipelines. A job is just an instance of execution of a config file that contains a recipe which instruct the runner what to do. That config file has for filename <code>.gitlab-ci.yml</code> and it needs to be at the root of your git project. As you may have guessed, the file is written in YAML. The pipeline is the complete set of jobs create by a specific file.</p><p>Because we want something simple, I&apos;m just going to put in place a shared runner. I don&apos;t have many projects running on my Gitlab and I&apos;m alone to work on them so it&apos;s more than enough for me.</p><hr><h2 id="set-up-of-the-gitlab-runner">Set up of the Gitlab Runner</h2><p>First of all, you have to install the Gitlab Runner software. I&apos;m not going to explain how to do that because the <a href="https://docs.gitlab.com/runner/install/?ref=blog.causticlabz.net">Gitlab Docs</a> does already a good job of that. So I&apos;m encouraging you to check it. In my case, I installed docker prior to the runner because I&apos;ll be using it in my automation. I have it installed on an unprivileged LXC container on my Proxmox server because it was the quickest and easiest solution for me.</p><blockquote>During the installation process, you&apos;ll be presented a list of executor and asked which to choose. If you wanna use my config as a base, you&apos;ll need docker installed and choosing it when asked.</blockquote><p>Is it installed for you? Good, now time to go in the admin panel of your main Gitlab instance. To access it go to <code>Menu &gt; Admin</code></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.causticlabz.net/content/images/2022/02/image-2.png" class="kg-image" alt="How to set up a self-hosted Gitlab Runner and publish files through SFTP" loading="lazy" width="355" height="277"><figcaption>Admin access</figcaption></figure><blockquote>You have to be logged in with an user that has admin rights on your Gitlab to have the option available</blockquote><p>Once there, under <code>Overview</code>, there&apos;s the <code>Runners</code> admin page</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.causticlabz.net/content/images/2022/02/image-3.png" class="kg-image" alt="How to set up a self-hosted Gitlab Runner and publish files through SFTP" loading="lazy" width="215" height="311"><figcaption>Runners admin page</figcaption></figure><p>Click on <code>Register an instance runner</code> then <code>Show runner installation and registration instructions</code></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.causticlabz.net/content/images/2022/02/image-4.png" class="kg-image" alt="How to set up a self-hosted Gitlab Runner and publish files through SFTP" loading="lazy" width="325" height="275"><figcaption>Registering the new runner</figcaption></figure><p>You&apos;ll be presented with a pop-up <code>Install a runner</code>. You&apos;ll be able to retrieve the information needed for registering your newly installed runner.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.causticlabz.net/content/images/2022/02/sceen-install-runner.png" class="kg-image" alt="How to set up a self-hosted Gitlab Runner and publish files through SFTP" loading="lazy" width="773" height="765" srcset="https://blog.causticlabz.net/content/images/size/w600/2022/02/sceen-install-runner.png 600w, https://blog.causticlabz.net/content/images/2022/02/sceen-install-runner.png 773w" sizes="(min-width: 720px) 720px"><figcaption>Install a runner pop-up</figcaption></figure><p>If you already have installed the Gitlab Runner software like I told you to do, you just have to look at the right environment (of course) and copying the command at the bottom. That command has to be executed on the runner machine itself.</p><p>Once done, it will appear in your Gitlab instance with the online status. By default they are set up as shared so we don&apos;t have anything more to do on that front.</p><h2 id="setting-up-cicd">Setting up CI/CD</h2><h3 id="before-we-start">Before we start</h3><p>We have to first enable the project to use the runner. To set it up, your user needs to have maintainer or owner role on the target project, other roles can&apos;t edit the settings of a project.</p><p>So, go to your Gitlab instance, open your project then go to <code>Settings</code> &gt; <code>CI/CD</code>. There&apos;s a section called <code>Runners</code> which is collapsed by default. Click on <code>Expand</code>. Make sure, in the right column, under the <code>Shared runners</code> section that <code>Enable Shared runners for this project</code> is enabled (like in the screenshot below).</p><figure class="kg-card kg-image-card"><img src="https://blog.causticlabz.net/content/images/2022/02/image-5.png" class="kg-image" alt="How to set up a self-hosted Gitlab Runner and publish files through SFTP" loading="lazy" width="253" height="61"></figure><p>Now, the foundation is laid to use that function.</p><h3 id="creating-the-config-file">Creating the config file</h3><p>This file contains all the commands for the runner. It&apos;s written in YAML and use a specific structure. If you have played with docker in the past, you&apos;ll be in a familiar environment. Especially because we use docker in this example.</p><p>You have to create it in the root folder of your git project. The file name has to be <code>.gitlab-ci.yml</code>. Now, every time you push your commit, Gitlab will parse the file and act accordingly to what&apos;s in it.</p><h2 id="pushing-files-through-sftp">Pushing files through SFTP</h2><p>What we want to do is really simple compared to what a development studio would need. But, it will be a good introduction to how to use this file with some environment variables set through Gitlab.</p><p>Because we use docker, we could think of this file as a mix of a Docker Compose File and a Dockerfile. That way, we can gather the kind of info needed to successfully create this file.</p><p>First, which base image to use? I&apos;m gonna use Alpine Linux, a small lightweight distro optimized to use in this type of environment.</p><p>Second, what commands do we want to execute to transfer the files? The easiest method is to use a software call LFTP that can handle all the transfer through a single command line. For the protocol, we&apos;ll use SFTP because it uses SSH which is already available on my test web server and I don&apos;t want to set up a complete FTP server just for testing. Sometimes, hosting provider could limit access to SFTP only so it could be useful to you.</p><blockquote>The whole process of choosing and iterating tests to finally founding the complete script has been covered quite well by <a href="https://tisgoud.nl/2020/03/deploy-to-sftp-from-gitlab-ci/?ref=blog.causticlabz.net">&apos;t is goud</a>, I encourage you to check it out.</blockquote><h3 id="commands">Commands</h3><figure class="kg-card kg-code-card"><pre><code class="language-bash">apk add --no-cache openssh lftp
mkdir /root/.ssh
chmod 700 /root/.ssh
touch /root/.ssh/known_hosts
chmod 600 /root/.ssh/known_hosts
ssh-keyscan -p $SFTP_PORT -H $SFTP_HOST &gt;&gt; /root/.ssh/known_hosts
lftp -e &quot;mirror --delete --parallel=5 --transfer-all --reverse -X .* --verbose website/ /var/www/html; bye&quot; -u $SFTP_USER,$SFTP_PASSWORD sftp://$SFTP_HOST -p $SFTP_PORT</code></pre><figcaption>As you can see, we use variables, we&apos;ll see later how to create those.</figcaption></figure><p>The first line is a command used to install OpenSSH to obtain SFTP compatibility on the local end and LFTP for obvious reasons.</p><p>The next 4 commands are used to create the <code>know_hosts</code> file because <code>ssh-keyscan</code> can&apos;t create it by itself in this context.</p><p>SSH-Keyscan is used to gather the info of the ssh server and put it to the <code>known_hosts</code> file. Now, no need to approve the key when first connecting to the ssh server. </p><p>LFTP will do all the transfers. It uses different parameters:</p><ul><li><code>-e &quot; &quot;</code> Command(s) to execute - They are placed in-between the double quotes</li><li><code>mirror</code> Mirror the remote folder on the local folder</li><li><code>--delete</code> Delete files and folders in the destination that are not present in the source</li><li><code>--parallel=n</code> N number of transfer to execute in parallel</li><li><code>--transfer-all</code> Force the transfer of all files even if it already exist in the destination</li><li><code>--reverse</code> Reverse the source and destination folder - the remote is now the destination</li><li><code>-X</code> Ignore the matching files</li><li><code>--verbose</code> Show all actions executed</li><li><code>source/folder destination/folder</code> Obviously the source and destination folders location</li><li><code>bye</code> Quit the command</li><li><code>-u USER,PWD</code> Specifies the user and password to connect</li><li><code>sftp://host</code> SFTP address to connect to</li><li><code>-p PORT</code> Port to use for the connection</li></ul><h3 id="full-file">Full file</h3><p>Let&apos;s breakdown the complete file</p><pre><code class="language-yaml">image: alpine:latest

before_script:
  - apk add --no-cache openssh lftp
  
build:  
  script:
    - mkdir /root/.ssh
    - chmod 700 /root/.ssh
    - touch /root/.ssh/known_hosts
    - chmod 600 /root/.ssh/known_hosts
    - ssh-keyscan -p $SFTP_PORT -H $SFTP_HOST &gt;&gt; /root/.ssh/known_hosts
    - lftp -e &quot;mirror --delete --parallel=5 --transfer-all --reverse -X .* --verbose website/ /var/www/html; bye&quot; -u $SFTP_USER,$SFTP_PASSWORD sftp://$SFTP_HOST -p $SFTP_PORT

  only:
    - main</code></pre><p>As you can see, the commands are split in two sections and a few other lines are added.</p><p>First line is the image we have chosen. I use alpine with the <code>:latest</code> tag. You should use a fixed version in production.</p><blockquote>DO NOT USE LATEST IN PRODUCTION !!! JUST FOR TESTING</blockquote><p>The <code>before_script</code> section contains the command to prepare the image before his use. Of course, the installation part of the software is done there.</p><p>The <code>build</code> section contains what I would describe as the computationally interesting part. It&apos;s called build because it&apos;s in this part that you would do the compilation process.</p><p>It contains the <code>script</code> section, it&apos;s quite obvious what that part does, it&apos;s all the commands to build the software.</p><p>It also contains the <code>only</code> section. You&apos;ll define there in which branch the commit will result in the job being executed. In this example, at every commit to the main branch, the job is executed.</p><h3 id="variables">Variables</h3><p>Now on the last part to finalize our set up of Gitlab. The variables !</p><p>So, go to your Gitlab instance, open your project then go to <code>Settings</code> &gt; <code>CI/CD</code>. There&apos;s a section called <code>Variables</code> which is collapsed by default. Click on <code>Expand</code>.</p><p>It&apos;s just a matter of clicking on <code>Add Variables</code>, entering the name of the variable in <code>Key</code> then putting the data we want to pass through in <code>Value</code> and finally clicking on <code>Add Variable</code></p><blockquote>The name in <code>key</code> is what it&apos;ll be used in the script preceded by <code>$</code></blockquote><blockquote>Check <code>Mask variable</code> for the password variable</blockquote><figure class="kg-card kg-image-card"><img src="https://blog.causticlabz.net/content/images/2022/02/image-6.png" class="kg-image" alt="How to set up a self-hosted Gitlab Runner and publish files through SFTP" loading="lazy" width="761" height="523" srcset="https://blog.causticlabz.net/content/images/size/w600/2022/02/image-6.png 600w, https://blog.causticlabz.net/content/images/2022/02/image-6.png 761w" sizes="(min-width: 720px) 720px"></figure><p>In this example, we need:</p><ul><li>the address of the server (SFTP_HOST)</li><li>the port used by SFTP (SFTP_PORT)</li><li>the user used for connection (SFTP_USER)</li><li>the password of the user (SFTP_PASSWORD)</li></ul><figure class="kg-card kg-image-card"><img src="https://blog.causticlabz.net/content/images/2022/02/image-7.png" class="kg-image" alt="How to set up a self-hosted Gitlab Runner and publish files through SFTP" loading="lazy" width="1377" height="249" srcset="https://blog.causticlabz.net/content/images/size/w600/2022/02/image-7.png 600w, https://blog.causticlabz.net/content/images/size/w1000/2022/02/image-7.png 1000w, https://blog.causticlabz.net/content/images/2022/02/image-7.png 1377w" sizes="(min-width: 720px) 720px"></figure><h2 id="commit">Commit!</h2><p>Now, everything is ready. At the next commit to the correct branch, you&apos;ll be able to see the jobs running in the CI/CD page of your project.</p><figure class="kg-card kg-image-card"><img src="https://blog.causticlabz.net/content/images/2022/02/image-8.png" class="kg-image" alt="How to set up a self-hosted Gitlab Runner and publish files through SFTP" loading="lazy" width="1043" height="325" srcset="https://blog.causticlabz.net/content/images/size/w600/2022/02/image-8.png 600w, https://blog.causticlabz.net/content/images/size/w1000/2022/02/image-8.png 1000w, https://blog.causticlabz.net/content/images/2022/02/image-8.png 1043w" sizes="(min-width: 720px) 720px"></figure><p>If you click on the status of the pipeline, you&apos;ll be able to get more info.</p><figure class="kg-card kg-image-card"><img src="https://blog.causticlabz.net/content/images/2022/02/image-9.png" class="kg-image" alt="How to set up a self-hosted Gitlab Runner and publish files through SFTP" loading="lazy" width="487" height="523"></figure><p>Then, if you click on the bottom button named after the section in our yaml file, you&apos;ll get the output of the job.</p><figure class="kg-card kg-image-card"><img src="https://blog.causticlabz.net/content/images/2022/02/image-10.png" class="kg-image" alt="How to set up a self-hosted Gitlab Runner and publish files through SFTP" loading="lazy" width="1975" height="951" srcset="https://blog.causticlabz.net/content/images/size/w600/2022/02/image-10.png 600w, https://blog.causticlabz.net/content/images/size/w1000/2022/02/image-10.png 1000w, https://blog.causticlabz.net/content/images/size/w1600/2022/02/image-10.png 1600w, https://blog.causticlabz.net/content/images/2022/02/image-10.png 1975w" sizes="(min-width: 720px) 720px"></figure><p>That&apos;s it! If you have any questions or comments, do not hesitate to contact me. Now it&apos;s time to start developing with a little bit less of hassle!</p>]]></content:encoded></item><item><title><![CDATA[How to set up Ghost CMS behind your own reverse proxy]]></title><description><![CDATA[Let's look how to set up correctly your Nginx reverse proxy and Ghost CMS to work completely through HTTPS.]]></description><link>https://blog.causticlabz.net/how-to-set-up-ghost-cms-behind-your-own-reverse-proxy/</link><guid isPermaLink="false">64ebeb40c7caff2ac2b74d85</guid><category><![CDATA[nginx]]></category><category><![CDATA[reverse proxy]]></category><category><![CDATA[network]]></category><category><![CDATA[ghost cms]]></category><dc:creator><![CDATA[Frédéric Gilson]]></dc:creator><pubDate>Sun, 06 Feb 2022 19:19:30 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1558494949-ef010cbdcc31?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDIyfHxuZXR3b3JrfGVufDB8fHx8MTY0NDA5NTA5NA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1558494949-ef010cbdcc31?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDIyfHxuZXR3b3JrfGVufDB8fHx8MTY0NDA5NTA5NA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="How to set up Ghost CMS behind your own reverse proxy"><p>When setting up this instance of Ghost CMS, I was faced with a little problem regarding my network config on my dedicated server. I use several VMs connected to a router VM which distribute the internet connection. One of those VMs receive all of the traffic for 80 &amp; 443 ports, it&apos;s my Nginx reverse proxy which itself proxies the connection to the differents &quot;servers&quot; VMs.</p><hr><p>As you might have guessed, one VM runs a Linux distro which powered Ghost CMS. The distro used is Ubuntu as it&apos;s the one used in the <a href="https://ghost.org/docs/install/ubuntu/?ref=blog.causticlabz.net">Ghost doc</a> (and most likely the best supported one).</p><p>The way Ghost CMS works is with a nodeJS app that generates all the files and serves them through the 2368 port by default. It doesn&apos;t listen to 80 or 443 for simplicity and security reasons. That&apos;s why the documentation require a nginx install on the host to proxy to <code>127.0.0.1:2368</code>. That way, it&apos;s nginx that handle as a reverse proxy all the SSL handshake if you want to use HTTPS (<strong>use it!</strong>).</p><p>And that&apos;s where the trouble begins for my config because it would mean reverse proxy over reverse proxy which doesn&apos;t play nice with HTTP redirect and preview functionality of Ghost CMS.</p><p>If you already have installed Ghost CMS and the stack needed for it. Chances are that you have installed Nginx prior to Ghost and you entered the domain of your website when asked during the setup. You might even have used HTTPS during this setup. Behind a reverse proxy, entering the HTTPS would have most likely failed.</p><p>Now, you have Ghost CMS installed with the local Nginx configured. Your first instinct would be to configure the main Nginx to proxy to the Ghost one. You can access your website this way but a problem is quickly discovered if you have set up SSL certificates on your main Nginx (and so you access the website through HTTPS). You can&apos;t have any website preview inside the admin dashboard because Ghost CMS try with HTTP and it can&apos;t handle the redirect.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.causticlabz.net/content/images/2022/02/screen_white_preview_blog.png" class="kg-image" alt="How to set up Ghost CMS behind your own reverse proxy" loading="lazy" width="1822" height="1119" srcset="https://blog.causticlabz.net/content/images/size/w600/2022/02/screen_white_preview_blog.png 600w, https://blog.causticlabz.net/content/images/size/w1000/2022/02/screen_white_preview_blog.png 1000w, https://blog.causticlabz.net/content/images/size/w1600/2022/02/screen_white_preview_blog.png 1600w, https://blog.causticlabz.net/content/images/2022/02/screen_white_preview_blog.png 1822w" sizes="(min-width: 1200px) 1200px"><figcaption>Blank preview in the admin panel</figcaption></figure><p>The solution is to bypass the local reverse proxy and use only your main reverse proxy to Ghost. The catch is that Ghost CMS listen for connection on <code>127.0.0.1:2368</code> which is not accessible by your main Nginx. You have to instruct Ghost to listen on <code>0.0.0.0:2368</code>.</p><p>Now, let&apos;s do all those configs.</p><hr><p>Connect to the server hosting your main reverse proxy.</p><p>First, create a config file in <code>/etc/nginx/sites-available/</code></p><blockquote>You can name it however you want but I use the domain it listen for as a filename like many Nginx administrator. (e.g. <code>blog.causticlabz.net</code>)</blockquote><p>In your config file put the following config:</p><pre><code class="language-nginx">server {
	# Replace all instance of blog.causticlabz.net with your own domain
    
    listen 80;
    listen [::]:80; # If you use IPv6
    
	server_name blog.causticlabz.net;
    
    access_log /var/log/nginx/blog.causticlabz.net.log;
    
    client_max_body_size 50M; # Enable file upload with a limit of 50Mb
    
    location / {
    	include proxy_params; # Include proxy paramaters needed, the same as the one explicitly written by Ghost CMS config
        proxy_pass http://192.168.1.2:2368$request_uri; # Replace with the IP of your server hosting Ghost CMS
    }
}</code></pre><blockquote>Note that I configure listening on 80 port. I use Let&apos;s Encrypt for my SSL certificates setup and it handles automatically the creation of the HTTPS part.</blockquote><p>Because Nginx use two folders containing the config files for available and enabled sites, we have to put the config file to the enabled folder too. You could copy it but it means two files to maintain so we&apos;ll create a symbolic link to have one file present in the two folders.</p><p><code>sudo ln -s /etc/nginx/sites-available/blog.causticlabz.net /etc/nginx/sites-enabled/blog.causticlabz.net</code></p><p>To set up your SSL certificates with Let&apos;s Encrypt, first install &quot;Certbot&quot; if you don&apos;t have it already. </p><p><code>sudo apt install certbot python3-certbot-nginx</code></p><p>Now, use Certbot to configure your certificates.</p><p><code>sudo certbot --nginx -d blog.causticlabz.net</code></p><blockquote>Replace with your domain, note that if you use www subdomain, you have to add to the command &quot;-d www.yourdomain.com&quot; or any other domains and subdomains to set up.</blockquote><p>Certbot will also automatically renew the certificates when they&apos;re about to expire. If it&apos;s the first time you use Certbot, it will ask for an email address to send alerts if needed then ask you to accept the EULA.</p><p>Certbot will ask how to set up the config file for nginx, if it needs to enable the redirection or not. Of course, the whole point in this exercise is to have the redirection enabled. So choose accordingly.</p><p>Certbot will then proceed to contact Let&apos;s Encrypt server, set up SSL certificates and adding them to your nginx config.</p><p>Everything is good for your main reverse proxy.</p><hr><p>Time to configure Ghost CMS. Connect to the server hosting it.</p><p>Change directory to the one hosting your Ghost CMS. Then, do the following command: </p><p><code>ghost config set server.host 0.0.0.0</code></p><p>That&apos;s it! Now you should be able to access your instance of Ghost CMS through HTTPS with everything working correctly inside it. If you have any questions or comments, do not hesitate to contact me. Now it&apos;s time to start writing some articles on your CMS!</p>]]></content:encoded></item></channel></rss>