Advertise with Googlier.comGrainThoughts of a Software Developer
https://blog.jvtrigueros.com/
Sun, 11 Feb 2024 23:50:38 +0000Sun, 11 Feb 2024 23:50:38 +0000Jekyll v4.3.3DietPi + Gitea: SQLite and CIFS Mount Installation Gotchas<p>Recently, I discussed with a friend how they organize their docker-compose stacks into
their personal home lab.They told me they used <a href="https://docs.portainer.io/">Portainer</a>
to manage all their containers. So I decided to give it a go.</p>
<p>At first glance, it seemed similar to what I already had: a <code class="language-plaintext highlighter-rouge">tmux</code> session, with each
<code class="language-plaintext highlighter-rouge">docker-compose</code> stack in its pane. But then I saw that you can setup a Portainer stack
by pointing it to a Git repository. Now this is more appealing! Doing this allows the
following:</p>
<ul>
<li>Version controlled <code class="language-plaintext highlighter-rouge">docker-compose.yaml</code> files</li>
<li>Trigger stack update via the UI or webhook</li>
<li>Environment variables via the UI</li>
</ul>
<p>I was sold!</p>
<p>I thought all I needed to do was install Portainer and Gitea, then set up some webhooks.</p>
<h2 id="planning">Planning</h2>
<p>My home lab setup is simple: an old laptop with Ubuntu and a Raspberry Pi with DietPi.
I run all my Docker workloads on the laptop and lightweight web applications on the Raspberry Pi.
Both machines do not have a lot of storage, so files are store on my NAS via CIFS mounts. These mounts
are accessed directly or mounted as volumes for Docker containers.</p>
<p>The plan was simple:</p>
<ul>
<li>DietPi
<ul>
<li>Install <a href="https://dietpi.com/docs/software/programming/#portainer">Portainer</a> and connect to Ubuntu’s <code class="language-plaintext highlighter-rouge">dockerd</code> via TCP</li>
<li>Install <a href="https://dietpi.com/docs/software/cloud/#gitea">Gitea</a> and create repos for each stacks</li>
<li>Create webhooks on Portainer and trigger them from Gitea after each push</li>
</ul>
</li>
<li>Ubuntu
<ul>
<li>Keep everything the same, except create git repos for each stack and publish to Gitea</li>
</ul>
</li>
</ul>
<h2 id="execution">Execution</h2>
<p>As expected, things didn’t go according to plan. DietPi makes it super simple
to install software, but when you need to deviate…it is HELL!</p>
<h2 id="problems-and-solutions">Problems and Solutions</h2>
<blockquote>
<p>Without a doubt, following the default instructions on the DietPi website will get Gitea up and running in under 5 minutes.</p>
</blockquote>
<p>To be clear, we’re installing Gitea on a Raspberry Pi running the DietPi distribution.
We will be using SQLite as our database and storing all of Gitea’s files on a CIFS mount.</p>
<p>If you’re following this as a guide, now is the time to install Gitea using <code class="language-plaintext highlighter-rouge">dietpi-software</code>.
But hold off on anything else; we’ll need to tweak a few things.</p>
<p>Here’s the list of changes that were required to make this all work.</p>
<h3 id="setup-gitea-user">Setup <code class="language-plaintext highlighter-rouge">gitea</code> user</h3>
<p>The <code class="language-plaintext highlighter-rouge">gitea</code> is setup without shell access, but it needs it ¯\<em>(ツ)</em>/¯.</p>
<p>To solve this, execute the command:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>useradd <span class="nt">-s</span> <span class="k">${</span><span class="nv">SHELL</span><span class="k">}</span> gitea
</code></pre></div></div>
<p>You may encounter LC_LANG warnings. To address these, run:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>locale-gen
Generating locales <span class="o">(</span>this might take a <span class="k">while</span><span class="o">)</span>...
en_US.UTF-8... <span class="k">done
</span>Generation complete.
</code></pre></div></div>
<h3 id="cifs-mount-permissions">CIFS Mount Permissions</h3>
<p>This change will allow Gitea to create directories in our CIFS mount.</p>
<p>DietPi includes a useful tool for mounting external drives, named <code class="language-plaintext highlighter-rouge">dietpi-drive_manager</code>.
Use it to connect your network drive.</p>
<p><img src="/assets/dietpi-drive-manager.png" alt="dietpi-drive-manager" /></p>
<p>DietPi uses <code class="language-plaintext highlighter-rouge">systemd</code> to manage mounts, with <code class="language-plaintext highlighter-rouge">/etc/fstab</code> serving as the key reference.</p>
<p>Our initial step is to assign ownership of the CIFS mount to the <code class="language-plaintext highlighter-rouge">gitea</code> user.
This is done by setting the <code class="language-plaintext highlighter-rouge">uid</code> option to <code class="language-plaintext highlighter-rouge">gitea</code>.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#----------------------------------------------------------------</span>
<span class="c"># NETWORK</span>
<span class="c">#----------------------------------------------------------------</span>
//192.168.1.33/git-hosting /mnt/git-hosting cifs <span class="nv">cred</span><span class="o">=</span>/var/lib/dietpi/dietpi-drive_manager/mnt-git-hosting.cred,iocharset<span class="o">=</span>utf8,uid<span class="o">=</span>gitea,gid<span class="o">=</span>dietpi,file_mode<span class="o">=</span>0770,dir_mode<span class="o">=</span>0770,vers<span class="o">=</span>2.1,nofail,noauto,x-systemd.automount
</code></pre></div></div>
<p>To play it safe, let’s keep the <code class="language-plaintext highlighter-rouge">gid</code> as <code class="language-plaintext highlighter-rouge">dietpi</code>. This precaution ensures that other
processes running under <code class="language-plaintext highlighter-rouge">dietpi</code> can still modify the directory if needed.</p>
<p><strong>Remember to run <code class="language-plaintext highlighter-rouge">systemctl daemon-reload</code> whenever you change <code class="language-plaintext highlighter-rouge">fstab</code>.</strong></p>
<p>To check if it worked, look at the directory’s owner with:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">ls</span> <span class="nt">-lah</span> /mnt/git-hosting
total 4.0K
drwxrwx--- 2 gitea dietpi 0 Feb 9 03:53 <span class="nb">.</span>
drwxr-xr-x 7 root root 4.0K Feb 9 04:27 ..
drwxrwx--- 2 gitea dietpi 0 Feb 9 03:53 @Recycle
drwxrwx--- 2 gitea dietpi 0 Feb 9 03:56 gitea
</code></pre></div></div>
<h3 id="sqlite-on-a-cifs-mount">SQLite on a CIFS mount</h3>
<p>Using SQLite on a CIFS mount is optional. You might prefer to use the MySQL database
that DietPi recommends and comes pre-installed.</p>
<p>If you place the database in a CIFS mount, you’ll get errors about the database being locked.
This is a known issue and the workaround is to mount the CIFS mount with the <code class="language-plaintext highlighter-rouge">nobrl</code> option.</p>
<blockquote>
<p>Don’t use <code class="language-plaintext highlighter-rouge">nobrl</code> as the default, this option prevents file locking. Multiple users modifying the same files could lead to corruption.</p>
</blockquote>
<p>To enable this, let’s modify <code class="language-plaintext highlighter-rouge">/etc/fstab</code> by adding this option. Follow the same steps above, now my entry looks like so:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#----------------------------------------------------------------</span>
<span class="c"># NETWORK</span>
<span class="c">#----------------------------------------------------------------</span>
//192.168.1.33/git-hosting /mnt/git-hosting cifs <span class="nv">cred</span><span class="o">=</span>/var/lib/dietpi/dietpi-drive_manager/mnt-git-hosting.cred,iocharset<span class="o">=</span>utf8,uid<span class="o">=</span>gitea,gid<span class="o">=</span>dietpi,file_mode<span class="o">=</span>0770,dir_mode<span class="o">=</span>0770,vers<span class="o">=</span>2.1,nofail,noauto,nobrl,x-systemd.automount
</code></pre></div></div>
<p>Lastly, run <code class="language-plaintext highlighter-rouge">systemctl daemon-reload</code> to save changes.</p>
<p>For more context, check out <a href="https://lchenghui.com/nobrl-for-mount-cifs">Lee Cheng Hui’s</a> blog post.</p>
<h3 id="dropbear-ssh-server">Dropbear SSH Server</h3>
<p>Although DietPi recommends Dropbear for SSH, it doesn’t seem compatible with Gitea’s SSH logins.</p>
<p>To fix this, run <code class="language-plaintext highlighter-rouge">dietpi-software</code> > SSH Server > OpenSSH Server.</p>
<p><img src="/assets/dietpi-ssh-server.png" alt="dietpi-ssh-server" /></p>
<p><img src="/assets/dietpi-openssh-server.png" alt="dietpi-openssh-server" /></p>
<p>Although OpenSSH Server is noted to use more resources, it was necessary for my setup to function correctly.</p>
<h3 id="access-local-ports-from-portainer">Access Local Ports from Portainer</h3>
<p>After installing Portainer on DietPi with <code class="language-plaintext highlighter-rouge">dietpi-software</code>, I encountered an issue:
Portainer couldn’t access the Gitea server on the same machine.</p>
<p>Gitea is running directly on the Raspberry Pi, while Portainer is running in a Docker container.
Here’s Gitea’s <a href="https://github.com/MichaIng/DietPi/blob/6a4b6e0486ffe363f6f75fda6f6d3718ac6aa627/dietpi/dietpi-software#L11784-L11796">Portainer</a>
install script. Portainer performs basic checks, essentially boiling down to a
Docker run command flagged with <code class="language-plaintext highlighter-rouge">restart=always</code>.</p>
<p>We can choose to forgo DietPi’s version and run this ourselves or modify the <code class="language-plaintext highlighter-rouge">dietpi-software</code> script.</p>
<p>I chose to modify.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vim /boot/dietpi/dietpi-software
</code></pre></div></div>
<p>Whichever your choice is, you need to update the <code class="language-plaintext highlighter-rouge">docker run</code> command by adding <code class="language-plaintext highlighter-rouge">--add-host=host.docker.internal:host-gateway</code>.
This will allow processes running inside Docker containers to access hosts’ processes via <code class="language-plaintext highlighter-rouge">host.docker.internal</code> instead of <code class="language-plaintext highlighter-rouge">localhost</code>.</p>
<p>My command now looks like this:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">G_EXEC_OUTPUT</span><span class="o">=</span>1 G_EXEC docker run <span class="nt">-d</span> <span class="nt">-p</span> <span class="s1">'9002:9000'</span> <span class="nt">--add-host</span><span class="o">=</span>host.docker.internal:host-gateway <span class="nt">--name</span><span class="o">=</span>portainer <span class="nt">--restart</span><span class="o">=</span>always <span class="nt">-v</span> <span class="s1">'/run/docker.sock:/var/run/docker.sock'</span> <span class="nt">-v</span> <span class="s1">'portainer_data:/data'</span> <span class="s1">'portainer/portainer-ce'</span>
</code></pre></div></div>
<p>Again, you can simply run this command without modifying the script.</p>
<h3 id="completing-gitea-setup">Completing Gitea Setup</h3>
<p>We haven’t finished setting up Gitea yet. Please visit the Gitea URL on port 3000
to complete the process. You can now select SQLite as the database and opt to store
all of Gitea’s data on the CIFS mount.</p>
<p>You will need to create a user, which will become the administrator of the Gitea instance.
Create a repository with a <code class="language-plaintext highlighter-rouge">docker-compose.yaml</code> and you should be good to go!</p>
<h3 id="webhooks-on-portainer-and-gitea">Webhooks on Portainer and Gitea</h3>
<p>With a Gitea repository ready, you can add a stack as follows:</p>
<p><img src="/assets/portainer-git-repo.png" alt="portainer-git-repository" /></p>
<p>Then enable GitOps, to reveal the webhook link.</p>
<p><img src="/assets/portainer-webhook.png" alt="portainer-webhook" /></p>
<p>The webhook url may have a host using your <code class="language-plaintext highlighter-rouge">.local</code> address. This didn’t work for me,
so I replaced that with <code class="language-plaintext highlighter-rouge">127.0.0.1</code> when adding it on Gitea’s webhook settings.</p>
<p><img src="/assets/gitea-webhook.png" alt="gitea-webhook" /></p>
<h2 id="conclusion">Conclusion</h2>
<p>After applying all these changes, we can now commit changes to <code class="language-plaintext highlighter-rouge">docker-compose.yaml</code> files
push them to Gitea and have it trigger a stack reload if there were any changes.</p>
<p>Discovering all these issues set me back a whole afternoon, if this helps at least one person
it was time well spent :D</p>
Sun, 11 Feb 2024 23:44:00 +0000
https://blog.jvtrigueros.com/2024/02/11/dietpi-gitea-sqlite-cifs/
https://blog.jvtrigueros.com/2024/02/11/dietpi-gitea-sqlite-cifs/dietpi,gitea,sqlite,self-hosted,homelabSnap Docker Read-Only Error<p>I repurposed a laptop with a broken display as an Ubuntu server on my local network. I did the installation via the Ubuntu live USB wizard and opted to have it install Docker. I didn’t realize that this would install it via <a href="https://snapcraft.io">snap</a>.</p>
<p>Why is this a problem?</p>
<blockquote>
<p>Honestly, it wasn’t.</p>
</blockquote>
<p>Until I attempted to mount a volume bound to a directory under <code class="language-plaintext highlighter-rouge">/opt/</code>. I kept getting this error:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker: Error response from daemon: error while creating mount source path '/cache': mkdir /cache: read-only file system.
</code></pre></div></div>
<p>I tried the following:</p>
<ul>
<li>Create the directory with my <code class="language-plaintext highlighter-rouge">$USER</code> and as <code class="language-plaintext highlighter-rouge">root</code></li>
<li>Give the directory <code class="language-plaintext highlighter-rouge">777</code> access permissions</li>
<li>Verified that there was enough space in the partition</li>
</ul>
<p><em>Nothing worked.</em></p>
<h2 id="solution">Solution</h2>
<p>Completely remove the Docker snap</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo snap stop docker
sudo snap remove docker
</code></pre></div></div>
<p>Then install Docker using the <a href="https://docs.docker.com/engine/install/ubuntu/">official</a> installation instructions.</p>
Fri, 09 Dec 2022 01:30:00 +0000
https://blog.jvtrigueros.com/2022/12/09/snap-docker-read-only/
https://blog.jvtrigueros.com/2022/12/09/snap-docker-read-only/Change Keyboard Layout on WSL2 for X11 Apps<p>For better or worse (worse really), I use the <a href="https://en.wikipedia.org/wiki/Dvorak_keyboard_layout">dvorak</a> keyboard layout. Typically, I manage the layout using the Operating System, which means my physical keyboard is a normal qwerty layout keyboard and it changes to dvorak via software. However, I recently started using an <a href="https://ergodox-ez.com/">ErgoDox EZ</a> and chose to flash the keyboard’s firmware to map physical keyboard to the dvorak layout. I can now plug in this keyboard on any machine, and, without change the OS settings, the keyboard will be in dvorak as expected. NICE!</p>
<p>This worked well up until I tried using Emacs via X11 on WSL2. Ubuntu set my keyboard layout as dvorak (<em>probably something I set up and forgot about it</em>), so when using the ErgoDox, none of the keys mapped to the correct value.</p>
<h2 id="solution">Solution</h2>
<p>To solve this, install X11 XKB utilities and use <code class="language-plaintext highlighter-rouge">setxkbmap</code> to manually change the keyboard layout:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>x11-xkb-utils
<span class="c"># Change the layout to qwerty</span>
setxkbmap us
<span class="c"># Change the layout back to dvorak</span>
setxkbmap us <span class="nt">-variant</span> dvorak
</code></pre></div></div>
<p>I use <a href="https://x410.dev">x410</a> to manage the X11 display server, highly recommended. They also have a <a href="https://x410.dev/cookbook/keyboard-layout/">cookbook</a> on managing your keyboard layout.</p>
Fri, 08 Jul 2022 16:48:00 +0000
https://blog.jvtrigueros.com/2022/07/08/change-keyboard-layout-wsl2-x11/
https://blog.jvtrigueros.com/2022/07/08/change-keyboard-layout-wsl2-x11/Migrate H2 from 1.x.y to 2.x.y<p>If you have seen the following error:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[HY000][50000] General error: "The write format 1 is smaller than the supported format 2 [2.1.210/5]" [50000-210]
The write format 1 is smaller than the supported format 2 [2.1.210/5].
</code></pre></div></div>
<p>You probably upgraded your H2 dependency from 1.x.y to 2.x.y due to a suggestion from <a href="https://github.com/dependabot">Dependabot</a>, like this one:</p>
<p><img src="/assets/2022-02-28-h2-dependabot-alerts.png" alt="dependabot alerts" /></p>
<p>While I’m glad these alerts exist, there’s little information on how to update your existing H2 database.</p>
<h2 id="solution">Solution</h2>
<p>I’ve gone about this by doing the following steps:</p>
<ul>
<li>Create a backup of the v1 H2 database</li>
<li>Create a new database v2 H2 database with that backup</li>
</ul>
<h3 id="download-h2-lib">Download H2 Lib</h3>
<p>The tricky part is that you must use the correct library version for this to work out, but otherwise it’s a straightforward process. Let’s begin by obtaining the H2 jar from Maven Central, we’ll need both v1 and v2:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Download v1</span>
curl <span class="nt">-Lo</span> h2-v1.jar https://search.maven.org/remotecontent<span class="se">\?</span>filepath<span class="se">\=</span>com/h2database/h2/1.4.200/h2-1.4.200.jar
<span class="c"># Download v2</span>
curl <span class="nt">-Lo</span> h2-v2.jar https://search.maven.org/remotecontent<span class="se">\?</span>filepath<span class="se">\=</span>com/h2database/h2/2.1.210/h2-2.1.210.jar
</code></pre></div></div>
<h3 id="create-backup-sql-script">Create Backup SQL Script</h3>
<blockquote>
<p>Please create a backup of your current H2 database at this time, it would be a shame if it got deleted.</p>
</blockquote>
<p>The H2 library contains a tool called <a href="https://h2database.com/javadoc/org/h2/tools/Script.html">Script</a> that we will use to generate the <code class="language-plaintext highlighter-rouge">backup.sql</code> script:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># For a set of all possible options, run with -help</span>
java <span class="nt">-cp</span> h2-v1.jar org.h2.tools.Script <span class="nt">-url</span> <span class="s2">"jdbc:h2:file:path/to/database/h2.db"</span> <span class="nt">-script</span> backup.sql
</code></pre></div></div>
<p>I’d take a quick peek at the <code class="language-plaintext highlighter-rouge">backup.sql</code> file just to make sure the data is there, but otherwise we’re done with this step.</p>
<h3 id="create-new-database-from-sql-script">Create new database from SQL Script</h3>
<p>Just like with the tool above, there’s a tool to run a script called <a href="https://h2database.com/javadoc/org/h2/tools/RunScript.html">RunScript</a> that we will use to create a new database from <code class="language-plaintext highlighter-rouge">backup.sql</code>:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># For a set of all possible options, run with -help</span>
java <span class="nt">-cp</span> h2-v2.jar org.h2.tools.RunScript <span class="nt">-url</span> <span class="s2">"jdbc:h2:file:path/to/database/h2-v2.db"</span> <span class="nt">-script</span> backup.sql
</code></pre></div></div>
<p>This should create a new H2 database as <code class="language-plaintext highlighter-rouge">h2-v2.db</code>. At this point, change your application to use this database, if everything works well, then you can delete the old one and rename the new one.</p>
<p>Enjoy!</p>
Tue, 01 Mar 2022 00:00:00 +0000
https://blog.jvtrigueros.com/2022/03/01/migrate-h2-database/
https://blog.jvtrigueros.com/2022/03/01/migrate-h2-database/Java Localization: Indonesia<p>Recently, I went through the exercise of externalizing the strings on a Discord bot, <a href="https://pawa.im">pawa</a>, so that
I can provide different translations. Since this is a JVM code base, the preferred path is to use a
<a href="https://docs.oracle.com/javase/tutorial/i18n/resbundle/concept.html"><code class="language-plaintext highlighter-rouge">ResourceBundle</code></a>. This would allow me to have
different Properties files for each of the languages that the application would support, and as long as these Properties
files are located in the classpath, the <code class="language-plaintext highlighter-rouge">getBundle</code> method will find the correct one to load.</p>
<p>In a contrived example, this is how it would work:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Default Bundle</span>
<span class="kd">var</span> <span class="py">bundle</span> <span class="p">=</span> <span class="nc">ResourceBundle</span><span class="p">.</span><span class="nf">getBundle</span><span class="p">(</span><span class="s">"strings"</span><span class="p">)</span>
<span class="kd">var</span> <span class="py">greeting</span> <span class="p">=</span> <span class="n">bundle</span><span class="p">.</span><span class="nf">getString</span><span class="p">(</span><span class="s">"greeting"</span><span class="p">)</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"greeting = $greeting"</span><span class="p">)</span>
<span class="c1">//=> greeting = hello</span>
<span class="c1">// Spanish Bundle</span>
<span class="n">bundle</span> <span class="p">=</span> <span class="nc">ResourceBundle</span><span class="p">.</span><span class="nf">getBundle</span><span class="p">(</span><span class="s">"strings"</span><span class="p">,</span> <span class="nc">Locale</span><span class="p">(</span><span class="s">"es"</span><span class="p">))</span>
<span class="n">greeting</span> <span class="p">=</span> <span class="n">bundle</span><span class="p">.</span><span class="nf">getString</span><span class="p">(</span><span class="s">"greeting"</span><span class="p">)</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"greeting = $greeting"</span><span class="p">)</span>
<span class="c1">//=> greeting = hola</span>
<span class="p">}</span>
</code></pre></div></div>
<p>You can use built in locales such as <code class="language-plaintext highlighter-rouge">Locale.CHINESE</code> or you can specify your own as I did in the example above. The only
requirement is that you stick to the <a href="https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes">ISO-639</a> language codes. While
the Locale constructor will accept 2-letter codes, it’s best to use the 3-letter code as it’s less ambiguous.</p>
<h3 id="incorrect-indonesian-language-code">Incorrect Indonesian Language Code</h3>
<p>When I was adding support for the Indonesian language, I made the mistake of using the language code <code class="language-plaintext highlighter-rouge">id</code> code. I structured
my resources as follows:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ exa -T src/main/resources
src/main/resources
├── strings.properties
├── strings_es.properties
└── strings_id.properties
</code></pre></div></div>
<blockquote>
<p>I did that because if you go to the <a href="https://en.wikipedia.org/wiki/Indonesian_language">Indonesian language</a> wikipedia page, it that the code is <code class="language-plaintext highlighter-rouge">id</code>.</p>
</blockquote>
<p>And this is the code snippet with the result:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// Indonesian Bundle</span>
<span class="n">bundle</span> <span class="p">=</span> <span class="nc">ResourceBundle</span><span class="p">.</span><span class="nf">getBundle</span><span class="p">(</span><span class="s">"strings"</span><span class="p">,</span> <span class="nc">Locale</span><span class="p">(</span><span class="s">"id"</span><span class="p">))</span>
<span class="n">greeting</span> <span class="p">=</span> <span class="n">bundle</span><span class="p">.</span><span class="nf">getString</span><span class="p">(</span><span class="s">"greeting"</span><span class="p">)</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"greeting = $greeting"</span><span class="p">)</span>
<span class="c1">//=> greeting = hello</span>
</code></pre></div></div>
<p>🤔 hmmm, the result should’ve been <code class="language-plaintext highlighter-rouge">greeting = halo</code>, however that’s not what I was seeing here.</p>
<p>This was confusing because, the locale object was correctly printing out the right language:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Locale</span><span class="p">(</span><span class="s">"id"</span><span class="p">).</span><span class="n">displayName</span> <span class="c1">//=> Indonesian</span>
</code></pre></div></div>
<p>So what was going on?</p>
<h3 id="read-the-docs">Read the Docs</h3>
<p>In the <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html"><code class="language-plaintext highlighter-rouge">Locale</code></a> documentation there’s a line that talks about deprecated ISO codes:</p>
<blockquote>
<p>Deprecated ISO language codes “iw”, “ji”, and “in” are converted to “he”, “yi”, and “id”, respectively.</p>
</blockquote>
<p>Which appears in the private static method <code class="language-plaintext highlighter-rouge">Locale.convertOldISOCodes</code></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">static</span> <span class="nc">String</span> <span class="nf">convertOldISOCodes</span><span class="o">(</span><span class="nc">String</span> <span class="n">language</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// we accept both the old and the new ISO codes for the languages whose ISO</span>
<span class="c1">// codes have changed, but we always store the OLD code, for backward compatibility</span>
<span class="n">language</span> <span class="o">=</span> <span class="nc">LocaleUtils</span><span class="o">.</span><span class="na">toLowerString</span><span class="o">(</span><span class="n">language</span><span class="o">).</span><span class="na">intern</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">language</span> <span class="o">==</span> <span class="s">"he"</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="s">"iw"</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">language</span> <span class="o">==</span> <span class="s">"yi"</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="s">"ji"</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">language</span> <span class="o">==</span> <span class="s">"id"</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="s">"in"</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">language</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>The confusing part was that I wasn’t specifying <code class="language-plaintext highlighter-rouge">in</code> but rather <code class="language-plaintext highlighter-rouge">id</code>. So something somewhere was over correcting for me, as we can see in the function
above, <code class="language-plaintext highlighter-rouge">id</code> is converted to <code class="language-plaintext highlighter-rouge">in</code>.</p>
<p>So…could I rename the resource <code class="language-plaintext highlighter-rouge">strings_id.properties</code> to <code class="language-plaintext highlighter-rouge">strings_in.properties</code> and call it a day? Let’s try it!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ exa -T src/main/resources
src/main/resources
├── strings.properties
├── strings_es.properties
└── strings_in.properties
</code></pre></div></div>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// Indonesian Bundle</span>
<span class="n">bundle</span> <span class="p">=</span> <span class="nc">ResourceBundle</span><span class="p">.</span><span class="nf">getBundle</span><span class="p">(</span><span class="s">"strings"</span><span class="p">,</span> <span class="nc">Locale</span><span class="p">(</span><span class="s">"id"</span><span class="p">))</span>
<span class="n">greeting</span> <span class="p">=</span> <span class="n">bundle</span><span class="p">.</span><span class="nf">getString</span><span class="p">(</span><span class="s">"greeting"</span><span class="p">)</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"greeting = $greeting"</span><span class="p">)</span>
<span class="c1">//=> greeting = halo</span>
</code></pre></div></div>
<p><strong>AHA!</strong></p>
<h3 id="solution">Solution</h3>
<p>So while that works, it’s misleading and may cause errors if…when I forget why I did that in the first place, so my final solution is to use the 3-letter
code as specified in the ISO-639-2 Standard. Finally landing on this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ exa -T src/main/resources
src/main/resources
├── strings.properties
├── strings_es.properties
└── strings_ind.properties
</code></pre></div></div>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// Indonesian Bundle</span>
<span class="n">bundle</span> <span class="p">=</span> <span class="nc">ResourceBundle</span><span class="p">.</span><span class="nf">getBundle</span><span class="p">(</span><span class="s">"strings"</span><span class="p">,</span> <span class="nc">Locale</span><span class="p">(</span><span class="s">"ind"</span><span class="p">))</span>
<span class="n">greeting</span> <span class="p">=</span> <span class="n">bundle</span><span class="p">.</span><span class="nf">getString</span><span class="p">(</span><span class="s">"greeting"</span><span class="p">)</span>
<span class="nf">println</span><span class="p">(</span><span class="s">"greeting = $greeting"</span><span class="p">)</span>
<span class="c1">//=> greeting = halo</span>
</code></pre></div></div>
Tue, 24 Aug 2021 00:16:00 +0000
https://blog.jvtrigueros.com/2021/08/24/java-locale-indonesia/
https://blog.jvtrigueros.com/2021/08/24/java-locale-indonesia/Migrating to Cloudflare Pages<p>When I first created this blog in 2015 😱, I spent a lot of time bike shedding on questions like:</p>
<ul>
<li>Which blogging platform should I use?</li>
<li>Where should I host this?</li>
<li>How should I go about hosting it?</li>
<li>Where should I host it?</li>
</ul>
<p>I made decisions based on what I thought I knew, but now it’s been 7 years and some processes have too much overhead.
Let’s look at each decision point and how I changed it to reach our ultimate goal of moving this whole thing to Cloudflare Pages.</p>
<h3 id="choosing-a-blogging-platform">Choosing a blogging platform</h3>
<p>Initially, I tried using the <a href="https://github.com/TryGhost/Ghost">Ghost</a> blogging platform, but ended up turning into a nightmare
because, while it was straightforward to get started, I needed to host this somewhere, and it was more maintenance than I cared
for at the time.</p>
<p>After that, I realized that all I needed was a simple Static Site Generator (SSG), so switched over to <a href="https://jekyll.com">Jekyll</a>
which is still true to date.</p>
<p>I began with Jekyll 3.4.0 inching my way to present day and landing on Jekyll 4.2.0.</p>
<p>Jekyll is straightforward:</p>
<ul>
<li>Create a markdown file with front-matter</li>
<li>Put it in the <code class="language-plaintext highlighter-rouge">_posts</code> directory</li>
<li>Run <code class="language-plaintext highlighter-rouge">jekyll build</code></li>
<li>Bam! Outcomes a static site under <code class="language-plaintext highlighter-rouge">_site</code></li>
</ul>
<p>I did spend a bit of time looking for a theme, and landed on <a href="https://github.com/nandomoreirame/zetsu">gets</a>, which I
customized a bit.</p>
<p>The only issue here was that because I was dead set on using Windows, I created wrapper scripts that would execute Ruby
commands inside a Docker container. This wasn’t ideal but it worked.</p>
<h3 id="hosting">Hosting</h3>
<p>This is where most of the complexity begins. At the time, GitHub Pages with Jekyll was well-supported, but I didn’t
want to host both the source and blog on the same platform. So instead I went to AWS S3.</p>
<p>Hosting in AWS S3 is simple enough, all one has to do is create a public bucket and drop the static site in it.</p>
<p>At this point, we need to create a CNAME in Name.com and the blog will live!</p>
<h3 id="https">HTTPS</h3>
<p>The issue with the setup above, is that if I wanted to use HTTPS, I needed to add yet another layer – Amazon CloudFront.</p>
<p>At this point, I should’ve turned around and reevaluated my decision, but instead I pressed on!</p>
<p>In addition to this new layer, I also needed an SSL certificate for my domain, so I had two options:</p>
<ul>
<li>Buy one</li>
<li>Use Let’s Encrypt</li>
</ul>
<p>I opted to go with <a href="https://letsencrypt.org/">Let’s Encrypt</a>, which is a great service that provides free SSL certificates.
The only gotcha is that they only last 90 days.</p>
<p>Once I created the CloudFront distribution, I used a Python CLI tool to generate and update the SSL certificates. Everything
worked great! This commands needs to run every 90 days.</p>
<h3 id="continuous-deployment">Continuous Deployment</h3>
<p>Copying files to S3 by hand isn’t that bad, but I wanted the ability to simply push to the main branch and have something
build the static files and push them to S3. Given how many blog posts I have written, this was definitely a waste of time 😅.</p>
<p>At that time, GitHub Actions wasn’t a thing, so I added TravisCI to the mix. Their documentation was great, so it was
trivial to get started.</p>
<p>Where I went wrong is that I opted to use a Ruby Gem called <code class="language-plaintext highlighter-rouge">s3_website</code> that facilitated uploading files to the S3 bucket
and invalidating the CloudFront cache. This all sounds great, but at some point this gem became unsupported, and it started
to fail, leading to some issues and needed a <a href="https://github.com/jvtrigueros/blog.jvtrigueros.com/commit/95fc0fa81104388da0cf8852a81c522aee8c4737">workaround</a>.</p>
<p>It all still worked!</p>
<h3 id="maintenance">Maintenance</h3>
<p>There are some <em>inactive</em> resources that I must be aware of but don’t require any changes:</p>
<ul>
<li>S3 Bucket</li>
<li>CloudFront distribution</li>
<li>TravisCI build scripts</li>
</ul>
<p>But there’s one piece that requires <em>active</em> maintenance:</p>
<ul>
<li>Renew Let’s Encrypt certificates every 90 days</li>
</ul>
<p>Simple, right?
<strong>NO!</strong></p>
<h3 id="problems">Problems</h3>
<p>Perhaps, I’m the problem here, and maybe I could’ve automated the process. <em>However,</em> since it was only run every 90
days, I would often forget some steps.</p>
<p>Yes, I did take notes. Yes, I scripted most of it. Yet, something would fail.</p>
<ul>
<li>Wrong Python version</li>
<li>Wrong package version</li>
<li>Package deprecated and/or renamed</li>
<li>Wrong AWS credentials</li>
</ul>
<p>So this week, I realized that most of my stack was obsolete, and I could move or eliminate parts of it.
<em>It was much simpler than I originally anticipated.</em></p>
<h2 id="the-great-migration">The Great Migration</h2>
<p>Before I started any work, I took tally of the pieces that needed to change:</p>
<ul>
<li>Blogging platform
<ul>
<li>Unchanged</li>
<li><em>Technically, I did upgrade the version of Jekyll, but that was unnecessary.</em></li>
</ul>
</li>
<li>Hosting
<ul>
<li>S3 ➔ Cloudflare Pages
<ul>
<li>I recently switched my domain name registrar from Name.com to Cloudflare, so this was an obvious choice</li>
</ul>
</li>
</ul>
</li>
<li>HTTPS
<ul>
<li>CloudFront ➔ Cloudflare
<ul>
<li>Cloudflare would handle both the CDN and ensuring an up-to-date SSL certificate.</li>
</ul>
</li>
</ul>
</li>
<li>Continuous Deployment
<ul>
<li>TravisCI ➔ Cloudflare Pages
<ul>
<li>Done via GitHub integration, Pages listens for changes on main branch, builds site and publishes it</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>As you can see, Cloudflare can handle most of the Ops side.</p>
<h3 id="cloudflare-pages">Cloudflare Pages</h3>
<p>It was surprising how simple it was to get this going. To onboard onto Cloudflare Pages, I registered the Cloudflare Pages app
with GitHub to give it access to the blog source. Then entered the build command <em>and that was it!</em></p>
<p>This took care of the Continuous Integration and eliminated the need for S3-type of code hosting. I don’t know where the build
artifacts are, and I don’t need to know.</p>
<p>Cloudflare Pages serves traffic via HTTPS and when using Cloudflare DNS to route the CNAME to the Pages location, it all
just works. This eliminates the need for manually managing a Let’s Encrypt certificate. I don’t know where the certificate comes
from, but it is a valid one and that’s all I need to know.</p>
<p>Maybe now I won’t have an excuse to not write :)</p>
<h3 id="less-is-more">Less is More</h3>
<p>That’s all!</p>
<p>If you have a process that you haven’t touched in some time, I urge you to take a second look. It may be simpler to update
and improve than you think.</p>
Thu, 19 Aug 2021 00:30:00 +0000
https://blog.jvtrigueros.com/2021/08/19/migrating-to-cloudflare-pages/
https://blog.jvtrigueros.com/2021/08/19/migrating-to-cloudflare-pages/ripgrep (rg)<p>On a <a href="/2019/01/31/an-alternative-to-grep">previous</a> post I talked about how <a href="https://github.com/ggreer/the_silver_searcher">The Silver Searcher</a>
was a very fast way to search through a codebase. I was made aware of a different
tool called <a href="https://github.com/BurntSushi/ripgrep">ripgrep</a>.</p>
<p>Let me just post this for comparison:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ rg -t clojure --stats -q main
210 matches
186 matched lines
90 files contained matches
447 files searched
0 bytes printed
1785713 bytes searched
0.171875 seconds spent searching
0.446368 seconds
❯ ag --clojure --stats-only main
349 matches
98 files contained matches
517 files searched
3435452 bytes searched
3.404591 seconds
</code></pre></div></div>
<p>Just, wow.</p>
<p><em>Yes, yes I see that <code class="language-plaintext highlighter-rouge">ag</code> searched in more files, this is because it didn’t fully
understand <code class="language-plaintext highlighter-rouge">.gitignore</code> files.</em></p>
<p>I don’t think there’s much I can add to this except go try it out! It did take
me some time to get used to the new CLI options but just read the manual and it’ll
take care of you.</p>
Sat, 06 Apr 2019 20:00:00 +0000
https://blog.jvtrigueros.com/2019/04/06/ripgrep/
https://blog.jvtrigueros.com/2019/04/06/ripgrep/linuxgrepripgrepAn Alternative to grep<hr />
<blockquote>
<p>As mentioned in the comments by <a href="https://twitter.com/samatjain">@samatjain</a>, <code class="language-plaintext highlighter-rouge">ripgrep</code> is a much faster alternative to both <code class="language-plaintext highlighter-rouge">the_silver_searcher</code> and <code class="language-plaintext highlighter-rouge">ack</code>. I’ve left this here for historical purposes.</p>
</blockquote>
<hr />
<p>If you spend any amount of time using a terminal, you have more than likely used <code class="language-plaintext highlighter-rouge">grep</code> to search for phrases or words
in a pile of text files. <code class="language-plaintext highlighter-rouge">grep</code> is a great tool and it’s installed by default on many servers, but if you’re a developer,
<code class="language-plaintext highlighter-rouge">grep</code> doesn’t do much aside from a simple text search.</p>
<p>As they say, you gotta use the right tool for the job, for this <code class="language-plaintext highlighter-rouge">ack</code> is the tool that’s very commonly recommended, but
it’s less commonly installed. Since we’re already needing to install something, I recommend using <a href="https://github.com/ggreer/the_silver_searcher">The Silver Searcher</a>.</p>
<h2 id="the-silver-searcher">The Silver Searcher</h2>
<p>Before adding The Silver Searcher (<code class="language-plaintext highlighter-rouge">ag</code>) to my toolset, I hadn’t heard of <code class="language-plaintext highlighter-rouge">ack</code>, so if you haven’t heard of it yourself,
don’t fret. In fact, both <code class="language-plaintext highlighter-rouge">ag</code> and <code class="language-plaintext highlighter-rouge">ack</code> share the same API so using one will give you the same muscle memory as the other.
But if you’re already using <code class="language-plaintext highlighter-rouge">ack</code>, I’d recommend trying it out for it’s <a href="https://github.com/ggreer/the_silver_searcher#whats-so-great-about-ag">performance</a>:</p>
<blockquote>
<p>Ack and Ag found the same results, but Ag was 34x faster (3.2 seconds vs 110 seconds). My ~/code directory is about 8GB.
Thanks to git/hg/ignore, Ag only searched 700MB of that.</p>
</blockquote>
<p>Let’s get to it.</p>
<h2 id="installing">Installing</h2>
<p>Getting this on macOS is just a <code class="language-plaintext highlighter-rouge">brew</code> command away:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>the_silver_searcher
</code></pre></div></div>
<p>On WSL, since it’s just Ubuntu you can do this:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt <span class="nb">install </span>silversearcher-ag
</code></pre></div></div>
<p>For other operating systems (even Windows), check out the <a href="https://github.com/ggreer/the_silver_searcher#installing">manual</a>.</p>
<p>As simple as that, if you’re comfortable with <code class="language-plaintext highlighter-rouge">ack</code> then you can stop here, and reap the benefits of <code class="language-plaintext highlighter-rouge">ag</code>’s increased
performance!</p>
<h2 id="examples">Examples</h2>
<p>Rather than going into the commands, let’s just dive into some examples of how I use <code class="language-plaintext highlighter-rouge">ag</code> which might provide some useful
patterns.</p>
<h3 id="find-usages">Find Usages</h3>
<p>Let’s say you’re exploring a codebase and want to know the usage of a function, <code class="language-plaintext highlighter-rouge">main</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ag <span class="nt">--clojure</span> main
</code></pre></div></div>
<p>Which gives you this result:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ ag <span class="nt">--clojure</span> main
project.clj
7: :main ^:skip-aot silver-demo.core
src/silver_demo/core.clj
4:<span class="o">(</span>defn <span class="nt">-main</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">ag</code> ignores searching directories related to version control (.git, .hg) and because we provided a language flag,
<code class="language-plaintext highlighter-rouge">--clojure</code>, then only Clojure files will be searched. In this case, there are two matches, we get the file path and the
line number of where that word was found.</p>
<p>That’s it!</p>
<p>I find myself using this pattern most of the time, IDEs can also do this, but doing this across multiple projects or a
big codebase is rarely a good experience.</p>
<h3 id="stats">Stats</h3>
<p>Sometimes you don’t need to know where certain matches are, just that they exist and how many. Using the <code class="language-plaintext highlighter-rouge">--stats-only</code>
flag will do just that.</p>
<p>Let’s try the same query on a much much larger</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ ag <span class="nt">--clojure</span> <span class="nt">--stats-only</span> main
142 matches
35 files contained matches
149 files searched
649117 bytes searched
0.086319 seconds
</code></pre></div></div>
<h2 id="conclusion">Conclusion</h2>
<p>I described a few use cases for the Silver Searcher (<code class="language-plaintext highlighter-rouge">ag</code>), I would urge you to try this out and see if it fits in your
terminal toolbox. <code class="language-plaintext highlighter-rouge">grep</code> is great for some use cases, but if you’re searching through code a lot, consider
installing <code class="language-plaintext highlighter-rouge">ag</code>.</p>
Thu, 31 Jan 2019 00:00:00 +0000
https://blog.jvtrigueros.com/2019/01/31/an-alternative-to-grep/
https://blog.jvtrigueros.com/2019/01/31/an-alternative-to-grep/linuxgrepUsing Metabase with Docker and SQLite<p><a href="https://metabase.com">Metabase</a>, is an application that connects to a existing database allowing you to run analytics
and create charts from their web application.</p>
<p>One of the great things about Metabase is that you can self-host it! Today, we’re specifically looking at the
<a href="https://metabase.com/docs/latest/operations-guide/running-metabase-on-docker.html">Running Metabase on Docker</a>
instructions.</p>
<p>Following the guide, I run Metabase as prescribed and load a small SQLite database:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run --rm -p 8080:3000 -v $PWD/data/settings.db:/opt/data.db metabase/metabase
</code></pre></div></div>
<p><em>Since it’s a SQLite database, the container needs to have physical access to the file</em></p>
<p>The Metabase application starts on http://localhost:8080 and I am able to begin with the setup. However, as soon as I
submit the initial form, Metabase crashes.</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>01-01 20:59:28 DEBUG analyze.fingerprint :: Saving fingerprint <span class="k">for </span>Field 55 <span class="s1">'name'</span>
<span class="c">#</span>
<span class="c"># A fatal error has been detected by the Java Runtime Environment:</span>
<span class="c">#</span>
<span class="c"># SIGSEGV (0xb) at pc=0x000000000000a786, pid=1, tid=0x00007f8984a4cae8</span>
<span class="c">#</span>
<span class="c"># JRE version: OpenJDK Runtime Environment (8.0_181-b13) (build 1.8.0_181-b13)</span>
<span class="c"># Java VM: OpenJDK 64-Bit Server VM (25.181-b13 mixed mode linux-amd64 compressed oops)</span>
<span class="c"># Derivative: IcedTea 3.9.0</span>
<span class="c"># Distribution: Custom build (Tue Oct 23 11:27:22 UTC 2018)</span>
<span class="c"># Problematic frame:</span>
<span class="c"># C 0x000000000000a786</span>
<span class="c">#</span>
<span class="c"># Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again</span>
<span class="c">#</span>
<span class="c"># An error report file with more information is saved as:</span>
<span class="c"># /tmp/hs_err_pid1.log</span>
<span class="c">#</span>
<span class="c"># If you would like to submit a bug report, please include</span>
<span class="c"># instructions on how to reproduce the bug and visit:</span>
<span class="c"># http://icedtea.classpath.org/bugzilla</span>
<span class="c">#</span>
</code></pre></div></div>
<p>But we can’t stop there!</p>
<p>At a first glance, I didn’t know what the issue could be, but I got curious as to how this image was built, so I checked
out the Github repo. I noticed that the base image used to run Metabase is Alpine based, <a href="https://github.com/metabase/metabase/blob/master/Dockerfile#L56"><code class="language-plaintext highlighter-rouge">openjdk:8-jre-alpine</code></a>. So that could be the issue!</p>
<p>I came up with this minimal Dockerfile:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FROM openjdk:8-jre-slim
ENV VERSION 0.31.2
WORKDIR /app
ADD https://raw.githubusercontent.com/metabase/metabase/v$VERSION/bin/start /app/bin/
ADD https://downloads.metabase.com/v$VERSION/metabase.jar /app/target/uberjar/
CMD ["bash", "/app/bin/start"]
</code></pre></div></div>
<p>And it worked!!!</p>
<p>So now the full command to use this image would be:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker build <span class="nt">-t</span> metabase/metabase:v0.31.2-custom <span class="nt">-f</span> Dockerfile <span class="nb">.</span>
docker run <span class="nt">--rm</span> <span class="nt">-p</span> 8080:3000 <span class="nt">-v</span> <span class="nv">$PWD</span>/data/settings.db:/opt/data.db metabase/metabase:v0.31.2-custom
</code></pre></div></div>
<p>This should get give you a Metabase instance that’s able to load your SQLite database, I’m currently running that on my
server with no issues. If you find a better way to go about this, I’m all ears!</p>
<p>I searched the <a href="https://github.com/metabase/metabase/issues?q=is%3Aissue+sqlite+sigsegv+is%3Aclosed">Github Issues</a>
for anything related to this, but there’s only one result that’s not this problem, so this could be an issue with <em>my</em>
configuration alone.</p>
Tue, 01 Jan 2019 00:00:00 +0000
https://blog.jvtrigueros.com/2019/01/01/using-metabase-with-docker-and-sqlite/
https://blog.jvtrigueros.com/2019/01/01/using-metabase-with-docker-and-sqlite/dockermetabasesqliteBatch Rename using PowerShell<p>This is more of a self-documenting post, as it doesn’t come up often but when it does, it’s not
immediately trivial. I’m talking about batch renaming a bunch of files in a folder.</p>
<p>In my particular case, I wanted to make this change:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Desktop 06.17.2017 - 22.34.02.01.mp4 -> Ori of the Blind Forest 06.17.2017 - 22.34.02.01.mp4
</code></pre></div></div>
<p>So it’s a simple prefix change. Normally, I stay stick to using Command Prompt in
<a href="http://cmder.net/">Cmder</a>. If you haven’t used this on Windows, it makes Command Prompt much
more usable. It includes some of the command line tools our friends in Linux land enjoy. Anyway,
for a task like this PowerShell is a much more apt tool because of its extensive built-in
commands.</p>
<h2 id="tldr">TL;DR</h2>
<p>If you want to replace the prefix (or any other part) of a filename with another type this
command:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dir | Rename-Item -NewName {$_.name -replace "old-text","new-text"}
</code></pre></div></div>
<h2 id="explanation">Explanation</h2>
<p>Let’s breakdown the commands used here:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">Get-ChildItem</code></li>
<li><code class="language-plaintext highlighter-rouge">|</code></li>
<li><code class="language-plaintext highlighter-rouge">Rename-Item</code></li>
<li><code class="language-plaintext highlighter-rouge">{...}</code></li>
</ol>
<p>First, <a href="https://technet.microsoft.com/en-us/library/ee176841.aspx"><code class="language-plaintext highlighter-rouge">Get-ChildItem</code></a> is aliased to <code class="language-plaintext highlighter-rouge">dir</code>,
but what that command does is output every item in the current directory as an item. The <code class="language-plaintext highlighter-rouge">|</code>, or pipe, is
used to pass each Item to the <code class="language-plaintext highlighter-rouge">Rename-Item</code> command.</p>
<p><a href="https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.management/rename-item"><code class="language-plaintext highlighter-rouge">Rename-Item</code></a>
has the following anatomy (simplified for this use case):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Rename-Item [-Path] <String> [-NewName] <String>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">[-Path]</code> is coming from <code class="language-plaintext highlighter-rouge">dir</code> and <code class="language-plaintext highlighter-rouge">[-NewName]</code> is the result of the script block — <code class="language-plaintext highlighter-rouge">{...}</code>.
The script block is an anonymous function that executes in place, in this case it is using the automatic
variable, <code class="language-plaintext highlighter-rouge">$_</code>, which is an Item that came from the pipe. The function calls the <code class="language-plaintext highlighter-rouge">.name</code> attribute which
returns a String, then we can call it’s <code class="language-plaintext highlighter-rouge">-replace</code> flag which will do what we want.</p>
<p>If we execute this command:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Rename-Item -NewName {$_.name -replace "Desktop","Ori of the Blind Forest"}
</code></pre></div></div>
<p>Taking this step by step and execute <code class="language-plaintext highlighter-rouge">dir</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>M:\Media\Videos\ShadowReplay\Ori of the Blind Forest
λ dir
Directory: M:\Media\Videos\ShadowReplay\Ori of the Blind Forest
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 6/17/2017 10:34 PM 12703402 Desktop 06.17.2017 - 22.34.02.01.mp4
</code></pre></div></div>
<p>So then the command past the pipe will look like this:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Rename-Item Desktop 06.17.2017 - 22.34.02.01.mp4 -NewName {Desktop 06.17.2017 - 22.34.02.01.mp4 -replace "Desktop","Ori of the Blind Forest"}
</code></pre></div></div>
<p>Then finally reducing to:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Rename-Item Desktop 06.17.2017 - 22.34.02.01.mp4 -NewName Ori of the Blind Forest 06.17.2017 - 22.34.02.01.mp4
</code></pre></div></div>
<p>I hope this helps someone, I know it’ll definitely help me in the future!</p>
Sat, 22 Jul 2017 00:00:00 +0000
https://blog.jvtrigueros.com/2017/07/22/batch-rename/
https://blog.jvtrigueros.com/2017/07/22/batch-rename/powershellscripts