<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Development Tools &#8211; My Shitty Code</title>
	<atom:link href="https://myshittycode.com/development-tools/feed/" rel="self" type="application/rss+xml" />
	<link>https://myshittycode.com</link>
	<description>Embracing the Messiness in Search of Epic Solutions</description>
	<lastBuildDate>Tue, 17 Feb 2026 13:56:55 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.1</generator>

<image>
	<url>https://myshittycode.com/wp-content/uploads/2022/04/cropped-icon-32x32.png</url>
	<title>Development Tools &#8211; My Shitty Code</title>
	<link>https://myshittycode.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">205304208</site>	<item>
		<title>FFmpeg: High-Quality MP4 to GIF</title>
		<link>https://myshittycode.com/2026/02/17/ffmpeg-high-quality-mp4-to-gif/</link>
					<comments>https://myshittycode.com/2026/02/17/ffmpeg-high-quality-mp4-to-gif/#respond</comments>
		
		<dc:creator><![CDATA[Shitty Author]]></dc:creator>
		<pubDate>Tue, 17 Feb 2026 13:56:53 +0000</pubDate>
				<category><![CDATA[Development Tools]]></category>
		<category><![CDATA[ffmpeg]]></category>
		<guid isPermaLink="false">https://myshittycode.com/?p=2710</guid>

					<description><![CDATA[<p>PROBLEM You want to generate high-quality GIFs from MP4 files without using extra software or online services. SOLUTION Install FFmpeg. Default FFmpeg conversions often look low quality because GIFs are limited to 256 colors and use a generic color map. So, a two-pass conversion will be used. Parameters:</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2026/02/17/ffmpeg-high-quality-mp4-to-gif/">FFmpeg: High-Quality MP4 to GIF</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p></p>



<h2 class="wp-block-heading">PROBLEM</h2>



<p>You want to generate high-quality GIFs from MP4 files without using extra software or online services.</p>



<h2 class="wp-block-heading">SOLUTION</h2>



<p>Install <a href="https://formulae.brew.sh/formula/ffmpeg" target="_blank" rel="noopener">FFmpeg</a>.</p>



<p>Default FFmpeg conversions often look low quality because GIFs are limited to 256 colors and use a generic color map. So, a two-pass conversion will be used.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
# Pass 1: Analyze the video to generate a palette
ffmpeg -i input.mp4 \
  -vf &quot;fps=10,scale=850:-1:flags=lanczos,palettegen&quot; \
  palette.png

# Pass 2: Apply the palette to create the GIF
ffmpeg -i input.mp4 \
  -i palette.png \
  -filter_complex &quot;fps=10,scale=850:-1:flags=lanczos&#x5B;x];&#x5B;x]&#x5B;1:v]paletteuse&quot; \
  output.gif
</pre></div>


<p>Parameters:</p>



<ul class="wp-block-list">
<li><strong>fps=10</strong>: Reduces frame rate and file size.</li>



<li><strong>scale=850:-1</strong>: Sets width to 850px, -1 will keep aspect ratio.</li>



<li><strong>flags=lanczos</strong>: Sharper scaling algorithm.</li>



<li><strong>palettegen</strong>: Generates optimal 256-color palette.</li>



<li><strong>paletteuse</strong>: Applies the generated palette.</li>
</ul>



<p></p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2026/02/17/ffmpeg-high-quality-mp4-to-gif/">FFmpeg: High-Quality MP4 to GIF</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myshittycode.com/2026/02/17/ffmpeg-high-quality-mp4-to-gif/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2710</post-id>	</item>
		<item>
		<title>MSAL: Caching Access Token</title>
		<link>https://myshittycode.com/2024/09/24/msal-caching-access-token/</link>
					<comments>https://myshittycode.com/2024/09/24/msal-caching-access-token/#respond</comments>
		
		<dc:creator><![CDATA[Shitty Author]]></dc:creator>
		<pubDate>Tue, 24 Sep 2024 18:13:59 +0000</pubDate>
				<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Development Tools]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[MacOS]]></category>
		<category><![CDATA[MSAL]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://myshittycode.com/?p=2701</guid>

					<description><![CDATA[<p>Why Cache Access Token with MSAL? Building upon the previous post that performs delegated access authentication with MSAL, suppose your program uses this function to get the access token. In this case, it will always launch the browser to complete the authentication flow and retrieve the access token every time the program runs. While it [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/09/24/msal-caching-access-token/">MSAL: Caching Access Token</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-rank-math-toc-block" id="rank-math-toc"><h2>Table of Contents</h2><nav><ul><li><a href="#why-cache-access-token-with-msal">Why Cache Access Token with MSAL?</a></li><li><a href="#solution-a-use-custom-persistence-cache">Solution A: Use custom persistence cache</a></li><li><a href="#solution-b-use-the-os-platforms-encrypted-vault-preferred">Solution B: Use the OS platform&#8217;s encrypted vault (PREFERRED)</a></li></ul></nav></div>



<h2 class="wp-block-heading" id="why-cache-access-token-with-msal">Why Cache Access Token with MSAL?</h2>



<p>Building upon <a href="https://myshittycode.com/2024/09/23/msal-delegated-access-authentication/" data-type="post" data-id="2678">the previous post</a> that performs delegated access authentication with MSAL, suppose your program uses this function to get the access token.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
def get_access_token():
    app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
    result = app.acquire_token_interactive(scopes=SCOPES)
    access_token = result&#x5B;&#039;access_token&#039;]

    return access_token
</pre></div>


<p>In this case, it will always launch the browser to complete the authentication flow and retrieve the access token every time the program runs.</p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="1650" height="1096" src="https://myshittycode.com/wp-content/uploads/2024/09/msal-browser.png?x45560" alt="" class="wp-image-2703" srcset="https://myshittycode.com/wp-content/uploads/2024/09/msal-browser.png 1650w, https://myshittycode.com/wp-content/uploads/2024/09/msal-browser-300x199.png 300w, https://myshittycode.com/wp-content/uploads/2024/09/msal-browser-1024x680.png 1024w, https://myshittycode.com/wp-content/uploads/2024/09/msal-browser-768x510.png 768w, https://myshittycode.com/wp-content/uploads/2024/09/msal-browser-1536x1020.png 1536w" sizes="(max-width: 1650px) 100vw, 1650px" /></figure>



<p>While it works, it introduces a slight latency to your program. Fortunately, MSAL provides the capability to cache the access token.</p>



<h2 class="wp-block-heading" id="solution-a-use-custom-persistence-cache">Solution A: Use custom persistence cache</h2>



<p>In this solution, we can introduce our custom persistence cache.</p>



<p>First, we can use these functions to load the access token from the cache file and save it to the cache file.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import os
import msal

...
CACHE_FILE = &#039;msal_token_cache.json&#039;

def load_cache():
    cache = msal.SerializableTokenCache()

    if os.path.exists(CACHE_FILE):
        with open(CACHE_FILE, &#039;rb&#039;) as f:
            cache.deserialize(f.read())

    return cache

def save_cache(cache):
    if cache.has_state_changed:
        with open(CACHE_FILE, &#039;wb&#039;) as f:
            f.write(cache.serialize().encode())
</pre></div>


<p>We can first attempt to get a valid access token from the cache. It will only get the token interactively via the browser if the access token doesn&#8217;t exist or has expired.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; highlight: [2,6,8,9,10,11,12,13,14,17]; title: ; notranslate">
def get_access_token():
    cache = load_cache()
    app = msal.PublicClientApplication(
        CLIENT_ID,
        authority=AUTHORITY,
        token_cache=cache,
    )
    result = None
    accounts = app.get_accounts()

    if accounts:
        result = app.acquire_token_silent(SCOPES, account=accounts&#x5B;0])

    if not result:
        result = app.acquire_token_interactive(scopes=SCOPES)

    save_cache(cache)
    access_token = result&#x5B;&#039;access_token&#039;]

    return access_token
</pre></div>


<p>While this solution works, the implementation code is very lengthy. Furthermore, the access token is stored in the cache file as clear text.</p>



<h2 class="wp-block-heading" id="solution-b-use-the-os-platforms-encrypted-vault-preferred">Solution B: Use the OS platform&#8217;s encrypted vault (PREFERRED)</h2>



<p>A Python package called <a href="https://github.com/AzureAD/microsoft-authentication-extensions-for-python" target="_blank" rel="noopener">msal-extensions</a> allows the access token to be stored securely in the OS platform&#8217;s encrypted vault. For example, If the program runs on MacOS, KeyChain will be used. Windows uses DPAPI, and Linux uses LibSecret.</p>



<p>To begin, install that Python package.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; highlight: [4]; title: ; notranslate">
# requirements.txt

msal
msal-extensions
</pre></div>


<p>The configuration is much simpler than Solution A.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; highlight: [2,5,11]; title: ; notranslate">
import msal
from msal_extensions import build_encrypted_persistence, PersistedTokenCache

...
CACHE_FILE = &#039;msal_token_cache.json&#039;

def get_access_token():
    app = msal.PublicClientApplication(
        CLIENT_ID,
        authority=AUTHORITY,
        token_cache=PersistedTokenCache(build_encrypted_persistence(CACHE_FILE)),
    )

    result = None
    accounts = app.get_accounts()

    if accounts:
        result = app.acquire_token_silent(SCOPES, account=accounts&#x5B;0])

    if not result:
        result = app.acquire_token_interactive(scopes=SCOPES)

    access_token = result&#x5B;&#039;access_token&#039;]

    return access_token
</pre></div>


<p>When running the program on MacOS, the access token is stored in the KeyChain.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1940" height="1068" src="https://myshittycode.com/wp-content/uploads/2024/09/msal-keychain.png?x45560" alt="" class="wp-image-2704" srcset="https://myshittycode.com/wp-content/uploads/2024/09/msal-keychain.png 1940w, https://myshittycode.com/wp-content/uploads/2024/09/msal-keychain-300x165.png 300w, https://myshittycode.com/wp-content/uploads/2024/09/msal-keychain-1024x564.png 1024w, https://myshittycode.com/wp-content/uploads/2024/09/msal-keychain-768x423.png 768w, https://myshittycode.com/wp-content/uploads/2024/09/msal-keychain-1536x846.png 1536w" sizes="(max-width: 1940px) 100vw, 1940px" /></figure>



<p><strong>Note:</strong> An empty cache file named msal_token_cache.json has also been created. Unfortunately, there is no option in <code>msal-extensions==1.2.0</code> to disable the creation of this file at the moment.</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/09/24/msal-caching-access-token/">MSAL: Caching Access Token</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myshittycode.com/2024/09/24/msal-caching-access-token/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2701</post-id>	</item>
		<item>
		<title>MSAL: Launching Specific Browser with Delegated Access Authentication</title>
		<link>https://myshittycode.com/2024/09/24/msal-launching-specific-browser-with-delegated-access-authentication/</link>
					<comments>https://myshittycode.com/2024/09/24/msal-launching-specific-browser-with-delegated-access-authentication/#respond</comments>
		
		<dc:creator><![CDATA[Shitty Author]]></dc:creator>
		<pubDate>Tue, 24 Sep 2024 15:16:59 +0000</pubDate>
				<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Development Tools]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[MacOS]]></category>
		<category><![CDATA[MSAL]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://myshittycode.com/?p=2689</guid>

					<description><![CDATA[<p>Why Launch a Specific Browser with MSAL? Building upon the previous post that performs delegated access authentication with MSAL, your institution may sometimes allow you to access Microsoft 365 products only on the approved browsers. By default, MSAL launches the default browser on your machine to obtain the access token interactively. If your institution only [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/09/24/msal-launching-specific-browser-with-delegated-access-authentication/">MSAL: Launching Specific Browser with Delegated Access Authentication</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-rank-math-toc-block" id="rank-math-toc"><h2>Table of Contents</h2><nav><ul><li><a href="#why-launch-a-specific-browser-with-msal">Why Launch a Specific Browser with MSAL?</a></li><li><a href="#overriding-with-browser-environment-variable">Overriding with BROWSER environment variable</a></li><li><a href="#what-if-the-browser-environment-variables-predefined-value-doesnt-work">What if the BROWSER environment variable&#8217;s predefined value doesn&#8217;t work?</a></li></ul></nav></div>



<h2 class="wp-block-heading" id="why-launch-a-specific-browser-with-msal">Why Launch a Specific Browser with MSAL?</h2>



<p>Building upon <a href="https://myshittycode.com/2024/09/23/msal-delegated-access-authentication/" data-type="post" data-id="2678">the previous post</a> that performs delegated access authentication with MSAL, your institution may sometimes allow you to access Microsoft 365 products only on the approved browsers.</p>



<p>By default, MSAL launches the default browser on your machine to obtain the access token interactively. </p>



<p>If your institution only allows Chrome and your default browser is Firefox, you will get the following message:</p>



<figure class="wp-block-image aligncenter size-full"><img decoding="async" width="774" height="690" src="https://myshittycode.com/wp-content/uploads/2024/09/firefox-blocked.png?x45560" alt="msal" class="wp-image-2693" srcset="https://myshittycode.com/wp-content/uploads/2024/09/firefox-blocked.png 774w, https://myshittycode.com/wp-content/uploads/2024/09/firefox-blocked-300x267.png 300w, https://myshittycode.com/wp-content/uploads/2024/09/firefox-blocked-768x685.png 768w" sizes="(max-width: 774px) 100vw, 774px" /></figure>



<h2 class="wp-block-heading" id="overriding-with-browser-environment-variable">Overriding with BROWSER environment variable</h2>



<p>MSAL relies on Python&#8217;s built-in module called <a href="https://github.com/AzureAD/microsoft-authentication-library-for-python/blob/331c16fdd803df6a629cb215a6372dbad93f0ba8/msal/oauth2cli/authcode.py#L68" target="_blank" rel="noopener">webbrowser</a> to launch the browser. This allows us to change the browser by setting the <a href="https://docs.python.org/3/library/webbrowser.html" target="_blank" rel="noopener">BROWSER environment variable</a> to the predefined values (&#8216;firefox&#8217;, &#8216;chrome&#8217;, etc.)</p>



<p>For example, to launch Firefox instead of the default browser, we can do this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; highlight: [5]; title: ; notranslate">
import os
import msal

# ... 
os.environ&#x5B;&#039;BROWSER&#039;] = &#039;firefox&#039;
app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
result = app.acquire_token_interactive(scopes=SCOPES)
</pre></div>


<h2 class="wp-block-heading" id="what-if-the-browser-environment-variables-predefined-value-doesnt-work">What if the BROWSER environment variable&#8217;s predefined value doesn&#8217;t work?</h2>



<p>I discovered that Python&#8217;s webbrowser failed to launch the correct browser, especially when dealing with Chromnium-based browsers.</p>



<p>For example, suppose my default browser is Brave, and I set the BROWSER environment variable to Chrome. In that case, it will still launch the Brave browser.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; highlight: [3]; title: ; notranslate">
# default browser = Brave

os.environ&#x5B;&#039;BROWSER&#039;] = &#039;chrome&#039;  # doesn&#039;t work
app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
result = app.acquire_token_interactive(scopes=SCOPES)
</pre></div>


<p>Fortunately, it is possible to <a href="https://github.com/AzureAD/microsoft-authentication-library-for-python/discussions/750#discussioncomment-10710600" target="_blank" rel="noopener">specify a full path ending with &#8216;%s&#8217;</a> instead:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; highlight: [3]; title: ; notranslate">
# default browser = Brave

os.environ&#x5B;&#039;BROWSER&#039;] = &#039;open -a /Applications/Google\\ Chrome.app %s&#039;  # works
app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
result = app.acquire_token_interactive(scopes=SCOPES)
</pre></div>


<p>If you are stuck on the &#8220;Approve sign in request&#8221; or<em> </em>&#8220;Set up your device to get access&#8221; screen indefinitely, consider clearing the browser cache and then try again.</p>



<p></p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/09/24/msal-launching-specific-browser-with-delegated-access-authentication/">MSAL: Launching Specific Browser with Delegated Access Authentication</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myshittycode.com/2024/09/24/msal-launching-specific-browser-with-delegated-access-authentication/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2689</post-id>	</item>
		<item>
		<title>Ansible: Handling Sudo Password with Homebrew</title>
		<link>https://myshittycode.com/2024/09/04/ansible-handling-sudo-password-with-homebrew/</link>
					<comments>https://myshittycode.com/2024/09/04/ansible-handling-sudo-password-with-homebrew/#respond</comments>
		
		<dc:creator><![CDATA[Shitty Author]]></dc:creator>
		<pubDate>Wed, 04 Sep 2024 15:45:24 +0000</pubDate>
				<category><![CDATA[Development Tools]]></category>
		<category><![CDATA[Ansible]]></category>
		<category><![CDATA[Homebrew]]></category>
		<guid isPermaLink="false">https://myshittycode.com/?p=2665</guid>

					<description><![CDATA[<p>Problem When using the Ansible playbook to run Homebrew-related modules, it will prompt for a sudo password where necessary on specific tasks. For example, using the community.general.homebrew_cask module to (un)install the apps under /Applications directory will prompt for a sudo password on each app. It is not possible to preemptively prompt for a sudo password [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/09/04/ansible-handling-sudo-password-with-homebrew/">Ansible: Handling Sudo Password with Homebrew</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-rank-math-toc-block" id="rank-math-toc"><h2>Table of Contents</h2><nav><ul><li><a href="#problem">Problem</a></li><li><a href="#does-not-work-too">Root Cause</a></li><li><a href="#solution">Solution</a></li></ul></nav></div>



<h2 class="wp-block-heading" id="problem">Problem</h2>



<p>When using the Ansible playbook to run Homebrew-related modules, it will prompt for a sudo password where necessary on specific tasks. For example, using the <a href="https://docs.ansible.com/ansible/latest/collections/community/general/homebrew_cask_module.html" target="_blank" rel="noopener">community.general.homebrew_cask</a> module to (un)install the apps under /Applications directory will prompt for a sudo password on each app.</p>



<p>It is not possible to preemptively prompt for a sudo password before running the Ansible playbook:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [2]; title: ; notranslate">
# Does not work!
sudo -v

ansible-playbook main.yml
</pre></div>


<p>It is also not possible to perform <a href="https://gist.github.com/cowboy/3118588" target="_blank" rel="noopener">clever tricks like this</a> to extend the sudo timeout:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [2]; title: ; notranslate">
# Does not work, too!
while true; do sudo -n true; sleep 60; kill -0 &quot;$$&quot; || exit; done 2&gt;/dev/null &amp;

ansible-playbook main.yml
</pre></div>


<h2 class="wp-block-heading" id="does-not-work-too">Root Cause</h2>



<p>This is not an Ansible problem. This behavior exists because Homebrew always clears the sudo password cache to prevent privilege escalation attacks [<a href="https://github.com/Homebrew/brew/issues/17905" target="_blank" rel="noopener">link 1</a>] [<a href="https://github.com/Homebrew/brew/pull/17694/commits/2adf25dcaf8d8c66124c5b76b8a41ae228a7bb02" target="_blank" rel="noopener">link 2</a>].</p>



<p>The upside is the <strong>community.general.homebrew_cask</strong> module provides a variable for passing in a sudo password, for example:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; highlight: [7]; title: ; notranslate">
- name: Install/Upgrade cask packages
  community.general.homebrew_cask:
    name: &#039;{{ item }}&#039;
    state: upgraded
    greedy: true
    install_options: force,no-quarantine
    sudo_password: &quot;{{ ansible_become_pass }}&quot;
  loop: &#039;{{ homebrew_cask_packages_present }}&#039;
</pre></div>


<h2 class="wp-block-heading" id="solution">Solution</h2>



<p>While storing passwords in a file is not ideal, it prevents these Ansible tasks from prompting for a sudo password each time. The <strong>ansible-vault</strong> command can be used to store the password (and other secrets) securely.</p>



<p>First, create an encrypted file.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
ansible-vault create vault.yml
</pre></div>


<p>You will be prompted for a new vault password. Once provided, enter the following into the file.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
ansible_become_pass: &#x5B;YOUR_SUDO_PASSWORD]
</pre></div>


<p>Now, your encrypted file should be created in the location you specified with the proper permission set:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [18]; title: ; notranslate">
$ ls -la
total 88
drwxr-xr-x@ 18 shitty.author  staff   576 Sep  4 09:06 .
drwxr-xr-x  12 shitty.author  staff   384 Jun 27 12:47 ..
-rw-r--r--   1 shitty.author  staff    99 Mar  1  2024 .ansible-lint
-rw-r--r--   1 shitty.author  staff   193 Mar 19 08:57 .editorconfig
drwxr-xr-x  16 shitty.author  staff   512 Sep  4 09:06 .git
-rw-r--r--   1 shitty.author  staff   243 Mar  1  2024 .gitignore
drwxr-xr-x   8 shitty.author  staff   256 Sep  4 09:35 .idea
drwxr-xr-x   6 shitty.author  staff   192 Mar  4  2024 .venv
-rw-r--r--   1 shitty.author  staff    72 Mar  1  2024 .yamllint
-rw-r--r--   1 shitty.author  staff  1079 Mar  1  2024 LICENSE.md
-rw-r--r--@  1 shitty.author  staff  1796 Sep  4 08:39 README.md
-rw-r--r--   1 shitty.author  staff   381 Mar  1  2024 ansible.cfg
-rw-r--r--   1 shitty.author  staff    65 Mar  1  2024 inventory.yml
-rw-r--r--@  1 shitty.author  staff  1450 Sep  4 09:06 main.yml
drwxr-xr-x  16 shitty.author  staff   512 Aug 12 09:32 roles
-rw-------@  1 shitty.author  staff   484 Sep  4 09:01 vault.yml
</pre></div>


<p>If you open vault.yml, the content should look gibberish.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
$ cat vault.yml                                                                                  ✔
$ANSIBLE_VAULT;1.1;AES256
62343635616463643935613965336336323366653565646137616238663266633936363463611364
6139353639666163323066653733663763323236663361380a646264623465616461646637315661
34396438316137383130366330313431653336396435656562356430333762373866663234383230
6265656164346531660a626431383230326664393839316131626330353562363164313439661863
31343363323236333531303139396662386531626165663732386233626538646338333133375936
6535626465393233386634393934623438393535626365313132
</pre></div>


<p>In the Ansible playbook, specify vault.yml under <strong>vars_files</strong>.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: yaml; highlight: [4,5]; title: ; notranslate">
---
- name: My Playbook
  hosts: all
  vars_files:
    - vault.yml
  roles:
    - ...
</pre></div>


<p>Finally, run the Ansible playbook with the <strong>&#8211;ask-vault-pass</strong> option, where you will be prompted for the vault password once before Ansible executes the playbook.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
ansible-playbook main.yml --ask-vault-pass
</pre></div>


<p>Now, you will not be prompted for a sudo password every time Homebrew-related tasks run.</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/09/04/ansible-handling-sudo-password-with-homebrew/">Ansible: Handling Sudo Password with Homebrew</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myshittycode.com/2024/09/04/ansible-handling-sudo-password-with-homebrew/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2665</post-id>	</item>
		<item>
		<title>NordVPN: Extracting WireGuard Configuration</title>
		<link>https://myshittycode.com/2024/06/08/nordvpn-extracting-wireguard-configuration/</link>
					<comments>https://myshittycode.com/2024/06/08/nordvpn-extracting-wireguard-configuration/#respond</comments>
		
		<dc:creator><![CDATA[Shitty Author]]></dc:creator>
		<pubDate>Sat, 08 Jun 2024 14:59:51 +0000</pubDate>
				<category><![CDATA[Development Tools]]></category>
		<category><![CDATA[Server]]></category>
		<category><![CDATA[GL.iNet]]></category>
		<category><![CDATA[NordVPN]]></category>
		<category><![CDATA[Travel Router]]></category>
		<category><![CDATA[VPN]]></category>
		<category><![CDATA[WireGuard]]></category>
		<guid isPermaLink="false">https://myshittycode.com/?p=2604</guid>

					<description><![CDATA[<p>This article shows how to extract the WireGuard configuration from NordVPN without additional tools and test it by configuring WireGuard on a GL.iNet travel router (ex: Beryl AX). Why WireGuard? While most modern routers support OpenVPN and WireGuard protocols, the latter is faster and more efficient when traveling through the encrypted tunnels, providing a superior [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/06/08/nordvpn-extracting-wireguard-configuration/">NordVPN: Extracting WireGuard Configuration</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>This article shows how to extract the WireGuard configuration from NordVPN without additional tools and test it by configuring WireGuard on a GL.iNet travel router (ex: Beryl AX).</p>



<div class="wp-block-rank-math-toc-block" id="rank-math-toc"><h2>Table of Contents</h2><nav><ul><li><a href="#why-wire-guard">Why WireGuard?</a></li><li><a href="#why-this-extra-step-when-using-nord-vpn">Why This Extra Step When Using NordVPN?</a></li><li><a href="#prerequisites">Prerequisites</a></li><li><a href="#step-1-generate-access-token-in-nord-vpn">Step 1: Generate Access Token in NordVPN</a></li><li><a href="#step-2-use-nord-vpn-ap-is-to-extract-wire-guard-configuration">Step 2: Use NordVPN APIs to Extract WireGuard Configuration</a></li><li><a href="#step-3-configure-wire-guard-on-router">Step 3: Configure WireGuard on Router</a></li></ul></nav></div>



<h2 class="wp-block-heading" id="why-wire-guard">Why WireGuard?</h2>



<p>While most modern routers support OpenVPN and WireGuard protocols, the latter is faster and more efficient when traveling through the encrypted tunnels, providing a superior VPN experience.</p>



<h2 class="wp-block-heading" id="why-this-extra-step-when-using-nord-vpn">Why This Extra Step When Using NordVPN?</h2>



<p>Unlike other VPN providers, NordVPN builds its proprietary solution, NordLynx, on WireGuard. Thus, it is not possible to configure it directly on your router unless you want to rely on the slower OpenVPN.</p>



<h2 class="wp-block-heading" id="prerequisites">Prerequisites</h2>



<ul class="wp-block-list">
<li>A NordVPN customer.</li>



<li>A router that supports WireGuard VPN protocol.</li>



<li>An environment to run Linux commands. Install <a href="https://jqlang.github.io/jq/download/" target="_blank" rel="noopener">jq</a> if it doesn&#8217;t exist.</li>



<li>Expert in CMD/CTRL+C and CMD/CTRL+V.</li>
</ul>



<h2 class="wp-block-heading" id="step-1-generate-access-token-in-nord-vpn">Step 1: Generate Access Token in NordVPN</h2>



<ul class="wp-block-list">
<li>Go to <a href="https://my.nordaccount.com/dashboard/nordvpn/manual-configuration/" target="_blank" rel="noreferrer noopener">this NordVPN link</a>.</li>



<li>Click on the <strong>Set up NordVPN manually</strong> button<strong>.</strong></li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="610" height="296" src="https://myshittycode.com/wp-content/uploads/2024/06/1-nordvpn-manual-setup.png?x45560" alt="" class="wp-image-2605" srcset="https://myshittycode.com/wp-content/uploads/2024/06/1-nordvpn-manual-setup.png 610w, https://myshittycode.com/wp-content/uploads/2024/06/1-nordvpn-manual-setup-300x146.png 300w" sizes="auto, (max-width: 610px) 100vw, 610px" /></figure>



<ul class="wp-block-list">
<li>After completing the email verification, you will land on a page that allows you to generate an access token. Click on the <strong>Generate new token</strong> button.</li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="605" height="542" src="https://myshittycode.com/wp-content/uploads/2024/06/2-nordvpn-generate-new-token.png?x45560" alt="" class="wp-image-2606" srcset="https://myshittycode.com/wp-content/uploads/2024/06/2-nordvpn-generate-new-token.png 605w, https://myshittycode.com/wp-content/uploads/2024/06/2-nordvpn-generate-new-token-300x269.png 300w" sizes="auto, (max-width: 605px) 100vw, 605px" /></figure>



<ul class="wp-block-list">
<li>Leave the token expiration as is. Click on the <strong>Generate token</strong> button.</li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="682" height="329" src="https://myshittycode.com/wp-content/uploads/2024/06/3-nordvpn-generate-token.png?x45560" alt="" class="wp-image-2608" srcset="https://myshittycode.com/wp-content/uploads/2024/06/3-nordvpn-generate-token.png 682w, https://myshittycode.com/wp-content/uploads/2024/06/3-nordvpn-generate-token-300x145.png 300w" sizes="auto, (max-width: 682px) 100vw, 682px" /></figure>



<ul class="wp-block-list">
<li>Copy the access token to a text file and close the pop-up dialog.</li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="680" height="338" src="https://myshittycode.com/wp-content/uploads/2024/06/4-nordvpn-copy-access-token.png?x45560" alt="" class="wp-image-2609" srcset="https://myshittycode.com/wp-content/uploads/2024/06/4-nordvpn-copy-access-token.png 680w, https://myshittycode.com/wp-content/uploads/2024/06/4-nordvpn-copy-access-token-300x149.png 300w" sizes="auto, (max-width: 680px) 100vw, 680px" /></figure>



<h2 class="wp-block-heading" id="step-2-use-nord-vpn-ap-is-to-extract-wire-guard-configuration">Step 2: Use NordVPN APIs to Extract WireGuard Configuration</h2>



<p>Fortunately, NordVPN provides a helpful Rest API that returns a list of recommended servers based on your current location. We can use this to query for a list of WireGuard-compatible servers.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
#!/usr/bin/env bash

ACCESS_TOKEN=&quot;&#x5B;YOUR-ACCESS-TOKEN]&quot;
TOTAL_CONFIGS=3
DNS=&quot;1.1.1.1&quot;

CREDENTIALS_URL=&quot;https://api.nordvpn.com/v1/users/services/credentials&quot;
SERVER_RECOMMENDATIONS_URL=&quot;https://api.nordvpn.com/v1/servers/recommendations?&amp;filters\&#x5B;servers_technologies\]\&#x5B;identifier\]=wireguard_udp&amp;limit=$TOTAL_CONFIGS&quot;

PRIVATE_KEY=$(curl -s -u token:&quot;$ACCESS_TOKEN&quot; &quot;$CREDENTIALS_URL&quot; | jq -r .nordlynx_private_key)

curl -s &quot;$SERVER_RECOMMENDATIONS_URL&quot; | \
  jq -r --arg private_key &quot;$PRIVATE_KEY&quot; --arg dns &quot;$DNS&quot; &#039;
    .&#x5B;] |
    {
      filename: (.locations&#x5B;0].country.name + &quot; - &quot; + .locations&#x5B;0].country.city.name + &quot; - &quot; + .hostname + &quot;.conf&quot;),
      ip: .station,
      publicKey: (.technologies | .&#x5B;] | select(.identifier == &quot;wireguard_udp&quot;) | .metadata | .&#x5B;] | .value)
    } |
    {
      filename: .filename,
      config: &#x5B;
        &quot;# &quot; + .filename,
        &quot;&quot;,
        &quot;&#x5B;Interface]&quot;,
        &quot;PrivateKey = \($private_key)&quot;,
        &quot;Address = 10.5.0.2/32&quot;,
        &quot;DNS = \($dns)&quot;,
        &quot;&quot;,
        &quot;&#x5B;Peer]&quot;,
        &quot;PublicKey = &quot; + .publicKey,
        &quot;AllowedIPs = 0.0.0.0/0, ::/0&quot;,
        &quot;Endpoint = &quot; + .ip + &quot;:51820&quot;
      ] | join(&quot;\n&quot;)
    } |
    &quot;echo \&quot;&quot; + .config + &quot;\&quot; &gt; \&quot;&quot; + .filename + &quot;\&quot;&quot;
  &#039; | sh
</pre></div>


<p>Required Changes:</p>



<ul class="wp-block-list">
<li><strong>Line 3</strong>: Replace <strong>[YOUR-ACCESS-TOKEN]</strong> with the access token you have just copied.</li>
</ul>



<p>Optional Changes:</p>



<ul class="wp-block-list">
<li><strong>Line 4</strong>: By default, this script generates 3 different WireGuard config files based on your location. If one of the servers is oversaturated, you can point to a different server next time without rerunning the script.</li>



<li><strong>Line 5</strong>: Currently, I use CloudFlare DNS (1.1.1.1) since it has the fastest response time. However, you can update it to point to your favorite DNS server, for example, Google&#8217;s 8.8.8.8.</li>
</ul>



<p>Other Helpful Explanations:</p>



<ul class="wp-block-list">
<li><strong>Line 10</strong>: Retrieve your private key.</li>



<li><strong>Line 12</strong>: Retrieve the recommended WireGuard-compatible servers based on your current location and generate the WireGuard config files.</li>
</ul>



<p>Suppose you plan to travel to a different location with your travel router. In that case, typically, you want to pre-configure your travel router with the nearest WireGuard servers based on your destinations before departing. To do this, you can choose a desired location in the NordVPN software installed on your current machine before rerunning this script. For example, you might pick Finland because you fancy eating the delicious Kaalikääryleet that you can&#8217;t pronounce. Still, you want to do it safely before Instagramming it in real-time during your long weekend.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="284" height="827" src="https://myshittycode.com/wp-content/uploads/2024/06/5-nordvpn-destination-server.png?x45560" alt="" class="wp-image-2613" srcset="https://myshittycode.com/wp-content/uploads/2024/06/5-nordvpn-destination-server.png 284w, https://myshittycode.com/wp-content/uploads/2024/06/5-nordvpn-destination-server-103x300.png 103w" sizes="auto, (max-width: 284px) 100vw, 284px" /></figure>



<p>After running the script, you should see 3 WireGuard configuration files created.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; highlight: [4,5,6]; title: ; notranslate">
$ ls -a | cat                                                                                ✔ 
.
..
Finland - Helsinki - fi183.nordvpn.com.conf
Finland - Helsinki - fi195.nordvpn.com.conf
Finland - Helsinki - fi198.nordvpn.com.conf
nordvpn-wireguard.sh
</pre></div>


<h2 class="wp-block-heading" id="step-3-configure-wire-guard-on-router">Step 3: Configure WireGuard on Router</h2>



<p>The following instructions apply to the GL.iNet travel routers, in my case, Beryl AX (GL-MT3000). Follow your router&#8217;s instructions as needed.</p>



<p><strong>IMPORTANT:</strong> Before proceeding, ensure you have disabled the VPN from your machine so that it relies on the VPN configured on the travel router based on the steps below.</p>



<ul class="wp-block-list">
<li>Change your SSID to point to your travel router.</li>



<li>Go to <a href="http://192.168.8.1/">http://192.168.8.1/</a></li>



<li>Log into the Admin Panel.</li>



<li>Go to <strong>VPN</strong> > <strong>WireGuard Client</strong>.</li>
</ul>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="553" src="https://myshittycode.com/wp-content/uploads/2024/06/6-router-wireguard-client-1024x553.png?x45560" alt="" class="wp-image-2625" srcset="https://myshittycode.com/wp-content/uploads/2024/06/6-router-wireguard-client-1024x553.png 1024w, https://myshittycode.com/wp-content/uploads/2024/06/6-router-wireguard-client-300x162.png 300w, https://myshittycode.com/wp-content/uploads/2024/06/6-router-wireguard-client-768x415.png 768w, https://myshittycode.com/wp-content/uploads/2024/06/6-router-wireguard-client.png 1254w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<ul class="wp-block-list">
<li>Upload the WireGuard configuration files. Rename the <strong>New Provider</strong> group to <strong>NordVPN</strong> to make it more meaningful.</li>
</ul>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="553" src="https://myshittycode.com/wp-content/uploads/2024/06/7-router-wireguard-config-1024x553.png?x45560" alt="" class="wp-image-2626" srcset="https://myshittycode.com/wp-content/uploads/2024/06/7-router-wireguard-config-1024x553.png 1024w, https://myshittycode.com/wp-content/uploads/2024/06/7-router-wireguard-config-300x162.png 300w, https://myshittycode.com/wp-content/uploads/2024/06/7-router-wireguard-config-768x415.png 768w, https://myshittycode.com/wp-content/uploads/2024/06/7-router-wireguard-config.png 1249w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<ul class="wp-block-list">
<li>Pick a server and click <strong>Start</strong>.</li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="650" height="345" src="https://myshittycode.com/wp-content/uploads/2024/06/8-router-start-server.png?x45560" alt="" class="wp-image-2627" srcset="https://myshittycode.com/wp-content/uploads/2024/06/8-router-start-server.png 650w, https://myshittycode.com/wp-content/uploads/2024/06/8-router-start-server-300x159.png 300w" sizes="auto, (max-width: 650px) 100vw, 650px" /></figure>



<ul class="wp-block-list">
<li>After a few seconds, the icon should change from orange to green.</li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="647" height="340" src="https://myshittycode.com/wp-content/uploads/2024/06/9-router-successful-connection.png?x45560" alt="" class="wp-image-2628" srcset="https://myshittycode.com/wp-content/uploads/2024/06/9-router-successful-connection.png 647w, https://myshittycode.com/wp-content/uploads/2024/06/9-router-successful-connection-300x158.png 300w" sizes="auto, (max-width: 647px) 100vw, 647px" /></figure>



<ul class="wp-block-list">
<li>Go to <a href="https://whatismyipaddress.com/" target="_blank" rel="noopener">https://whatismyipaddress.com/</a> to verify your IP. The IP should point to your VPN server&#8217;s location.</li>
</ul>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="626" src="https://myshittycode.com/wp-content/uploads/2024/06/10-whats-my-ip-1024x626.png?x45560" alt="" class="wp-image-2629" srcset="https://myshittycode.com/wp-content/uploads/2024/06/10-whats-my-ip-1024x626.png 1024w, https://myshittycode.com/wp-content/uploads/2024/06/10-whats-my-ip-300x183.png 300w, https://myshittycode.com/wp-content/uploads/2024/06/10-whats-my-ip-768x469.png 768w, https://myshittycode.com/wp-content/uploads/2024/06/10-whats-my-ip.png 1227w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<ul class="wp-block-list">
<li>Enjoy your <a href="https://www.google.com/search?sca_esv=b1ced915f33cbde0&amp;sca_upv=1&amp;q=Kaalik%C3%A4%C3%A4ryleet&amp;uds=ADvngMiIMiMH9LyyITANaU-tP7Tx_LoyDqWcuxLdAWjbiiPItTQIMJGqCilQ2P9bV1Dv6O-7T2R2WCCQCUAhxHSNZBXI7F-P9Lgqb8hx22E96rmdQ6wSH_7tq3REoY56wvh7NG60WLKZI4VCaaPBp1i0LfZE9RFaY_CmVZT2gnwKxtj0GgYDgVbx3eDESic00sPypgG1A0AVu4aWss3lmCsEJl7wi5kFTFiN1r0XgRykkdXUCaukfrxB5iO82Ulrno5Sy3CnaRDSnoeot-F_lu-Hbt21Rh6sTXTGTzkA3GbutH2O6ROk5Jv_DeyhhPz34u0R0kL3NOvaf1_plkXKARr8IzQdJR8YuA&amp;udm=2&amp;prmd=ivsnmbtz&amp;sa=X&amp;ved=2ahUKEwjmscz8ocyGAxUNJNAFHT8YGMYQtKgLegQIDBAB&amp;biw=1673&amp;bih=1399&amp;dpr=1" target="_blank" rel="noopener">Kaalikääryleet</a>!</li>
</ul>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/06/08/nordvpn-extracting-wireguard-configuration/">NordVPN: Extracting WireGuard Configuration</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myshittycode.com/2024/06/08/nordvpn-extracting-wireguard-configuration/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2604</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 
Lazy Loading (feed)
Database Caching using Disk

Served from: myshittycode.com @ 2026-02-21 11:48:29 by W3 Total Cache
-->